diff --git a/beam-apps/apps/uniswap/src/api.rs b/beam-apps/apps/uniswap/src/api.rs index c8f66cf..f9e872f 100644 --- a/beam-apps/apps/uniswap/src/api.rs +++ b/beam-apps/apps/uniswap/src/api.rs @@ -1,4 +1,4 @@ -use serde_json::{Value, json}; +use serde_json::{Number, Value, json}; use crate::{Error, Result}; @@ -55,58 +55,82 @@ pub fn check_approval_payload(request: &QuoteRequest) -> Value { pub fn quote_payload(request: &QuoteRequest) -> Value { json!({ "amount": request.amount, + "permitAmount": "EXACT", "protocols": ["V2", "V3", "V4"], "recipient": request.recipient, - "slippageTolerance": request.slippage_bps, + "routingPreference": "BEST_PRICE", + "slippageTolerance": slippage_tolerance(request.slippage_bps), + "swapper": request.wallet, "tokenIn": request.token_in, "tokenInChainId": request.chain_id, "tokenOut": request.token_out, "tokenOutChainId": request.chain_id, "type": "EXACT_INPUT", - "walletAddress": request.wallet, + "urgency": "normal", }) } -pub fn swap_payload(quote: &QuoteResponse, wallet: &str) -> Value { +pub fn swap_payload(quote: &QuoteResponse, _wallet: &str) -> Value { json!({ "quote": quote.quote, + "refreshGasPrice": true, "simulateTransaction": true, - "walletAddress": wallet, + "urgency": "normal", }) } pub fn parse_quote(value: Value, request: &QuoteRequest) -> Result { + let quote = value.get("quote").cloned().unwrap_or_else(|| value.clone()); validate_optional_field( - &value, + "e, &["tokenInChainId", "chainId"], + &[], &request.chain_id.to_string(), )?; - validate_optional_field(&value, &["tokenOutChainId"], &request.chain_id.to_string())?; - validate_optional_field(&value, &["tokenIn", "inputToken"], &request.token_in)?; - validate_optional_field(&value, &["tokenOut", "outputToken"], &request.token_out)?; - let amount_out = - first_string(&value, &["amountOut", "output", "quoteAmount"]).ok_or_else(|| { - Error::InvalidUniswapResponse { - reason: "quote missing output amount".to_string(), - } - })?; - let quote_id = first_string(&value, &["quoteId", "requestId", "routingId"]) + validate_optional_field( + "e, + &["tokenOutChainId"], + &[], + &request.chain_id.to_string(), + )?; + validate_optional_field( + "e, + &["tokenIn", "inputToken"], + &[&["input", "token"]], + &request.token_in, + )?; + validate_optional_field( + "e, + &["tokenOut", "outputToken"], + &[&["output", "token"]], + &request.token_out, + )?; + let amount_out = first_string_or_path( + "e, + &["amountOut", "quoteAmount"], + &[&["output", "amount"]], + ) + .ok_or_else(|| Error::InvalidUniswapResponse { + reason: "quote missing output amount".to_string(), + })?; + let quote_id = first_string("e, &["quoteId", "requestId", "routingId"]) + .or_else(|| first_string(&value, &["quoteId", "requestId", "routingId"])) .unwrap_or_else(|| "uniswap-quote".to_string()); - let route = first_string(&value, &["routing", "routeString", "route"]) + let route = first_string(&value, &["routing"]) + .or_else(|| first_string("e, &["routing", "routeString", "route"])) .unwrap_or_else(|| "classic".to_string()); - if route.to_ascii_lowercase().contains("dutch") - || route.to_ascii_lowercase().contains("uniswapx") - { + if is_order_route(&route) { return Err(Error::UnsupportedUniswapRoute { route }); } Ok(QuoteResponse { amount_out, - minimum_amount_out: first_string( - &value, + minimum_amount_out: first_string_or_path( + "e, &["amountOutMinimum", "minimumAmountOut", "minAmountOut"], + &[&["output", "minAmount"]], ), - quote: value, + quote, quote_id, route, valid_for_seconds: 180, @@ -140,8 +164,13 @@ pub fn approval_spender(data: &str) -> Option { Some(format!("0x{}", &data[8 + 24..8 + 64])) } -fn validate_optional_field(value: &Value, keys: &[&str], expected: &str) -> Result<()> { - let Some(actual) = first_string(value, keys) else { +fn validate_optional_field( + value: &Value, + keys: &[&str], + paths: &[&[&str]], + expected: &str, +) -> Result<()> { + let Some(actual) = first_string_or_path(value, keys, paths) else { return Ok(()); }; if !actual.eq_ignore_ascii_case(expected) { @@ -153,6 +182,16 @@ fn validate_optional_field(value: &Value, keys: &[&str], expected: &str) -> Resu Ok(()) } +fn slippage_tolerance(slippage_bps: u32) -> Value { + let value = f64::from(slippage_bps) / 100.0; + Value::Number(Number::from_f64(value).unwrap_or_else(|| Number::from(0))) +} + +fn is_order_route(route: &str) -> bool { + let route = route.to_ascii_lowercase(); + route.contains("dutch") || route.contains("uniswapx") || route == "priority" +} + fn parse_transaction(value: &Value) -> Option { Some(UniswapTransaction { data: first_string(value, &["data", "calldata", "input"])?, @@ -172,3 +211,16 @@ fn first_string(value: &Value, keys: &[&str]) -> Option { }) }) } + +fn first_string_or_path(value: &Value, keys: &[&str], paths: &[&[&str]]) -> Option { + first_string(value, keys).or_else(|| paths.iter().find_map(|path| path_string(value, path))) +} + +fn path_string(value: &Value, path: &[&str]) -> Option { + let value = path.iter().try_fold(value, |value, key| value.get(key))?; + match value { + Value::String(value) => Some(value.clone()), + Value::Number(value) => Some(value.to_string()), + _ => None, + } +} diff --git a/beam-apps/apps/uniswap/src/host.rs b/beam-apps/apps/uniswap/src/host.rs index 671c17d..9419add 100644 --- a/beam-apps/apps/uniswap/src/host.rs +++ b/beam-apps/apps/uniswap/src/host.rs @@ -5,6 +5,7 @@ use crate::{Error, Result, selector}; const HOST_API_VERSION: u32 = 1; const HOST_RESPONSE_CAPACITY: usize = 2 * 1024 * 1024; +const MAX_ERROR_BODY_CHARS: usize = 500; #[derive(Clone, Debug, Eq, PartialEq)] pub struct PlanContext { @@ -38,6 +39,8 @@ pub struct HostMetadata { pub app_version: String, pub chain: String, pub chain_id: u64, + #[serde(default)] + pub debug: bool, pub host_api_version: u32, pub manifest_sha256: String, pub now: u64, @@ -230,10 +233,18 @@ pub fn http_json(method: &str, url: &str, value: &Value) -> Result { name: "content-type".to_string(), value: "application/json".to_string(), }, + HttpHeader { + name: "accept".to_string(), + value: "application/json".to_string(), + }, HttpHeader { name: "x-api-key".to_string(), value: crate::public_api_key().to_string(), }, + HttpHeader { + name: "x-permit2-disabled".to_string(), + value: "true".to_string(), + }, ], body, }))?; @@ -243,8 +254,14 @@ pub fn http_json(method: &str, url: &str, value: &Value) -> Result { } })?; if !(200..300).contains(&response.status) { + let detail = error_body(&response.body) + .map(|body| format!(": {body}")) + .unwrap_or_default(); return Err(Error::HostCallFailed { - message: format!("{} returned status {}", response.url, response.status), + message: format!( + "{} returned status {}{}", + response.url, response.status, detail + ), }); } serde_json::from_slice(&response.body).map_err(|err| Error::InvalidHostResponse { @@ -252,6 +269,14 @@ pub fn http_json(method: &str, url: &str, value: &Value) -> Result { }) } +fn error_body(body: &[u8]) -> Option { + let body = core::str::from_utf8(body).ok()?.trim(); + if body.is_empty() { + return None; + } + Some(body.chars().take(MAX_ERROR_BODY_CHARS).collect()) +} + pub fn resolve_address(value: Option<&str>) -> Result { let response = host_call(HostRequest::ResolveAddress { value: value.map(str::to_string), diff --git a/beam-apps/apps/uniswap/src/lib.rs b/beam-apps/apps/uniswap/src/lib.rs index 2b5a361..0c8747e 100644 --- a/beam-apps/apps/uniswap/src/lib.rs +++ b/beam-apps/apps/uniswap/src/lib.rs @@ -94,12 +94,21 @@ fn run_guest(input_ptr: *const u8, input_len: usize) -> Result { } fn run_swap(invocation: GuestInvocation) -> Result { + let debug_enabled = invocation.metadata.debug; + debug(debug_enabled, "swap:start"); let args = SwapArgs::parse(&invocation.args)?; + debug(debug_enabled, "swap:args:parsed"); let chain = invocation.metadata.chain.clone(); let wallet = invocation.metadata.wallet.clone(); + debug(debug_enabled, "swap:recipient:resolve"); let recipient = host::resolve_address(args.recipient.as_deref())?; + debug(debug_enabled, "swap:recipient:resolved"); + debug(debug_enabled, "swap:sell-token:metadata"); let sell = host::token_metadata(&chain, &args.sell_token)?; + debug(debug_enabled, "swap:sell-token:metadata-loaded"); + debug(debug_enabled, "swap:buy-token:metadata"); let buy = host::token_metadata(&chain, &args.buy_token)?; + debug(debug_enabled, "swap:buy-token:metadata-loaded"); let amount_raw = amount_to_raw(&args.amount, sell.decimals)?; let min_receive_raw = args .min_receive @@ -115,37 +124,53 @@ fn run_swap(invocation: GuestInvocation) -> Result { token_out: buy.address.clone(), wallet: wallet.clone(), }; - let quote = parse_quote( - host::http_json( - "POST", - "https://trade-api.gateway.uniswap.org/v1/quote", - "e_payload("e_request), - )?, - "e_request, + debug(debug_enabled, "swap:quote:request"); + let quote_value = host::http_json( + "POST", + "https://trade-api.gateway.uniswap.org/v1/quote", + "e_payload("e_request), )?; + debug(debug_enabled, "swap:quote:response"); + let quote = parse_quote(quote_value, "e_request)?; + debug(debug_enabled, "swap:quote:parsed"); let approval = if sell.is_native { + debug(debug_enabled, "swap:approval:skipped-native-token"); None } else { + debug(debug_enabled, "swap:approval:request"); let value = host::http_json( "POST", "https://trade-api.gateway.uniswap.org/v1/check_approval", &check_approval_payload("e_request), )?; + debug(debug_enabled, "swap:approval:response"); Some(ApprovalResponse { transaction: find_transaction(&value), }) }; - let allowance = approval + let allowance = match approval .as_ref() .and_then(|approval| approval.transaction.as_ref()) .and_then(|transaction| approval_spender(&transaction.data)) - .map(|spender| host::allowance(&chain, &sell.address, &spender)) - .transpose()?; + { + Some(spender) => { + debug(debug_enabled, "swap:allowance:request"); + let allowance = host::allowance(&chain, &sell.address, &spender)?; + debug(debug_enabled, "swap:allowance:response"); + Some(allowance) + } + None => { + debug(debug_enabled, "swap:allowance:skipped"); + None + } + }; + debug(debug_enabled, "swap:swap:request"); let swap_value = host::http_json( "POST", "https://trade-api.gateway.uniswap.org/v1/swap", &swap_payload("e, &wallet), )?; + debug(debug_enabled, "swap:swap:response"); let mut swap = SwapResponse { transaction: find_transaction(&swap_value).ok_or_else(|| { Error::InvalidUniswapResponse { @@ -154,7 +179,9 @@ fn run_swap(invocation: GuestInvocation) -> Result { })?, raw: swap_value, }; + debug(debug_enabled, "swap:transaction:parsed"); if swap.transaction.gas_limit.is_none() || swap.transaction.gas_price.is_none() { + debug(debug_enabled, "swap:gas:request"); let (gas_limit, gas_price) = host::gas( &chain, &swap.transaction.to, @@ -163,10 +190,17 @@ fn run_swap(invocation: GuestInvocation) -> Result { )?; swap.transaction.gas_limit = swap.transaction.gas_limit.or(gas_limit); swap.transaction.gas_price = swap.transaction.gas_price.or(Some(gas_price)); + debug(debug_enabled, "swap:gas:response"); } - simulate_best_effort(&chain, approval.as_ref(), &swap); + debug(debug_enabled, "swap:simulation:start"); + simulate_best_effort(debug_enabled, &chain, approval.as_ref(), &swap); + debug(debug_enabled, "swap:simulation:complete"); + debug(debug_enabled, "swap:balance:request"); + let sell_balance = host::balance(&chain, &sell.address)?; + debug(debug_enabled, "swap:balance:response"); - build_swap_plan(SwapPlanInput { + debug(debug_enabled, "swap:plan:build"); + let plan = build_swap_plan(SwapPlanInput { allowance, amount_raw, args, @@ -175,18 +209,27 @@ fn run_swap(invocation: GuestInvocation) -> Result { expires_at: invocation.metadata.now, min_receive_raw, quote, - sell_balance: host::balance(&chain, &sell.address)?, + sell_balance, sell, approval, swap, - }) + })?; + debug(debug_enabled, "swap:plan:built"); + + Ok(plan) } -fn simulate_best_effort(chain: &str, approval: Option<&ApprovalResponse>, swap: &SwapResponse) { +fn simulate_best_effort( + debug_enabled: bool, + chain: &str, + approval: Option<&ApprovalResponse>, + swap: &SwapResponse, +) { if let Some(transaction) = approval .and_then(|approval| approval.transaction.as_ref()) .filter(|transaction| approval_spender(&transaction.data).is_some()) { + debug(debug_enabled, "swap:simulation:approval:request"); let spender = approval_spender(&transaction.data); if let Err(err) = host::simulate( chain, @@ -196,8 +239,11 @@ fn simulate_best_effort(chain: &str, approval: Option<&ApprovalResponse>, swap: spender.as_deref(), ) { let _ = host::diagnostic("warn", &format!("approval simulation skipped: {err}")); + } else { + debug(debug_enabled, "swap:simulation:approval:response"); } } + debug(debug_enabled, "swap:simulation:swap:request"); if let Err(err) = host::simulate( chain, &swap.transaction.to, @@ -206,6 +252,14 @@ fn simulate_best_effort(chain: &str, approval: Option<&ApprovalResponse>, swap: None, ) { let _ = host::diagnostic("warn", &format!("swap simulation skipped: {err}")); + } else { + debug(debug_enabled, "swap:simulation:swap:response"); + } +} + +fn debug(enabled: bool, message: &str) { + if enabled { + let _ = host::diagnostic("debug", message); } } diff --git a/beam-apps/apps/uniswap/src/plan.rs b/beam-apps/apps/uniswap/src/plan.rs index 3a63ca3..b716c47 100644 --- a/beam-apps/apps/uniswap/src/plan.rs +++ b/beam-apps/apps/uniswap/src/plan.rs @@ -131,15 +131,7 @@ fn swap_step(input: &SwapPlanInput) -> ActionStep { let transaction = &input.swap.transaction; ActionStep { kind: "transaction".to_string(), - metadata: json!({ - "buy": input.buy.label, - "quote_id": input.quote.quote_id, - "route": input.quote.route, - "sell": input.sell.label, - "slippage_bps": input.args.slippage_bps, - "swap": input.swap.raw, - "transaction": transaction_json(transaction), - }), + metadata: swap_metadata(input, transaction), selector: selector(&transaction.data), spender: None, summary: format!( @@ -151,6 +143,33 @@ fn swap_step(input: &SwapPlanInput) -> ActionStep { } } +fn swap_metadata(input: &SwapPlanInput, transaction: &UniswapTransaction) -> Value { + let mut metadata = json!({ + "buy": input.buy.label, + "quote_id": input.quote.quote_id, + "route": input.quote.route, + "sell": input.sell.label, + "slippage_bps": input.args.slippage_bps, + "transaction": transaction_json(transaction), + }); + if let Some(request_id) = raw_string(&input.swap.raw, "requestId") { + metadata["request_id"] = json!(request_id); + } + if let Some(gas_fee) = raw_string(&input.swap.raw, "gasFee") { + metadata["gas_fee"] = json!(gas_fee); + } + + metadata +} + +fn raw_string(value: &Value, key: &str) -> Option { + match value.get(key)? { + Value::Number(value) => Some(value.to_string()), + Value::String(value) => Some(value.clone()), + _ => None, + } +} + fn ensure_balance(input: &SwapPlanInput) -> Result<()> { if parse_uint(&input.sell_balance)? < parse_uint(&input.amount_raw)? { return Err(Error::InsufficientBalance { diff --git a/beam-apps/apps/uniswap/src/tests.rs b/beam-apps/apps/uniswap/src/tests.rs index 2742d68..47d6b7f 100644 --- a/beam-apps/apps/uniswap/src/tests.rs +++ b/beam-apps/apps/uniswap/src/tests.rs @@ -2,7 +2,8 @@ use serde_json::json; use crate::{ ApprovalResponse, PlanContext, QuoteRequest, SwapArgs, SwapPlanInput, SwapResponse, SwapToken, - UniswapTransaction, approval_spender, build_swap_plan, parse_quote, public_api_key, selector, + UniswapTransaction, approval_spender, build_swap_plan, parse_quote, public_api_key, + quote_payload, selector, swap_payload, }; #[test] @@ -73,6 +74,80 @@ fn public_api_key_is_embed_ready() { assert!(!key.contains('\r')); } +#[test] +fn quote_payload_uses_current_uniswap_schema() { + let payload = quote_payload("e_request()); + + assert_eq!( + payload["swapper"].as_str(), + Some("0x3333333333333333333333333333333333333333") + ); + assert_eq!(payload.get("walletAddress"), None); + assert_eq!(payload["slippageTolerance"].as_f64(), Some(0.5)); + assert_eq!(payload["permitAmount"].as_str(), Some("EXACT")); +} + +#[test] +fn quote_parser_accepts_nested_current_response() { + let request = quote_request(); + let quote = parse_quote( + json!({ + "requestId": "request-1", + "routing": "CLASSIC", + "quote": { + "chainId": 8453, + "input": { + "amount": "10000000", + "token": "0x1111111111111111111111111111111111111111", + }, + "output": { + "amount": "100", + "minAmount": "99", + "token": "0x0000000000000000000000000000000000000000", + }, + "quoteId": "quote-1", + "routeString": "[V3] USDC -- 0.05% ETH", + "swapper": "0x3333333333333333333333333333333333333333", + } + }), + &request, + ) + .expect("parse current quote response"); + + assert_eq!(quote.amount_out, "100"); + assert_eq!(quote.minimum_amount_out.as_deref(), Some("99")); + assert_eq!(quote.quote_id, "quote-1"); + assert_eq!(quote.route, "CLASSIC"); + assert_eq!( + quote.quote["swapper"].as_str(), + Some(request.wallet.as_str()) + ); +} + +#[test] +fn swap_payload_omits_legacy_wallet_address() { + let quote = parse_quote( + json!({ + "amountOut": "100", + "quoteId": "quote-1", + "route": "classic", + "tokenInChainId": 8453, + "tokenOutChainId": 8453, + "tokenIn": "0x1111111111111111111111111111111111111111", + "tokenOut": "0x0000000000000000000000000000000000000000", + }), + "e_request(), + ) + .expect("parse quote"); + + let payload = swap_payload("e, "0x3333333333333333333333333333333333333333"); + + assert_eq!(payload.get("walletAddress"), None); + assert_eq!(payload["simulateTransaction"].as_bool(), Some(true)); + assert_eq!(payload["refreshGasPrice"].as_bool(), Some(true)); + assert_eq!(payload["quote"]["quoteId"].as_str(), Some("quote-1")); +} + #[test] fn builds_approval_and_swap_action_plan() { let args = SwapArgs::parse(&[ @@ -120,7 +195,16 @@ fn builds_approval_and_swap_action_plan() { transaction: Some(transaction("0x095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba30000000000000000000000000000000000000000000000000000000000989680")), }), swap: SwapResponse { - raw: json!({ "transaction": { "to": "0x2222222222222222222222222222222222222222" } }), + raw: json!({ + "gasFee": "123", + "quote": { + "route": [ + { "protocol": "V3", "tokenIn": "USDC", "tokenOut": "ETH" }, + ], + }, + "requestId": "swap-request-1", + "transaction": { "to": "0x2222222222222222222222222222222222222222" }, + }), transaction: transaction("0x3593564c"), }, }) @@ -129,6 +213,13 @@ fn builds_approval_and_swap_action_plan() { assert_eq!(plan.steps.len(), 2); assert_eq!(plan.steps[0].kind, "erc20-approval"); assert_eq!(plan.steps[1].kind, "transaction"); + assert_eq!(plan.steps[1].metadata.get("swap"), None); + assert_eq!(plan.steps[1].metadata.get("quote"), None); + assert_eq!( + plan.steps[1].metadata["request_id"].as_str(), + Some("swap-request-1") + ); + assert_eq!(plan.steps[1].metadata["gas_fee"].as_str(), Some("123")); assert!( plan.bindings .iter() diff --git a/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/manifest.json b/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/manifest.json index 64199df..123dc1e 100644 --- a/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/manifest.json +++ b/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/manifest.json @@ -7,7 +7,7 @@ "description": "Prepare public Uniswap swaps through Beam-mediated HTTP, chain, approval, and transaction planning.", "min_beam_version": "0.2.1", "wasm": { - "sha256": "sha256:18aa6e8845772beccef895e83bbf2d1f3783d24fbf0fb8feee75d1eacfa1a7f9", + "sha256": "sha256:58c5dbc8343f5281392269b72e0193b2c39f6a4b36942df1bea464699a36cdc2", "entrypoint": "beam_app_main" }, "catalog": { @@ -219,6 +219,6 @@ "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:847162262ed6ee2a090a77fdc0b7caa2bf90cbfc385fb4f18b271c00a9558f36" + "value": "sha256:e27bed5240e9eeb775f4830f1e46149e514057c6e4b4ca3574f027fa12d9daa1" } } diff --git a/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/manifest.json.sig b/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/manifest.json.sig index d927149..53eeada 100644 --- a/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/manifest.json.sig +++ b/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/manifest.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:a7bac552c206a4a3a86a0261d6e8efe4e374b62780e692669d4055c7f2933178" + "value": "sha256:a529b79e6b23e784176b3f2f78334d26b5d7b8dba15b66262829eda3b15d360b" } diff --git a/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/module.wasm b/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/module.wasm index 852d975..4d88a6a 100644 Binary files a/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/module.wasm and b/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/module.wasm differ diff --git a/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/version.json.sig b/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/version.json.sig index 2a5951d..250c2e2 100644 --- a/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/version.json.sig +++ b/beam-apps/fixtures/broad-wildcard/apps/uniswap/1.0.2/version.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:c066a56b6703a1d421195be37474192a11a960ce259991ff3cb91fd2f062c8de" + "value": "sha256:447860c5bbc66e4f9b418bb2c67155a5c276812828da8c417a20387882fee0dc" } diff --git a/beam-apps/fixtures/broad-wildcard/index.json b/beam-apps/fixtures/broad-wildcard/index.json index d6d0817..b3923c1 100644 --- a/beam-apps/fixtures/broad-wildcard/index.json +++ b/beam-apps/fixtures/broad-wildcard/index.json @@ -12,13 +12,13 @@ "version": "1.0.2", "min_beam_version": "0.2.1", "manifest_url": "https://registry.beam.payy.network/apps/uniswap/1.0.2/manifest.json", - "manifest_sha256": "sha256:07b1223ee0855508eb0c46743a3345e96422f97a7cf12175c504bfee8850341b", + "manifest_sha256": "sha256:b3bee997c062dbf20de57c4176a010b3578bf4a5e7c8a57017733bd5a95e2b73", "module_url": "https://registry.beam.payy.network/apps/uniswap/1.0.2/module.wasm", - "module_sha256": "sha256:18aa6e8845772beccef895e83bbf2d1f3783d24fbf0fb8feee75d1eacfa1a7f9", + "module_sha256": "sha256:58c5dbc8343f5281392269b72e0193b2c39f6a4b36942df1bea464699a36cdc2", "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:c066a56b6703a1d421195be37474192a11a960ce259991ff3cb91fd2f062c8de" + "value": "sha256:447860c5bbc66e4f9b418bb2c67155a5c276812828da8c417a20387882fee0dc" } } ] @@ -27,6 +27,6 @@ "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:02b57fe91af9b8247f4b71214bbe382ea4b09a63461bd05c5e59e61987a5c98d" + "value": "sha256:f1ad11d143f310620be1bc78a9d02fb7db7ec51d05acae195c1115167aef3dee" } } diff --git a/beam-apps/fixtures/broad-wildcard/index.json.sig b/beam-apps/fixtures/broad-wildcard/index.json.sig index 1593252..4193503 100644 --- a/beam-apps/fixtures/broad-wildcard/index.json.sig +++ b/beam-apps/fixtures/broad-wildcard/index.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:02b57fe91af9b8247f4b71214bbe382ea4b09a63461bd05c5e59e61987a5c98d" + "value": "sha256:f1ad11d143f310620be1bc78a9d02fb7db7ec51d05acae195c1115167aef3dee" } diff --git a/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/manifest.json b/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/manifest.json index 5ec8c40..4b4bf56 100644 --- a/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/manifest.json +++ b/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/manifest.json @@ -7,7 +7,7 @@ "description": "Prepare public Uniswap swaps through Beam-mediated HTTP, chain, approval, and transaction planning.", "min_beam_version": "0.2.1", "wasm": { - "sha256": "sha256:18aa6e8845772beccef895e83bbf2d1f3783d24fbf0fb8feee75d1eacfa1a7f9", + "sha256": "sha256:58c5dbc8343f5281392269b72e0193b2c39f6a4b36942df1bea464699a36cdc2", "entrypoint": "beam_app_main" }, "catalog": { @@ -290,6 +290,6 @@ "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:a7bac552c206a4a3a86a0261d6e8efe4e374b62780e692669d4055c7f2933178" + "value": "sha256:a529b79e6b23e784176b3f2f78334d26b5d7b8dba15b66262829eda3b15d360b" } } diff --git a/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/manifest.json.sig b/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/manifest.json.sig index d927149..53eeada 100644 --- a/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/manifest.json.sig +++ b/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/manifest.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:a7bac552c206a4a3a86a0261d6e8efe4e374b62780e692669d4055c7f2933178" + "value": "sha256:a529b79e6b23e784176b3f2f78334d26b5d7b8dba15b66262829eda3b15d360b" } diff --git a/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/module.wasm b/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/module.wasm index 852d975..4d88a6a 100644 Binary files a/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/module.wasm and b/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/module.wasm differ diff --git a/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/version.json.sig b/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/version.json.sig index 2a5951d..250c2e2 100644 --- a/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/version.json.sig +++ b/beam-apps/fixtures/invalid-digest/apps/uniswap/1.0.2/version.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:c066a56b6703a1d421195be37474192a11a960ce259991ff3cb91fd2f062c8de" + "value": "sha256:447860c5bbc66e4f9b418bb2c67155a5c276812828da8c417a20387882fee0dc" } diff --git a/beam-apps/fixtures/invalid-digest/index.json b/beam-apps/fixtures/invalid-digest/index.json index d689508..ff3fde0 100644 --- a/beam-apps/fixtures/invalid-digest/index.json +++ b/beam-apps/fixtures/invalid-digest/index.json @@ -12,13 +12,13 @@ "version": "1.0.2", "min_beam_version": "0.2.1", "manifest_url": "https://registry.beam.payy.network/apps/uniswap/1.0.2/manifest.json", - "manifest_sha256": "sha256:07b1223ee0855508eb0c46743a3345e96422f97a7cf12175c504bfee8850341b", + "manifest_sha256": "sha256:b3bee997c062dbf20de57c4176a010b3578bf4a5e7c8a57017733bd5a95e2b73", "module_url": "https://registry.beam.payy.network/apps/uniswap/1.0.2/module.wasm", "module_sha256": "sha256:0000000000000000000000000000000000000000000000000000000000000000", "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:c066a56b6703a1d421195be37474192a11a960ce259991ff3cb91fd2f062c8de" + "value": "sha256:447860c5bbc66e4f9b418bb2c67155a5c276812828da8c417a20387882fee0dc" } } ] @@ -27,6 +27,6 @@ "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:293d1e56cb7ac2c07c4136e2fd94afd9a8d175702bb8e8bd9c5afce93184fa7a" + "value": "sha256:1d37a1f8f1729015d06f67214042aed05298a44e21bc07c63c266cb19e3f421d" } } diff --git a/beam-apps/fixtures/invalid-digest/index.json.sig b/beam-apps/fixtures/invalid-digest/index.json.sig index 1593252..4193503 100644 --- a/beam-apps/fixtures/invalid-digest/index.json.sig +++ b/beam-apps/fixtures/invalid-digest/index.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:02b57fe91af9b8247f4b71214bbe382ea4b09a63461bd05c5e59e61987a5c98d" + "value": "sha256:f1ad11d143f310620be1bc78a9d02fb7db7ec51d05acae195c1115167aef3dee" } diff --git a/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/manifest.json b/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/manifest.json index 007c650..200fe0c 100644 --- a/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/manifest.json +++ b/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/manifest.json @@ -7,7 +7,7 @@ "description": "Prepare public Uniswap swaps through Beam-mediated HTTP, chain, approval, and transaction planning.", "min_beam_version": "0.2.1", "wasm": { - "sha256": "sha256:18aa6e8845772beccef895e83bbf2d1f3783d24fbf0fb8feee75d1eacfa1a7f9", + "sha256": "sha256:58c5dbc8343f5281392269b72e0193b2c39f6a4b36942df1bea464699a36cdc2", "entrypoint": "beam_app_main" }, "catalog": { @@ -221,6 +221,6 @@ "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:bb1fb10740ad500c6a37064acf750be47297a8a8889c2d4df6333accbb5a8ff6" + "value": "sha256:8e136bd9b04f736be0593506305b42957f53f8ee94b21857275645315d52dc32" } } diff --git a/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/manifest.json.sig b/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/manifest.json.sig index d927149..53eeada 100644 --- a/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/manifest.json.sig +++ b/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/manifest.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:a7bac552c206a4a3a86a0261d6e8efe4e374b62780e692669d4055c7f2933178" + "value": "sha256:a529b79e6b23e784176b3f2f78334d26b5d7b8dba15b66262829eda3b15d360b" } diff --git a/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/module.wasm b/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/module.wasm index 852d975..4d88a6a 100644 Binary files a/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/module.wasm and b/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/module.wasm differ diff --git a/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/version.json.sig b/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/version.json.sig index 2a5951d..250c2e2 100644 --- a/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/version.json.sig +++ b/beam-apps/fixtures/malformed-permissions/apps/uniswap/1.0.2/version.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:c066a56b6703a1d421195be37474192a11a960ce259991ff3cb91fd2f062c8de" + "value": "sha256:447860c5bbc66e4f9b418bb2c67155a5c276812828da8c417a20387882fee0dc" } diff --git a/beam-apps/fixtures/malformed-permissions/index.json b/beam-apps/fixtures/malformed-permissions/index.json index d6d0817..b3923c1 100644 --- a/beam-apps/fixtures/malformed-permissions/index.json +++ b/beam-apps/fixtures/malformed-permissions/index.json @@ -12,13 +12,13 @@ "version": "1.0.2", "min_beam_version": "0.2.1", "manifest_url": "https://registry.beam.payy.network/apps/uniswap/1.0.2/manifest.json", - "manifest_sha256": "sha256:07b1223ee0855508eb0c46743a3345e96422f97a7cf12175c504bfee8850341b", + "manifest_sha256": "sha256:b3bee997c062dbf20de57c4176a010b3578bf4a5e7c8a57017733bd5a95e2b73", "module_url": "https://registry.beam.payy.network/apps/uniswap/1.0.2/module.wasm", - "module_sha256": "sha256:18aa6e8845772beccef895e83bbf2d1f3783d24fbf0fb8feee75d1eacfa1a7f9", + "module_sha256": "sha256:58c5dbc8343f5281392269b72e0193b2c39f6a4b36942df1bea464699a36cdc2", "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:c066a56b6703a1d421195be37474192a11a960ce259991ff3cb91fd2f062c8de" + "value": "sha256:447860c5bbc66e4f9b418bb2c67155a5c276812828da8c417a20387882fee0dc" } } ] @@ -27,6 +27,6 @@ "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:02b57fe91af9b8247f4b71214bbe382ea4b09a63461bd05c5e59e61987a5c98d" + "value": "sha256:f1ad11d143f310620be1bc78a9d02fb7db7ec51d05acae195c1115167aef3dee" } } diff --git a/beam-apps/fixtures/malformed-permissions/index.json.sig b/beam-apps/fixtures/malformed-permissions/index.json.sig index 1593252..4193503 100644 --- a/beam-apps/fixtures/malformed-permissions/index.json.sig +++ b/beam-apps/fixtures/malformed-permissions/index.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:02b57fe91af9b8247f4b71214bbe382ea4b09a63461bd05c5e59e61987a5c98d" + "value": "sha256:f1ad11d143f310620be1bc78a9d02fb7db7ec51d05acae195c1115167aef3dee" } diff --git a/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/manifest.json b/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/manifest.json index e1b0e0b..475d76c 100644 --- a/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/manifest.json +++ b/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/manifest.json @@ -7,7 +7,7 @@ "description": "Prepare public Uniswap swaps through Beam-mediated HTTP, chain, approval, and transaction planning.", "min_beam_version": "0.2.1", "wasm": { - "sha256": "sha256:18aa6e8845772beccef895e83bbf2d1f3783d24fbf0fb8feee75d1eacfa1a7f9", + "sha256": "sha256:58c5dbc8343f5281392269b72e0193b2c39f6a4b36942df1bea464699a36cdc2", "entrypoint": "beam_app_main" }, "catalog": { @@ -129,6 +129,6 @@ "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:2baa744c8f1d81ebe4e28aff957d05f1371ed81e22c604ed4fc4a9dabce0c1d8" + "value": "sha256:ce538d2a143e48a1d5dd7bd36896f3727f4148e589ddca7d601e6d5b4b5d5f9b" } } diff --git a/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/manifest.json.sig b/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/manifest.json.sig index d927149..53eeada 100644 --- a/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/manifest.json.sig +++ b/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/manifest.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:a7bac552c206a4a3a86a0261d6e8efe4e374b62780e692669d4055c7f2933178" + "value": "sha256:a529b79e6b23e784176b3f2f78334d26b5d7b8dba15b66262829eda3b15d360b" } diff --git a/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/module.wasm b/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/module.wasm index 852d975..4d88a6a 100644 Binary files a/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/module.wasm and b/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/module.wasm differ diff --git a/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/version.json.sig b/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/version.json.sig index 2a5951d..250c2e2 100644 --- a/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/version.json.sig +++ b/beam-apps/fixtures/missing-fields/apps/uniswap/1.0.2/version.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:c066a56b6703a1d421195be37474192a11a960ce259991ff3cb91fd2f062c8de" + "value": "sha256:447860c5bbc66e4f9b418bb2c67155a5c276812828da8c417a20387882fee0dc" } diff --git a/beam-apps/fixtures/missing-fields/index.json b/beam-apps/fixtures/missing-fields/index.json index d6d0817..b3923c1 100644 --- a/beam-apps/fixtures/missing-fields/index.json +++ b/beam-apps/fixtures/missing-fields/index.json @@ -12,13 +12,13 @@ "version": "1.0.2", "min_beam_version": "0.2.1", "manifest_url": "https://registry.beam.payy.network/apps/uniswap/1.0.2/manifest.json", - "manifest_sha256": "sha256:07b1223ee0855508eb0c46743a3345e96422f97a7cf12175c504bfee8850341b", + "manifest_sha256": "sha256:b3bee997c062dbf20de57c4176a010b3578bf4a5e7c8a57017733bd5a95e2b73", "module_url": "https://registry.beam.payy.network/apps/uniswap/1.0.2/module.wasm", - "module_sha256": "sha256:18aa6e8845772beccef895e83bbf2d1f3783d24fbf0fb8feee75d1eacfa1a7f9", + "module_sha256": "sha256:58c5dbc8343f5281392269b72e0193b2c39f6a4b36942df1bea464699a36cdc2", "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:c066a56b6703a1d421195be37474192a11a960ce259991ff3cb91fd2f062c8de" + "value": "sha256:447860c5bbc66e4f9b418bb2c67155a5c276812828da8c417a20387882fee0dc" } } ] @@ -27,6 +27,6 @@ "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:02b57fe91af9b8247f4b71214bbe382ea4b09a63461bd05c5e59e61987a5c98d" + "value": "sha256:f1ad11d143f310620be1bc78a9d02fb7db7ec51d05acae195c1115167aef3dee" } } diff --git a/beam-apps/fixtures/missing-fields/index.json.sig b/beam-apps/fixtures/missing-fields/index.json.sig index 1593252..4193503 100644 --- a/beam-apps/fixtures/missing-fields/index.json.sig +++ b/beam-apps/fixtures/missing-fields/index.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:02b57fe91af9b8247f4b71214bbe382ea4b09a63461bd05c5e59e61987a5c98d" + "value": "sha256:f1ad11d143f310620be1bc78a9d02fb7db7ec51d05acae195c1115167aef3dee" } diff --git a/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/manifest.json b/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/manifest.json index 4c68d88..b418ab9 100644 --- a/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/manifest.json +++ b/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/manifest.json @@ -7,7 +7,7 @@ "description": "Prepare public Uniswap swaps through Beam-mediated HTTP, chain, approval, and transaction planning.", "min_beam_version": "999.0.0", "wasm": { - "sha256": "sha256:18aa6e8845772beccef895e83bbf2d1f3783d24fbf0fb8feee75d1eacfa1a7f9", + "sha256": "sha256:58c5dbc8343f5281392269b72e0193b2c39f6a4b36942df1bea464699a36cdc2", "entrypoint": "beam_app_main" }, "catalog": { @@ -290,6 +290,6 @@ "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:0b2b6fa6ae3017fbc1316892bd357652c70f66a9b3f9dabe7e313946c604b07f" + "value": "sha256:29e99d1f0d955b6002cc80bc9f4e3c4d7fb80d62da8e153b582ba18529d0a0a0" } } diff --git a/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/manifest.json.sig b/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/manifest.json.sig index d927149..53eeada 100644 --- a/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/manifest.json.sig +++ b/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/manifest.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:a7bac552c206a4a3a86a0261d6e8efe4e374b62780e692669d4055c7f2933178" + "value": "sha256:a529b79e6b23e784176b3f2f78334d26b5d7b8dba15b66262829eda3b15d360b" } diff --git a/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/module.wasm b/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/module.wasm index 852d975..4d88a6a 100644 Binary files a/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/module.wasm and b/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/module.wasm differ diff --git a/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/version.json.sig b/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/version.json.sig index 2a5951d..250c2e2 100644 --- a/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/version.json.sig +++ b/beam-apps/fixtures/unsupported-beam/apps/uniswap/1.0.2/version.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:c066a56b6703a1d421195be37474192a11a960ce259991ff3cb91fd2f062c8de" + "value": "sha256:447860c5bbc66e4f9b418bb2c67155a5c276812828da8c417a20387882fee0dc" } diff --git a/beam-apps/fixtures/unsupported-beam/index.json b/beam-apps/fixtures/unsupported-beam/index.json index d6d0817..b3923c1 100644 --- a/beam-apps/fixtures/unsupported-beam/index.json +++ b/beam-apps/fixtures/unsupported-beam/index.json @@ -12,13 +12,13 @@ "version": "1.0.2", "min_beam_version": "0.2.1", "manifest_url": "https://registry.beam.payy.network/apps/uniswap/1.0.2/manifest.json", - "manifest_sha256": "sha256:07b1223ee0855508eb0c46743a3345e96422f97a7cf12175c504bfee8850341b", + "manifest_sha256": "sha256:b3bee997c062dbf20de57c4176a010b3578bf4a5e7c8a57017733bd5a95e2b73", "module_url": "https://registry.beam.payy.network/apps/uniswap/1.0.2/module.wasm", - "module_sha256": "sha256:18aa6e8845772beccef895e83bbf2d1f3783d24fbf0fb8feee75d1eacfa1a7f9", + "module_sha256": "sha256:58c5dbc8343f5281392269b72e0193b2c39f6a4b36942df1bea464699a36cdc2", "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:c066a56b6703a1d421195be37474192a11a960ce259991ff3cb91fd2f062c8de" + "value": "sha256:447860c5bbc66e4f9b418bb2c67155a5c276812828da8c417a20387882fee0dc" } } ] @@ -27,6 +27,6 @@ "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:02b57fe91af9b8247f4b71214bbe382ea4b09a63461bd05c5e59e61987a5c98d" + "value": "sha256:f1ad11d143f310620be1bc78a9d02fb7db7ec51d05acae195c1115167aef3dee" } } diff --git a/beam-apps/fixtures/unsupported-beam/index.json.sig b/beam-apps/fixtures/unsupported-beam/index.json.sig index 1593252..4193503 100644 --- a/beam-apps/fixtures/unsupported-beam/index.json.sig +++ b/beam-apps/fixtures/unsupported-beam/index.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:02b57fe91af9b8247f4b71214bbe382ea4b09a63461bd05c5e59e61987a5c98d" + "value": "sha256:f1ad11d143f310620be1bc78a9d02fb7db7ec51d05acae195c1115167aef3dee" } diff --git a/beam-apps/fixtures/valid/apps/uniswap/1.0.2/manifest.json b/beam-apps/fixtures/valid/apps/uniswap/1.0.2/manifest.json index 5ec8c40..4b4bf56 100644 --- a/beam-apps/fixtures/valid/apps/uniswap/1.0.2/manifest.json +++ b/beam-apps/fixtures/valid/apps/uniswap/1.0.2/manifest.json @@ -7,7 +7,7 @@ "description": "Prepare public Uniswap swaps through Beam-mediated HTTP, chain, approval, and transaction planning.", "min_beam_version": "0.2.1", "wasm": { - "sha256": "sha256:18aa6e8845772beccef895e83bbf2d1f3783d24fbf0fb8feee75d1eacfa1a7f9", + "sha256": "sha256:58c5dbc8343f5281392269b72e0193b2c39f6a4b36942df1bea464699a36cdc2", "entrypoint": "beam_app_main" }, "catalog": { @@ -290,6 +290,6 @@ "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:a7bac552c206a4a3a86a0261d6e8efe4e374b62780e692669d4055c7f2933178" + "value": "sha256:a529b79e6b23e784176b3f2f78334d26b5d7b8dba15b66262829eda3b15d360b" } } diff --git a/beam-apps/fixtures/valid/apps/uniswap/1.0.2/manifest.json.sig b/beam-apps/fixtures/valid/apps/uniswap/1.0.2/manifest.json.sig index d927149..53eeada 100644 --- a/beam-apps/fixtures/valid/apps/uniswap/1.0.2/manifest.json.sig +++ b/beam-apps/fixtures/valid/apps/uniswap/1.0.2/manifest.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:a7bac552c206a4a3a86a0261d6e8efe4e374b62780e692669d4055c7f2933178" + "value": "sha256:a529b79e6b23e784176b3f2f78334d26b5d7b8dba15b66262829eda3b15d360b" } diff --git a/beam-apps/fixtures/valid/apps/uniswap/1.0.2/module.wasm b/beam-apps/fixtures/valid/apps/uniswap/1.0.2/module.wasm index 852d975..4d88a6a 100644 Binary files a/beam-apps/fixtures/valid/apps/uniswap/1.0.2/module.wasm and b/beam-apps/fixtures/valid/apps/uniswap/1.0.2/module.wasm differ diff --git a/beam-apps/fixtures/valid/apps/uniswap/1.0.2/version.json.sig b/beam-apps/fixtures/valid/apps/uniswap/1.0.2/version.json.sig index 2a5951d..250c2e2 100644 --- a/beam-apps/fixtures/valid/apps/uniswap/1.0.2/version.json.sig +++ b/beam-apps/fixtures/valid/apps/uniswap/1.0.2/version.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:c066a56b6703a1d421195be37474192a11a960ce259991ff3cb91fd2f062c8de" + "value": "sha256:447860c5bbc66e4f9b418bb2c67155a5c276812828da8c417a20387882fee0dc" } diff --git a/beam-apps/fixtures/valid/index.json b/beam-apps/fixtures/valid/index.json index d6d0817..b3923c1 100644 --- a/beam-apps/fixtures/valid/index.json +++ b/beam-apps/fixtures/valid/index.json @@ -12,13 +12,13 @@ "version": "1.0.2", "min_beam_version": "0.2.1", "manifest_url": "https://registry.beam.payy.network/apps/uniswap/1.0.2/manifest.json", - "manifest_sha256": "sha256:07b1223ee0855508eb0c46743a3345e96422f97a7cf12175c504bfee8850341b", + "manifest_sha256": "sha256:b3bee997c062dbf20de57c4176a010b3578bf4a5e7c8a57017733bd5a95e2b73", "module_url": "https://registry.beam.payy.network/apps/uniswap/1.0.2/module.wasm", - "module_sha256": "sha256:18aa6e8845772beccef895e83bbf2d1f3783d24fbf0fb8feee75d1eacfa1a7f9", + "module_sha256": "sha256:58c5dbc8343f5281392269b72e0193b2c39f6a4b36942df1bea464699a36cdc2", "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:c066a56b6703a1d421195be37474192a11a960ce259991ff3cb91fd2f062c8de" + "value": "sha256:447860c5bbc66e4f9b418bb2c67155a5c276812828da8c417a20387882fee0dc" } } ] @@ -27,6 +27,6 @@ "signature": { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:02b57fe91af9b8247f4b71214bbe382ea4b09a63461bd05c5e59e61987a5c98d" + "value": "sha256:f1ad11d143f310620be1bc78a9d02fb7db7ec51d05acae195c1115167aef3dee" } } diff --git a/beam-apps/fixtures/valid/index.json.sig b/beam-apps/fixtures/valid/index.json.sig index 1593252..4193503 100644 --- a/beam-apps/fixtures/valid/index.json.sig +++ b/beam-apps/fixtures/valid/index.json.sig @@ -1,5 +1,5 @@ { "algorithm": "sha256-dev", "key_id": "payy-dev-2026-05", - "value": "sha256:02b57fe91af9b8247f4b71214bbe382ea4b09a63461bd05c5e59e61987a5c98d" + "value": "sha256:f1ad11d143f310620be1bc78a9d02fb7db7ec51d05acae195c1115167aef3dee" } diff --git a/pkg/beam-cli/src/apps/host.rs b/pkg/beam-cli/src/apps/host.rs index 38eee14..f342d8e 100644 --- a/pkg/beam-cli/src/apps/host.rs +++ b/pkg/beam-cli/src/apps/host.rs @@ -33,6 +33,8 @@ pub struct HostMetadata { pub app_version: String, pub chain: String, pub chain_id: u64, + #[serde(default)] + pub debug: bool, pub host_api_version: u32, pub manifest_sha256: String, pub now: u64, diff --git a/pkg/beam-cli/src/apps/runtime.rs b/pkg/beam-cli/src/apps/runtime.rs index 8a175e6..6e8ff04 100644 --- a/pkg/beam-cli/src/apps/runtime.rs +++ b/pkg/beam-cli/src/apps/runtime.rs @@ -2,29 +2,30 @@ use std::path::Path; use contextful::ResultContextExt; -use serde::{Deserialize, Serialize}; -use serde_json::Value; use wasmi::{Config, Engine, Linker, Module, Store}; use crate::{ apps::{ Error, Result, host::HostMetadata, - model::{ActionPlan, AppManifest, InstalledApp}, + model::{AppManifest, InstalledApp}, store::now, }, - output::{CommandOutput, OutputMode}, runtime::BeamApp, }; +mod command; +mod debug; mod guest; -use guest::{HostState, guest_alloc, register_host_imports, typed_func, unpack_ptr_len}; +pub use command::GuestCommandResult; +use command::{CommandRun, run_guest_command}; +use debug::{app_debug, app_debug_enabled}; +use guest::{HostState, register_host_imports, typed_func}; const WASM_MAGIC: &[u8; 4] = b"\0asm"; -const HOST_API_VERSION: u32 = 1; -const MAX_GUEST_RESPONSE_BYTES: usize = 2 * 1024 * 1024; -const WASM_FUEL: u64 = 30_000_000; +pub(super) const HOST_API_VERSION: u32 = 1; +pub(super) const WASM_FUEL: u64 = 30_000_000; pub fn validate_wasm_module(app_id: &str, entrypoint: &str, path: &Path) -> Result<()> { let bytes = std::fs::read(path).context("read beam app wasm module")?; @@ -99,81 +100,25 @@ impl AppRuntime { module_path: &Path, args: &[String], ) -> Result { - let bytes = std::fs::read(module_path).context("read beam app wasm module")?; - let module = Module::new(&self.engine, &bytes).context("compile beam app wasm module")?; + app_debug(&format!( + "run command start app={} version={} args={}", + manifest.id, + manifest.version, + args.len() + )); let metadata = self.metadata(app, manifest, installed).await?; - let invocation = GuestInvocation { + run_guest_command(CommandRun { + app: app.clone(), args: args.to_vec(), - host_api_version: HOST_API_VERSION, - metadata: metadata.clone(), - output_mode: output_mode_label(app.output_mode).to_string(), - }; - let mut store = Store::new( - &self.engine, - HostState::new(app.clone(), manifest.permissions.clone(), metadata), - ); - store - .set_fuel(WASM_FUEL) - .context("set beam app wasm fuel")?; - store.limiter(|state| &mut state.limits); - let mut linker = >::new(&self.engine); - register_host_imports(&mut linker)?; - let instance = linker - .instantiate_and_start(&mut store, &module) - .context("instantiate beam app wasm module")?; - - let memory = - instance - .get_memory(&store, "memory") - .ok_or_else(|| Error::MissingWasmExport { - app: manifest.id.clone(), - export: "memory".to_string(), - })?; - let alloc = typed_func::(&store, &instance, "beam_alloc", &manifest.id)?; - let free = typed_func::<(i32, i32), ()>(&store, &instance, "beam_free", &manifest.id)?; - let main = typed_func::<(i32, i32), i64>( - &store, - &instance, - &manifest.wasm.entrypoint, - &manifest.id, - )?; - let input = serde_json::to_vec(&invocation).context("serialize beam app invocation")?; - let input_ptr = guest_alloc(&mut store, &alloc, input.len())?; - memory - .write(&mut store, input_ptr, &input) - .context("write beam app invocation")?; - let packed = main - .call(&mut store, (input_ptr as i32, input.len() as i32)) - .context("call beam app command")?; - free.call(&mut store, (input_ptr as i32, input.len() as i32)) - .context("free beam app invocation")?; - let (output_ptr, output_len) = unpack_ptr_len(packed)?; - if output_len > MAX_GUEST_RESPONSE_BYTES { - return Err(Error::InvalidGuestOutput { - reason: format!("guest response too large: {output_len} bytes"), - }); - } - let mut output = vec![0_u8; output_len]; - memory - .read(&store, output_ptr, &mut output) - .context("read beam app command output")?; - free.call(&mut store, (output_ptr as i32, output_len as i32)) - .context("free beam app command output")?; - - let response = - serde_json::from_slice::(&output).context("decode beam app output")?; - match response { - GuestResponse::ActionPlan { plan } => Ok(GuestCommandResult::ActionPlan(*plan)), - GuestResponse::Output { value } => { - let text = value - .get("message") - .and_then(Value::as_str) - .unwrap_or("App command completed") - .to_string(); - Ok(GuestCommandResult::Output(CommandOutput::new(text, value))) - } - GuestResponse::Error { message } => Err(Error::GuestCommandFailed { message }), - } + engine: self.engine.clone(), + entrypoint: manifest.wasm.entrypoint.clone(), + manifest_id: manifest.id.clone(), + metadata, + module_path: module_path.to_path_buf(), + output_mode: app.output_mode, + permissions: manifest.permissions.clone(), + runtime_handle: tokio::runtime::Handle::current(), + }) } async fn metadata( @@ -192,6 +137,7 @@ impl AppRuntime { app_version: manifest.version.clone(), chain: chain.entry.key, chain_id: chain.entry.chain_id, + debug: app_debug_enabled(), host_api_version: HOST_API_VERSION, manifest_sha256: installed.manifest_sha256.clone(), now: now(), @@ -200,36 +146,3 @@ impl AppRuntime { }) } } - -#[derive(Debug)] -pub enum GuestCommandResult { - ActionPlan(ActionPlan), - Output(CommandOutput), -} - -#[derive(Serialize)] -struct GuestInvocation { - args: Vec, - host_api_version: u32, - metadata: HostMetadata, - output_mode: String, -} - -#[derive(Deserialize)] -#[serde(tag = "kind", rename_all = "kebab-case")] -enum GuestResponse { - ActionPlan { plan: Box }, - Output { value: Value }, - Error { message: String }, -} - -fn output_mode_label(mode: OutputMode) -> &'static str { - match mode { - OutputMode::Default => "default", - OutputMode::Json => "json", - OutputMode::Yaml => "yaml", - OutputMode::Markdown => "markdown", - OutputMode::Compact => "compact", - OutputMode::Quiet => "quiet", - } -} diff --git a/pkg/beam-cli/src/apps/runtime/command.rs b/pkg/beam-cli/src/apps/runtime/command.rs new file mode 100644 index 0000000..ed3f465 --- /dev/null +++ b/pkg/beam-cli/src/apps/runtime/command.rs @@ -0,0 +1,184 @@ +use std::{path::PathBuf, thread}; + +use contextful::ResultContextExt; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use tokio::runtime::Handle; +use wasmi::{Engine, Linker, Module, Store}; + +use crate::{ + apps::{ + Error, Result, + host::HostMetadata, + model::{ActionPlan, AppPermissions}, + }, + output::{CommandOutput, OutputMode}, + runtime::BeamApp, +}; + +use super::{ + HOST_API_VERSION, WASM_FUEL, + debug::app_debug, + guest::{HostState, guest_alloc, register_host_imports, typed_func, unpack_ptr_len}, +}; + +const MAX_GUEST_RESPONSE_BYTES: usize = 2 * 1024 * 1024; +// wasmi uses native stack while interpreting long guest calls; run app execution +// off the main thread with enough headroom for JSON-heavy app flows. +const WASM_THREAD_STACK_BYTES: usize = 512 * 1024 * 1024; + +pub(super) struct CommandRun { + pub(super) app: BeamApp, + pub(super) args: Vec, + pub(super) engine: Engine, + pub(super) entrypoint: String, + pub(super) manifest_id: String, + pub(super) metadata: HostMetadata, + pub(super) module_path: PathBuf, + pub(super) output_mode: OutputMode, + pub(super) permissions: AppPermissions, + pub(super) runtime_handle: Handle, +} + +#[derive(Debug)] +pub enum GuestCommandResult { + ActionPlan(ActionPlan), + Output(CommandOutput), +} + +pub(super) fn run_guest_command(input: CommandRun) -> Result { + let thread_name = format!("beam-app-wasm-{}", input.manifest_id); + let thread = thread::Builder::new() + .name(thread_name) + .stack_size(WASM_THREAD_STACK_BYTES) + .spawn(|| run_guest_command_on_thread(input)) + .context("spawn beam app wasm thread")?; + + thread.join().map_err(|_| Error::InvalidGuestOutput { + reason: "beam app wasm thread panicked".to_string(), + })? +} + +fn run_guest_command_on_thread(input: CommandRun) -> Result { + let bytes = std::fs::read(&input.module_path).context("read beam app wasm module")?; + app_debug(&format!("wasm module read bytes={}", bytes.len())); + let module = Module::new(&input.engine, &bytes).context("compile beam app wasm module")?; + app_debug("wasm module compiled"); + let invocation = GuestInvocation { + args: input.args, + host_api_version: HOST_API_VERSION, + metadata: input.metadata.clone(), + output_mode: output_mode_label(input.output_mode).to_string(), + }; + let mut store = Store::new( + &input.engine, + HostState::new( + input.app, + input.permissions, + input.metadata, + input.runtime_handle, + ), + ); + store + .set_fuel(WASM_FUEL) + .context("set beam app wasm fuel")?; + store.limiter(|state| &mut state.limits); + let mut linker = >::new(&input.engine); + register_host_imports(&mut linker)?; + let instance = linker + .instantiate_and_start(&mut store, &module) + .context("instantiate beam app wasm module")?; + app_debug("wasm module instantiated"); + + let memory = instance + .get_memory(&store, "memory") + .ok_or_else(|| Error::MissingWasmExport { + app: input.manifest_id.clone(), + export: "memory".to_string(), + })?; + let alloc = typed_func::(&store, &instance, "beam_alloc", &input.manifest_id)?; + let free = typed_func::<(i32, i32), ()>(&store, &instance, "beam_free", &input.manifest_id)?; + let main = + typed_func::<(i32, i32), i64>(&store, &instance, &input.entrypoint, &input.manifest_id)?; + let input_json = serde_json::to_vec(&invocation).context("serialize beam app invocation")?; + app_debug(&format!( + "guest invocation serialized bytes={}", + input_json.len() + )); + let input_ptr = guest_alloc(&mut store, &alloc, input_json.len())?; + memory + .write(&mut store, input_ptr, &input_json) + .context("write beam app invocation")?; + app_debug("calling guest entrypoint"); + let packed = main + .call(&mut store, (input_ptr as i32, input_json.len() as i32)) + .context("call beam app command")?; + app_debug("guest entrypoint returned"); + free.call(&mut store, (input_ptr as i32, input_json.len() as i32)) + .context("free beam app invocation")?; + read_guest_response(&mut store, &memory, &free, packed) +} + +fn read_guest_response( + store: &mut Store, + memory: &wasmi::Memory, + free: &wasmi::TypedFunc<(i32, i32), ()>, + packed: i64, +) -> Result { + let (output_ptr, output_len) = unpack_ptr_len(packed)?; + app_debug(&format!("guest output ptr={output_ptr} bytes={output_len}")); + if output_len > MAX_GUEST_RESPONSE_BYTES { + return Err(Error::InvalidGuestOutput { + reason: format!("guest response too large: {output_len} bytes"), + }); + } + let mut output = vec![0_u8; output_len]; + memory + .read(&*store, output_ptr, &mut output) + .context("read beam app command output")?; + free.call(store, (output_ptr as i32, output_len as i32)) + .context("free beam app command output")?; + + let response = + serde_json::from_slice::(&output).context("decode beam app output")?; + app_debug("guest output decoded"); + match response { + GuestResponse::ActionPlan { plan } => Ok(GuestCommandResult::ActionPlan(*plan)), + GuestResponse::Output { value } => { + let text = value + .get("message") + .and_then(Value::as_str) + .unwrap_or("App command completed") + .to_string(); + Ok(GuestCommandResult::Output(CommandOutput::new(text, value))) + } + GuestResponse::Error { message } => Err(Error::GuestCommandFailed { message }), + } +} + +#[derive(Serialize)] +struct GuestInvocation { + args: Vec, + host_api_version: u32, + metadata: HostMetadata, + output_mode: String, +} + +#[derive(Deserialize)] +#[serde(tag = "kind", rename_all = "kebab-case")] +enum GuestResponse { + ActionPlan { plan: Box }, + Output { value: Value }, + Error { message: String }, +} + +fn output_mode_label(mode: OutputMode) -> &'static str { + match mode { + OutputMode::Default => "default", + OutputMode::Json => "json", + OutputMode::Yaml => "yaml", + OutputMode::Markdown => "markdown", + OutputMode::Compact => "compact", + OutputMode::Quiet => "quiet", + } +} diff --git a/pkg/beam-cli/src/apps/runtime/debug.rs b/pkg/beam-cli/src/apps/runtime/debug.rs new file mode 100644 index 0000000..1595a49 --- /dev/null +++ b/pkg/beam-cli/src/apps/runtime/debug.rs @@ -0,0 +1,95 @@ +use std::env; + +use serde_json::Value; + +use crate::apps::host::HostRequest; + +pub(super) fn app_debug_enabled() -> bool { + env::var("BEAM_APP_DEBUG") + .map(|value| { + let value = value.to_ascii_lowercase(); + matches!(value.as_str(), "1" | "true" | "yes" | "on") + }) + .unwrap_or(false) +} + +pub(super) fn app_debug(message: &str) { + if app_debug_enabled() { + eprintln!("[beam-cli/apps/debug] {message}"); + } +} + +pub(super) fn host_request_summary(request: &HostRequest) -> String { + match request { + HostRequest::AppMetadata => "app-metadata".to_string(), + HostRequest::Args { args } => format!("args count={}", args.len()), + HostRequest::StructuredOutput { value } => { + format!("structured-output {}", value_shape(value)) + } + HostRequest::Diagnostic { level, message } => { + format!("diagnostic level={level} message={message}") + } + HostRequest::HttpFetch(request) => format!( + "http-fetch method={} url={} request_bytes={}", + request.method, + request.url, + request.body.len() + ), + HostRequest::ChainRead(request) => format!( + "chain-read operation={:?} chain={} target={} selector={}", + request.operation, + request.chain, + optional_value(&request.target), + optional_value(&request.selector) + ), + HostRequest::SimulateTransaction(transaction) => format!( + "simulate-transaction chain={} target={} selector={} spender={}", + transaction.chain, + transaction.target, + optional_value(&transaction.selector), + optional_value(&transaction.spender) + ), + HostRequest::SubmitTransaction(transaction) => format!( + "submit-transaction chain={} target={} selector={}", + transaction.chain, + transaction.target, + optional_value(&transaction.selector) + ), + HostRequest::PollReceipt { tx_hash } => format!("poll-receipt tx_hash={tx_hash}"), + HostRequest::ResolveAddress { value } => { + format!("resolve-address provided={}", value.is_some()) + } + HostRequest::AppStorageGet { key } => format!("storage-get key={key}"), + HostRequest::AppStorageSet { key, value } => { + format!("storage-set key={key} {}", value_shape(value)) + } + HostRequest::AppStorageRemove { key } => format!("storage-remove key={key}"), + } +} + +pub(super) fn host_value_summary(value: &Value) -> String { + if let Some(status) = value.get("status").and_then(Value::as_u64) { + let body_bytes = value + .get("body") + .and_then(Value::as_array) + .map(Vec::len) + .unwrap_or_default(); + return format!("status={status} response_bytes={body_bytes}"); + } + value_shape(value) +} + +fn value_shape(value: &Value) -> String { + match value { + Value::Array(values) => format!("array_len={}", values.len()), + Value::Object(values) => format!("object_keys={}", values.len()), + Value::String(value) => format!("string_len={}", value.len()), + Value::Number(_) => "number".to_string(), + Value::Bool(_) => "bool".to_string(), + Value::Null => "null".to_string(), + } +} + +fn optional_value(value: &Option) -> &str { + value.as_deref().unwrap_or("") +} diff --git a/pkg/beam-cli/src/apps/runtime/guest.rs b/pkg/beam-cli/src/apps/runtime/guest.rs index 4cf1394..24e8ed1 100644 --- a/pkg/beam-cli/src/apps/runtime/guest.rs +++ b/pkg/beam-cli/src/apps/runtime/guest.rs @@ -16,6 +16,8 @@ use crate::{ runtime::BeamApp, }; +use super::debug::{app_debug, app_debug_enabled, host_request_summary, host_value_summary}; + const MAX_WASM_MEMORY_BYTES: usize = 64 * 1024 * 1024; pub(super) struct HostState { @@ -24,11 +26,17 @@ pub(super) struct HostState { pub(super) limits: StoreLimits, metadata: Option, permissions: Option, + runtime_handle: Option, structured_output: Option, } impl HostState { - pub(super) fn new(app: BeamApp, permissions: AppPermissions, metadata: HostMetadata) -> Self { + pub(super) fn new( + app: BeamApp, + permissions: AppPermissions, + metadata: HostMetadata, + runtime_handle: Handle, + ) -> Self { Self { app: Some(app), diagnostics: Vec::new(), @@ -37,6 +45,7 @@ impl HostState { .build(), metadata: Some(metadata), permissions: Some(permissions), + runtime_handle: Some(runtime_handle), structured_output: None, } } @@ -50,6 +59,7 @@ impl HostState { .build(), metadata: None, permissions: None, + runtime_handle: None, structured_output: None, } } @@ -148,6 +158,10 @@ fn host_call( .context("read beam app host request")?; let request = serde_json::from_slice::(&request_bytes) .context("decode beam app host request")?; + let request_summary = app_debug_enabled().then(|| host_request_summary(&request)); + if let Some(summary) = &request_summary { + app_debug(&format!("host call start {summary}")); + } let app = caller .data() .app @@ -172,18 +186,33 @@ fn host_call( })?; let mut structured_output = caller.data().structured_output.clone(); let mut diagnostics = caller.data().diagnostics.clone(); - let result = tokio::task::block_in_place(|| { - Handle::current().block_on(handle_host_request( - &app, - &permissions, - &metadata, - request, - &mut structured_output, - &mut diagnostics, - )) - }); + let runtime_handle = + caller + .data() + .runtime_handle + .clone() + .ok_or_else(|| Error::InvalidHostRequest { + reason: "host runtime handle missing".to_string(), + })?; + let result = runtime_handle.block_on(handle_host_request( + &app, + &permissions, + &metadata, + request, + &mut structured_output, + &mut diagnostics, + )); caller.data_mut().structured_output = structured_output; caller.data_mut().diagnostics = diagnostics; + if let Some(summary) = &request_summary { + match &result { + Ok(value) => app_debug(&format!( + "host call ok {summary}; {}", + host_value_summary(value) + )), + Err(error) => app_debug(&format!("host call error {summary}; {error}")), + } + } let response = match result { Ok(value) => HostCallResponse::ok(value), Err(error) => HostCallResponse::error(format_error_chain(&error)),