Skip to main content

boundless_market/request_builder/
request_id_layer.rs

1// Copyright 2026 Boundless Foundation, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::{Adapt, Layer, RequestParams};
16use crate::{contracts::boundless_market::BoundlessMarketService, contracts::RequestId};
17use alloy::{network::Ethereum, providers::Provider};
18use derive_builder::Builder;
19
20/// Mode for generating request IDs.
21///
22/// Controls how the request ID's index is generated.
23#[non_exhaustive]
24#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
25pub enum RequestIdLayerMode {
26    /// Generate a random ID for each request.
27    ///
28    /// This is the default mode and generates unpredictable request IDs.
29    #[default]
30    Rand,
31
32    /// Use the account's nonce as the ID.
33    ///
34    /// This creates sequential IDs based on the number of transactions sent from the account.
35    Nonce,
36}
37
38/// Configuration for the [RequestIdLayer].
39///
40/// Controls how request IDs are generated.
41#[non_exhaustive]
42#[derive(Clone, Builder)]
43pub struct RequestIdLayerConfig {
44    /// The mode to use for generating request IDs.
45    #[builder(default)]
46    pub mode: RequestIdLayerMode,
47}
48
49/// A layer responsible for generating request IDs.
50///
51/// This layer creates unique identifiers for proof requests based on the
52/// configured generation mode.
53#[non_exhaustive]
54#[derive(Clone)]
55pub struct RequestIdLayer<P> {
56    /// The BoundlessMarket service used to generate request IDs.
57    pub boundless_market: BoundlessMarketService<P>,
58
59    /// Configuration controlling ID generation behavior.
60    pub config: RequestIdLayerConfig,
61}
62
63impl RequestIdLayerConfig {
64    /// Creates a new builder for constructing a [RequestIdLayerConfig].
65    ///
66    /// This provides a way to customize the request ID generation behavior.
67    pub fn builder() -> RequestIdLayerConfigBuilder {
68        Default::default()
69    }
70}
71
72impl<P: Clone> From<BoundlessMarketService<P>> for RequestIdLayer<P> {
73    fn from(boundless_market: BoundlessMarketService<P>) -> Self {
74        RequestIdLayer { boundless_market, config: Default::default() }
75    }
76}
77
78impl Default for RequestIdLayerConfig {
79    fn default() -> Self {
80        Self::builder().build().expect("implementation error in Default for RequestIdLayerConfig")
81    }
82}
83
84impl<P> RequestIdLayer<P> {
85    /// Creates a new [RequestIdLayer] with the specified market service and configuration.
86    ///
87    /// The BoundlessMarket service is used to generate request IDs according to the
88    /// configured mode.
89    pub fn new(boundless_market: BoundlessMarketService<P>, config: RequestIdLayerConfig) -> Self {
90        Self { boundless_market, config }
91    }
92}
93
94impl<P> Layer<()> for RequestIdLayer<P>
95where
96    P: Provider<Ethereum> + 'static + Clone,
97{
98    type Output = RequestId;
99    type Error = anyhow::Error;
100
101    async fn process(&self, (): ()) -> Result<Self::Output, Self::Error> {
102        let id_u256 = match self.config.mode {
103            RequestIdLayerMode::Nonce => self.boundless_market.request_id_from_nonce().await?,
104            RequestIdLayerMode::Rand => self.boundless_market.request_id_from_rand().await?,
105        };
106        Ok(id_u256.try_into().expect("generated request ID should always be valid"))
107    }
108}
109
110impl<P> Adapt<RequestIdLayer<P>> for RequestParams
111where
112    P: Provider<Ethereum> + 'static + Clone,
113{
114    type Output = RequestParams;
115    type Error = anyhow::Error;
116
117    async fn process_with(self, layer: &RequestIdLayer<P>) -> Result<Self::Output, Self::Error> {
118        tracing::trace!("Processing {self:?} with RequestIdLayer");
119
120        if self.request_id.is_some() {
121            return Ok(self);
122        }
123
124        let request_id = layer.process(()).await?;
125        Ok(self.with_request_id(request_id))
126    }
127}