diff --git a/examples/starknet-react-next/src/components/providers/StarknetProvider.tsx b/examples/starknet-react-next/src/components/providers/StarknetProvider.tsx index 913f10ec1..9941ed46e 100644 --- a/examples/starknet-react-next/src/components/providers/StarknetProvider.tsx +++ b/examples/starknet-react-next/src/components/providers/StarknetProvider.tsx @@ -66,16 +66,16 @@ const cartridge = new CartridgeConnector({ }, // theme: "dope-wars", // colorMode: "light" - // prefunds: [ - // { - // address: ETH_TOKEN_ADDRESS, - // min: "300000000000000", - // }, - // { - // address: PAPER_TOKEN_ADDRESS, - // min: "100", - // }, - // ], + prefunds: [ + { + address: ETH_TOKEN_ADDRESS, + min: "300000000000000", + }, + // { + // address: PAPER_TOKEN_ADDRESS, + // min: "100", + // }, + ], }); function provider(chain: Chain) { diff --git a/packages/account-wasm/pkg/account_wasm.d.ts b/packages/account-wasm/pkg/account_wasm.d.ts index 5400d6d99..9bc54115d 100644 --- a/packages/account-wasm/pkg/account_wasm.d.ts +++ b/packages/account-wasm/pkg/account_wasm.d.ts @@ -4,19 +4,6 @@ export interface JsEstimateFeeDetails { nonce: Felt; } -export interface JsOutsideExecution { - caller: Felt; - executeBefore: number; - executeAfter: number; - calls: JsCall[]; - nonce: Felt; -} - -export interface JsInvocationsDetails { - nonce: Felt; - maxFee: Felt; -} - export type Felts = JsFelt[]; export type JsFelt = Felt; @@ -31,6 +18,19 @@ export interface JsCredentials { privateKey: Felt; } +export interface JsInvocationsDetails { + nonce: Felt; + maxFee: Felt; +} + +export interface JsOutsideExecution { + caller: Felt; + executeBefore: number; + executeAfter: number; + calls: JsCall[]; + nonce: Felt; +} + export interface JsCall { contractAddress: Felt; entrypoint: string; @@ -131,6 +131,15 @@ export class CartridgeAccount { * @returns {Promise} */ delegateAccount(): Promise; +/** +* @param {any} owner +* @param {any} username +* @param {(JsPolicy)[]} policies +* @param {bigint} expires_at +* @param {bigint} initial_deposit +* @returns {any} +*/ + static externalDeploymentCalls(owner: any, username: any, policies: (JsPolicy)[], expires_at: bigint, initial_deposit: bigint): any; } /** */ diff --git a/packages/account-wasm/pkg/account_wasm_bg.js b/packages/account-wasm/pkg/account_wasm_bg.js index 3161f8d58..0683b1894 100644 --- a/packages/account-wasm/pkg/account_wasm_bg.js +++ b/packages/account-wasm/pkg/account_wasm_bg.js @@ -12,15 +12,6 @@ function getObject(idx) { return heap[idx]; } let heap_next = heap.length; -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1); - const idx = heap_next; - heap_next = heap[idx]; - - heap[idx] = obj; - return idx; -} - function dropObject(idx) { if (idx < 132) return; heap[idx] = heap_next; @@ -33,7 +24,20 @@ function takeObject(idx) { return ret; } -let WASM_VECTOR_LEN = 0; +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; + +let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); let cachedUint8ArrayMemory0 = null; @@ -44,6 +48,13 @@ function getUint8ArrayMemory0() { return cachedUint8ArrayMemory0; } +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +let WASM_VECTOR_LEN = 0; + const lTextEncoder = typeof TextEncoder === 'undefined' ? (0, module.require)('util').TextEncoder : TextEncoder; let cachedTextEncoder = new lTextEncoder('utf-8'); @@ -113,17 +124,6 @@ function getDataViewMemory0() { return cachedDataViewMemory0; } -const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; - -let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); - -cachedTextDecoder.decode(); - -function getStringFromWasm0(ptr, len) { - ptr = ptr >>> 0; - return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); -} - function debugString(val) { // primitive types const type = typeof val; @@ -220,7 +220,7 @@ function makeMutClosure(arg0, arg1, dtor, f) { return real; } function __wbg_adapter_44(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hab75a3c637f96005(arg0, arg1, addHeapObject(arg2)); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha5780fd1342283cf(arg0, arg1, addHeapObject(arg2)); } function passArrayJsValueToWasm0(array, malloc) { @@ -240,8 +240,8 @@ function handleError(f, args) { wasm.__wbindgen_exn_store(addHeapObject(e)); } } -function __wbg_adapter_185(arg0, arg1, arg2, arg3) { - wasm.wasm_bindgen__convert__closures__invoke2_mut__h47b629288504f118(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); +function __wbg_adapter_186(arg0, arg1, arg2, arg3) { + wasm.wasm_bindgen__convert__closures__invoke2_mut__h11630e69f33a5871(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); } const CartridgeAccountFinalization = (typeof FinalizationRegistry === 'undefined') @@ -462,6 +462,31 @@ export class CartridgeAccount { const ret = wasm.cartridgeaccount_delegateAccount(this.__wbg_ptr); return takeObject(ret); } + /** + * @param {any} owner + * @param {any} username + * @param {(JsPolicy)[]} policies + * @param {bigint} expires_at + * @param {bigint} initial_deposit + * @returns {any} + */ + static externalDeploymentCalls(owner, username, policies, expires_at, initial_deposit) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArrayJsValueToWasm0(policies, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.cartridgeaccount_externalDeploymentCalls(retptr, addHeapObject(owner), addHeapObject(username), ptr0, len0, expires_at, initial_deposit); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } } const CartridgeSessionAccountFinalization = (typeof FinalizationRegistry === 'undefined') @@ -577,13 +602,18 @@ export class CartridgeSessionAccount { } } +export function __wbindgen_object_drop_ref(arg0) { + takeObject(arg0); +}; + export function __wbindgen_object_clone_ref(arg0) { const ret = getObject(arg0); return addHeapObject(ret); }; -export function __wbindgen_object_drop_ref(arg0) { - takeObject(arg0); +export function __wbindgen_error_new(arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); }; export function __wbindgen_string_get(arg0, arg1) { @@ -595,11 +625,6 @@ export function __wbindgen_string_get(arg0, arg1) { getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; -export function __wbindgen_error_new(arg0, arg1) { - const ret = new Error(getStringFromWasm0(arg0, arg1)); - return addHeapObject(ret); -}; - export function __wbindgen_string_new(arg0, arg1) { const ret = getStringFromWasm0(arg0, arg1); return addHeapObject(ret); @@ -1033,7 +1058,7 @@ export function __wbg_new_b85e72ed1bfd57f9(arg0, arg1) { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_185(a, state0.b, arg0, arg1); + return __wbg_adapter_186(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -1137,8 +1162,8 @@ export function __wbindgen_memory() { return addHeapObject(ret); }; -export function __wbindgen_closure_wrapper2417(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 474, __wbg_adapter_44); +export function __wbindgen_closure_wrapper2435(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 471, __wbg_adapter_44); return addHeapObject(ret); }; diff --git a/packages/account-wasm/pkg/account_wasm_bg.wasm b/packages/account-wasm/pkg/account_wasm_bg.wasm index 7998b32fa..620455955 100644 Binary files a/packages/account-wasm/pkg/account_wasm_bg.wasm and b/packages/account-wasm/pkg/account_wasm_bg.wasm differ diff --git a/packages/account-wasm/pkg/account_wasm_bg.wasm.d.ts b/packages/account-wasm/pkg/account_wasm_bg.wasm.d.ts index 9fbaf1306..77999073c 100644 --- a/packages/account-wasm/pkg/account_wasm_bg.wasm.d.ts +++ b/packages/account-wasm/pkg/account_wasm_bg.wasm.d.ts @@ -15,6 +15,7 @@ export function cartridgeaccount_revokeSession(a: number, b: number): void; export function cartridgeaccount_signMessage(a: number, b: number, c: number): number; export function cartridgeaccount_deploySelf(a: number, b: number): number; export function cartridgeaccount_delegateAccount(a: number): number; +export function cartridgeaccount_externalDeploymentCalls(a: number, b: number, c: number, d: number, e: number, f: number, g: number): void; export function __wbg_cartridgesessionaccount_free(a: number, b: number): void; export function cartridgesessionaccount_new(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number): void; export function cartridgesessionaccount_new_as_registered(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number): void; @@ -24,7 +25,7 @@ export function cartridgesessionaccount_execute_from_outside(a: number, b: numbe export function __wbindgen_malloc(a: number, b: number): number; export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number; export const __wbindgen_export_2: WebAssembly.Table; -export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hab75a3c637f96005(a: number, b: number, c: number): void; +export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha5780fd1342283cf(a: number, b: number, c: number): void; export function __wbindgen_add_to_stack_pointer(a: number): number; export function __wbindgen_exn_store(a: number): void; -export function wasm_bindgen__convert__closures__invoke2_mut__h47b629288504f118(a: number, b: number, c: number, d: number): void; +export function wasm_bindgen__convert__closures__invoke2_mut__h11630e69f33a5871(a: number, b: number, c: number, d: number): void; diff --git a/packages/account-wasm/src/deployment.rs b/packages/account-wasm/src/deployment.rs new file mode 100644 index 000000000..40b2bf0e1 --- /dev/null +++ b/packages/account-wasm/src/deployment.rs @@ -0,0 +1,175 @@ +use account_sdk::abigen::controller::{self, Signer as AbigenSigner}; +use account_sdk::account::session::hash::{AllowedMethod, Session}; +use account_sdk::account::session::raw_session::RawSession; +use account_sdk::constants::{ACCOUNT_CLASS_HASH, ETH_CONTRACT_ADDRESS, UDC_ADDRESS}; +use account_sdk::signers::HashSigner; +use cainome::cairo_serde::CairoSerde; +use serde::{Deserialize, Serialize}; +use serde_wasm_bindgen::to_value; +use serde_with::serde_as; +use starknet::{ + core::utils::{get_udc_deployed_address, UdcUniqueness}, + signers::SigningKey, +}; +use starknet::core::serde::unsigned_field_element::UfeHex; +use starknet_types_core::felt::Felt; +use wasm_bindgen::JsValue; + +use crate::types::call::JsCall; +use crate::Result; + +#[serde_as] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct JsDeployment { + #[serde_as(as = "UfeHex")] + pub address: Felt, + pub calls: Vec, + pub session_key: Felt, +} + + +pub struct JsDeploymentBuilder { + owner: Felt, + username: Felt, + delegate: Felt, + allowed_methods: Vec, + expires_at: u64, + initial_deposit: u64, +} + +impl JsDeploymentBuilder { + pub fn new(owner: Felt, username: Felt) -> Self { + Self { + owner, + username, + delegate: Felt::ZERO, + allowed_methods: Vec::new(), + expires_at: 0, + initial_deposit: 0, + } + } + + pub fn with_allowed_methods(mut self, methods: Vec, expires_at: u64) -> Self { + self.allowed_methods = methods; + self.expires_at = expires_at; + self + } + + pub fn with_initial_deposit(mut self, initial_deposit: u64) -> Self { + self.initial_deposit = initial_deposit; + self + } + + pub fn with_delegate_account(mut self, delegate: Felt) -> Self { + self.delegate = delegate; + self + } + + pub fn build(self) -> Result { + let constructor_calldata = Self::create_constructor_calldata(self.owner); + let address = get_udc_deployed_address( + self.username, + ACCOUNT_CLASS_HASH, + &UdcUniqueness::NotUnique, + &constructor_calldata, + ); + let session_signer = SigningKey::from_random(); + + let mut js_calls = vec![ + Self::create_udc_deploy_call(self.username, &constructor_calldata), + Self::create_register_session_call( + address, + self.owner, + self.allowed_methods, + self.expires_at, + &session_signer, + )?, + ]; + + if self.initial_deposit > 0 { + js_calls.extend(Self::create_initial_deposit_calls( + address, + self.initial_deposit, + )); + } + + if self.delegate != Felt::ZERO { + js_calls.push(Self::create_set_delegate_call(address, self.owner)); + } + + let deployment = JsDeployment { + address, + calls: js_calls, + session_key: session_signer.secret_scalar(), + }; + + Ok(to_value(&deployment)?) + } + + fn create_constructor_calldata(owner: Felt) -> Vec { + let mut calldata = + controller::Owner::cairo_serialize(&controller::Owner::Account(owner.into())); + calldata.extend(Option::::cairo_serialize(&None)); + calldata + } + + fn create_register_session_call( + address: Felt, + owner: Felt, + methods: Vec, + expires_at: u64, + session_signer: &SigningKey, + ) -> Result { + let session = Session::new(methods, expires_at, &session_signer.signer())?; + + Ok(JsCall { + contract_address: address, + entrypoint: "register_session".to_string(), + calldata: [ + ::cairo_serialize(&session.raw()), + vec![owner], + ] + .concat(), + }) + } + + fn create_udc_deploy_call(salt: Felt, constructor_calldata: &[Felt]) -> JsCall { + let mut calldata = vec![ + ACCOUNT_CLASS_HASH, + salt, + Felt::ZERO, // unique false + Felt::from(constructor_calldata.len()), + ]; + calldata.extend_from_slice(constructor_calldata); + + JsCall { + contract_address: UDC_ADDRESS, + entrypoint: "deployContract".to_string(), + calldata, + } + } + + fn create_set_delegate_call(address: Felt, delegate: Felt) -> JsCall { + JsCall { + contract_address: address, + entrypoint: "set_delegate_account".to_string(), + calldata: vec![delegate], + } + } + + fn create_initial_deposit_calls(address: Felt, initial_deposit: u64) -> Vec { + vec![ + JsCall { + contract_address: ETH_CONTRACT_ADDRESS, + entrypoint: "approve".to_string(), + calldata: vec![address, initial_deposit.into(), Felt::ZERO], + }, + JsCall { + contract_address: ETH_CONTRACT_ADDRESS, + entrypoint: "transfer".to_string(), + calldata: vec![address, initial_deposit.into(), Felt::ZERO], + }, + ] + } +} diff --git a/packages/account-wasm/src/lib.rs b/packages/account-wasm/src/lib.rs index d125947d8..33dc7e9f8 100644 --- a/packages/account-wasm/src/lib.rs +++ b/packages/account-wasm/src/lib.rs @@ -1,3 +1,4 @@ +mod deployment; mod errors; mod signer; mod storage; @@ -19,6 +20,7 @@ use account_sdk::signers::{HashSigner, Signer}; use base64::engine::general_purpose; use base64::Engine; use coset::{CborSerializable, CoseKey}; +use deployment::JsDeploymentBuilder; use serde_wasm_bindgen::{from_value, to_value}; use signer::BrowserBackend; use starknet::accounts::{Account, ConnectedAccount}; @@ -296,6 +298,28 @@ impl CartridgeAccount { Ok(JsFelt(res)) } + + #[wasm_bindgen(js_name = externalDeploymentCalls)] + pub fn external_deployment_calls( + owner: JsValue, + username: JsValue, + policies: Vec, + expires_at: u64, + initial_deposit: u64, + ) -> Result { + let owner = from_value(owner)?; + let username = from_value(username)?; + let methods = policies + .into_iter() + .map(TryFrom::try_from) + .collect::, _>>()?; + + JsDeploymentBuilder::new(owner, username) + .with_allowed_methods(methods, expires_at) + .with_delegate_account(owner) + .with_initial_deposit(initial_deposit) + .build() + } } #[wasm_bindgen] diff --git a/packages/account-wasm/src/types/call.rs b/packages/account-wasm/src/types/call.rs index 87a2b39fa..18b9b1aa7 100644 --- a/packages/account-wasm/src/types/call.rs +++ b/packages/account-wasm/src/types/call.rs @@ -45,3 +45,13 @@ impl TryFrom for types::Call { }) } } + +impl From for JsCall { + fn from(value: types::Call) -> Self { + JsCall { + contract_address: value.to.into(), + entrypoint: value.selector.to_string(), + calldata: value.calldata, + } + } +} diff --git a/packages/account_sdk/build.rs b/packages/account_sdk/build.rs index 9709b354d..1576341af 100644 --- a/packages/account_sdk/build.rs +++ b/packages/account_sdk/build.rs @@ -21,6 +21,9 @@ fn generate_class_hashes() { r#"use starknet::macros::felt; use starknet_crypto::Felt; +pub const UDC_ADDRESS: Felt = + felt!("0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf"); + pub const ACCOUNT_CLASS_HASH: Felt = felt!("{controller_class_hash:#x}"); diff --git a/packages/account_sdk/src/constants.rs b/packages/account_sdk/src/constants.rs index 648bae6f6..7c585bf60 100644 --- a/packages/account_sdk/src/constants.rs +++ b/packages/account_sdk/src/constants.rs @@ -1,6 +1,9 @@ use starknet::macros::felt; use starknet_crypto::Felt; +pub const UDC_ADDRESS: Felt = + felt!("0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf"); + pub const ACCOUNT_CLASS_HASH: Felt = felt!("0x24a9edbfa7082accfceabf6a92d7160086f346d622f28741bf1c651c412c9ab"); @@ -9,3 +12,6 @@ pub const ACCOUNT_COMPILED_CLASS_HASH: Felt = pub const ERC_20_COMPILED_CLASS_HASH: Felt = felt!("0x732654ca6baa90ff202d2fcc35fb39766eb34842a7e5ac6dbf7714af71f1dab"); + +pub const ETH_CONTRACT_ADDRESS: Felt = + felt!("0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"); diff --git a/packages/keychain/src/components/Execute/index.tsx b/packages/keychain/src/components/Execute/index.tsx index de86e2cde..250eee4f6 100644 --- a/packages/keychain/src/components/Execute/index.tsx +++ b/packages/keychain/src/components/Execute/index.tsx @@ -22,8 +22,7 @@ export const CONTRACT_ETH = "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"; export function Execute() { - const { controller, context, origin, paymaster, cancel } = - useConnection(); + const { controller, context, origin, paymaster, cancel } = useConnection(); const ctx = context as ExecuteCtx; const [maxFee, setMaxFee] = useState(0n); @@ -52,10 +51,7 @@ export function Execute() { const estimateFees = async () => { try { - const balance = await getEthBalance( - account, - controller.address, - ); + const balance = await getEthBalance(account, controller.address); setEthBalance(balance); const maxFee = await calculateMaxFee(ctx, account, calls, balance); @@ -73,7 +69,7 @@ export function Execute() { const getEthBalance = async (account, address) => { const res = await account.callContract({ - contractAddress:CONTRACT_ETH, + contractAddress: CONTRACT_ETH, entrypoint: "balanceOf", calldata: [BigInt(address).toString()], }); @@ -207,7 +203,7 @@ export function Execute() { -