switchboard_starknet_sdk/
lib.rs1pub mod sdk;
2pub use sdk::*;
3
4pub mod utils;
5pub use utils::*;
6
7use std::env;
8use std::sync::Arc;
9pub use switchboard_common::{
10 ChainResultInfo,
11 StarknetCall,
12 StarknetFunctionResult,
13 StarknetFunctionResultV0,
14 FunctionResult,
15 Gramine,
16 FunctionResultV1,
17 StarknetFunctionRequestType,
18 SbError as SwitchboardClientError,
19};
20use starknet_crypto::poseidon_hash_many;
21use starknet::{
22 core::types::FieldElement,
23 accounts::Call as NativeCall,
24 signers::{LocalWallet, SigningKey},
25};
26use crate::utils::{generate_signer, load_env_felt};
27
28pub trait SbFunctionParameters {
29 fn decode(data: &[u8]) -> Option<Self>
30 where
31 Self: Sized;
32}
33
34#[derive(Clone, Copy, Debug, Default)]
35pub struct NoParams;
36impl SbFunctionParameters for NoParams {
37 fn decode(_data: &[u8]) -> Option<Self>
38 where
39 Self: Sized,
40 {
41 Some(Self {})
42 }
43}
44
45
46#[derive(Clone)]
47pub struct StarknetFunctionRunner {
48 pub function_id: FieldElement,
49 pub enclave_key: SigningKey,
50 pub enclave_wallet: LocalWallet,
51 pub signer: FieldElement,
52 pub verifying_contract: FieldElement,
53 pub chain_id: u64,
54 pub call_id: FieldElement,
55 pub params: Vec<FieldElement>,
56 pub is_routine: bool,
57 pub call_data: Vec<u8>,
58}
59
60impl std::fmt::Display for StarknetFunctionRunner {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 write!(
63 f,
64 "SwitchboardFunctionRunner: signer: {}, verifying_contract: {}, function_id: {}",
65 self.signer,
66 self.verifying_contract.to_string(),
67 self.function_id.to_string(),
68 )
69 }
70}
71
72impl StarknetFunctionRunner {
73 pub fn new() -> Result<StarknetFunctionRunner, SwitchboardClientError> {
74 let enclave_key = generate_signer();
75 let enclave_wallet = LocalWallet::from_signing_key(enclave_key.clone());
76 let signer = enclave_key.verifying_key().scalar();
77 let chain_id = env::var("CHAIN_ID").unwrap();
78 let verifying_contract = load_env_felt("VERIFYING_CONTRACT")?;
79 let function_id = load_env_felt("FUNCTION_KEY")?;
80
81 let mut is_routine: bool = true;
82 let params: Vec<FieldElement>;
83 let data: Vec<u8>;
84
85 let call_id = if let Some(key) = load_env_felt("FUNCTION_ROUTINE_KEY").ok() {
86 let routine_data = env::var("FUNCTION_ROUTINE_DATA").unwrap();
88 data = routine_data.clone().into_bytes().to_vec();
89 let routine: ViewRoutine = routine_data.try_into()?;
90 params = routine.params;
91 key
92 } else {
93 is_routine = false;
94 let request_data = env::var("FUNCTION_REQUEST_DATA").unwrap();
95 data = request_data.clone().into_bytes().to_vec();
96 let request: ViewRequest = request_data.try_into()?;
97 params = request.params;
98 load_env_felt("FUNCTION_REQUEST_KEY").expect(
99 "FUNCTION_REQUEST_KEY or FUNCTION_ROUTINE_KEY must be set"
100 )
101 };
102
103 Ok(Self {
104 function_id,
105 enclave_key,
106 enclave_wallet,
107 signer,
108 verifying_contract,
109 params,
110 call_id,
111 chain_id: chain_id.parse().unwrap_or(1),
112 is_routine,
113 call_data: data,
114 })
115 }
116 pub fn get_result(
117 &self,
118 calls: Vec<NativeCall>,
119 ) -> Result<FunctionResult, SwitchboardClientError> {
120 let txs: Vec<StarknetCall> = calls
121 .into_iter()
122 .map(|call| {
123 let txn = StarknetCall {
124 to: call.to.to_bytes_be().to_vec(),
125 selector: call.selector.to_bytes_be().to_vec(),
126 calldata: call.calldata.iter().map(|p| p.to_bytes_be().to_vec()).collect(),
127 };
128 txn
129 })
130 .collect();
131
132 let chain_result_info = ChainResultInfo::Starknet(
133 StarknetFunctionResult::V0(
134 StarknetFunctionResultV0 {
135 txs,
136 request_type: if self.is_routine {
137 StarknetFunctionRequestType::Routine(
138 self.call_data.clone()
139 )
140 } else {
141 StarknetFunctionRequestType::Request(
142 self.call_data.clone()
143 )
144 },
145 function_id: self.function_id.to_bytes_be().to_vec(),
146 function_request_id: self.call_id.to_bytes_be().to_vec(),
147 },
148 ),
149 );
150
151 let quote_raw =
152 Gramine::generate_quote(&self.enclave_key.verifying_key().scalar().to_bytes_be())
153 .unwrap_or_default();
154
155 if quote_raw.len() == 0 {
156 println!(
157 "WARNING: Error generating quote. This is likely due to the enclave not being initialized."
158 )
159 }
160
161 Ok(FunctionResult::V1(FunctionResultV1 {
162 quote: quote_raw,
163 signer: self
164 .enclave_key
165 .verifying_key()
166 .scalar()
167 .to_bytes_be()
168 .to_vec(),
169 signature: format!(
170 "{}",
171 self.enclave_key
172 .sign(&hash(&chain_result_info))
173 .unwrap()
174 ).into_bytes().to_vec(),
175 chain_result_info,
176 error_code: 0,
177 }))
178 }
179
180 pub fn emit(
184 &self,
185 calls: Vec<NativeCall>, ) -> Result<(), SwitchboardClientError> {
187 self.get_result(calls)
188 .map_err(|e| SwitchboardClientError::CustomError {
189 message: "failed to run function verify".to_string(),
190 source: Arc::new(e),
191 })
192 .unwrap()
193 .emit();
194 Ok(())
195 }
196
197 pub fn emit_error(
199 &self,
200 error_code: u8,
201 ) -> Result<(), SwitchboardClientError> {
202
203 let chain_result_info = ChainResultInfo::Starknet(
204 StarknetFunctionResult::V0(
205 StarknetFunctionResultV0 {
206 txs: vec![],
207 request_type: if self.is_routine {
208 StarknetFunctionRequestType::Routine(
209 self.call_data.clone()
210 )
211 } else {
212 StarknetFunctionRequestType::Request(
213 self.call_data.clone()
214 )
215 },
216 function_id: self.function_id.to_bytes_be().to_vec(),
217 function_request_id: self.call_id.to_bytes_be().to_vec(),
218 },
219 ),
220 );
221 let quote_raw =
222 Gramine::generate_quote(&self.enclave_key.verifying_key().scalar().to_bytes_be())
223 .unwrap_or_default();
224
225 if quote_raw.len() == 0 {
226 println!(
227 "WARNING: Error generating quote. This is likely due to the enclave not being initialized."
228 )
229 }
230
231 FunctionResult::V1(FunctionResultV1 {
232 quote: quote_raw,
233 signer: self
234 .enclave_key
235 .verifying_key()
236 .scalar()
237 .to_bytes_be()
238 .to_vec(),
239 signature: format!(
240 "{}",
241 self.enclave_key
242 .sign(&hash(&chain_result_info))
243 .unwrap()
244 ).into_bytes().to_vec(),
245 chain_result_info,
246 error_code,
247 }).emit();
248
249 Ok(())
250 }
251}
252
253fn hash(chain_result_info: &ChainResultInfo) -> FieldElement {
254 let chain_result_info_json = serde_json::to_string(chain_result_info).unwrap();
255 poseidon_hash_many(&str_to_cairo_long_string(&chain_result_info_json))
256}