From d1e9198d4ff6783ae0e51b78372b34d0afe55d49 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 1 Apr 2025 14:30:43 +0900 Subject: [PATCH 1/7] feat(controller): start generalizing to support redirects for mobile --- dojo.h | 120 ++++++++--------- dojo.hpp | 75 ++++++----- dojo.pyx | 98 +++++++------- src/c/mod.rs | 364 +++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 417 insertions(+), 240 deletions(-) diff --git a/dojo.h b/dojo.h index 42b7282..b70bd96 100644 --- a/dojo.h +++ b/dojo.h @@ -7,10 +7,10 @@ namespace dojo_bindings { #endif // __cplusplus -struct ToriiClient; struct Policy; struct ControllerAccount; struct Call; +struct ToriiClient; struct Controller; struct Entity; struct Query; @@ -64,26 +64,22 @@ typedef struct Error { char *message; } Error; -typedef enum ResultToriiClient_Tag { - OkToriiClient, - ErrToriiClient, -} ResultToriiClient_Tag; +typedef enum Resultbool_Tag { + Okbool, + Errbool, +} Resultbool_Tag; -typedef struct ResultToriiClient { - ResultToriiClient_Tag tag; +typedef struct Resultbool { + Resultbool_Tag tag; union { struct { - struct ToriiClient *ok; + bool ok; }; struct { struct Error err; }; }; -} ResultToriiClient; - -typedef struct FieldElement { - uint8_t data[32]; -} FieldElement; +} Resultbool; typedef enum ResultControllerAccount_Tag { OkControllerAccount, @@ -102,22 +98,9 @@ typedef struct ResultControllerAccount { }; } ResultControllerAccount; -typedef enum Resultbool_Tag { - Okbool, - Errbool, -} Resultbool_Tag; - -typedef struct Resultbool { - Resultbool_Tag tag; - union { - struct { - bool ok; - }; - struct { - struct Error err; - }; - }; -} Resultbool; +typedef struct FieldElement { + uint8_t data[32]; +} FieldElement; typedef enum ResultFieldElement_Tag { OkFieldElement, @@ -785,29 +768,14 @@ typedef struct EnumOption { extern "C" { #endif // __cplusplus -/** - * Creates a new Torii client instance - * - * # Parameters - * * `torii_url` - URL of the Torii server - * * `libp2p_relay_url` - URL of the libp2p relay server - * * `world` - World address as a FieldElement - * - * # Returns - * Result containing pointer to new ToriiClient instance or error - */ -struct ResultToriiClient client_new(const char *torii_url, - const char *libp2p_relay_url, - struct FieldElement world); - /** * Initiates a connection to establish a new session account * * This function: * 1. Generates a new signing key pair - * 2. Starts a local HTTP server to receive the callback - * 3. Opens the keychain session URL in browser - * 4. Waits for callback with session details + * 2. If redirect_uri is provided: Uses it for callback + * 3. If redirect_uri is null: Starts a local HTTP server for callback + * 4. Opens the keychain session URL in browser * 5. Creates and stores the session * 6. Calls the provided callback with the new session account * @@ -822,25 +790,53 @@ struct ResultToriiClient client_new(const char *torii_url, * * `policies` - Pointer to array of Policy structs defining session permissions * * `policies_len` - Length of the policies array * * `account_callback` - Function pointer called with the new session account when ready - * - * # Example - * ```c - * void on_account(SessionAccount* account) { - * // Handle new session account - * } - * - * controller_connect( - * "https://rpc.example.com", - * policies, - * policies_length, - * on_account - * ); - * ``` + * * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. + * If provided, will be used for callback instead of starting a local server. */ void controller_connect(const char *rpc_url, const struct Policy *policies, uintptr_t policies_len, - void (*account_callback)(struct ControllerAccount*)); + void (*account_callback)(struct ControllerAccount*), + const char *redirect_uri); + +/** + * Initiates a connection to establish a new session account using deep linking + * + * This function: + * 1. Generates a new signing key pair + * 2. Stores the signing key and callback state + * 3. Opens the keychain session URL with redirect URI + * 4. Returns immediately - app will be reopened via deep link + * + * # Safety + * This function is marked as unsafe because it: + * - Handles raw C pointers + * - Performs FFI operations + * - Manages system keyring entries + * + * # Parameters + * * `rpc_url` - Pointer to null-terminated string containing the RPC endpoint URL + * * `redirect_uri` - Pointer to null-terminated string containing the deep link URI + * * `policies` - Pointer to array of Policy structs defining session permissions + * * `policies_len` - Length of the policies array + * * `account_callback` - Function pointer called with the new session account when ready + */ +void controller_connect_mobile(const char *rpc_url, + const char *redirect_uri, + const struct Policy *policies, + uintptr_t policies_len, + void (*account_callback)(struct ControllerAccount*)); + +/** + * Handles the deep link callback when app is reopened + * + * # Parameters + * * `callback_data` - Base64 encoded callback data from the deep link + * + * # Returns + * Result containing success boolean or error + */ +struct Resultbool handle_deep_link_callback(const char *callback_data); /** * Retrieves a stored session account if one exists and is valid diff --git a/dojo.hpp b/dojo.hpp index 5bfe50a..cc939ab 100644 --- a/dojo.hpp +++ b/dojo.hpp @@ -6,10 +6,10 @@ namespace dojo_bindings { -struct ToriiClient; struct Policy; struct ControllerAccount; struct Call; +struct ToriiClient; struct Ty; struct Query; struct Subscription; @@ -907,26 +907,13 @@ struct EntityKeysClause { extern "C" { -/// Creates a new Torii client instance -/// -/// # Parameters -/// * `torii_url` - URL of the Torii server -/// * `libp2p_relay_url` - URL of the libp2p relay server -/// * `world` - World address as a FieldElement -/// -/// # Returns -/// Result containing pointer to new ToriiClient instance or error -Result client_new(const char *torii_url, - const char *libp2p_relay_url, - FieldElement world); - /// Initiates a connection to establish a new session account /// /// This function: /// 1. Generates a new signing key pair -/// 2. Starts a local HTTP server to receive the callback -/// 3. Opens the keychain session URL in browser -/// 4. Waits for callback with session details +/// 2. If redirect_uri is provided: Uses it for callback +/// 3. If redirect_uri is null: Starts a local HTTP server for callback +/// 4. Opens the keychain session URL in browser /// 5. Creates and stores the session /// 6. Calls the provided callback with the new session account /// @@ -941,24 +928,48 @@ Result client_new(const char *torii_url, /// * `policies` - Pointer to array of Policy structs defining session permissions /// * `policies_len` - Length of the policies array /// * `account_callback` - Function pointer called with the new session account when ready -/// -/// # Example -/// ```c -/// void on_account(SessionAccount* account) { -/// // Handle new session account -/// } -/// -/// controller_connect( -/// "https://rpc.example.com", -/// policies, -/// policies_length, -/// on_account -/// ); -/// ``` +/// * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. +/// If provided, will be used for callback instead of starting a local server. void controller_connect(const char *rpc_url, const Policy *policies, uintptr_t policies_len, - void (*account_callback)(ControllerAccount*)); + void (*account_callback)(ControllerAccount*), + const char *redirect_uri); + +/// Initiates a connection to establish a new session account using deep linking +/// +/// This function: +/// 1. Generates a new signing key pair +/// 2. Stores the signing key and callback state +/// 3. Opens the keychain session URL with redirect URI +/// 4. Returns immediately - app will be reopened via deep link +/// +/// # Safety +/// This function is marked as unsafe because it: +/// - Handles raw C pointers +/// - Performs FFI operations +/// - Manages system keyring entries +/// +/// # Parameters +/// * `rpc_url` - Pointer to null-terminated string containing the RPC endpoint URL +/// * `redirect_uri` - Pointer to null-terminated string containing the deep link URI +/// * `policies` - Pointer to array of Policy structs defining session permissions +/// * `policies_len` - Length of the policies array +/// * `account_callback` - Function pointer called with the new session account when ready +void controller_connect_mobile(const char *rpc_url, + const char *redirect_uri, + const Policy *policies, + uintptr_t policies_len, + void (*account_callback)(ControllerAccount*)); + +/// Handles the deep link callback when app is reopened +/// +/// # Parameters +/// * `callback_data` - Base64 encoded callback data from the deep link +/// +/// # Returns +/// Result containing success boolean or error +Result handle_deep_link_callback(const char *callback_data); /// Retrieves a stored session account if one exists and is valid /// diff --git a/dojo.pyx b/dojo.pyx index cb6f39f..d6ffde8 100644 --- a/dojo.pyx +++ b/dojo.pyx @@ -50,18 +50,15 @@ cdef extern from *: cdef struct Error: char *message; - cdef enum ResultToriiClient_Tag: - OkToriiClient, - ErrToriiClient, + cdef enum Resultbool_Tag: + Okbool, + Errbool, - cdef struct ResultToriiClient: - ResultToriiClient_Tag tag; - ToriiClient *ok; + cdef struct Resultbool: + Resultbool_Tag tag; + bool ok; Error err; - cdef struct FieldElement: - uint8_t data[32]; - cdef enum ResultControllerAccount_Tag: OkControllerAccount, ErrControllerAccount, @@ -71,14 +68,8 @@ cdef extern from *: ControllerAccount *ok; Error err; - cdef enum Resultbool_Tag: - Okbool, - Errbool, - - cdef struct Resultbool: - Resultbool_Tag tag; - bool ok; - Error err; + cdef struct FieldElement: + uint8_t data[32]; cdef enum ResultFieldElement_Tag: OkFieldElement, @@ -492,26 +483,13 @@ cdef extern from *: const char *name; Ty *ty; - # Creates a new Torii client instance - # - # # Parameters - # * `torii_url` - URL of the Torii server - # * `libp2p_relay_url` - URL of the libp2p relay server - # * `world` - World address as a FieldElement - # - # # Returns - # Result containing pointer to new ToriiClient instance or error - ResultToriiClient client_new(const char *torii_url, - const char *libp2p_relay_url, - FieldElement world); - # Initiates a connection to establish a new session account # # This function: # 1. Generates a new signing key pair - # 2. Starts a local HTTP server to receive the callback - # 3. Opens the keychain session URL in browser - # 4. Waits for callback with session details + # 2. If redirect_uri is provided: Uses it for callback + # 3. If redirect_uri is null: Starts a local HTTP server for callback + # 4. Opens the keychain session URL in browser # 5. Creates and stores the session # 6. Calls the provided callback with the new session account # @@ -526,24 +504,48 @@ cdef extern from *: # * `policies` - Pointer to array of Policy structs defining session permissions # * `policies_len` - Length of the policies array # * `account_callback` - Function pointer called with the new session account when ready - # - # # Example - # ```c - # void on_account(SessionAccount* account) { - # // Handle new session account - # } - # - # controller_connect( - # "https://rpc.example.com", - # policies, - # policies_length, - # on_account - # ); - # ``` + # * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. + # If provided, will be used for callback instead of starting a local server. void controller_connect(const char *rpc_url, const Policy *policies, uintptr_t policies_len, - void (*account_callback)(ControllerAccount*)); + void (*account_callback)(ControllerAccount*), + const char *redirect_uri); + + # Initiates a connection to establish a new session account using deep linking + # + # This function: + # 1. Generates a new signing key pair + # 2. Stores the signing key and callback state + # 3. Opens the keychain session URL with redirect URI + # 4. Returns immediately - app will be reopened via deep link + # + # # Safety + # This function is marked as unsafe because it: + # - Handles raw C pointers + # - Performs FFI operations + # - Manages system keyring entries + # + # # Parameters + # * `rpc_url` - Pointer to null-terminated string containing the RPC endpoint URL + # * `redirect_uri` - Pointer to null-terminated string containing the deep link URI + # * `policies` - Pointer to array of Policy structs defining session permissions + # * `policies_len` - Length of the policies array + # * `account_callback` - Function pointer called with the new session account when ready + void controller_connect_mobile(const char *rpc_url, + const char *redirect_uri, + const Policy *policies, + uintptr_t policies_len, + void (*account_callback)(ControllerAccount*)); + + # Handles the deep link callback when app is reopened + # + # # Parameters + # * `callback_data` - Base64 encoded callback data from the deep link + # + # # Returns + # Result containing success boolean or error + Resultbool handle_deep_link_callback(const char *callback_data); # Retrieves a stored session account if one exists and is valid # diff --git a/src/c/mod.rs b/src/c/mod.rs index b0bf3dd..3e10b52 100644 --- a/src/c/mod.rs +++ b/src/c/mod.rs @@ -1,13 +1,13 @@ mod types; -use std::ffi::{c_void, CStr, CString}; +use std::ffi::{CStr, CString, c_void}; use std::fs; use std::net::SocketAddr; use std::ops::Deref; use std::os::raw::c_char; use std::str::FromStr; -use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Duration; use account_sdk::abigen::controller::OutsideExecutionV3; @@ -19,13 +19,13 @@ use account_sdk::account::session::hash::Session; use account_sdk::provider::{CartridgeJsonRpcProvider, CartridgeProvider}; use account_sdk::signers::Signer; use account_sdk::utils::time::get_current_timestamp; +use axum::Router; use axum::extract::State; -use axum::http::{header, HeaderValue, Method, StatusCode}; +use axum::http::{HeaderValue, Method, StatusCode, header}; use axum::response::IntoResponse; use axum::routing::post; -use axum::Router; -use base64::engine::general_purpose::STANDARD as BASE64; use base64::Engine as _; +use base64::engine::general_purpose::STANDARD as BASE64; use cainome::cairo_serde::{self, ByteArray, CairoSerde}; use crypto_bigint::U256; use directories::ProjectDirs; @@ -41,7 +41,7 @@ use starknet::core::utils::get_contract_address; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{JsonRpcClient, Provider as _}; use starknet::signers::{LocalWallet, SigningKey, VerifyingKey}; -use starknet_crypto::{poseidon_hash_many, Felt}; +use starknet_crypto::{Felt, poseidon_hash_many}; use stream_cancel::{StreamExt as _, Tripwire}; use tokio::net::TcpListener; use tokio::runtime::Runtime; @@ -69,42 +69,11 @@ lazy_static! { Arc::new(Runtime::new().expect("Failed to create Tokio runtime")); } -/// Creates a new Torii client instance -/// -/// # Parameters -/// * `torii_url` - URL of the Torii server -/// * `libp2p_relay_url` - URL of the libp2p relay server -/// * `world` - World address as a FieldElement -/// -/// # Returns -/// Result containing pointer to new ToriiClient instance or error -#[no_mangle] -pub unsafe extern "C" fn client_new( - torii_url: *const c_char, - libp2p_relay_url: *const c_char, - world: types::FieldElement, -) -> Result<*mut ToriiClient> { - let torii_url = unsafe { CStr::from_ptr(torii_url).to_string_lossy().into_owned() }; - let libp2p_relay_url = - unsafe { CStr::from_ptr(libp2p_relay_url).to_string_lossy().into_owned() }; - - let client_future = TClient::new(torii_url, libp2p_relay_url, (&world).into()); - - let client = match RUNTIME.block_on(client_future) { - Ok(client) => client, - Err(e) => return Result::Err(e.into()), - }; - - let relay_runner = client.relay_runner(); - RUNTIME.spawn(async move { - relay_runner.lock().await.run().await; - }); - - Result::Ok(Box::into_raw(Box::new(ToriiClient { inner: client, logger: None }))) -} - -// State struct to share data with callback handler #[derive(Clone)] +#[cfg_attr( + any(target_os = "ios", target_os = "android"), + derive(serde::Serialize, serde::Deserialize) +)] struct CallbackState { shutdown_tx: tokio::sync::mpsc::Sender<()>, rpc_url: String, @@ -207,9 +176,9 @@ async fn handle_callback(State(state): State, body: String) -> im /// /// This function: /// 1. Generates a new signing key pair -/// 2. Starts a local HTTP server to receive the callback -/// 3. Opens the keychain session URL in browser -/// 4. Waits for callback with session details +/// 2. If redirect_uri is provided: Uses it for callback +/// 3. If redirect_uri is null: Starts a local HTTP server for callback +/// 4. Opens the keychain session URL in browser /// 5. Creates and stores the session /// 6. Calls the provided callback with the new session account /// @@ -224,26 +193,15 @@ async fn handle_callback(State(state): State, body: String) -> im /// * `policies` - Pointer to array of Policy structs defining session permissions /// * `policies_len` - Length of the policies array /// * `account_callback` - Function pointer called with the new session account when ready -/// -/// # Example -/// ```c -/// void on_account(SessionAccount* account) { -/// // Handle new session account -/// } -/// -/// controller_connect( -/// "https://rpc.example.com", -/// policies, -/// policies_length, -/// on_account -/// ); -/// ``` +/// * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. +/// If provided, will be used for callback instead of starting a local server. #[no_mangle] pub unsafe extern "C" fn controller_connect( rpc_url: *const c_char, policies: *const Policy, policies_len: usize, account_callback: extern "C" fn(*mut ControllerAccount), + redirect_uri: *const c_char, ) { let rpc_url = unsafe { CStr::from_ptr(rpc_url).to_string_lossy().into_owned() }; let policies = unsafe { std::slice::from_raw_parts(policies, policies_len) }; @@ -261,12 +219,120 @@ pub unsafe extern "C" fn controller_connect( let keyring = Entry::new("dojo-keyring", &format!("{:#x}", verifying_key)).unwrap(); keyring.set_password(&format!("{:#x}", signing_key.secret_scalar())).unwrap(); - // Create shutdown channel - let (shutdown_tx, mut shutdown_rx) = tokio::sync::mpsc::channel(1); + let mut url = url::Url::parse(constants::KEYCHAIN_SESSION_URL).unwrap(); + if !redirect_uri.is_null() { + // Use provided redirect URI + url.query_pairs_mut().append_pair("redirect_uri", &unsafe { + CStr::from_ptr(redirect_uri).to_string_lossy().into_owned() + }) + } else { + // Create shutdown channel + let (shutdown_tx, mut shutdown_rx) = tokio::sync::mpsc::channel(1); + + // Create state with RPC URL and shutdown sender + let state = CallbackState { + shutdown_tx, + rpc_url: rpc_url.clone(), + policies: account_policies, + private_key: signing_key, + public_key: verifying_key, + account_callback, + }; + + // Set up the HTTP callback server with state and CORS + let app = Router::new() + .route("/callback", post(handle_callback)) + .layer( + CorsLayer::new() + .allow_origin(AllowOrigin::exact(HeaderValue::from_static( + "https://x.cartridge.gg", + ))) + .allow_methods([Method::POST]) + .allow_headers([header::CONTENT_TYPE]), + ) + .with_state(state); + + // Find an available port by trying to bind to port 0 + let addr = SocketAddr::from(([127, 0, 0, 1], 0)); + let listener = RUNTIME.block_on(TcpListener::bind(addr)).unwrap(); + let bound_addr = listener.local_addr().unwrap(); + + let server = axum::serve(listener, app); + + // Spawn server with graceful shutdown + RUNTIME.spawn(async move { + server + .with_graceful_shutdown(async move { + shutdown_rx.recv().await; + println!("Shutting down server"); + }) + .await + .unwrap(); + }); + + println!("Listening on {}", bound_addr); + let callback_url = + format!("http://{}/callback", bound_addr).replace("127.0.0.1", "localhost"); + + url.query_pairs_mut().append_pair("callback_uri", &callback_url); + } + + url.query_pairs_mut() + .append_pair("public_key", &format!("{:#x}", verifying_key)) + .append_pair("rpc_url", &rpc_url) + .append_pair("policies", &serde_json::to_string(&policies).unwrap()); + + open::that(url.as_str()).unwrap(); +} - // Create state with RPC URL and shutdown sender - let state = CallbackState { - shutdown_tx, +/// Initiates a connection to establish a new session account using deep linking +/// +/// This function: +/// 1. Generates a new signing key pair +/// 2. Stores the signing key and callback state +/// 3. Opens the keychain session URL with redirect URI +/// 4. Returns immediately - app will be reopened via deep link +/// +/// # Safety +/// This function is marked as unsafe because it: +/// - Handles raw C pointers +/// - Performs FFI operations +/// - Manages system keyring entries +/// +/// # Parameters +/// * `rpc_url` - Pointer to null-terminated string containing the RPC endpoint URL +/// * `redirect_uri` - Pointer to null-terminated string containing the deep link URI +/// * `policies` - Pointer to array of Policy structs defining session permissions +/// * `policies_len` - Length of the policies array +/// * `account_callback` - Function pointer called with the new session account when ready +#[no_mangle] +pub unsafe extern "C" fn controller_connect_mobile( + rpc_url: *const c_char, + redirect_uri: *const c_char, + policies: *const Policy, + policies_len: usize, + account_callback: extern "C" fn(*mut ControllerAccount), +) { + let rpc_url = unsafe { CStr::from_ptr(rpc_url).to_string_lossy().into_owned() }; + let redirect_uri = unsafe { CStr::from_ptr(redirect_uri).to_string_lossy().into_owned() }; + let policies = unsafe { std::slice::from_raw_parts(policies, policies_len) }; + let account_policies = policies + .iter() + .map(|p| account_sdk::account::session::policy::Policy::Call(p.into())) + .collect::>(); + let policies = policies.iter().map(|p| p.into()).collect::>(); + + // Generate new random signing key + let signing_key = SigningKey::from_random(); + let verifying_key = signing_key.verifying_key().scalar(); + + // Store signing key in system keyring + let keyring = Entry::new("dojo-keyring", &format!("{:#x}", verifying_key)).unwrap(); + keyring.set_password(&format!("{:#x}", signing_key.secret_scalar())).unwrap(); + + // Store callback state in keyring + let callback_state = CallbackState { + shutdown_tx: tokio::sync::mpsc::channel(1).0, // Dummy sender since we don't need shutdown for mobile rpc_url: rpc_url.clone(), policies: account_policies, private_key: signing_key, @@ -274,43 +340,14 @@ pub unsafe extern "C" fn controller_connect( account_callback, }; - // Set up the HTTP callback server with state and CORS - let app = Router::new() - .route("/callback", post(handle_callback)) - .layer( - CorsLayer::new() - .allow_origin(AllowOrigin::exact(HeaderValue::from_static( - "https://x.cartridge.gg", - ))) - .allow_methods([Method::POST]) - .allow_headers([header::CONTENT_TYPE]), - ) - .with_state(state); - - // Find an available port by trying to bind to port 0 - let addr = SocketAddr::from(([127, 0, 0, 1], 0)); - let listener = RUNTIME.block_on(TcpListener::bind(addr)).unwrap(); - let bound_addr = listener.local_addr().unwrap(); - - let server = axum::serve(listener, app); - - // Spawn server with graceful shutdown - RUNTIME.spawn(async move { - server - .with_graceful_shutdown(async move { - shutdown_rx.recv().await; - println!("Shutting down server"); - }) - .await - .unwrap(); - }); - - println!("Listening on {}", bound_addr); + // Store callback state in keyring using public key as identifier + let state_keyring = + Entry::new("dojo-callback-state", &format!("{:#x}", verifying_key)).unwrap(); + state_keyring.set_password(&serde_json::to_string(&callback_state).unwrap()).unwrap(); - let callback_url = format!("http://{}/callback", bound_addr).replace("127.0.0.1", "localhost"); let mut url = url::Url::parse(constants::KEYCHAIN_SESSION_URL).unwrap(); url.query_pairs_mut() - .append_pair("callback_uri", &callback_url) + .append_pair("callback_uri", &redirect_uri) .append_pair("public_key", &format!("{:#x}", verifying_key)) .append_pair("rpc_url", &rpc_url) .append_pair("policies", &serde_json::to_string(&policies).unwrap()); @@ -318,6 +355,137 @@ pub unsafe extern "C" fn controller_connect( open::that(url.as_str()).unwrap(); } +/// Handles the deep link callback when app is reopened +/// +/// # Parameters +/// * `callback_data` - Base64 encoded callback data from the deep link +/// +/// # Returns +/// Result containing success boolean or error +#[no_mangle] +pub unsafe extern "C" fn handle_deep_link_callback(callback_data: *const c_char) -> Result { + let callback_data = unsafe { CStr::from_ptr(callback_data).to_string_lossy().into_owned() }; + + // Decode base64 payload + let padded = match callback_data.len() % 4 { + 0 => callback_data, + n => callback_data + &"=".repeat(4 - n), + }; + let decoded = match BASE64.decode(padded) { + Ok(d) => d, + Err(e) => { + println!("Failed to decode payload: {}", e); + return Result::Err(Error { + message: CString::new("Failed to decode callback data").unwrap().into_raw(), + }); + } + }; + + // Parse JSON from decoded bytes + let payload: RegisterSessionResponse = match serde_json::from_slice(&decoded) { + Ok(p) => p, + Err(e) => { + println!("Failed to deserialize payload: {}", e); + return Result::Err(Error { + message: CString::new("Failed to parse callback data").unwrap().into_raw(), + }); + } + }; + + // Retrieve callback state using public key from payload + let state_keyring = match Entry::new("dojo-keyring", &format!("{:#x}", payload.public_key)) { + Ok(k) => k, + Err(_) => { + return Result::Err(Error { + message: CString::new("Failed to retrieve callback state").unwrap().into_raw(), + }); + } + }; + + let state_json = match state_keyring.get_password() { + Ok(s) => s, + Err(_) => { + return Result::Err(Error { + message: CString::new("Failed to retrieve callback state data").unwrap().into_raw(), + }); + } + }; + + let state: CallbackState = match serde_json::from_str(&state_json) { + Ok(s) => s, + Err(_) => { + return Result::Err(Error { + message: CString::new("Failed to parse callback state").unwrap().into_raw(), + }); + } + }; + + let provider = CartridgeJsonRpcProvider::new(Url::from_str(&state.rpc_url).unwrap()); + let chain_id = RUNTIME.block_on(provider.chain_id()).unwrap(); + + let project_dirs = ProjectDirs::from("org", "dojoengine", "dojo"); + if let Some(proj_dirs) = project_dirs { + let data_dir = proj_dirs.data_dir(); + fs::create_dir_all(data_dir).unwrap(); + + let account_file = data_dir.join("sessions.json"); + let mut sessions_storage = + SessionsStorage::from_file(account_file.clone()).unwrap_or_default(); + + sessions_storage.active = format!("{:#x}/{:#x}", payload.address, chain_id); + sessions_storage.sessions.entry(sessions_storage.active.clone()).or_default().push( + RegisteredSession { + public_key: state.public_key, + expires_at: payload.expires_at, + policies: state.policies.clone(), + }, + ); + sessions_storage.accounts.insert( + sessions_storage.active.clone(), + RegisteredAccount { + username: payload.username.clone(), + address: payload.address, + owner_guid: payload.owner_guid, + chain_id, + rpc_url: state.rpc_url, + }, + ); + + fs::write(account_file.clone(), serde_json::to_string_pretty(&sessions_storage).unwrap()) + .unwrap(); + println!("Account data saved to {}", account_file.display()); + } + + let signer = Signer::Starknet(state.private_key); + let session = Session::new( + state.policies.clone(), + payload.expires_at, + &signer.clone().into(), + Felt::ZERO, + ) + .unwrap(); + + let session_account = SessionAccount::new_as_registered( + provider, + signer, + payload.address, + chain_id, + payload.owner_guid, + session, + ); + + // Call the callback with the new account + (state.account_callback)(Box::into_raw(Box::new(ControllerAccount { + account: session_account, + username: payload.username, + }))); + + // Clean up the stored callback state + let _ = state_keyring.delete_password(); + + Result::Ok(true) +} + /// Retrieves a stored session account if one exists and is valid /// /// # Parameters @@ -609,7 +777,7 @@ pub unsafe extern "C" fn controller_execute_raw( let calldata = unsafe { std::slice::from_raw_parts(calldata, calldata_len).to_vec() }; let calldata = calldata.into_iter().map(|c| (&c).into()).collect::>(); - let call = (*controller).account.execute_v3(calldata); + let call = (*controller).0.execute_v3(calldata); match RUNTIME.block_on(call.send()) { Ok(result) => Result::Ok((&result.transaction_hash).into()), From 5bfa4f2cf8c7c47f3c8ce67c84250b9ec06a5ae1 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 1 Apr 2025 14:35:14 +0900 Subject: [PATCH 2/7] fix url construct --- src/c/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/c/mod.rs b/src/c/mod.rs index 3e10b52..ccfe32c 100644 --- a/src/c/mod.rs +++ b/src/c/mod.rs @@ -224,7 +224,7 @@ pub unsafe extern "C" fn controller_connect( // Use provided redirect URI url.query_pairs_mut().append_pair("redirect_uri", &unsafe { CStr::from_ptr(redirect_uri).to_string_lossy().into_owned() - }) + }); } else { // Create shutdown channel let (shutdown_tx, mut shutdown_rx) = tokio::sync::mpsc::channel(1); From 41b4837e61b018503a84a9c00b536505b3cfcde4 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 1 Apr 2025 15:39:45 +0900 Subject: [PATCH 3/7] serialize url directly --- dojo.h | 48 +++------ dojo.hpp | 46 +++------ dojo.pyx | 48 +++------ src/c/mod.rs | 283 ++++++++++++--------------------------------------- src/types.rs | 3 +- 5 files changed, 107 insertions(+), 321 deletions(-) diff --git a/dojo.h b/dojo.h index b70bd96..bcdd096 100644 --- a/dojo.h +++ b/dojo.h @@ -7,6 +7,7 @@ namespace dojo_bindings { #endif // __cplusplus +struct CallbackState; struct Policy; struct ControllerAccount; struct Call; @@ -773,11 +774,9 @@ extern "C" { * * This function: * 1. Generates a new signing key pair - * 2. If redirect_uri is provided: Uses it for callback + * 2. If redirect_uri is provided: Returns CallbackState for deep link handling * 3. If redirect_uri is null: Starts a local HTTP server for callback * 4. Opens the keychain session URL in browser - * 5. Creates and stores the session - * 6. Calls the provided callback with the new session account * * # Safety * This function is marked as unsafe because it: @@ -792,51 +791,28 @@ extern "C" { * * `account_callback` - Function pointer called with the new session account when ready * * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. * If provided, will be used for callback instead of starting a local server. - */ -void controller_connect(const char *rpc_url, - const struct Policy *policies, - uintptr_t policies_len, - void (*account_callback)(struct ControllerAccount*), - const char *redirect_uri); - -/** - * Initiates a connection to establish a new session account using deep linking * - * This function: - * 1. Generates a new signing key pair - * 2. Stores the signing key and callback state - * 3. Opens the keychain session URL with redirect URI - * 4. Returns immediately - app will be reopened via deep link - * - * # Safety - * This function is marked as unsafe because it: - * - Handles raw C pointers - * - Performs FFI operations - * - Manages system keyring entries - * - * # Parameters - * * `rpc_url` - Pointer to null-terminated string containing the RPC endpoint URL - * * `redirect_uri` - Pointer to null-terminated string containing the deep link URI - * * `policies` - Pointer to array of Policy structs defining session permissions - * * `policies_len` - Length of the policies array - * * `account_callback` - Function pointer called with the new session account when ready + * # Returns + * If redirect_uri is provided, returns pointer to CallbackState that must be used with handle_deep_link_callback. + * If redirect_uri is null, returns null pointer. */ -void controller_connect_mobile(const char *rpc_url, - const char *redirect_uri, - const struct Policy *policies, - uintptr_t policies_len, - void (*account_callback)(struct ControllerAccount*)); +struct CallbackState *controller_connect(const char *rpc_url, + const struct Policy *policies, + uintptr_t policies_len, + void (*account_callback)(struct ControllerAccount*), + const char *redirect_uri); /** * Handles the deep link callback when app is reopened * * # Parameters * * `callback_data` - Base64 encoded callback data from the deep link + * * `state` - CallbackState pointer returned from controller_connect * * # Returns * Result containing success boolean or error */ -struct Resultbool handle_deep_link_callback(const char *callback_data); +struct Resultbool handle_deep_link_callback(const char *callback_data, struct CallbackState *state); /** * Retrieves a stored session account if one exists and is valid diff --git a/dojo.hpp b/dojo.hpp index cc939ab..c216019 100644 --- a/dojo.hpp +++ b/dojo.hpp @@ -6,6 +6,7 @@ namespace dojo_bindings { +struct CallbackState; struct Policy; struct ControllerAccount; struct Call; @@ -911,11 +912,9 @@ extern "C" { /// /// This function: /// 1. Generates a new signing key pair -/// 2. If redirect_uri is provided: Uses it for callback +/// 2. If redirect_uri is provided: Returns CallbackState for deep link handling /// 3. If redirect_uri is null: Starts a local HTTP server for callback /// 4. Opens the keychain session URL in browser -/// 5. Creates and stores the session -/// 6. Calls the provided callback with the new session account /// /// # Safety /// This function is marked as unsafe because it: @@ -930,46 +929,25 @@ extern "C" { /// * `account_callback` - Function pointer called with the new session account when ready /// * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. /// If provided, will be used for callback instead of starting a local server. -void controller_connect(const char *rpc_url, - const Policy *policies, - uintptr_t policies_len, - void (*account_callback)(ControllerAccount*), - const char *redirect_uri); - -/// Initiates a connection to establish a new session account using deep linking -/// -/// This function: -/// 1. Generates a new signing key pair -/// 2. Stores the signing key and callback state -/// 3. Opens the keychain session URL with redirect URI -/// 4. Returns immediately - app will be reopened via deep link /// -/// # Safety -/// This function is marked as unsafe because it: -/// - Handles raw C pointers -/// - Performs FFI operations -/// - Manages system keyring entries -/// -/// # Parameters -/// * `rpc_url` - Pointer to null-terminated string containing the RPC endpoint URL -/// * `redirect_uri` - Pointer to null-terminated string containing the deep link URI -/// * `policies` - Pointer to array of Policy structs defining session permissions -/// * `policies_len` - Length of the policies array -/// * `account_callback` - Function pointer called with the new session account when ready -void controller_connect_mobile(const char *rpc_url, - const char *redirect_uri, - const Policy *policies, - uintptr_t policies_len, - void (*account_callback)(ControllerAccount*)); +/// # Returns +/// If redirect_uri is provided, returns pointer to CallbackState that must be used with handle_deep_link_callback. +/// If redirect_uri is null, returns null pointer. +CallbackState *controller_connect(const char *rpc_url, + const Policy *policies, + uintptr_t policies_len, + void (*account_callback)(ControllerAccount*), + const char *redirect_uri); /// Handles the deep link callback when app is reopened /// /// # Parameters /// * `callback_data` - Base64 encoded callback data from the deep link +/// * `state` - CallbackState pointer returned from controller_connect /// /// # Returns /// Result containing success boolean or error -Result handle_deep_link_callback(const char *callback_data); +Result handle_deep_link_callback(const char *callback_data, CallbackState *state); /// Retrieves a stored session account if one exists and is valid /// diff --git a/dojo.pyx b/dojo.pyx index d6ffde8..0407f83 100644 --- a/dojo.pyx +++ b/dojo.pyx @@ -35,6 +35,9 @@ cdef extern from *: cdef struct Account: pass + cdef struct CallbackState: + pass + cdef struct ControllerAccount: pass @@ -487,11 +490,9 @@ cdef extern from *: # # This function: # 1. Generates a new signing key pair - # 2. If redirect_uri is provided: Uses it for callback + # 2. If redirect_uri is provided: Returns CallbackState for deep link handling # 3. If redirect_uri is null: Starts a local HTTP server for callback # 4. Opens the keychain session URL in browser - # 5. Creates and stores the session - # 6. Calls the provided callback with the new session account # # # Safety # This function is marked as unsafe because it: @@ -506,46 +507,25 @@ cdef extern from *: # * `account_callback` - Function pointer called with the new session account when ready # * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. # If provided, will be used for callback instead of starting a local server. - void controller_connect(const char *rpc_url, - const Policy *policies, - uintptr_t policies_len, - void (*account_callback)(ControllerAccount*), - const char *redirect_uri); - - # Initiates a connection to establish a new session account using deep linking # - # This function: - # 1. Generates a new signing key pair - # 2. Stores the signing key and callback state - # 3. Opens the keychain session URL with redirect URI - # 4. Returns immediately - app will be reopened via deep link - # - # # Safety - # This function is marked as unsafe because it: - # - Handles raw C pointers - # - Performs FFI operations - # - Manages system keyring entries - # - # # Parameters - # * `rpc_url` - Pointer to null-terminated string containing the RPC endpoint URL - # * `redirect_uri` - Pointer to null-terminated string containing the deep link URI - # * `policies` - Pointer to array of Policy structs defining session permissions - # * `policies_len` - Length of the policies array - # * `account_callback` - Function pointer called with the new session account when ready - void controller_connect_mobile(const char *rpc_url, - const char *redirect_uri, - const Policy *policies, - uintptr_t policies_len, - void (*account_callback)(ControllerAccount*)); + # # Returns + # If redirect_uri is provided, returns pointer to CallbackState that must be used with handle_deep_link_callback. + # If redirect_uri is null, returns null pointer. + CallbackState *controller_connect(const char *rpc_url, + const Policy *policies, + uintptr_t policies_len, + void (*account_callback)(ControllerAccount*), + const char *redirect_uri); # Handles the deep link callback when app is reopened # # # Parameters # * `callback_data` - Base64 encoded callback data from the deep link + # * `state` - CallbackState pointer returned from controller_connect # # # Returns # Result containing success boolean or error - Resultbool handle_deep_link_callback(const char *callback_data); + Resultbool handle_deep_link_callback(const char *callback_data, CallbackState *state); # Retrieves a stored session account if one exists and is valid # diff --git a/src/c/mod.rs b/src/c/mod.rs index ccfe32c..4cba452 100644 --- a/src/c/mod.rs +++ b/src/c/mod.rs @@ -25,7 +25,7 @@ use axum::http::{HeaderValue, Method, StatusCode, header}; use axum::response::IntoResponse; use axum::routing::post; use base64::Engine as _; -use base64::engine::general_purpose::STANDARD as BASE64; +use base64::engine::general_purpose::STANDARD_NO_PAD as BASE64; use cainome::cairo_serde::{self, ByteArray, CairoSerde}; use crypto_bigint::U256; use directories::ProjectDirs; @@ -70,44 +70,20 @@ lazy_static! { } #[derive(Clone)] -#[cfg_attr( - any(target_os = "ios", target_os = "android"), - derive(serde::Serialize, serde::Deserialize) -)] struct CallbackState { shutdown_tx: tokio::sync::mpsc::Sender<()>, - rpc_url: String, + rpc_url: Url, policies: Vec, private_key: SigningKey, public_key: Felt, account_callback: extern "C" fn(*mut ControllerAccount), } -// Modify handle_callback to call the callback -async fn handle_callback(State(state): State, body: String) -> impl IntoResponse { - // Decode base64 payload - let padded = match body.len() % 4 { - 0 => body, - n => body + &"=".repeat(4 - n), - }; - let decoded = match BASE64.decode(padded) { - Ok(d) => d, - Err(e) => { - println!("Failed to decode payload: {}", e); - return StatusCode::BAD_REQUEST; - } - }; - - // Parse JSON from decoded bytes - let payload: RegisterSessionResponse = match serde_json::from_slice(&decoded) { - Ok(p) => p, - Err(e) => { - println!("Failed to deserialize payload: {}", e); - return StatusCode::BAD_REQUEST; - } - }; - - let provider = CartridgeJsonRpcProvider::new(Url::from_str(&state.rpc_url).unwrap()); +async fn process_session_callback( + state: CallbackState, + payload: RegisterSessionResponse, +) -> anyhow::Result<()> { + let provider = CartridgeJsonRpcProvider::new(state.rpc_url.clone()); let chain_id = provider.chain_id().await.unwrap(); let project_dirs = ProjectDirs::from("org", "dojoengine", "dojo"); @@ -167,20 +143,41 @@ async fn handle_callback(State(state): State, body: String) -> im username: payload.username, }))); - // Signal shutdown after handling callback - state.shutdown_tx.send(()).await.unwrap(); - StatusCode::OK + Ok(()) +} + +// Modify handle_callback to use the shared function +async fn handle_callback(State(state): State, body: String) -> impl IntoResponse { + let decoded = match BASE64.decode(body) { + Ok(d) => d, + Err(e) => { + println!("Failed to decode payload: {}", e); + return StatusCode::BAD_REQUEST; + } + }; + + // Parse JSON from decoded bytes + let payload: RegisterSessionResponse = match serde_json::from_slice(&decoded) { + Ok(p) => p, + Err(e) => { + println!("Failed to deserialize payload: {}", e); + return StatusCode::BAD_REQUEST; + } + }; + + match process_session_callback(state, payload).await { + Ok(_) => StatusCode::OK, + Err(_) => StatusCode::INTERNAL_SERVER_ERROR, + } } /// Initiates a connection to establish a new session account /// /// This function: /// 1. Generates a new signing key pair -/// 2. If redirect_uri is provided: Uses it for callback +/// 2. If redirect_uri is provided: Returns CallbackState for deep link handling /// 3. If redirect_uri is null: Starts a local HTTP server for callback /// 4. Opens the keychain session URL in browser -/// 5. Creates and stores the session -/// 6. Calls the provided callback with the new session account /// /// # Safety /// This function is marked as unsafe because it: @@ -195,6 +192,10 @@ async fn handle_callback(State(state): State, body: String) -> im /// * `account_callback` - Function pointer called with the new session account when ready /// * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. /// If provided, will be used for callback instead of starting a local server. +/// +/// # Returns +/// If redirect_uri is provided, returns pointer to CallbackState that must be used with handle_deep_link_callback. +/// If redirect_uri is null, returns null pointer. #[no_mangle] pub unsafe extern "C" fn controller_connect( rpc_url: *const c_char, @@ -202,8 +203,8 @@ pub unsafe extern "C" fn controller_connect( policies_len: usize, account_callback: extern "C" fn(*mut ControllerAccount), redirect_uri: *const c_char, -) { - let rpc_url = unsafe { CStr::from_ptr(rpc_url).to_string_lossy().into_owned() }; +) -> *mut CallbackState { + let rpc_url = Url::parse(&unsafe { CStr::from_ptr(rpc_url).to_string_lossy().into_owned() }).unwrap(); let policies = unsafe { std::slice::from_raw_parts(policies, policies_len) }; let account_policies = policies .iter() @@ -220,11 +221,23 @@ pub unsafe extern "C" fn controller_connect( keyring.set_password(&format!("{:#x}", signing_key.secret_scalar())).unwrap(); let mut url = url::Url::parse(constants::KEYCHAIN_SESSION_URL).unwrap(); - if !redirect_uri.is_null() { + let callback_state = if !redirect_uri.is_null() { // Use provided redirect URI url.query_pairs_mut().append_pair("redirect_uri", &unsafe { CStr::from_ptr(redirect_uri).to_string_lossy().into_owned() }); + + // Create callback state for deep link handling + let (shutdown_tx, _) = tokio::sync::mpsc::channel(1); + let state = CallbackState { + shutdown_tx, + rpc_url: rpc_url.clone(), + policies: account_policies, + private_key: signing_key, + public_key: verifying_key, + account_callback, + }; + Box::into_raw(Box::new(state)) } else { // Create shutdown channel let (shutdown_tx, mut shutdown_rx) = tokio::sync::mpsc::channel(1); @@ -273,105 +286,36 @@ pub unsafe extern "C" fn controller_connect( println!("Listening on {}", bound_addr); let callback_url = format!("http://{}/callback", bound_addr).replace("127.0.0.1", "localhost"); - url.query_pairs_mut().append_pair("callback_uri", &callback_url); - } - - url.query_pairs_mut() - .append_pair("public_key", &format!("{:#x}", verifying_key)) - .append_pair("rpc_url", &rpc_url) - .append_pair("policies", &serde_json::to_string(&policies).unwrap()); - - open::that(url.as_str()).unwrap(); -} - -/// Initiates a connection to establish a new session account using deep linking -/// -/// This function: -/// 1. Generates a new signing key pair -/// 2. Stores the signing key and callback state -/// 3. Opens the keychain session URL with redirect URI -/// 4. Returns immediately - app will be reopened via deep link -/// -/// # Safety -/// This function is marked as unsafe because it: -/// - Handles raw C pointers -/// - Performs FFI operations -/// - Manages system keyring entries -/// -/// # Parameters -/// * `rpc_url` - Pointer to null-terminated string containing the RPC endpoint URL -/// * `redirect_uri` - Pointer to null-terminated string containing the deep link URI -/// * `policies` - Pointer to array of Policy structs defining session permissions -/// * `policies_len` - Length of the policies array -/// * `account_callback` - Function pointer called with the new session account when ready -#[no_mangle] -pub unsafe extern "C" fn controller_connect_mobile( - rpc_url: *const c_char, - redirect_uri: *const c_char, - policies: *const Policy, - policies_len: usize, - account_callback: extern "C" fn(*mut ControllerAccount), -) { - let rpc_url = unsafe { CStr::from_ptr(rpc_url).to_string_lossy().into_owned() }; - let redirect_uri = unsafe { CStr::from_ptr(redirect_uri).to_string_lossy().into_owned() }; - let policies = unsafe { std::slice::from_raw_parts(policies, policies_len) }; - let account_policies = policies - .iter() - .map(|p| account_sdk::account::session::policy::Policy::Call(p.into())) - .collect::>(); - let policies = policies.iter().map(|p| p.into()).collect::>(); - - // Generate new random signing key - let signing_key = SigningKey::from_random(); - let verifying_key = signing_key.verifying_key().scalar(); - - // Store signing key in system keyring - let keyring = Entry::new("dojo-keyring", &format!("{:#x}", verifying_key)).unwrap(); - keyring.set_password(&format!("{:#x}", signing_key.secret_scalar())).unwrap(); - - // Store callback state in keyring - let callback_state = CallbackState { - shutdown_tx: tokio::sync::mpsc::channel(1).0, // Dummy sender since we don't need shutdown for mobile - rpc_url: rpc_url.clone(), - policies: account_policies, - private_key: signing_key, - public_key: verifying_key, - account_callback, + std::ptr::null_mut() }; - // Store callback state in keyring using public key as identifier - let state_keyring = - Entry::new("dojo-callback-state", &format!("{:#x}", verifying_key)).unwrap(); - state_keyring.set_password(&serde_json::to_string(&callback_state).unwrap()).unwrap(); - - let mut url = url::Url::parse(constants::KEYCHAIN_SESSION_URL).unwrap(); url.query_pairs_mut() - .append_pair("callback_uri", &redirect_uri) .append_pair("public_key", &format!("{:#x}", verifying_key)) - .append_pair("rpc_url", &rpc_url) + .append_pair("rpc_url", &rpc_url.to_string()) .append_pair("policies", &serde_json::to_string(&policies).unwrap()); open::that(url.as_str()).unwrap(); + callback_state } /// Handles the deep link callback when app is reopened /// /// # Parameters /// * `callback_data` - Base64 encoded callback data from the deep link +/// * `state` - CallbackState pointer returned from controller_connect /// /// # Returns /// Result containing success boolean or error #[no_mangle] -pub unsafe extern "C" fn handle_deep_link_callback(callback_data: *const c_char) -> Result { +pub unsafe extern "C" fn handle_deep_link_callback( + callback_data: *const c_char, + state: *mut CallbackState, +) -> Result { let callback_data = unsafe { CStr::from_ptr(callback_data).to_string_lossy().into_owned() }; + let state = unsafe { Box::from_raw(state) }; - // Decode base64 payload - let padded = match callback_data.len() % 4 { - 0 => callback_data, - n => callback_data + &"=".repeat(4 - n), - }; - let decoded = match BASE64.decode(padded) { + let decoded = match BASE64.decode(callback_data) { Ok(d) => d, Err(e) => { println!("Failed to decode payload: {}", e); @@ -392,98 +336,8 @@ pub unsafe extern "C" fn handle_deep_link_callback(callback_data: *const c_char) } }; - // Retrieve callback state using public key from payload - let state_keyring = match Entry::new("dojo-keyring", &format!("{:#x}", payload.public_key)) { - Ok(k) => k, - Err(_) => { - return Result::Err(Error { - message: CString::new("Failed to retrieve callback state").unwrap().into_raw(), - }); - } - }; - - let state_json = match state_keyring.get_password() { - Ok(s) => s, - Err(_) => { - return Result::Err(Error { - message: CString::new("Failed to retrieve callback state data").unwrap().into_raw(), - }); - } - }; - - let state: CallbackState = match serde_json::from_str(&state_json) { - Ok(s) => s, - Err(_) => { - return Result::Err(Error { - message: CString::new("Failed to parse callback state").unwrap().into_raw(), - }); - } - }; - - let provider = CartridgeJsonRpcProvider::new(Url::from_str(&state.rpc_url).unwrap()); - let chain_id = RUNTIME.block_on(provider.chain_id()).unwrap(); - - let project_dirs = ProjectDirs::from("org", "dojoengine", "dojo"); - if let Some(proj_dirs) = project_dirs { - let data_dir = proj_dirs.data_dir(); - fs::create_dir_all(data_dir).unwrap(); - - let account_file = data_dir.join("sessions.json"); - let mut sessions_storage = - SessionsStorage::from_file(account_file.clone()).unwrap_or_default(); - - sessions_storage.active = format!("{:#x}/{:#x}", payload.address, chain_id); - sessions_storage.sessions.entry(sessions_storage.active.clone()).or_default().push( - RegisteredSession { - public_key: state.public_key, - expires_at: payload.expires_at, - policies: state.policies.clone(), - }, - ); - sessions_storage.accounts.insert( - sessions_storage.active.clone(), - RegisteredAccount { - username: payload.username.clone(), - address: payload.address, - owner_guid: payload.owner_guid, - chain_id, - rpc_url: state.rpc_url, - }, - ); - - fs::write(account_file.clone(), serde_json::to_string_pretty(&sessions_storage).unwrap()) - .unwrap(); - println!("Account data saved to {}", account_file.display()); - } - - let signer = Signer::Starknet(state.private_key); - let session = Session::new( - state.policies.clone(), - payload.expires_at, - &signer.clone().into(), - Felt::ZERO, - ) - .unwrap(); - - let session_account = SessionAccount::new_as_registered( - provider, - signer, - payload.address, - chain_id, - payload.owner_guid, - session, - ); - - // Call the callback with the new account - (state.account_callback)(Box::into_raw(Box::new(ControllerAccount { - account: session_account, - username: payload.username, - }))); - - // Clean up the stored callback state - let _ = state_keyring.delete_password(); - - Result::Ok(true) + // Process the callback using shared function + RUNTIME.block_on(process_callback(*state, payload)) } /// Retrieves a stored session account if one exists and is valid @@ -777,14 +631,11 @@ pub unsafe extern "C" fn controller_execute_raw( let calldata = unsafe { std::slice::from_raw_parts(calldata, calldata_len).to_vec() }; let calldata = calldata.into_iter().map(|c| (&c).into()).collect::>(); - let call = (*controller).0.execute_v3(calldata); + let call = (*controller).account.execute_v3(calldata); match RUNTIME.block_on(call.send()) { Ok(result) => Result::Ok((&result.transaction_hash).into()), - Err(e) => { - println!("Error executing call: {:?}", e); - Result::Err(e.into()) - } + Err(e) => Result::Err(e.into()), } } diff --git a/src/types.rs b/src/types.rs index bc49c2f..5c2045d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -14,6 +14,7 @@ use starknet::signers::LocalWallet; use starknet_crypto::Felt; use stream_cancel::Trigger; use torii_client::client::Client; +use url::Url; use wasm_bindgen::prelude::*; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -63,7 +64,7 @@ pub struct RegisteredAccount { pub address: Felt, pub owner_guid: Felt, pub chain_id: Felt, - pub rpc_url: String, + pub rpc_url: Url, } impl SessionsStorage { From 6c9dabf43ca1af92764a17e5920cd671d59a1ee7 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 1 Apr 2025 16:02:58 +0900 Subject: [PATCH 4/7] return controller from process session --- dojo.h | 39 ++++++++++++++++++++------------------- dojo.hpp | 5 +++-- dojo.pyx | 23 ++++++++++++----------- src/c/mod.rs | 32 +++++++++++++++++++++++--------- 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/dojo.h b/dojo.h index bcdd096..a507a09 100644 --- a/dojo.h +++ b/dojo.h @@ -65,23 +65,6 @@ typedef struct Error { char *message; } Error; -typedef enum Resultbool_Tag { - Okbool, - Errbool, -} Resultbool_Tag; - -typedef struct Resultbool { - Resultbool_Tag tag; - union { - struct { - bool ok; - }; - struct { - struct Error err; - }; - }; -} Resultbool; - typedef enum ResultControllerAccount_Tag { OkControllerAccount, ErrControllerAccount, @@ -103,6 +86,23 @@ typedef struct FieldElement { uint8_t data[32]; } FieldElement; +typedef enum Resultbool_Tag { + Okbool, + Errbool, +} Resultbool_Tag; + +typedef struct Resultbool { + Resultbool_Tag tag; + union { + struct { + bool ok; + }; + struct { + struct Error err; + }; + }; +} Resultbool; + typedef enum ResultFieldElement_Tag { OkFieldElement, ErrFieldElement, @@ -810,9 +810,10 @@ struct CallbackState *controller_connect(const char *rpc_url, * * `state` - CallbackState pointer returned from controller_connect * * # Returns - * Result containing success boolean or error + * Result containing pointer to ControllerAccount or error */ -struct Resultbool handle_deep_link_callback(const char *callback_data, struct CallbackState *state); +struct ResultControllerAccount handle_deep_link_callback(const char *callback_data, + struct CallbackState *state); /** * Retrieves a stored session account if one exists and is valid diff --git a/dojo.hpp b/dojo.hpp index c216019..f53376a 100644 --- a/dojo.hpp +++ b/dojo.hpp @@ -946,8 +946,9 @@ CallbackState *controller_connect(const char *rpc_url, /// * `state` - CallbackState pointer returned from controller_connect /// /// # Returns -/// Result containing success boolean or error -Result handle_deep_link_callback(const char *callback_data, CallbackState *state); +/// Result containing pointer to ControllerAccount or error +Result handle_deep_link_callback(const char *callback_data, + CallbackState *state); /// Retrieves a stored session account if one exists and is valid /// diff --git a/dojo.pyx b/dojo.pyx index 0407f83..3e813a9 100644 --- a/dojo.pyx +++ b/dojo.pyx @@ -53,15 +53,6 @@ cdef extern from *: cdef struct Error: char *message; - cdef enum Resultbool_Tag: - Okbool, - Errbool, - - cdef struct Resultbool: - Resultbool_Tag tag; - bool ok; - Error err; - cdef enum ResultControllerAccount_Tag: OkControllerAccount, ErrControllerAccount, @@ -74,6 +65,15 @@ cdef extern from *: cdef struct FieldElement: uint8_t data[32]; + cdef enum Resultbool_Tag: + Okbool, + Errbool, + + cdef struct Resultbool: + Resultbool_Tag tag; + bool ok; + Error err; + cdef enum ResultFieldElement_Tag: OkFieldElement, ErrFieldElement, @@ -524,8 +524,9 @@ cdef extern from *: # * `state` - CallbackState pointer returned from controller_connect # # # Returns - # Result containing success boolean or error - Resultbool handle_deep_link_callback(const char *callback_data, CallbackState *state); + # Result containing pointer to ControllerAccount or error + ResultControllerAccount handle_deep_link_callback(const char *callback_data, + CallbackState *state); # Retrieves a stored session account if one exists and is valid # diff --git a/src/c/mod.rs b/src/c/mod.rs index 4cba452..c009af2 100644 --- a/src/c/mod.rs +++ b/src/c/mod.rs @@ -82,7 +82,7 @@ struct CallbackState { async fn process_session_callback( state: CallbackState, payload: RegisterSessionResponse, -) -> anyhow::Result<()> { +) -> anyhow::Result { let provider = CartridgeJsonRpcProvider::new(state.rpc_url.clone()); let chain_id = provider.chain_id().await.unwrap(); @@ -137,13 +137,22 @@ async fn process_session_callback( session, ); - // Call the callback with the new account - (state.account_callback)(Box::into_raw(Box::new(ControllerAccount { + let controller_account = ControllerAccount { account: session_account, username: payload.username, - }))); + }; + + // Call the callback if function pointer is not null + let fn_ptr = state.account_callback as *const (); + if !fn_ptr.is_null() { + let controller_copy = ControllerAccount { + account: controller_account.account.clone(), + username: controller_account.username.clone(), + }; + (state.account_callback)(Box::into_raw(Box::new(controller_copy))); + } - Ok(()) + Ok(controller_account) } // Modify handle_callback to use the shared function @@ -306,12 +315,12 @@ pub unsafe extern "C" fn controller_connect( /// * `state` - CallbackState pointer returned from controller_connect /// /// # Returns -/// Result containing success boolean or error +/// Result containing pointer to ControllerAccount or error #[no_mangle] pub unsafe extern "C" fn handle_deep_link_callback( callback_data: *const c_char, state: *mut CallbackState, -) -> Result { +) -> Result<*mut ControllerAccount> { let callback_data = unsafe { CStr::from_ptr(callback_data).to_string_lossy().into_owned() }; let state = unsafe { Box::from_raw(state) }; @@ -336,8 +345,13 @@ pub unsafe extern "C" fn handle_deep_link_callback( } }; - // Process the callback using shared function - RUNTIME.block_on(process_callback(*state, payload)) + // Process the callback using shared function and return the controller account + match RUNTIME.block_on(process_session_callback(*state, payload)) { + Ok(controller) => Result::Ok(Box::into_raw(Box::new(controller))), + Err(e) => Result::Err(Error { + message: CString::new(format!("Failed to process callback: {}", e)).unwrap().into_raw(), + }), + } } /// Retrieves a stored session account if one exists and is valid From dc17bc1707dba89e72827ba08ae99471cf31235e Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 1 Apr 2025 16:31:23 +0900 Subject: [PATCH 5/7] fix up controller funcs --- dojo.h | 70 ++++++++++++++++++++++++++++++++++++-------------- dojo.hpp | 18 ++++++++++--- dojo.pyx | 45 +++++++++++++++++++++++--------- src/c/mod.rs | 72 +++++++++++++++++++++++++++++++++++----------------- 4 files changed, 148 insertions(+), 57 deletions(-) diff --git a/dojo.h b/dojo.h index a507a09..cd68907 100644 --- a/dojo.h +++ b/dojo.h @@ -65,6 +65,23 @@ typedef struct Error { char *message; } Error; +typedef enum Resultbool_Tag { + Okbool, + Errbool, +} Resultbool_Tag; + +typedef struct Resultbool { + Resultbool_Tag tag; + union { + struct { + bool ok; + }; + struct { + struct Error err; + }; + }; +} Resultbool; + typedef enum ResultControllerAccount_Tag { OkControllerAccount, ErrControllerAccount, @@ -86,39 +103,39 @@ typedef struct FieldElement { uint8_t data[32]; } FieldElement; -typedef enum Resultbool_Tag { - Okbool, - Errbool, -} Resultbool_Tag; +typedef enum ResultFieldElement_Tag { + OkFieldElement, + ErrFieldElement, +} ResultFieldElement_Tag; -typedef struct Resultbool { - Resultbool_Tag tag; +typedef struct ResultFieldElement { + ResultFieldElement_Tag tag; union { struct { - bool ok; + struct FieldElement ok; }; struct { struct Error err; }; }; -} Resultbool; +} ResultFieldElement; -typedef enum ResultFieldElement_Tag { - OkFieldElement, - ErrFieldElement, -} ResultFieldElement_Tag; +typedef enum ResultToriiClient_Tag { + OkToriiClient, + ErrToriiClient, +} ResultToriiClient_Tag; -typedef struct ResultFieldElement { - ResultFieldElement_Tag tag; +typedef struct ResultToriiClient { + ResultToriiClient_Tag tag; union { struct { - struct FieldElement ok; + struct ToriiClient *ok; }; struct { struct Error err; }; }; -} ResultFieldElement; +} ResultToriiClient; typedef struct CArrayu8 { uint8_t *data; @@ -810,10 +827,10 @@ struct CallbackState *controller_connect(const char *rpc_url, * * `state` - CallbackState pointer returned from controller_connect * * # Returns - * Result containing pointer to ControllerAccount or error + * Result containing success boolean or error */ -struct ResultControllerAccount handle_deep_link_callback(const char *callback_data, - struct CallbackState *state); +struct Resultbool controller_handle_deep_link_callback(const char *callback_data, + struct CallbackState *state); /** * Retrieves a stored session account if one exists and is valid @@ -928,6 +945,21 @@ struct ResultFieldElement controller_execute_from_outside(struct ControllerAccou */ void client_set_logger(struct ToriiClient *client, void (*logger)(const char*)); +/** + * Creates a new Torii client instance + * + * # Parameters + * * `torii_url` - URL of the Torii server + * * `libp2p_relay_url` - URL of the libp2p relay server + * * `world` - World address as a FieldElement + * + * # Returns + * Result containing pointer to new ToriiClient instance or error + */ +struct ResultToriiClient client_new(const char *torii_url, + const char *libp2p_relay_url, + struct FieldElement world); + /** * Publishes a message to the network * diff --git a/dojo.hpp b/dojo.hpp index f53376a..067c0f0 100644 --- a/dojo.hpp +++ b/dojo.hpp @@ -946,9 +946,8 @@ CallbackState *controller_connect(const char *rpc_url, /// * `state` - CallbackState pointer returned from controller_connect /// /// # Returns -/// Result containing pointer to ControllerAccount or error -Result handle_deep_link_callback(const char *callback_data, - CallbackState *state); +/// Result containing success boolean or error +Result controller_handle_deep_link_callback(const char *callback_data, CallbackState *state); /// Retrieves a stored session account if one exists and is valid /// @@ -1045,6 +1044,19 @@ Result controller_execute_from_outside(ControllerAccount *controll /// * `logger` - Callback function that takes a C string parameter void client_set_logger(ToriiClient *client, void (*logger)(const char*)); +/// Creates a new Torii client instance +/// +/// # Parameters +/// * `torii_url` - URL of the Torii server +/// * `libp2p_relay_url` - URL of the libp2p relay server +/// * `world` - World address as a FieldElement +/// +/// # Returns +/// Result containing pointer to new ToriiClient instance or error +Result client_new(const char *torii_url, + const char *libp2p_relay_url, + FieldElement world); + /// Publishes a message to the network /// /// # Parameters diff --git a/dojo.pyx b/dojo.pyx index 3e813a9..f25d877 100644 --- a/dojo.pyx +++ b/dojo.pyx @@ -53,6 +53,15 @@ cdef extern from *: cdef struct Error: char *message; + cdef enum Resultbool_Tag: + Okbool, + Errbool, + + cdef struct Resultbool: + Resultbool_Tag tag; + bool ok; + Error err; + cdef enum ResultControllerAccount_Tag: OkControllerAccount, ErrControllerAccount, @@ -65,15 +74,6 @@ cdef extern from *: cdef struct FieldElement: uint8_t data[32]; - cdef enum Resultbool_Tag: - Okbool, - Errbool, - - cdef struct Resultbool: - Resultbool_Tag tag; - bool ok; - Error err; - cdef enum ResultFieldElement_Tag: OkFieldElement, ErrFieldElement, @@ -83,6 +83,15 @@ cdef extern from *: FieldElement ok; Error err; + cdef enum ResultToriiClient_Tag: + OkToriiClient, + ErrToriiClient, + + cdef struct ResultToriiClient: + ResultToriiClient_Tag tag; + ToriiClient *ok; + Error err; + cdef struct CArrayu8: uint8_t *data; uintptr_t data_len; @@ -524,9 +533,8 @@ cdef extern from *: # * `state` - CallbackState pointer returned from controller_connect # # # Returns - # Result containing pointer to ControllerAccount or error - ResultControllerAccount handle_deep_link_callback(const char *callback_data, - CallbackState *state); + # Result containing success boolean or error + Resultbool controller_handle_deep_link_callback(const char *callback_data, CallbackState *state); # Retrieves a stored session account if one exists and is valid # @@ -623,6 +631,19 @@ cdef extern from *: # * `logger` - Callback function that takes a C string parameter void client_set_logger(ToriiClient *client, void (*logger)(const char*)); + # Creates a new Torii client instance + # + # # Parameters + # * `torii_url` - URL of the Torii server + # * `libp2p_relay_url` - URL of the libp2p relay server + # * `world` - World address as a FieldElement + # + # # Returns + # Result containing pointer to new ToriiClient instance or error + ResultToriiClient client_new(const char *torii_url, + const char *libp2p_relay_url, + FieldElement world); + # Publishes a message to the network # # # Parameters diff --git a/src/c/mod.rs b/src/c/mod.rs index c009af2..18b01d4 100644 --- a/src/c/mod.rs +++ b/src/c/mod.rs @@ -5,7 +5,6 @@ use std::fs; use std::net::SocketAddr; use std::ops::Deref; use std::os::raw::c_char; -use std::str::FromStr; use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Duration; @@ -70,7 +69,7 @@ lazy_static! { } #[derive(Clone)] -struct CallbackState { +pub(crate) struct CallbackState { shutdown_tx: tokio::sync::mpsc::Sender<()>, rpc_url: Url, policies: Vec, @@ -82,7 +81,7 @@ struct CallbackState { async fn process_session_callback( state: CallbackState, payload: RegisterSessionResponse, -) -> anyhow::Result { +) -> anyhow::Result<()> { let provider = CartridgeJsonRpcProvider::new(state.rpc_url.clone()); let chain_id = provider.chain_id().await.unwrap(); @@ -137,22 +136,15 @@ async fn process_session_callback( session, ); - let controller_account = ControllerAccount { + // Call the callback with the new account + (state.account_callback)(Box::into_raw(Box::new(ControllerAccount { account: session_account, username: payload.username, - }; - - // Call the callback if function pointer is not null - let fn_ptr = state.account_callback as *const (); - if !fn_ptr.is_null() { - let controller_copy = ControllerAccount { - account: controller_account.account.clone(), - username: controller_account.username.clone(), - }; - (state.account_callback)(Box::into_raw(Box::new(controller_copy))); - } + }))); - Ok(controller_account) + // Shutdown the server if we're not using a redirect URI + state.shutdown_tx.send(()).await.unwrap(); + Ok(()) } // Modify handle_callback to use the shared function @@ -315,12 +307,12 @@ pub unsafe extern "C" fn controller_connect( /// * `state` - CallbackState pointer returned from controller_connect /// /// # Returns -/// Result containing pointer to ControllerAccount or error +/// Result containing success boolean or error #[no_mangle] -pub unsafe extern "C" fn handle_deep_link_callback( +pub unsafe extern "C" fn controller_handle_deep_link_callback( callback_data: *const c_char, state: *mut CallbackState, -) -> Result<*mut ControllerAccount> { +) -> Result { let callback_data = unsafe { CStr::from_ptr(callback_data).to_string_lossy().into_owned() }; let state = unsafe { Box::from_raw(state) }; @@ -345,11 +337,11 @@ pub unsafe extern "C" fn handle_deep_link_callback( } }; - // Process the callback using shared function and return the controller account + // Process the callback using shared function match RUNTIME.block_on(process_session_callback(*state, payload)) { - Ok(controller) => Result::Ok(Box::into_raw(Box::new(controller))), + Ok(_) => Result::Ok(true), Err(e) => Result::Err(Error { - message: CString::new(format!("Failed to process callback: {}", e)).unwrap().into_raw(), + message: CString::new(format!("Failed to process session callback: {}", e)).unwrap().into_raw(), }), } } @@ -427,7 +419,7 @@ pub unsafe extern "C" fn controller_account( let signing_key_hex = keyring.get_password().ok()?; // Initialize provider and signer - let provider = CartridgeJsonRpcProvider::new(Url::from_str(&account.rpc_url).unwrap()); + let provider = CartridgeJsonRpcProvider::new(account.rpc_url.clone()); let signing_key = SigningKey::from_secret_scalar(Felt::from_hex(&signing_key_hex).unwrap()); let signer = Signer::Starknet(signing_key); @@ -720,6 +712,40 @@ pub unsafe extern "C" fn client_set_logger( } } +/// Creates a new Torii client instance +/// +/// # Parameters +/// * `torii_url` - URL of the Torii server +/// * `libp2p_relay_url` - URL of the libp2p relay server +/// * `world` - World address as a FieldElement +/// +/// # Returns +/// Result containing pointer to new ToriiClient instance or error +#[no_mangle] +pub unsafe extern "C" fn client_new( + torii_url: *const c_char, + libp2p_relay_url: *const c_char, + world: types::FieldElement, +) -> Result<*mut ToriiClient> { + let torii_url = unsafe { CStr::from_ptr(torii_url).to_string_lossy().into_owned() }; + let libp2p_relay_url = + unsafe { CStr::from_ptr(libp2p_relay_url).to_string_lossy().into_owned() }; + + let client_future = TClient::new(torii_url, libp2p_relay_url, (&world).into()); + + let client = match RUNTIME.block_on(client_future) { + Ok(client) => client, + Err(e) => return Result::Err(e.into()), + }; + + let relay_runner = client.relay_runner(); + RUNTIME.spawn(async move { + relay_runner.lock().await.run().await; + }); + + Result::Ok(Box::into_raw(Box::new(ToriiClient { inner: client, logger: None }))) +} + /// Publishes a message to the network /// /// # Parameters From 627ae9387d71dbd2356fa291b9c1cfc36a58ebc3 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 1 Apr 2025 16:32:51 +0900 Subject: [PATCH 6/7] fmt --- src/c/mod.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/c/mod.rs b/src/c/mod.rs index 18b01d4..fe714f4 100644 --- a/src/c/mod.rs +++ b/src/c/mod.rs @@ -1,12 +1,12 @@ mod types; -use std::ffi::{CStr, CString, c_void}; +use std::ffi::{c_void, CStr, CString}; use std::fs; use std::net::SocketAddr; use std::ops::Deref; use std::os::raw::c_char; -use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; use std::time::Duration; use account_sdk::abigen::controller::OutsideExecutionV3; @@ -18,13 +18,13 @@ use account_sdk::account::session::hash::Session; use account_sdk::provider::{CartridgeJsonRpcProvider, CartridgeProvider}; use account_sdk::signers::Signer; use account_sdk::utils::time::get_current_timestamp; -use axum::Router; use axum::extract::State; -use axum::http::{HeaderValue, Method, StatusCode, header}; +use axum::http::{header, HeaderValue, Method, StatusCode}; use axum::response::IntoResponse; use axum::routing::post; -use base64::Engine as _; +use axum::Router; use base64::engine::general_purpose::STANDARD_NO_PAD as BASE64; +use base64::Engine as _; use cainome::cairo_serde::{self, ByteArray, CairoSerde}; use crypto_bigint::U256; use directories::ProjectDirs; @@ -40,7 +40,7 @@ use starknet::core::utils::get_contract_address; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{JsonRpcClient, Provider as _}; use starknet::signers::{LocalWallet, SigningKey, VerifyingKey}; -use starknet_crypto::{Felt, poseidon_hash_many}; +use starknet_crypto::{poseidon_hash_many, Felt}; use stream_cancel::{StreamExt as _, Tripwire}; use tokio::net::TcpListener; use tokio::runtime::Runtime; @@ -191,12 +191,12 @@ async fn handle_callback(State(state): State, body: String) -> im /// * `policies` - Pointer to array of Policy structs defining session permissions /// * `policies_len` - Length of the policies array /// * `account_callback` - Function pointer called with the new session account when ready -/// * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. -/// If provided, will be used for callback instead of starting a local server. +/// * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. If +/// provided, will be used for callback instead of starting a local server. /// /// # Returns -/// If redirect_uri is provided, returns pointer to CallbackState that must be used with handle_deep_link_callback. -/// If redirect_uri is null, returns null pointer. +/// If redirect_uri is provided, returns pointer to CallbackState that must be used with +/// handle_deep_link_callback. If redirect_uri is null, returns null pointer. #[no_mangle] pub unsafe extern "C" fn controller_connect( rpc_url: *const c_char, @@ -205,7 +205,8 @@ pub unsafe extern "C" fn controller_connect( account_callback: extern "C" fn(*mut ControllerAccount), redirect_uri: *const c_char, ) -> *mut CallbackState { - let rpc_url = Url::parse(&unsafe { CStr::from_ptr(rpc_url).to_string_lossy().into_owned() }).unwrap(); + let rpc_url = + Url::parse(&unsafe { CStr::from_ptr(rpc_url).to_string_lossy().into_owned() }).unwrap(); let policies = unsafe { std::slice::from_raw_parts(policies, policies_len) }; let account_policies = policies .iter() @@ -341,7 +342,9 @@ pub unsafe extern "C" fn controller_handle_deep_link_callback( match RUNTIME.block_on(process_session_callback(*state, payload)) { Ok(_) => Result::Ok(true), Err(e) => Result::Err(Error { - message: CString::new(format!("Failed to process session callback: {}", e)).unwrap().into_raw(), + message: CString::new(format!("Failed to process session callback: {}", e)) + .unwrap() + .into_raw(), }), } } From 7998f41685a99fa9fd05dab12a7764e68526e867 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 1 Apr 2025 18:05:38 +0900 Subject: [PATCH 7/7] update main.c --- dojo.h | 8 ++++---- dojo.hpp | 8 ++++---- dojo.pyx | 8 ++++---- example/main.c | 19 +++++++++++-------- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/dojo.h b/dojo.h index cd68907..50ec91d 100644 --- a/dojo.h +++ b/dojo.h @@ -806,12 +806,12 @@ extern "C" { * * `policies` - Pointer to array of Policy structs defining session permissions * * `policies_len` - Length of the policies array * * `account_callback` - Function pointer called with the new session account when ready - * * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. - * If provided, will be used for callback instead of starting a local server. + * * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. If + * provided, will be used for callback instead of starting a local server. * * # Returns - * If redirect_uri is provided, returns pointer to CallbackState that must be used with handle_deep_link_callback. - * If redirect_uri is null, returns null pointer. + * If redirect_uri is provided, returns pointer to CallbackState that must be used with + * handle_deep_link_callback. If redirect_uri is null, returns null pointer. */ struct CallbackState *controller_connect(const char *rpc_url, const struct Policy *policies, diff --git a/dojo.hpp b/dojo.hpp index 067c0f0..d498858 100644 --- a/dojo.hpp +++ b/dojo.hpp @@ -927,12 +927,12 @@ extern "C" { /// * `policies` - Pointer to array of Policy structs defining session permissions /// * `policies_len` - Length of the policies array /// * `account_callback` - Function pointer called with the new session account when ready -/// * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. -/// If provided, will be used for callback instead of starting a local server. +/// * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. If +/// provided, will be used for callback instead of starting a local server. /// /// # Returns -/// If redirect_uri is provided, returns pointer to CallbackState that must be used with handle_deep_link_callback. -/// If redirect_uri is null, returns null pointer. +/// If redirect_uri is provided, returns pointer to CallbackState that must be used with +/// handle_deep_link_callback. If redirect_uri is null, returns null pointer. CallbackState *controller_connect(const char *rpc_url, const Policy *policies, uintptr_t policies_len, diff --git a/dojo.pyx b/dojo.pyx index f25d877..e595aba 100644 --- a/dojo.pyx +++ b/dojo.pyx @@ -514,12 +514,12 @@ cdef extern from *: # * `policies` - Pointer to array of Policy structs defining session permissions # * `policies_len` - Length of the policies array # * `account_callback` - Function pointer called with the new session account when ready - # * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. - # If provided, will be used for callback instead of starting a local server. + # * `redirect_uri` - Optional pointer to null-terminated string containing the redirect URI. If + # provided, will be used for callback instead of starting a local server. # # # Returns - # If redirect_uri is provided, returns pointer to CallbackState that must be used with handle_deep_link_callback. - # If redirect_uri is null, returns null pointer. + # If redirect_uri is provided, returns pointer to CallbackState that must be used with + # handle_deep_link_callback. If redirect_uri is null, returns null pointer. CallbackState *controller_connect(const char *rpc_url, const Policy *policies, uintptr_t policies_len, diff --git a/example/main.c b/example/main.c index 2fcc306..ed619fa 100644 --- a/example/main.c +++ b/example/main.c @@ -1,10 +1,10 @@ -#include "dojo.h" +#include "../dojo.h" #include #include #include // Add this global variable near the top of the file -static struct SessionAccount* g_session_account = NULL; +static struct ControllerAccount* g_session_account = NULL; void on_entity_state_update(FieldElement key, CArrayStruct models) { @@ -52,7 +52,7 @@ void hex_to_bytes(const char *hex, FieldElement *felt) } } -void on_account_connected(struct SessionAccount *account) +void on_account_connected(struct ControllerAccount *account) { // Store the account in our global variable g_session_account = account; @@ -74,7 +74,7 @@ int main() FieldElement katana; hex_to_bytes("0x03dc18a09d1dc893eb1abce2e0d33b8cc285ea430a4bdd30bccf0c8638e59659", &katana); - ResultToriiClient resClient = client_new(torii_url, rpc_url, "/ip4/127.0.0.1/tcp/9090", world); + ResultToriiClient resClient = client_new(torii_url, "/ip4/127.0.0.1/tcp/9090", world); if (resClient.tag == ErrToriiClient) { printf("Failed to create client: %s\n", resClient.err.message); @@ -95,12 +95,15 @@ int main() } struct Provider *controller_provider = resControllerProvider.ok; - ResultController resSessionAccount = controller_account(policies, 2); - if (resSessionAccount.tag == OkController) { + FieldElement chain_id; + hex_to_bytes("0x534e5f474f45524c49", &chain_id); + + ResultControllerAccount resSessionAccount = controller_account(policies, 2, chain_id); + if (resSessionAccount.tag == OkControllerAccount) { printf("Session account already connected\n"); g_session_account = resSessionAccount.ok; } else { - controller_connect("https://api.cartridge.gg/x/knet-controller/katana", policies, 2, on_account_connected); + controller_connect("https://api.cartridge.gg/x/knet-controller/katana", policies, 2, on_account_connected, NULL); } // Wait for the account to be connected @@ -197,7 +200,7 @@ int main() Query query = {}; query.limit = 100; query.clause.tag = NoneClause; - ResultCArrayEntity resEntities = client_entities(client, &query); + ResultCArrayEntity resEntities = client_entities(client, &query, false); if (resEntities.tag == ErrCArrayEntity) { printf("Failed to get entities: %s\n", resEntities.err.message);