starknet-modular-account
This project is an implementation of a Modular Account for Starknet. It includes a reference account; a set of extensible/reusable components to build your own tool; modules, including a sessionkey validator; a set of SDKs based on starknet.js to use the account in a Dapp or a signer; tests to demonstrate how to use the account and a documentation.
⚠️ ⚠️ ⚠️ this project is very much a work-in-progress and has not been audited yet ⚠️ ⚠️ ⚠️
Why Develop Another Smart Account?
Starknet has been a place of innovation for Smart Accounts since the very start. Thank to actors like Starkware, Openzeppelin, Argent, Ledger, Cartridge, Braavos or Equilibrium, the user experience has no equivalent. However, implementations are not always compatible and it is hard for teams that need specific features to get it from even one provider or to develop it.
Meanwhile, the Smart Account Ecosystem on Ethereum has grown and a number of ideas have shown up that could be implemented on Starknet. starknet-modular-account is an extensible account that allow developers to write modules for specific use cases. You should see it as an experiment or a wish to get:
- extensibility: we could add features to Starknet that we can already find with Ethereum Smart Account and Multisig like Safe, Kernel or the Alchemy accounts
- interoperability: we could develop modules with clean interfaces that would be re-used by widespread accounts
- security: we could audit components not large account code base
- ease of use: we could reference a module registry that accounts providers would reference and user understand
Doing it as a community project would help everybody to get those feature now if they want...
License
This project is licensed under the MIT License
Concepts
Table of Contents
Accounts and the Modular Account
The starknet modular account is a configurable account that provides standard features and delegates others to some modules of choice. For instance, the account enables users to add and remove modules; it allows to execute a method of a module. On the other hand, the account delegates the validation of transactions to modules. This account/module split simplifies the development of specific features; it provides an account that can easily evolve to match the user requirements and be extended...
This document presents the different aspects and interfaces of the modular account as well as modules. It introduces how the account works in general and how the modular account is different. This document is useful for developing new modules. It also provides hints about accessing and managing the modular account from an application.
Account Interface
Abstract accounts or smart accounts are key components of the Starknet protocol. On most blockchains, the source of a transaction is a public key, i.e. the counterpart of your keys that remain secret. In the case of Starknet, the source of a transaction is always an account address. This leads to an immediate question that is "How do you create an account?". As you can guess, you need to run a transaction create an account. In some cases, you will not even have an account to proceed. If this question is out of the scope of this document, this answer to that question is pretty "smart" and we encourage you to find it.
So, except for the account creation that relies on a specific syscall, Starknet
interacts with the account both before and during the execution of a
transaction. How the account behaves is not part of the protocol which leaves a
lot of freedom to developers. To work correctly, however, Starknet requires the
account to implement the __validate__
and __execute__
entrypoints in an
account contract. To say it otherwise, that is the interface that does the
account.
The account interface as described in both SNIP-6 and the comments on this thread. The code below provides the standard definition of a Starknet account! A Starknet account is a Starknet contract that implements the following interface:
#![allow(unused)] fn main() { struct Call { to: ContractAddress, selector: felt252, calldata: Array<felt252> } /// @title SRC-6 Standard Account trait ISRC6 { /// @notice Execute a transaction through the account /// @param calls The list of calls to execute /// @return The list of each call's serialized return value fn __execute__(calls: Array<Call>) -> Array<Span<felt252>>; /// @notice Assert whether the transaction is valid to be executed /// @param calls The list of calls to execute /// @return The string 'VALID' represented as felt when is valid fn __validate__(calls: Array<Call>) -> felt252; /// @notice Assert whether a given signature for a given hash is valid /// @param hash The hash of the data /// @param signature The signature to validate /// @return The string 'VALID' represented as felt when the signature is valid fn is_valid_signature(hash: Array<felt252>, signature: Array<felt252>) -> felt252; } }
It is very simple both to understand and to conform to the account development specification. As a result, an account can be develop that does things that you would not expect from a regular chain like:
- blindly validate all the transactions that are submitted. This scenario that we call the Yasager or Yeasayer is a very interesting to code; at least to learn more about accounts
- execute something very different from what the transaction requests. For instance we could store a signature that would allow to execute the requested transaction later on a different call and execute nothing
The Modular Account
The starknet modular account is an implementation of an account so that you can
develop and register a module that are triggered from inside the __validate__
and/or __execute__
entrypoints. The benefit of this approach is that you
can develop some code that change the behavior of the account without rewriting
a whole account.
There are 2 types of modules for the starknet modular account:
- Validator Modules that are triggered when validating a transaction
- Executor Modules that are triggered when executing a transaction
Note: Only Validator Modules are implemented for now. Executor Modules are part of the Modular Account roadmap.
Validator and Core Validator Modules
A validator module is a class that implements the following interface and can be used by the account to delegate the transaction validation:
#![allow(unused)] fn main() { /// @title Validator Module Interface trait IValidator { fn validate(calls: Array<Call>) -> felt252; } }
As you can guess Validator modules "replace" the __validate__
entrypoint of
the account:
Note:
__validate__
is a reserved entrypoint name for the account and the cairo compiler treat them differently. That is why the module entrypoint are namedvalidate
and not__validate__
but that should have been the case.
A Validator is a Core Validator if it also implements the ICoreValidator
interface:
#![allow(unused)] fn main() { /// @title Core Validator Module Interface trait ICoreValidator<TState> { fn is_valid_signature(self: @TState, hash: Array<felt252>, signature: Array<felt252>) -> felt252; fn initialize(ref self: TState, public_key: Array<felt252>); } }
The core validator has some enhanced features, including the ability to check
a signature from another validator. This can be leverage to support and verify
offchain. This is possible with the is_valid_signature
function that
can be used, not only to check transactions but also signed messages. The
initialize
function is used to setup the core module configuration at
the installation time.
When installing the modular account, not only the public key of the signer is
required like on most account but the Core validator module class hash(*)
is also mandatory. So "the" Core validator module is a module, i.e. a class,
that contains all the validation logic for the account, i.e. the __validate__
and the is_valid_signature
functions.
For now, the only core validator module available is the stark module; it:
- computes the transaction into a a pedersen hash
- validate the transaction signature with the stark curve
That is why the modular account constructor is the following:
#![allow(unused)] fn main() { #[constructor] fn constructor(ref self: ContractState, core_validator: felt252, public_key: Array<felt252>) { self.account.initializer(core_validator, public_key); } }
(*) the core_validator is a felt252 and not a ClassHash type due to some technical constraints on the validation of the account deployment. However the external representation of a ClassHash is the same as the one from a felt252.
Note: the public_key is an Array
so that it can be used to support private keys that are larger than the felt232. In the case of the Stark Curve, we simply put the public key in an array of one
Validator Modules and Prefix Call
So if there is a core validator module that is used by default by the modular account, other validator modules can also be used in addition to it. In order to be triggered, a number of conditions must be met:
- the validator module class must be declared to the network
- the validator module class has must be added to the account with the module management API as described in Module Management
- some metadata in the form of a prefix call(**) must be added to the transaction
Assuming the module is declared in the network and installed in the modular account, adding a prefix will trigger it. But what is a prefix? Let's say a transaction is a set of calls like this:
[call1, call2, ..., callN]
A prefixCall with the following structure has to be created:
const prefixCall: Call = {
contractAddress: accountAddress,
// selector!("__module_validate__")
entrypoint: moduleEntrypointSelector,
calldata: [
moduleClassHash,
...otherValidatorArgs,
]
}
And the transaction that will be requested will actually be the following:
[prefixCall, call1, call2, ..., callN]
As a matter of fact the prefixCall
does not modify the execution of the
transaction that will be made of the other calls from 1 to N. Instead, it
modifies the behavior of the account __validate__
entrypoint that will check
the validation is compliant with the module requirements. In this call, the
following values are used:
accountAddress
is the account address. If you use another address the call will failmoduleEntrypointSelector
is thesn_keccak
of__module_validate__
so it is a fixed valuemoduleClassHash
that is passed as the first parameter of thecalldata
is the module class hash taht is being usedotherValidatorArgs
is an array of felt252 that can be used to pass some parameters to the module so that it can actually validate the transactions. For instance in the case of a Yasager module, it could be that no other parameters are used.
(**) As discussed in the roadmap, we are exploring some changes so that the validator module does not need to use a prefix call. However the SDKs are masking the complexity associated with generating that call and there is not guarranty for now that this change that will land in the signature will actually work.
Executor Modules
The executor module are implemented for now. They will rely on the same usage of
a prefix call with a moduleEntrypointSelector
that is the sn_keccak
of
__module_execute__
.
Additional Account Interfaces
The modular account provides another set of interfaces to interact with modules. The code below shows the interface definition:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IModule<TState> { fn __module_validate__(self: @TState, calldata: Array<felt252>); fn __module_execute__(self: @TState, calldata: Array<felt252>); fn add_module(ref self: TState, class_hash: ClassHash); fn remove_module(ref self: TState, class_hash: ClassHash); fn update_core_module(ref self: TState, class_hash: ClassHash); fn get_core_module(self: @TState) -> ClassHash; fn is_module(self: @TState, class_hash: ClassHash) -> bool; fn call_on_module(self: @TState, class_hash: ClassHash, call: Call) -> Array<felt252>; fn execute_on_module(ref self: TState, class_hash: ClassHash, call: Call) -> Array<felt252>; } }
Module Management
The account provides 2 sets of interfaces to manage the core module and other modules:
get_core_module
andupdate_core_module
enables to check the current core module and to change it for another module.add_module
,remove_module
andis_module
enables to add, remove and check the account modules that are not the core validator module
Note:
update_core_module
is very risky and probably contains flows right now
Module Configuration
Module configuration depends on the module and requires you call the module entrypoint from the account. The account provides 2 helper functions that are used by the SDKs to grant those accesses. The functions are:
call_on_module
a view function that allows interactions with the view functions of a moduleexecute_on_module
that can be used to run transactions on the module from- the account (and only from it).
Prefix functions
__module_validate__
and __module_execute__
are functions that do nothing.
They are part of the account so that when you prefix the calls the transaction
does not fail because Starknet nodes checks for the existence of an entrypoint
as part of the transaction validation process.
Upgrade and Backward Compatibility
The modular account implements the openzeppelin Upgradeable
interface that is
the following:
#![allow(unused)] fn main() { fn upgrade(ref self: ContractState, new_class_hash: ClassHash) }
It enables both to upgrade accounts and to move to another version of the
account like the one from openzeppelin, argent or braavo. Be Careful
moving to another account requires you update the internal state of the account
so that it works correctly and upgrading without updating the state will for
sure break it in an IRREVERSIBLE way. The
experiments/starknet-bootstrap-account
provides some the basic principle that
can be used to perform those migrations but it needs to be developed and
battle-tested. We do not know if the reverse, i.e. moving from another account
to the
Modules
The project comes with 2 validator modules:
- The stark validator that can be used as a core validator for the modular account. This relies on a pedersen-based hash of the transaction and the stark curve signature verification primitives to validate transactions.
- The sessionkey validator that requires an access to the core validator. The sessionkey validator requires an offchain authorisation to be granted by the account signer and allows a 3rd party to run a limited number of transactions with the account.
Validator Interfaces
As mentioned earlier, validators must implement the following interface to work properly. How the implementation is done depends on the requirements...
#![allow(unused)] fn main() { /// @title Validator Module Interface trait IValidator { fn validate(calls: Array<Call>) -> felt252; } }
In addition, if the validator is a Core validator, it must also implement the following interface:
#![allow(unused)] fn main() { /// @title Core Validator Module Interface trait ICoreValidator<TState> { fn is_valid_signature(self: @TState, hash: Array<felt252>, signature: Array<felt252>) -> felt252; fn initialize(ref self: TState, public_key: Array<felt252>); } }
Validator Configuration
In order to allow calls and execution on the account with the entrypoints from
the module, the module must also implement the following interface. The call
and the execute
functions map the input and output respectively between the
call_on_module
and execute_on_module
on the account and the view functions
and the external functions of the module on the other side.
#![allow(unused)] fn main() { pub trait IConfigure { fn call(self: @TState, call: Call) -> Array<felt252>; fn execute(ref self: TState, call: Call) -> Array<felt252>; } }
As a result, to provide an access to the configuration of a module, not only use must add the required entrypoint in the module but you must also provide the mapping in one of these 2 functions.
Validator Entrypoints
The entrypoints associated with each module are specific to the module. They can be whatever is required by the module. Below are the entrypoints:
- For the stark validator
#![allow(unused)] fn main() { pub trait IPublicKeys{ fn add_public_key(ref self: ContractState, new_public_key: felt252); fn get_public_keys(self: @ContractState) -> Array<felt252>; fn get_threshold(self: @ContractState) -> u8; fn remove_public_key(ref self: ContractState, old_public_key: felt252); fn set_threshold(ref self: ContractState, new_threshold: u8); } }
- For the Eth validator
#![allow(unused)] fn main() { pub trait IPublicKey<TState> { fn set_public_key(ref self: TState, new_public_key: EthPublicKey); fn get_public_key(self: @TState) -> EthPublicKey; } }
- For the sessionkey validator
#![allow(unused)] fn main() { pub trait IDisableSessionKey { fn disable_session_key(ref self: ContractState, sessionkey: felt252); fn is_disabled_session_key(self: @ContractState, sessionkey: felt252); } }
Using SDKs
starknet-modular-account comes with 2 SDKs that leverage starknet.js.
- @0xknwn/starknet-modular-account provides the SmartrAccount class that extends starknet.js account to support multiple signers and helps to manage modules. It also provides the AccountModuleInterface that should be used by module SDKs.
- @0xknwn/starknet-module-sessionkey
provides the
SessionKeyModule
that implements theAccountModuleInterface
as well as tools to configure the sessionkey module, including thePolicyManager
and thePolicyGrantor
classes. - @0xknwn/starknet-module
provides the
EthModule
that implements theAccountModuleInterface
.
In addition, the project provides another SDK called @0xknwn/starknet-test-helpers that can be used to create helper classes outside of this repository. It is used to demonstrate the 2 main SDKs.
This section provides a set of tutorials about how to use the account and modules. If you want to understand how modules are working internally, you should check Modules Internals.
Installing SDKs
SDKs are provided as NPM packages for an easy use. If you plan to use them, you should have a Javascript or a Typescript project configured,
Create a typescript project
If not already done the script below shows how to create a minimalistic typescript project:
mkdir documentation-examples && cd documentation-examples
npm init -y
git init
npm install typescript --save-dev
npx tsc --init
Obviously you might want to adapt the script to your requirements and that is out of the scope of this documentation...
The rest of the documentation assume typescript is used and the javascript
file are generated in the dist
directory. To change the default settings
edit the tsconfig.json
and change the following:
- set the
outDir
property to bedist
- change the
target
to bees2020
or later. That is because we intensively usebigint
in the project and it was not supported withes2016
This is an example of a working tsconfig.json
:
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"outDir": "./dist/",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
Then create a simple src
directory with a index.ts file in it:
mkdir -p src
echo 'console.log("success");' > src/index.ts
To transpile typescript in javascript, run:
npx tsc --build
To run the output, run:
node dist/index.js
Install the Modular Account SDK
The modular account SDK is named @0xknwn/starknet-modular-account
. To add it
to your project, run the command below:
npm install --save @0xknwn/starknet-modular-account
npm install --save starknet@next
Install the SessionKey Module SDK
If you plan to use the sessionkey module, you will need the
@0xknwn/starknet-module-sessionkey
SDK but very likely also the @0xknwn/starknet-modular-account
SDK. To install the 2 packages, run the command below:
npm install --save \
@0xknwn/starknet-modular-account \
@0xknwn/starknet-module-sessionkey
npm install --save starknet@next
Deploying the Modular Account
Declare a the SmartrAccount and StarkValidator classes
If you are working on a network that does not have the classes already
declared, you will need to declare them. The modular account main SDK, aka
@0xknwn/starknet-modular-account
contains class and a helper function named
declareClass
to declare the class to the network. To use it, you need to
pass:
- A starknet.js
Account
as a first parameter - The name of the class to declare as the 2nd parameter. They are
SmartrAccount
for the modular account andStarkValidator
for the Stark Core Validator
Below is an example of a script that declares the 2 classes.
// file src/01-declare-class.ts
import { RpcProvider, Account } from "starknet";
import { declareClass, classNames } from "@0xknwn/starknet-modular-account";
// these are the settings for the devnet with --seed=0
// change them to mee your requirements
const providerURL = "http://127.0.0.1:5050/rpc";
const ozAccountAddress =
"0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691";
const ozPrivateKey = "0x71d7bb07b9a64f6f78ac4c816aff4da9";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const account = new Account(
provider,
ozAccountAddress,
ozPrivateKey,
"1",
"0x3"
);
const { classHash: smartrAccountClassHash } = await declareClass(
account,
classNames.SmartrAccount
);
console.log("smartrAccount class hash:", smartrAccountClassHash);
const { classHash: starkValidatorClassHash } = await declareClass(
account,
classNames.StarkValidator
);
console.log("starkValidator class hash:", starkValidatorClassHash);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Note: To declare the class, the account you use must be loaded with ETH.
Assuming you have named the script src/01-declare-class.ts
, to transpile it
and run it, use the script below:
npx tsc --build
node dist/01-declare-class.js
The output should return the hashes for the 2 classes.
Verify the SmartrAccount and StarkValidator class hash
The 2 class hashes do NOT depend on the deployment or the network. So you
can find them at any time with the classHash
helper that comes with the
SDK. The script below shows how to use that function:
// file src/01-check-class.ts
import { classHash, classNames } from "@0xknwn/starknet-modular-account";
console.log("smartrAccount class hash:", classHash(classNames.SmartrAccount));
console.log("starkValidator class hash:", classHash(classNames.StarkValidator));
Assuming you have named the script src/01-check-class.ts
, you can transpile
and run it:
npx tsc --build
node dist/01-check-class.js
Charge ETH to the SmartrAccount Address to deploy it
Here again, the SDK provides a helper function called deployAccount
to
help with the deployment of the modular account. Before you move forward with
the account, you must compute the account address with accountAddress
and
send ETH to it. To proceed, create a file named src/01-load-eth.ts
with this
content:
{{#include ../experiments/documentation-examples/src/01-load-eth.ts}}
Note: You must create a file
abi/ERC20.ts
that contains the ABI of an ERC20 in order to call it from a contract.
Transpile and run the script:
npx tsc --build
node dist/01-load-eth.js
Deploy the Modular Account
Now that the address has some ETH on it, you can deploy the account with the
deployAccount
helper. Create a file named src/01-deploy-account.ts
like
below:
// file src/01-deploy-account.ts
import { RpcProvider, Account, Signer, CallData } from "starknet";
import {
accountAddress,
classHash,
deployAccount,
SmartrAccount,
SmartrAccountABI,
classNames,
} from "@0xknwn/starknet-modular-account";
// these are the settings for the devnet with --seed=0
// change them to mee your requirements
const providerURL = "http://127.0.0.1:5050/rpc";
const smartrAccountPrivateKey = "0x1";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const smartrSigner = new Signer(smartrAccountPrivateKey);
const smartrAccountPublicKey = await smartrSigner.getPubKey();
const starkValidatorClassHash = classHash(classNames.StarkValidator);
const calldata = new CallData(SmartrAccountABI).compile("constructor", {
core_validator: starkValidatorClassHash,
args: [smartrAccountPublicKey],
});
const smartrAccountAddress = accountAddress(
classNames.SmartrAccount,
smartrAccountPublicKey,
calldata
);
const smartrAccount = new SmartrAccount(
provider,
smartrAccountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const address = await deployAccount(
smartrAccount,
classNames.SmartrAccount,
smartrAccountPublicKey,
calldata
);
if (address !== smartrAccountAddress) {
throw new Error(
`The account should have been deployed to ${smartrAccountAddress}, instead ${address}`
);
}
console.log("accountAddress", smartrAccountAddress);
console.log("public key", smartrAccountPublicKey);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/01-deploy-account.js
Using the modular account from the SDK
You can use rely on the SmartrAccount
class to use the account. The script
below shows all the requirements to compute the class hash, the address and
instantiate the account:
// file src/01-using-account.ts
import { RpcProvider, Signer, CallData } from "starknet";
import {
accountAddress,
classHash,
SmartrAccount,
SmartrAccountABI,
classNames,
} from "@0xknwn/starknet-modular-account";
// these are the settings for the devnet with --seed=0
// change them to mee your requirements
const providerURL = "http://127.0.0.1:5050/rpc";
const smartrAccountPrivateKey = "0x1";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const smartrSigner = new Signer(smartrAccountPrivateKey);
const smartrAccountPublicKey = await smartrSigner.getPubKey();
const starkValidatorClassHash = classHash(classNames.StarkValidator);
const calldata = new CallData(SmartrAccountABI).compile("constructor", {
core_validator: starkValidatorClassHash,
args: [smartrAccountPublicKey],
});
const smartrAccountAddress = accountAddress(
classNames.SmartrAccount,
smartrAccountPublicKey,
calldata
);
const smartrAccount = new SmartrAccount(
provider,
smartrAccountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
console.log("address", smartrAccount.address);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/01-using-account.js
Using the Stark Validator
The Stark Validator Module can be used as a Core Validator for the account. It is used by default by the modular account in many configuration. In this section of the documentation, you will see how you can use the Moduler Account with the Stark Validator Module as a Core Module.
- Using the Stark Validator
Note: This section assumes the
SmartrAccount
class has been instantiated in thesmartrAccount
variable as shown in Using the modular account from the SDK. It also assumes theCounter
contract that comes with the project has been deploys to thecounterAddress
and theCounterABI
class is available. The02-setup.ts
script that comes with this project ensure those steps are executed.
Interacting with a Contract
The starknet modular account SDK provides the SmartrAccount
class that extends
the starknet.js Account class. As you can see from the script below, using the
SmartrAccount
is exactly like using the Account
class, you can:
- instantiate the account with an
RpcProvider
, anaddress
and aSigner
or private key - use the account in a
Contract
to call view functions - use the
execute
function of the account to call an external function of a contract.SmartrAccount
provides the same methods asAccount
// file src/02-execute-tx.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import { init, CounterABI } from "./02-init";
import { RpcProvider, Contract } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, counterAddress, smartrAccountPrivateKey } =
await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const counter = new Contract(CounterABI, counterAddress, account);
let currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
const call = counter.populate("increment");
const { transaction_hash } = await account.execute(call);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/02-execute-tx.js
Interacting with the Stark Validator
The SmartrAccount
class, however, provides more than just the regular
Account
class. It can interact with functions that are part of the module
and not part of the account. In the case of the Stark Validator, those
functions are:
#![allow(unused)] fn main() { fn get_public_key(self: @TState) -> felt252; fn set_public_key(ref self: TState, new_public_key: felt252); }
To execute a function that is part of the module you need:
- to figure out the stark validator module class hash
- to check the module is installed on the account. That is something that is setup at the account deployment time
- to use one of
callOnModule
for view functions orexecuteOnModule
for running transactions on the SmartrAccount.
The sections below dig into the details of these operations.
Getting the stark validator module class hash
This is something we have already done previously. You can use
classHash("StaekValidator")
after your imported the classHash
function from
@0xknwn/starknet-modular-account
like below:
// file src/02-check-class.ts
import { classHash, classNames } from "@0xknwn/starknet-modular-account";
console.log("starkValidator class hash:", classHash(classNames.StarkValidator));
To execute the script, make sure you have deployed the account in the network and run the following commands:
npx tsc --build
node dist/02-check-class.js
Check the module is installed on the account
The SmartrAccount
provides a method isModule
that can be used to know if
a module is installed with the account.
// file src/02-module-installed.ts
import {
SmartrAccount,
classHash,
classNames,
} from "@0xknwn/starknet-modular-account";
import { init } from "./02-init";
import { RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const isInstalled = await account.isModule(
classHash(classNames.StarkValidator)
);
console.log(
"module",
classHash(classNames.StarkValidator),
"is installed",
isInstalled
);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/02-module-installed.js
Calling views functions in the module
To execute a view function on the module, we must build the argumemt list with
the CallData
class. Thwn we can call the callOnModule
function from
SmartrAccount
with the module class hash, the function name and the calldata
like below:
// file src/02-registered-publickeys.ts
import {
StarkValidatorABI,
SmartrAccount,
classHash,
classNames,
} from "@0xknwn/starknet-modular-account";
import { init } from "./02-init";
import { CallData, RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const moduleCallData = new CallData(StarkValidatorABI);
const calldata = await moduleCallData.compile("get_public_key", {});
const publickey = await account.callOnModule(
classHash(classNames.StarkValidator),
"get_public_key",
calldata
);
console.log("publickey is", `0x${BigInt(publickey[0]).toString(16)}`);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/02-registered-publickey.js
Executing external functions in the module
To execute an external function on the module, we must build the argumemt list
with the CallData
class. Then we can call the executeOnModule
function from
SmartrAccount
with the module class hash, the function name and the calldata
like below. Here we will register a second public key for the same account:
// file src/02-add-publickey.ts
import {
StarkValidatorABI,
SmartrAccount,
classHash,
classNames,
} from "@0xknwn/starknet-modular-account";
import { init } from "./02-init";
import { CallData, RpcProvider, Signer } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const newAccountPrivateKey = "0x2";
const main = async () => {
const signer = new Signer(newAccountPrivateKey);
const newAccountPublicKey = await signer.getPubKey();
console.log("second account public key", newAccountPublicKey);
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const moduleCallData = new CallData(StarkValidatorABI);
const calldata = await moduleCallData.compile("set_public_key", {
new_public_key: newAccountPublicKey,
});
const { transaction_hash } = await account.executeOnModule(
classHash(classNames.StarkValidator),
"set_public_key",
calldata
);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/02-update-publickey.js
You can re-run the script from the previous example to check the account has two registered public key:
node dist/02-registered-publickey.js
Interacting with a Contract with the new registered key
You now can interact with the SmartrAccount
with your new private key like
below:
// file src/02-execute-tx-pk2.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import { init, CounterABI } from "./02-init";
import { RpcProvider, Contract } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const newSmartrAccountPrivateKey = "0x2";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, counterAddress } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
newSmartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const counter = new Contract(CounterABI, counterAddress, account);
let currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
const call = counter.populate("increment");
const { transaction_hash } = await account.execute(call);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/02-execute-tx-pk2.js
Interacting with the Contract with the SDK Stark Module
You can use the StarkValidator as a secondary module, even when installed as a Core module. To run a transaction with such a configuration, all you have to do is to call the StarkModule with the account address when creating the SmartrAccount by adding a 4th parameter to the constructor. The script below shows the change:
// file src/02-execute-tx-pk2-with-module.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import { StarkModule } from "@0xknwn/starknet-module";
import { init, CounterABI } from "./02-init";
import { RpcProvider, Contract } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const newSmartrAccountPrivateKey = "0x2";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, counterAddress } = await init();
const module = new StarkModule(accountAddress);
const account = new SmartrAccount(
provider,
accountAddress,
newSmartrAccountPrivateKey,
module,
"1",
"0x3"
);
const counter = new Contract(CounterABI, counterAddress, account);
let currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
const call = counter.populate("increment");
const { transaction_hash } = await account.execute(call);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
If you check the transaction, you can see the call is prefixed by
a call to __validate_module__
with the account address and StarkValidator
class hash as a first parameter. Transpile and run the script:
npx tsc --build
node dist/02-execute-tx-pk2-with-module.js
Note: The Starkmodule is part of
@0xknwn/starknet-module
and this SDK as to be installed if you want to interact with the StarkModule as a secondary module.
Using the Multisig Validator
When installed with the Multisig Validator Module, the starknet modular account
not only can have multiple signers registered but it can also require N of those
signers to agree to sign a transaction. This Multisig feature takes place
offchain and the SmartrAccount
class provides the framework manage several
signers on the same account to sign the a transaction. This is what is shown in
this section.
- Using the Multisig Validator
Note 1: This section assumes the
SmartrAccount
class has been instantiated in thesmartrAccount
variable as shown in Using the modular account from the SDK. It also assumes theCounter
contract that comes with the project has been deploys to thecounterAddress
and theCounterABI
class are available. The03-setup.ts
script that comes with this project ensure those steps are executed.
Note 2: This multi-signer model has some limits: the exchange of the transaction between signers is not managed by the SDK and requires some synchronization between the actors. In addition, the fact the transaction is a regular transaction that involves the account Nonce generated by the network prevents from using it at a large scale. The starknet modular account can be used to implement more advanced models to workaround those 2 issues.
Interacting with the Multisig Validator
The SmartrAccount
class, however, provides more than just the regular
Account
class. It can interact with functions that are part of the module
and not part of the account. In the case of the Multisig Validator, those
functions are:
#![allow(unused)] fn main() { fn get_public_keys(self: @TState) -> Array<felt252>; fn add_public_key(ref self: TState, new_public_key: felt252); fn remove_public_key(ref self: TState, old_public_key: felt252); fn get_threshold(self: @TState) -> u8; fn set_threshold(ref self: TState, new_threshold: u8); }
To execute a function that is part of the module you need:
- to figure out the Multisig Validator module class hash
- to check the module is installed on the account. That is something that is setup at the account deployment time
- to use one of
callOnModule
for view functions orexecuteOnModule
for running transactions on the SmartrAccount.
The sections below dig into the details of these operations.
Getting the stark validator module class hash
This is something we have already done previously. You can use
classHash("MultisigValidator")
after your imported the classHash
function
from @0xknwn/starknet-module
like below:
// file src/03-check-class.ts
import {
classHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
console.log(
"MultisigValidator class hash:",
classHash(moduleClassNames.MultisigValidator)
);
To execute the script, make sure you have deployed the account in the network and run the following commands:
npx tsc --build
node dist/03-check-class.js
Check the module is installed on the account
The SmartrAccount
provides a method isModule
that can be used to know if
a module is installed with the account.
// file src/03-module-installed.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
classHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./03-init";
import { RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const isInstalled = await account.isModule(
classHash(moduleClassNames.MultisigValidator)
);
console.log(
"module",
classHash(moduleClassNames.MultisigValidator),
"is installed",
isInstalled
);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/03-module-installed.js
Calling views functions in the module
To execute a view function on the module, we must build the argumemt list with
the CallData
class. Thwn we can call the callOnModule
function from
SmartrAccount
with the module class hash, the function name and the calldata
like below:
// file src/03-registered-publickeys.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
MultisigValidatorABI,
classHash as moduleClassHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./03-init";
import { CallData, RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const moduleCallData = new CallData(MultisigValidatorABI);
const calldata = moduleCallData.compile("get_public_keys", {});
const publickeysList = await account.callOnModule(
moduleClassHash(moduleClassNames.MultisigValidator),
"get_public_keys",
calldata
);
console.log("number of public keys for module", publickeysList.length);
publickeysList.forEach((publickey, idx) => {
console.log("publickey #", idx + 1, `0x${publickey.toString(16)}`);
});
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/03-registered-publickeys.js
Executing external functions in the module
To execute an external function on the module, we must build the argumemt list
with the CallData
class. Then we can call the executeOnModule
function from
SmartrAccount
with the module class hash, the function name and the calldata
like below. Here we will register a second public key for the same account:
// file src/03-add-publickeys.ts
import {
SmartrAccountABI,
SmartrAccount,
} from "@0xknwn/starknet-modular-account";
import {
MultisigValidatorABI,
classHash as moduleClassHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./03-init";
import { CallData, RpcProvider, Signer, hash, type Call } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const secondAccountPrivateKey = "0x2";
const thirdAccountPrivateKey = "0x3";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const module_class_hash = moduleClassHash(moduleClassNames.MultisigValidator);
const calls: Call[] = [];
for (const privateKey of [secondAccountPrivateKey, thirdAccountPrivateKey]) {
const signer = new Signer(privateKey);
const publicKey = await signer.getPubKey();
console.log("new account public key", publicKey);
const moduleCallData = new CallData(MultisigValidatorABI);
const moduleCalldata = moduleCallData.compile("add_public_key", {
new_public_key: publicKey,
});
const accountCallData = new CallData(SmartrAccountABI);
const calldata = accountCallData.compile("execute_on_module", {
class_hash: module_class_hash,
call: {
selector: hash.getSelectorFromName("add_public_key"),
to: accountAddress,
calldata: moduleCalldata,
},
});
const call: Call = {
entrypoint: "execute_on_module",
contractAddress: accountAddress,
calldata,
};
calls.push(call);
}
const { transaction_hash } = await account.execute(calls);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/03-add-publickeys.js
You can re-run the script from the previous example to check the account has two registered public key:
node dist/03-registered-publickeys.js
The output should look like that:
number of public keys for module 3
publickey # 1 0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca
publickey # 2 0x759ca09377679ecd535a81e83039658bf40959283187c654c5416f439403cf5
publickey # 3 0x411494b501a98abd8262b0da1351e17899a0c4ef23dd2f96fec5ba847310b20
Interacting with a Contract with the new registered key
You now can interact with the SmartrAccount
with your second private key like
below:
// file src/03-execute-tx.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import { init, CounterABI } from "./03-init";
import { RpcProvider, Contract } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const secondAccountPrivateKey = "0x2";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, counterAddress } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
secondAccountPrivateKey,
undefined,
"1",
"0x3"
);
const counter = new Contract(CounterABI, counterAddress, account);
let currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
const call = counter.populate("increment");
const { transaction_hash } = await account.execute(call);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/03-execute-tx-pk2.js
Changing the Account Threshold to 2
By changing the Multisig Validator Threshold to 2, you force transactions to be signed by 2 of the 3 signers of the account. Run a script like below to change the threshold:
// file src/03-increase-threshold.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
MultisigValidatorABI,
classHash as moduleClassHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./03-init";
import { CallData, RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const moduleCallData = new CallData(MultisigValidatorABI);
const calldata = moduleCallData.compile("set_threshold", {
new_threshold: 2,
});
const { transaction_hash } = await account.executeOnModule(
moduleClassHash(moduleClassNames.MultisigValidator),
"set_threshold",
calldata
);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/03-increase-threshold.js
You can check the current threshold on the account with the script below:
// file src/04-get-threshold.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
MultisigValidatorABI,
classHash as moduleClassHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./03-init";
import { CallData, RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const moduleCallData = new CallData(MultisigValidatorABI);
const calldata = await moduleCallData.compile("get_threshold", {});
const threshold = await account.callOnModule(
moduleClassHash(moduleClassNames.MultisigValidator),
"get_threshold",
calldata
);
threshold.forEach((threshold) => console.log("threshold", threshold));
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Run the script with the command below:
npx tsc --build
node dist/03-get-threshold.js
Checking you can NOT run a transaction with a single signer
The script below executes a transaction with a single signer as it was the case in the previous section:
// file src/03-execute-tx.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import { init, CounterABI } from "./03-init";
import { RpcProvider, Contract } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, counterAddress, smartrAccountPrivateKey } =
await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const counter = new Contract(CounterABI, counterAddress, account);
let currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
const call = counter.populate("increment");
const { transaction_hash } = await account.execute(call);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Make sure you have deployed the account and the counter contract in the network and run the following commands:
npx tsc --build
node dist/03-execute-tx.js
You are now getting an error saying the signature is invalid like below:
Execution failed.
Failure reason: 0x4163636f756e743a20696e76616c6964207369676e6174757265
('Account: invalid signature')
Running a Multiple Signer Transaction
To run a transaction with multiple signers, you need to instantiate several
SmartrAccount
, each one with a different signer. Because you have set the
threshold, you need to instantiate 2 accounts.
Once done, proceed in 3 steps:
- Step 1: generate the transaction details. This requires you create the calls
but also you set some details about it, including: the Fees, the Nonce, the
Version and the Chain. The
SmartrAccount
class uses the provider to get the chain id. To get the other details, you should run theprepareMultisig
that returns the details associated with the transaction. - Step 2: have all the signers generate their part of the signature. The
signMultisig
takes the list of calls and the details you have generated and provides the signature as an array of string - Step 3: Execute the transaction with all the signatures from Step 2. This
could be done by anyone, including one of the account you have already
created. The
executeMultisig
function takes the list of calls, the details and an array that contains all the signatures.
The script below signs the transaction with 2 signers and to run the increment
external function of the Counter
contract. It shows the value of the counter
before and after the call:
// file src/03-execute-tx-multiple-signers.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import { init, CounterABI } from "./03-init";
import { RpcProvider, Contract, ArraySignatureType } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const secondSmartrAccountPrivateKey = "0x2";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, counterAddress, smartrAccountPrivateKey } =
await init();
const firstAccount = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const secondAccount = new SmartrAccount(
provider,
accountAddress,
secondSmartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
// Before you start check the value of the counter
const counter = new Contract(CounterABI, counterAddress, provider);
let currentCounter = await counter.call("get");
console.log("currentCounter value", currentCounter);
// Step 1: Prepare the transaction and get the details
const call = counter.populate("increment");
const calls = [call];
const detail = await firstAccount.prepareMultisig(calls);
console.log("below are the details assciated with the transaction");
console.log(detail);
// Step 2: Sign the transaction with 2 signers
// (because the threshold on the account is currently 2)
const firstSignature: ArraySignatureType = await firstAccount.signMultisig(
calls,
detail
);
console.log("first signature is", firstSignature);
const secondSignature: ArraySignatureType = await secondAccount.signMultisig(
calls,
detail
);
console.log("second signature is", secondSignature);
// Step 3: Execute the transaction
const { transaction_hash } = await firstAccount.executeMultisig(
calls,
detail,
[...firstSignature, ...secondSignature]
);
const receipt = await firstAccount.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
// Once finished, check the value of the counter again
currentCounter = await counter.call("get");
console.log("currentCounter value", currentCounter);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/03-execute-tx-multiple-signers.js
Reset the threshold to one
As for any transaction, you need to run a multi-signed transaction to reset the
account threshold back to one. The script below build the call to
execute_on_module
and run it with multiple signer:
// file src/03-decrease-threshold.ts
import {
SmartrAccount,
SmartrAccountABI,
} from "@0xknwn/starknet-modular-account";
import {
MultisigValidatorABI,
classHash as moduleClassHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./03-init";
import {
CallData,
RpcProvider,
hash,
type Call,
type ArraySignatureType,
} from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const secondSmartrAccountPrivateKey = "0x2";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const firstAccount = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const secondAccount = new SmartrAccount(
provider,
accountAddress,
secondSmartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
// Before you start build the set_threshold call
const moduleCallData = new CallData(MultisigValidatorABI);
const moduleCalldata = moduleCallData.compile("set_threshold", {
new_threshold: 1,
});
const accountCallData = new CallData(SmartrAccountABI);
const calldata = accountCallData.compile("execute_on_module", {
class_hash: moduleClassHash(moduleClassNames.MultisigValidator),
call: {
selector: hash.getSelectorFromName("set_threshold"),
to: accountAddress,
calldata: moduleCalldata,
},
});
const call: Call = {
entrypoint: "execute_on_module",
contractAddress: accountAddress,
calldata,
};
const calls = [call];
// Step 1: Prepare the transaction and get the details
const detail = await firstAccount.prepareMultisig(calls);
// Step 2: Sign the transaction with 2 signers
// (because the threshold on the account is currently 2)
const firstSignature: ArraySignatureType = await firstAccount.signMultisig(
calls,
detail
);
const secondSignature: ArraySignatureType = await secondAccount.signMultisig(
calls,
detail
);
// Step 3: Execute the transaction
const { transaction_hash } = await firstAccount.executeMultisig(
calls,
detail,
[...firstSignature, ...secondSignature]
);
const receipt = await firstAccount.waitForTransaction(transaction_hash);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Execute the script with the following commands:
npx tsc --build
node dist/03-decrease-threshold.js
You can check the threshold is back to one:
node dist/03-get-threshold.js
Remove Registered Keys
You can now run transaction with a single signer on the account. The script below shows how to remove 2 public keys from a single call:
// file src/03-remove-publickeys.ts
import {
SmartrAccountABI,
SmartrAccount,
} from "@0xknwn/starknet-modular-account";
import {
MultisigValidatorABI,
classHash as moduleClassHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./03-init";
import { CallData, RpcProvider, Signer, hash, type Call } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const secondAccountPrivateKey = "0x2";
const thirdAccountPrivateKey = "0x3";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const module_class_hash = moduleClassHash(moduleClassNames.MultisigValidator);
const calls: Call[] = [];
for (const privateKey of [secondAccountPrivateKey, thirdAccountPrivateKey]) {
const signer = new Signer(privateKey);
const publicKey = await signer.getPubKey();
console.log("account public key to remove", publicKey);
const moduleCallData = new CallData(MultisigValidatorABI);
const moduleCalldata = moduleCallData.compile("remove_public_key", {
old_public_key: publicKey,
});
const accountCallData = new CallData(SmartrAccountABI);
const calldata = accountCallData.compile("execute_on_module", {
class_hash: module_class_hash,
call: {
selector: hash.getSelectorFromName("remove_public_key"),
to: accountAddress,
calldata: moduleCalldata,
},
});
const call: Call = {
entrypoint: "execute_on_module",
contractAddress: accountAddress,
calldata,
};
calls.push(call);
}
const { transaction_hash } = await account.execute(calls);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/03-remove-publickeys.js
You can check the 2 of the 3 public keys have been removed:
node dist/03-registered-publickeys.js
Using the Eth Validator
The Eth Validator Module can both work as a Secondary or as the Core Validator for the account. It requires a separate SDK. In this section of the documentation, you will see how you can use the Moduler Account to interact with the Eth Validator Module.
- Using the Eth Validator
Note: This section assumes the
SmartrAccount
class has been instantiated in thesmartrAccount
variable as shown in Using the modular account from the SDK. It also assumes theCounter
contract that comes with the project has been deploys to thecounterAddress
and theCounterABI
class is available. The05-setup.ts
script that comes with this project ensure those steps are executed.
Installing the Eth Validator SDK
If you plan to use the Eth Validatoi module, you might need the
@0xknwn/starknet-module
SDK in addition to the
@0xknwn/starknet-modular-account
SDK. To install it, run:
npm install --save \
@0xknwn/starknet-module
Declaring the Eth Validator
If you are working on a network that does not have the eth validator class
already declared, you will need to declare it. The Eth validator module SDK, aka
@0xknwn/starknet-module
contains a helper function named declareClass
to
declare the class to the network. To use it, you need to pass:
- A starknet.js
Account
as a first parameter - The name of the class to declare as the 2nd parameter. For the Eth Validator,
the name is
EthValidator
Below is an example of a script that declares the new classes.
// file src/04-declare-eth-validator.ts
import { RpcProvider, Account } from "starknet";
import {
declareClass,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
// these are the settings for the devnet with --seed=0
// change them to mee your requirements
const providerURL = "http://127.0.0.1:5050/rpc";
const ozAccountAddress =
"0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691";
const ozPrivateKey = "0x71d7bb07b9a64f6f78ac4c816aff4da9";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const account = new Account(
provider,
ozAccountAddress,
ozPrivateKey,
"1",
"0x3"
);
const { classHash: ethValidatorClassHash } = await declareClass(
account,
moduleClassNames.EthValidator
);
console.log("EthValidator class hash:", ethValidatorClassHash);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Note: To declare the class, the account you use must be loaded with ETH.
Assuming you have named the script src/04-declare-eth-validator.ts
, transpile and run
it:
npx tsc --build
node dist/04-declare-eth-validator.js
The output should return the hash for the class.
Verify the Eth Validator class hash
The class hash does NOT depend on the deployment or the network. So you
can find them at any time with the classHash
helper that comes with the
SDK. The script below shows how to use that function:
// file src/04-check-eth-validator.ts
import {
classHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
console.log(
"Computed EthValidator class hash:",
classHash(moduleClassNames.EthValidator)
);
Transpile and run the script:
npx tsc --build
node dist/04-check-eth-validator.js
Using the Eth Validator as a Secondary Validator
The simplest way to use the Eth Validator is to add it as a module to an
existing account and execute a transaction with the EthModule
class from
the @0xknwn/starknet-module
.
Register the Eth Validator as a Module
The modular account SDK comes with the addModule
, removeModule
and
isModule
. You can use those 3 functions to manage the module in the account
once it has been declared to the network. To register the module in the account,
use addModule
:
// file src/04-add-module.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
classHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./04-init";
import { RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const { transaction_hash } = await account.addModule(
classHash(moduleClassNames.EthValidator)
);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
const isInstalled = await account.isModule(
classHash(moduleClassNames.EthValidator)
);
console.log(
"module",
classHash(moduleClassNames.EthValidator),
"is installed",
isInstalled
);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/04-add-module.js
Register the public key associated with your Eth Private Key
Every module comes with a set of Management API. In the case of the Eth Validator, the associated interfaces are the following:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IPublicKey<TState> { fn set_public_key(ref self: TState, new_public_key: EthPublicKey); fn get_public_key(self: @TState) -> EthPublicKey; } }
Now that you have installed the module, you can create an ETH private key and register the associated public key in the module. For the purpose of the demonstration, we will use an arbitrary (and now unsafe) private/public key pair:
- private key: 0xb28ebb20fb1015da6e6367d1b5dba9b52862a06dbb3a4022e4749b6987ac1bd2
- public key:
- x: 0xd31cf702f5c89d49c567dcfd568bc4869e343506749f69d849eb408802cfa646
- y: 0x348c7bbf341964c306669365292c0066c23a2fedd131907534677aa3e22db2fc
Because Starknet types can only manage felt252 that are smaller than uint256
the format used by EthPublicKey
is actually an array<felt252>
that is made
of [x.low, x.high, y.low, y.high]
. To register the public key, use the
script below:
// file src/04-register-publickey.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
classHash as ethClassHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { EthSigner, cairo } from "starknet";
import { init } from "./04-init";
import { RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const ethPrivateKey =
"0xb28ebb20fb1015da6e6367d1b5dba9b52862a06dbb3a4022e4749b6987ac1bd2";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const module_class_hash = ethClassHash(moduleClassNames.EthValidator);
const signer = new EthSigner(ethPrivateKey);
const publicKey = await signer.getPubKey();
const coords = publicKey.slice(2, publicKey.length);
const x = coords.slice(0, 64);
const x_felts = cairo.uint256(`0x${x}`);
const y = coords.slice(64, 128);
const y_felts = cairo.uint256(`0x${y}`);
console.log("x:", `0x${x}`);
console.log("(x.low:", x_felts.low, ", x.high:", x_felts.high, ")");
console.log("y:", `0x${y}`);
console.log("(y.low:", y_felts.low, ", y.high:", y_felts.high, ")");
const { transaction_hash } = await account.executeOnModule(
module_class_hash,
"set_public_key",
[
x_felts.low.toString(),
x_felts.high.toString(),
y_felts.low.toString(),
y_felts.high.toString(),
]
);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/04-register-publickey.js
You can check the public key is correctly registered with the script below:
// file src/04-get-publickey.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
EthValidatorABI,
classHash as ethClassHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./04-init";
import { CallData, RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const moduleCallData = new CallData(EthValidatorABI);
const calldata = moduleCallData.compile("get_public_key", {});
const public_keys = await account.callOnModule(
ethClassHash(moduleClassNames.EthValidator),
"get_public_key",
calldata
);
public_keys.forEach((public_key, idx) =>
console.log(`public key (${idx}):`, public_key)
);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/04-get-publickey.js
Run a transaction with the EthModule
The EthModule
is an implementation of a module that can be passed to
the SmartrAccount
and manages the decoration of the transaction under the
hood. To fully instantiate that module, you will need:
- to instantiate the
EthModule
module from the SDK - to use the
EthSigner
provided by Starknet.js with the Private Key - to instantiate the
SmartrAccount
with the 2 classes above
Then you can run a transaction, exactly as you would do with any Starknet.js
account. The example below execute the increment
entrypoint on the Counter
contract:
// file src/04-execute-tx.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import { init, CounterABI } from "./04-init";
import { RpcProvider, Contract, EthSigner } from "starknet";
import { EthModule } from "@0xknwn/starknet-module";
const providerURL = "http://127.0.0.1:5050/rpc";
const ethPrivateKey =
"0xb28ebb20fb1015da6e6367d1b5dba9b52862a06dbb3a4022e4749b6987ac1bd2";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, counterAddress } = await init();
console.log("accountAddress", accountAddress);
const signer = new EthSigner(ethPrivateKey);
const ethModule = new EthModule(accountAddress);
const account = new SmartrAccount(
provider,
accountAddress,
signer,
ethModule,
"1",
"0x3"
);
console.log("counterAddress", counterAddress);
const counter = new Contract(CounterABI, counterAddress, account);
let currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
const call = counter.populate("increment");
const { transaction_hash } = await account.execute(call, {
version: "0x3",
resourceBounds: {
l2_gas: {
max_amount: "0x0",
max_price_per_unit: "0x0",
},
l1_gas: {
max_amount: "0x2f10",
max_price_per_unit: "0x22ecb25c00",
},
},
});
console.log("transaction_hash", transaction_hash);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/04-execute-tx.js
Remove the Eth Validator Module
You can use removeModule
and isModule
to remove the module from the account
with the script below:
// file src/04-remove-module.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
classHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./04-init";
import { RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const { transaction_hash } = await account.removeModule(
classHash(moduleClassNames.EthValidator)
);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
const isInstalled = await account.isModule(
classHash(moduleClassNames.EthValidator)
);
console.log(
"module",
classHash(moduleClassNames.EthValidator),
"has been removed",
isInstalled
);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/04-remove-module.js
Using the Eth Validator as the Core Validator
You can also use the Eth Validator as a Core Validator for the account. For that
purpose you will deploy a new account and use the EthSigner to validate the
account deployment without registering the EthModule
in the
SmartrAccount
. In order to proceed you need to:
- Generate the public key as an Array of felt252
- Compute the account address
- Send ETH to the modular account address
- Deploy the Account
Compute the Public Key as an Array of felt252
The EthSigner helps to generate the public key from the private key. Once you have the public key you should slice to get an array of 4 pieces like below:
const signer = new EthSigner(ethPrivateKey);
const publicKey = await signer.getPubKey();
const coords = publicKey.slice(2, publicKey.length);
const x = coords.slice(0, 64);
const x_felts = cairo.uint256(`0x${x}`);
const y = coords.slice(64, 128);
const y_felts = cairo.uint256(`0x${y}`);
const publicKeyArray = [
x_felts.low.toString(),
x_felts.high.toString(),
y_felts.low.toString(),
y_felts.high.toString(),
];
Compute the Account Address
Once you have the public key, you should use the accountAddress
function from
@0xknwn/starknet-modular-account
to compute the address of the account you
will install. As a Salt, we will use the hash.computeHashOnElements
from
the public key like below:
const publicKeyHash = hash.computeHashOnElements(publicKeyArray);
const computedAccountAddress = accountAddress(
"SmartrAccount",
publicKeyHash,
[ethClassHash("EthValidator"), "0x4", ...publicKeyArray]
);
Note: The "0x4" that is inserted in the calldata is here to indicate there are 4 pieces to the publci key:
Send ETH to the SmartrAccount Address to deploy it
To deploy the account, you need to have ETH associated with the target account
address. Assuming you have access to an account with ETH, this is how you send
eth to the computedAccountAddress
:
const account = new SmartrAccount(
provider,
ozAccountAddress,
smartrAccountPrivateKey
);
const ETH = new Contract(ERC20ABI, ethAddress, account);
const initial_EthTransfer = cairo.uint256(5n * 10n ** 15n);
const call = ETH.populate("transfer", {
recipient: computedAccountAddress,
amount: initial_EthTransfer,
});
const { transaction_hash } = await account.execute(call);
const output = await account.waitForTransaction(transaction_hash);
Deploy the Account with the Eth Validator as Core
To deploy the account, you will need to use the deployAccount
helper function
from @0xknwn/starknet-modular-account
with a SmartrAccount
that has been
instantiated with a EthSigner
like below:
const ethSmartrSigner = new EthSigner(smartrAccountPrivateKey);
const ethAccount = new SmartrAccount(
provider,
computedAccountAddress,
ethSmartrSigner
);
const address = await deployAccount(
ethAccount,
"SmartrAccount",
publicKeyHash,
[ethClassHash("EthValidator"), "0x4", ...publicKeyArray]
);
The Script Code
You will find below the whole script that does the account deployment:
// file src/04-deploy-account.ts
import {
RpcProvider,
EthSigner,
Contract,
cairo,
hash,
Account,
} from "starknet";
import {
accountAddress,
deployAccount,
SmartrAccount,
classNames as accountClassName,
} from "@0xknwn/starknet-modular-account";
import {
classHash as ethClassHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./04-init";
import { ABI as ERC20ABI } from "./abi/ERC20";
const strkAddress =
"0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
// these are the settings for the devnet with --seed=0
// change them to mee your requirements
const providerURL = "http://127.0.0.1:5050/rpc";
// const providerURL = "http://127.0.0.1:5050/rpc";
const ethPrivateKey =
"0xb28ebb20fb1015da6e6367d1b5dba9b52862a06dbb3a4022e4749b6987ac1bd2";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { ozAccountAddress, ozAccountPrivateKey } = await init();
// Step 1 - Get the public key from the Eth Signer
const ethSmartrSigner = new EthSigner(ethPrivateKey);
const publicKey = await ethSmartrSigner.getPubKey();
const coords = publicKey.slice(2, publicKey.length);
const x = coords.slice(0, 64);
const x_felts = cairo.uint256(`0x${x}`);
const y = coords.slice(64, 128);
const y_felts = cairo.uint256(`0x${y}`);
const publicKeyArray = [
x_felts.low.toString(),
x_felts.high.toString(),
y_felts.low.toString(),
y_felts.high.toString(),
];
// Step 2 - Compute the account address
const publicKeyHash = hash.computeHashOnElements(publicKeyArray);
const computedAccountAddress = accountAddress(
accountClassName.SmartrAccount,
publicKeyHash,
[ethClassHash(moduleClassNames.EthValidator), "0x4", ...publicKeyArray]
);
// Step 3 - Send STRK to the computed account address
const account = new Account(
provider,
ozAccountAddress,
ozAccountPrivateKey,
"1",
"0x3"
);
const STRK = new Contract(ERC20ABI, strkAddress, account);
const initial_strkTransfer = cairo.uint256(50000n * 10n ** 15n);
const { transaction_hash } = await STRK.transfer(
computedAccountAddress,
initial_strkTransfer
);
const output = await account.waitForTransaction(transaction_hash);
if (!output.isSuccess()) {
throw new Error("Could not send STRK to the expected address");
}
// Step 4 - Deploy the account with the EthValidator as Core Validator
const ethAccount = new SmartrAccount(
provider,
computedAccountAddress,
ethSmartrSigner,
undefined,
"1",
"0x3"
);
const address = await deployAccount(
ethAccount,
accountClassName.SmartrAccount,
publicKeyHash,
[ethClassHash(moduleClassNames.EthValidator), "0x4", ...publicKeyArray],
{
version: "0x3",
resourceBounds: {
l2_gas: {
max_amount: "0x0",
max_price_per_unit: "0x0",
},
l1_gas: {
max_amount: "0x2f10",
max_price_per_unit: "0x22ecb25c00",
},
},
}
);
if (address !== computedAccountAddress) {
throw new Error(
`The account should have been deployed to ${computedAccountAddress}, instead ${address}`
);
}
console.log("accountAddress", computedAccountAddress);
console.log("public key", publicKeyArray);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run it:
npx tsc --build
node dist/04-deploy-account.js
Running a transaction with the Eth Validator as Core
Running a transaction with the EthValidator as a Core is no more complex than running a transaction on a regular account. All you need to do is
- get the account address that could have been saved from earlier
- instantiate the
SmartrAccount
with the Starknet.js EthSigner - execute the transaction
Below is an example that assumes you have deployed the account with the
04-deploy-account.ts
script earlier:
// file src/04-execute-tx-core.ts
import {
SmartrAccount,
accountAddress,
classNames as accountClassNames,
} from "@0xknwn/starknet-modular-account";
import { init, CounterABI } from "./04-init";
import { RpcProvider, Contract, EthSigner, cairo, hash } from "starknet";
import {
classHash as ethClassHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
const providerURL = "http://127.0.0.1:5050/rpc";
const ethPrivateKey =
"0xb28ebb20fb1015da6e6367d1b5dba9b52862a06dbb3a4022e4749b6987ac1bd2";
const main = async () => {
// recompute the account address
const signer = new EthSigner(ethPrivateKey);
const publicKey = await signer.getPubKey();
const coords = publicKey.slice(2, publicKey.length);
const x = coords.slice(0, 64);
const x_felts = cairo.uint256(`0x${x}`);
const y = coords.slice(64, 128);
const y_felts = cairo.uint256(`0x${y}`);
const publicKeyArray = [
x_felts.low.toString(),
x_felts.high.toString(),
y_felts.low.toString(),
y_felts.high.toString(),
];
const publicKeyHash = hash.computeHashOnElements(publicKeyArray);
const computedAccountAddress = accountAddress(
accountClassNames.SmartrAccount,
publicKeyHash,
[ethClassHash(moduleClassNames.EthValidator), "0x4", ...publicKeyArray]
);
// execute the transaction
const provider = new RpcProvider({ nodeUrl: providerURL });
const { counterAddress } = await init();
const account = new SmartrAccount(
provider,
computedAccountAddress,
signer,
undefined,
"1",
"0x3"
);
const counter = new Contract(CounterABI, counterAddress, account);
let currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
const call = counter.populate("increment");
const { transaction_hash } = await account.execute(call, {
version: "0x3",
resourceBounds: {
l2_gas: {
max_amount: "0x0",
max_price_per_unit: "0x0",
},
l1_gas: {
max_amount: "0x2f10",
max_price_per_unit: "0x22ecb25c00",
},
},
});
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run it:
npx tsc --build
node dist/04-execute-tx-core.js
As you can see from the script:
- You do not need the
EthModule
to interact with the account. That is because the validator is used as a Core Validator and, as such, the transaction does not required to be prefixed - Running transactions is the same as running a transaction with the Stark validator. Only the signature changes.
Using the P256 Validator
The P256 Validator Module can both work as a Secondary or as the Core Validator for the account. It requires the module SDK. In this section of the documentation, you will see how you can use the Modular Account to interact with the P256 Validator Module.
- Using the P256 Validator
Note: This section assumes the
SmartrAccount
class has been instantiated in thesmartrAccount
variable as shown in Using the modular account from the SDK. It also assumes theCounter
contract that comes with the project has been deploys to thecounterAddress
and theCounterABI
class is available. The05-setup.ts
script that comes with this project ensure those steps are executed.
Installing the P256 Validator SDK
If you plan to use the P256 Validatoi module, you might need the
@0xknwn/starknet-module
SDK in addition to the
@0xknwn/starknet-modular-account
SDK. To install it, run:
npm install --save \
@0xknwn/starknet-module
Declaring the P256 Validator
If you are working on a network that does not have the P256 validator class
already declared, you will need to declare it. The P256 validator module SDK, aka
@0xknwn/starknet-module
contains a helper function named declareClass
to
declare the class to the network. To use it, you need to pass:
- A starknet.js
Account
as a first parameter - The name of the class to declare as the 2nd parameter. For the P256 Validator,
the name is
P256Validator
Below is an example of a script that declares the new classes.
// file src/05-declare-p256-validator.ts
import { RpcProvider, Account } from "starknet";
import {
declareClass,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
// these are the settings for the devnet with --seed=0
// change them to mee your requirements
const ozAccountAddress =
"0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691";
const ozPrivateKey = "0x71d7bb07b9a64f6f78ac4c816aff4da9";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const account = new Account(
provider,
ozAccountAddress,
ozPrivateKey,
"1",
"0x3"
);
const { classHash: p256ValidatorClassHash } = await declareClass(
account,
moduleClassNames.P256Validator
);
console.log("P256Validator class hash:", p256ValidatorClassHash);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Note: To declare the class, the account you use must be loaded with P256.
Assuming you have named the script src/05-declare-p256-validator.ts
, transpile and run
it:
npx tsc --build
node dist/05-declare-p256-validator.js
The output should return the hash for the class.
Verify the P256 Validator class hash
The class hash does NOT depend on the deployment or the network. So you
can find them at any time with the classHash
helper that comes with the
SDK. The script below shows how to use that function:
// file src/05-check-p256-validator.ts
import {
classHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
console.log(
"Computed P256Validator class hash:",
classHash(moduleClassNames.P256Validator)
);
Transpile and run the script:
npx tsc --build
node dist/05-check-p256-validator.js
Using the P256 Validator as a Secondary Validator
The simplest way to use the P256 Validator is to add it as a module to an
existing account and execute a transaction with the P256Module
class from
the @0xknwn/starknet-module
.
Register the P256 Validator as a Module
The modular account SDK comes with the addModule
, removeModule
and
isModule
. You can use those 3 functions to manage the module in the account
once it has been declared to the network. To register the module in the account,
use addModule
:
// file src/05-add-module.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
classHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./05-init";
import { RpcProvider } from "starknet";
const ozAccountAddress =
"0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691";
const ozPrivateKey = "0x71d7bb07b9a64f6f78ac4c816aff4da9";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const { transaction_hash } = await account.addModule(
classHash(moduleClassNames.P256Validator)
);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
const isInstalled = await account.isModule(
classHash(moduleClassNames.P256Validator)
);
console.log(
"module",
classHash(moduleClassNames.P256Validator),
"is installed:",
isInstalled
);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/05-add-module.js
Register the public key associated with your P256 Private Key
Every module comes with a set of Management API. In the case of the P256 Validator, the associated interfaces are the following:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IPublicKey<TState> { fn set_public_key(ref self: TState, new_public_key: P256PublicKey); fn get_public_key(self: @TState) -> P256PublicKey; } }
Now that you have installed the module, you can create an ETH private key and register the associated public key in the module. For the purpose of the demonstration, we will use an arbitrary (and now unsafe) private/public key pair:
- private key: 0x1efecf7ee1e25bb87098baf2aaab0406167aae0d5ea9ba0d31404bf01886bd0e
- public key:
- x: 0x097420e05fbc83afe4d73b31890187d0cacf2c3653e27f434701a91625f916c2
- x.low: 269579757328574126121444003492591638210
- x.high: 12566025211498978771503502663570524112
- y: 0x98a304ff544db99c864308a9b3432324adc6c792181bae33fe7a4cbd48cf263a
- y.low: 230988565823064299531546210785320445498
- y.high: 202889101106158949967186230758848275236
- x: 0x097420e05fbc83afe4d73b31890187d0cacf2c3653e27f434701a91625f916c2
Because Starknet types can only manage felt252 that are smaller than uint256
the format used by P256PublicKey
is actually an array<felt252>
that is made
of [x.low, x.high, y.low, y.high]
. To register the public key, use the
script below:
// file src/05-register-publickey.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
classHash as moduleClassHash,
P256Signer,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { cairo } from "starknet";
import { init } from "./05-init";
import { RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const p256PrivateKey =
"0x1efecf7ee1e25bb87098baf2aaab0406167aae0d5ea9ba0d31404bf01886bd0e";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const module_class_hash = moduleClassHash(moduleClassNames.P256Validator);
const signer = new P256Signer(p256PrivateKey);
const publicKey = await signer.getPubKey();
const coords = publicKey.slice(2, publicKey.length);
const x = coords.slice(0, 64);
const x_felts = cairo.uint256(`0x${x}`);
const y = coords.slice(64, 128);
const y_felts = cairo.uint256(`0x${y}`);
console.log("x:", `0x${x}`);
console.log("(x.low:", x_felts.low, ", x.high:", x_felts.high, ")");
console.log("y:", `0x${y}`);
console.log("(y.low:", y_felts.low, ", y.high:", y_felts.high, ")");
const { transaction_hash } = await account.executeOnModule(
module_class_hash,
"set_public_key",
[
x_felts.low.toString(),
x_felts.high.toString(),
y_felts.low.toString(),
y_felts.high.toString(),
]
);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/05-register-publickey.js
You can check the public key is correctly registered with the script below:
// file src/05-get-publickey.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
classHash as moduleClassHash,
P256ValidatorABI,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./05-init";
import { CallData, RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const moduleCallData = new CallData(P256ValidatorABI);
const calldata = moduleCallData.compile("get_public_key", {});
const public_keys = await account.callOnModule(
moduleClassHash(moduleClassNames.P256Validator),
"get_public_key",
calldata
);
public_keys.forEach((public_key, idx) =>
console.log(`public key (${idx}):`, public_key)
);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/05-get-publickey.js
Run a transaction with the P256Module
The P256Module
is an implementation of a module that can be passed to
the SmartrAccount
and manages the decoration of the transaction under the
hood. To fully instantiate that module, you will need:
- to instantiate the
P256Module
module from the SDK - to use the
P256Signer
provided by `@0xknwn/starknet-module with the Private Key - to instantiate the
SmartrAccount
with the 2 classes above
Then you can run a transaction, exactly as you would do with any Starknet.js
account. The example below execute the increment
entrypoint on the Counter
contract:
// file src/05-execute-tx.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import { init, CounterABI } from "./05-init";
import { RpcProvider, Contract } from "starknet";
import { P256Module, P256Signer } from "@0xknwn/starknet-module";
const providerURL = "http://127.0.0.1:5050/rpc";
const p256PrivateKey =
"0x1efecf7ee1e25bb87098baf2aaab0406167aae0d5ea9ba0d31404bf01886bd0e";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, counterAddress, smartrAccountPrivateKey } =
await init();
console.log("accountAddress", accountAddress);
const signer = new P256Signer(p256PrivateKey);
const p256Module = new P256Module(accountAddress);
const account = new SmartrAccount(
provider,
accountAddress,
signer,
p256Module,
"1",
"0x3"
);
console.log("counterAddress", counterAddress);
const counter = new Contract(CounterABI, counterAddress, account);
let currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
const call = counter.populate("increment");
const { transaction_hash } = await account.execute(call, {
version: "0x3",
resourceBounds: {
l2_gas: {
max_amount: "0x0",
max_price_per_unit: "0x0",
},
l1_gas: {
max_amount: "0x2f10",
max_price_per_unit: "0x22ecb25c00",
},
},
});
console.log("transaction_hash", transaction_hash);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/05-execute-tx.js
Remove the P256 Validator Module
You can use removeModule
and isModule
to remove the module from the account
with the script below:
// file src/05-remove-module.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
classHash,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./05-init";
import { RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const { transaction_hash } = await account.removeModule(
classHash(moduleClassNames.P256Validator)
);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
const isInstalled = await account.isModule(
classHash(moduleClassNames.P256Validator)
);
console.log(
"module",
classHash(moduleClassNames.P256Validator),
"is installed:",
isInstalled
);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/05-remove-module.js
Using the P256 Validator as the Core Validator
You can also use the P256 Validator as a Core Validator for the account. For that
purpose you will deploy a new account and use the P256Signer to validate the
account deployment without registering the P256Module
in the
SmartrAccount
. In order to proceed you need to:
- Generate the public key as an Array of felt252
- Compute the account address
- Send ETH to the modular account address
- Deploy the Account
Compute the Public Key as an Array of felt252
The P256Signer helps to generate the public key from the private key. Once you have the public key you should slice to get an array of 4 pieces like below:
const p236SmartrSigner = new P256Signer(p256PrivateKey);
const publicKey = await p236SmartrSigner.getPubKey();
const coords = publicKey.slice(2, publicKey.length);
const x = coords.slice(0, 64);
const x_felts = cairo.uint256(`0x${x}`);
const y = coords.slice(64, 128);
const y_felts = cairo.uint256(`0x${y}`);
const publicKeyArray = [
x_felts.low.toString(),
x_felts.high.toString(),
y_felts.low.toString(),
y_felts.high.toString(),
];
Compute the Account Address
Once you have the public key, you should use the accountAddress
function from
@0xknwn/starknet-modular-account
to compute the address of the account you
will install. As a Salt, we will use the hash.computeHashOnElements
from
the public key like below:
const publicKeyHash = hash.computeHashOnElements(publicKeyArray);
const computedAccountAddress = accountAddress(
"SmartrAccount",
publicKeyHash,
[moduleClassHash("P256Validator"), "0x4", ...publicKeyArray]
);
Note: The "0x4" that is inserted in the calldata is here to indicate there are 4 pieces to the public key:
Send ETH to the SmartrAccount Address to deploy it
To deploy the account, you need to have ETH associated with the target account
address. Assuming you have access to an account with ETH, this is how you send
p256 to the computedAccountAddress
:
const account = new SmartrAccount(
provider,
ozAccountAddress,
smartrAccountPrivateKey
);
const ETH = new Contract(ERC20ABI, ethAddress, account);
const initial_EthTransfer = cairo.uint256(5n * 10n ** 15n);
const call = ETH.populate("transfer", {
recipient: computedAccountAddress,
amount: initial_EthTransfer,
});
const { transaction_hash } = await account.execute(call);
const output = await account.waitForTransaction(transaction_hash);
Deploy the Account with the P256 Validator as Core
To deploy the account, you will need to use the deployAccount
helper function
from @0xknwn/starknet-modular-account
with a SmartrAccount
that has been
instantiated with a P256Signer
like below:
const p256Account = new SmartrAccount(
provider,
computedAccountAddress,
p236SmartrSigner
);
const address = await deployAccount(
p256Account,
"SmartrAccount",
publicKeyHash,
[moduleClassHash("P256Validator"), "0x4", ...publicKeyArray]
);
The Script Code
You will find below the whole script that does the account deployment:
// file src/05-deploy-account.ts
import { RpcProvider, Contract, cairo, hash, Account } from "starknet";
import {
accountAddress,
deployAccount,
SmartrAccount,
classNames as accountClassNames,
} from "@0xknwn/starknet-modular-account";
import {
classHash as P256ClassHash,
P256Signer,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
import { init } from "./05-init";
import { ABI as ERC20ABI } from "./abi/ERC20";
const strkAddress =
"0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
// these are the settings for the devnet with --seed=0
// change them to mee your requirements
const providerURL = "http://127.0.0.1:5050/rpc";
const p256PrivateKey =
"0x1efecf7ee1e25bb87098baf2aaab0406167aae0d5ea9ba0d31404bf01886bd0e";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { ozAccountAddress, ozAccountPrivateKey } = await init();
// Step 1 - Get the public key from the Eth Signer
const p236SmartrSigner = new P256Signer(p256PrivateKey);
const publicKey = await p236SmartrSigner.getPubKey();
const coords = publicKey.slice(2, publicKey.length);
const x = coords.slice(0, 64);
const x_felts = cairo.uint256(`0x${x}`);
const y = coords.slice(64, 128);
const y_felts = cairo.uint256(`0x${y}`);
const publicKeyArray = [
x_felts.low.toString(),
x_felts.high.toString(),
y_felts.low.toString(),
y_felts.high.toString(),
];
// Step 2 - Compute the account address
const publicKeyHash = hash.computeHashOnElements(publicKeyArray);
const computedAccountAddress = accountAddress(
accountClassNames.SmartrAccount,
publicKeyHash,
[P256ClassHash(moduleClassNames.P256Validator), "0x4", ...publicKeyArray]
);
// Step 3 - Send STRK to the computed account address
const account = new Account(
provider,
ozAccountAddress,
ozAccountPrivateKey,
"1",
"0x3"
);
const STRK = new Contract(ERC20ABI, strkAddress, account);
const initial_strkTransfer = cairo.uint256(50000n * 10n ** 15n);
const { transaction_hash } = await STRK.transfer(
computedAccountAddress,
initial_strkTransfer
);
const output = await account.waitForTransaction(transaction_hash);
if (!output.isSuccess()) {
throw new Error("Could not send STRK to the expected address");
}
// Step 4 - Deploy the account with the P256Validator as Core Validator
const p256Account = new SmartrAccount(
provider,
computedAccountAddress,
p236SmartrSigner,
undefined,
"1",
"0x3"
);
const address = await deployAccount(
p256Account,
accountClassNames.SmartrAccount,
publicKeyHash,
[P256ClassHash(moduleClassNames.P256Validator), "0x4", ...publicKeyArray],
{
version: "0x3",
resourceBounds: {
l2_gas: {
max_amount: "0x0",
max_price_per_unit: "0x0",
},
l1_gas: {
max_amount: "0x2f10",
max_price_per_unit: "0x22ecb25c00",
},
},
}
);
if (address !== computedAccountAddress) {
throw new Error(
`The account should have been deployed to ${computedAccountAddress}, instead ${address}`
);
}
console.log("accountAddress", computedAccountAddress);
console.log("public key", publicKeyArray);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run it:
npx tsc --build
node dist/05-deploy-account.js
Running a transaction with the P256 Validator as Core
Running a transaction with the P256Validator as a Core is no more complex than running a transaction on a regular account. All you need to do is
- get the account address that could have been saved from earlier
- instantiate the
SmartrAccount
with the Starknet.js P256Signer - execute the transaction
Below is an example that assumes you have deployed the account with the
05-deploy-account.ts
script earlier:
// file src/05-execute-tx-core.ts
import {
SmartrAccount,
accountAddress,
classNames as accountlassNames,
} from "@0xknwn/starknet-modular-account";
import { init, CounterABI } from "./05-init";
import { RpcProvider, Contract, cairo, hash } from "starknet";
import {
classHash as moduleClassHash,
P256Signer,
classNames as moduleClassNames,
} from "@0xknwn/starknet-module";
const providerURL = "http://127.0.0.1:5050/rpc";
const p256PrivateKey =
"0x1efecf7ee1e25bb87098baf2aaab0406167aae0d5ea9ba0d31404bf01886bd0e";
const main = async () => {
// recompute the account address
const signer = new P256Signer(p256PrivateKey);
const publicKey = await signer.getPubKey();
const coords = publicKey.slice(2, publicKey.length);
const x = coords.slice(0, 64);
const x_felts = cairo.uint256(`0x${x}`);
const y = coords.slice(64, 128);
const y_felts = cairo.uint256(`0x${y}`);
const publicKeyArray = [
x_felts.low.toString(),
x_felts.high.toString(),
y_felts.low.toString(),
y_felts.high.toString(),
];
const publicKeyHash = hash.computeHashOnElements(publicKeyArray);
const computedAccountAddress = accountAddress(
accountlassNames.SmartrAccount,
publicKeyHash,
[moduleClassHash(moduleClassNames.P256Validator), "0x4", ...publicKeyArray]
);
// execute the transaction
const provider = new RpcProvider({ nodeUrl: providerURL });
const { counterAddress } = await init();
const account = new SmartrAccount(
provider,
computedAccountAddress,
signer,
undefined,
"1",
"0x3"
);
const counter = new Contract(CounterABI, counterAddress, account);
let currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
const call = counter.populate("increment");
const { transaction_hash } = await account.execute(call, {
version: "0x3",
resourceBounds: {
l2_gas: {
max_amount: "0x0",
max_price_per_unit: "0x0",
},
l1_gas: {
max_amount: "0x2f10",
max_price_per_unit: "0x22ecb25c00",
},
},
});
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run it:
npx tsc --build
node dist/05-execute-tx-core.js
As you can see from the script:
- You do not need the
P256Module
to interact with the account. That is because the validator is used as a Core Validator and, as such, the transaction does not required to be prefixed - Running transactions is the same as running a transaction with the Stark validator. Only the signature changes!
Using the SessionKey Validator
The starknet modular account comes with the @0xknwn/starknet-module-sessionkey
SDK that you can use to work with the sessionkey validator. In this section,
you will see, how to install and use the validator, how to request a sessionkey
and how to use it.
- Using the SessionKey Validator
Note: This section assumes the
SmartrAccount
class has been instantiated in thesmartrAccount
variable as shown in Using the modular account from the SDK. It also assumes theCounter
contract that comes with the project has been deploys to thecounterAddress
and theCounterABI
class is available. The06-setup.ts
script that comes with this project ensure those steps are executed.
Install the SessionKey Validator
@0xknwn/starknet-module-sessionkey
is a SDK that complements
@0xknwn/starknet-modular-account
and provide the tools to manage the module
and interact with it from the SmartrAccount
. Start by adding the module to
the project with a command like the one below:
npm install --save @0xknwn/starknet-module-sessionkey
If you are working on a network that does not have the sessionkey module class
declared already, like the devnet, you should declare the class. The
declareClass
function that comes with the SDK allow such an installation.
Below is an example of a script that install the module class:
// file src/06-declare-class.ts
import { RpcProvider, Account } from "starknet";
import {
declareClass,
classNames as sessionkeyClassNames,
} from "@0xknwn/starknet-module-sessionkey";
// these are the settings for the devnet with --seed=0
// change them to mee your requirements
const providerURL = "http://127.0.0.1:5050/rpc";
const ozAccountAddress =
"0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691";
const ozPrivateKey = "0x71d7bb07b9a64f6f78ac4c816aff4da9";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const account = new Account(
provider,
ozAccountAddress,
ozPrivateKey,
"1",
"0x3"
);
const { classHash: sessionkeyValidatorClassHash } = await declareClass(
account,
sessionkeyClassNames.SessionKeyValidator
);
console.log("SessionKeyValidator class hash:", sessionkeyValidatorClassHash);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Note: To declare the class, the account you use must be loaded with ETH. In order to do it, you can use any account, including the modular account.
Transpile it and run the script with the commands below:
npx tsc --build
node dist/06-declare-class.js
Note: only the
declareClass
function from@0xknwn/starknet-module-sessionkey
can declare the Sessionkey Validator class. If you are using thedeclareClass
function from@0xknwn/starknet-modular-account
, you will be able to declare the Stark Validator and the Modular Account class but not the SessionKey Validator.
Register the SessionKey Validator as a Module
The modular account SDK comes with the addModule
, removeModule
and
isModule
. You can use those 3 functions to manage the module in the account
once it has been declared to the network. To register the module in the account,
use addModule
:
// file src/06-add-module.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import { classHash, classNames as sessionkeyClassNames } from "@0xknwn/starknet-module-sessionkey";
import { init } from "./06-init";
import { RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const { transaction_hash } = await account.addModule(
classHash(sessionkeyClassNames.SessionKeyValidator)
);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
const isInstalled = await account.isModule(classHash(sessionkeyClassNames.SessionKeyValidator));
console.log(
"module",
classHash(sessionkeyClassNames.SessionKeyValidator),
"is installed",
isInstalled
);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/06-add-module.js
Requesting a Session Key
Once the module is installed in the account, a dapp can request a grant associated with a private key. In order to get that authorization, it must first build a session key request. A request is made of the following elements:
- the
account address
. A session key is granted to an account and cannot be used with another one. - the
sessionkey validator module class hash
. Because an account could have several sessionkey module installed, we want to make sure the session key is granted to a very specific module. - the
core validator class hash
. Here we want to make sure the sessionkey grantor is the core validator for the account to prevent checking a signature with another module from the account - the dapp
public key
that is associated with the session key request. Once the session key is authorized, the dapp will only be able to sign transactions with its private key counterpart. - an
expiration time
. Here again, we want the session key to be limited in time and we strongly suggest the signer keep that period short. - an array of policies, i.e a list of (1)
contract addresses
and (2)entrypoints
the dapp will be able to call with that session key. Keep in mind that many contracts are upgradeable and that a good practice would be to grant an access only to a contract that is opensource, audited and not upgradeable. - the chain id. This is something that might be remove in a later version of the session key because the account address already depends of the chain id. However, this element is mandatory to request a sessionkey grant
Create and Manage Policies
Policies are very specific to session keys and as such, the sessionkey SDK, i.e
@0xknwn/starknet-module-sessionkey
provides a class named PolicyManager
that
helps to manage them. There are 2 main reasons to use that class:
- When requesting a sessionkey. That is because you do not use the array of
policies but a hash of each one of them and a merkle tree of those hashes.
The
PolicyManager
that comes with the SDK provides you with that Merkle tree root that is what is used to request the sessionkey grant. - When signing a transaction with the session key. That is because when you sign a transaction, not only you need to provide a valid signature of the transaction with the private key that has been granted the access but you also need to provide the merkle proofs that the call that are used in the transaction are part of the sessionkey policies.
So assuming you want to grant an access to the increment
and increment_by
entrypoints of the Counter class and assuming counterAddress
contains counter
address, to use the PolicyManager
class, you will call its constructor like
below:
const policyManager = new PolicyManager([
{ contractAddress: counterContract.address, selector: "increment" },
{ contractAddress: counterContract.address, selector: "increment_by" },
]);
The policyManager.getRoot()
function will return the root of the merkle tree
associated with the policies.
Note: The second useful function of
PolicyManager
is thegetProof(policy)
function. However, once the session key module registered inSmartrAccount
that function is used by the account and you should not use it by yourself.
Create a SessionKeyModule
The SessionKeyModule
is an implementation of a module that can be passed to
the SmartrAccount
and manages the decoration of the transaction under the
hood. To fully instantiate that module, you will need:
- all the data above
- to request/get an authorization from the core validator signer
- to add the signature provided by the grantor back into the module
The first step consists in instantiating the SessionKeyModule
with all the
data associated with the session key:
const sessionKeyModule = new SessionKeyModule(
sessionkeyPublicKey,
accountAddress,
sessionkeyClassHash,
chain,
expires,
policyManager
);
Request a Grant by the Account Core Validator Signer
To request the authorization you should call the request
method on the module
with the starkValidatorClassHash
like below:
const request = await sessionKeyModule.request(starkValidatorClassHash);
Note: this step is very important because it stores the
starkValidatorClassHash
in the module.
Get the Session Key Grant
Now, you need to use the core validator signer to provide the signature that
will be necessary for the module to be activated. The sessionkey SDK provides
the SessionKeyGrantor
to help you with signing the request. To generate
the signature, run the sign
method on it:
const grantor = new SessionKeyGrantor(
starkValidatorClassHash,
smartrAccountPrivateKey
);
const signature = await grantor.sign(sessionKeyModule);
Register the Core Validator Signature with the SessionKeyModule
To finish with the module configuration, you just have to add the signature to it like below:
sessionKeyModule.add_signature(signature);
Create an Account with the SessionKeyModule
Now you can create an Account with the SmartrAccount
class. The class
constructor can use a module as a 4th argument of the constructor like below:
const smartrAccountWithSessionKey = new SmartrAccount(
provider,
accountAddress,
sessionkeyPrivateKey,
sessionKeyModule
);
Note: here you are not using the private key from the core validator signer but the private key generated by your dapp.
Source Code
The overall sessionkey request/grant process with the execution of a transaction on the counter contract is available as a single script below:
// file src/06-sessionkey-transaction.ts
import {
SmartrAccount,
classHash,
classNames as accountClassNames,
} from "@0xknwn/starknet-modular-account";
import {
classHash as sessionkeyClassHash,
PolicyManager,
SessionKeyModule,
SessionKeyGrantor,
classNames as sessionkeyClassNames,
} from "@0xknwn/starknet-module-sessionkey";
import { init, CounterABI } from "./06-init";
import { RpcProvider, Signer, Contract } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const sessionkeyPrivateKey = "0x4";
const main = async () => {
const { counterAddress, smartrAccountPrivateKey, accountAddress } =
await init();
const provider = new RpcProvider({ nodeUrl: providerURL });
// Step 1: Collect all the necessary information to request a sessionkey
// Authorization to the modular account signers
const signer = new Signer(sessionkeyPrivateKey);
const sessionkeyPublicKey = await signer.getPubKey();
const chain = await provider.getChainId();
const expires = BigInt(Math.floor(Date.now() / 1000) + 24 * 60 * 60); // 24 hours from now
const policyManager = new PolicyManager([
{ contractAddress: counterAddress, selector: "increment" },
{ contractAddress: counterAddress, selector: "increment_by" },
]);
// Step 2: Create a session key module that can be used to request a session
// key and to create a SmartrAccount with the signed session key that can
// execute transactions as any regular Account
const sessionKeyModule = new SessionKeyModule(
sessionkeyPublicKey,
accountAddress,
sessionkeyClassHash(sessionkeyClassNames.SessionKeyValidator),
chain,
`0x${expires.toString(16)}`,
policyManager
);
// Step 3: Generate the sessionkey grant request
// that is an important step to request a session key because that is when
// the core validator class is registered with the session key module
const request = await sessionKeyModule.request(
classHash(accountClassNames.StarkValidator)
);
console.log("request", request);
// Step 4: Use the SessionKeyGrantor helper class to sign the request
const grantor = new SessionKeyGrantor(
classHash(accountClassNames.StarkValidator),
smartrAccountPrivateKey
);
const signature = await grantor.sign(sessionKeyModule);
// Step 5: Register the signatures to the session key module
sessionKeyModule.add_signature(signature);
// Step 6: Create the SmartrAccount with the session key module
const smartrAccountWithSessionKey = new SmartrAccount(
provider,
accountAddress,
sessionkeyPrivateKey,
sessionKeyModule,
"1",
"0x3"
);
// Step 7: Execute transactions with the session key module
const counter = new Contract(
CounterABI,
counterAddress,
smartrAccountWithSessionKey
);
let currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
const call = counter.populate("increment");
const { transaction_hash } = await smartrAccountWithSessionKey.execute(call);
const receipt =
await smartrAccountWithSessionKey.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
currentCounter = await counter.call("get");
console.log("currentCounter", currentCounter);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run it:
npx tsc --build
node dist/06-sessionkey-transaction.js
Execute a Transaction with the Session Key
As you can see from above, executing a transaction with a session key is
exactly the same as executing any transaction with a starknet.js account. All
you need to do is to use the SmartrAccount
that comes with the
@0xknwn/starknet-modular-account
SDK with the SessionKeyModule
from the
@0xknwn/starknet-module-sessionkey
SDK.
Invalidate the Session Key
If needed, you can block a session key. To proceed, you would need the hash of
the session key as shown in the request
. That is the hash that is actually
signed by the core validator signer and stored in sessionkeyHash
in the
script. Then, you can use disable_session_key
entrypoint on the module with
the executeOnModule
entrypoint of the account:
// file src/06-block-sessionkey.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
classHash as sessionkeyClassHash,
SessionKeyValidatorABI,
classNames as sessionkeyClassNames,
} from "@0xknwn/starknet-module-sessionkey";
import { init } from "./06-init";
import { CallData, RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const sessionkeyHash = "0x7";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const moduleCallData = new CallData(SessionKeyValidatorABI);
const calldata = moduleCallData.compile("disable_session_key", {
sessionkey: sessionkeyHash,
});
const { transaction_hash } = await account.executeOnModule(
sessionkeyClassHash(sessionkeyClassNames.SessionKeyValidator),
"disable_session_key",
calldata
);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script:
npx tsc --build
node dist/06-block-sessionkey.js
Remove the SessionKey Validator Module from the Account
To remove the module from the account, use removeModule
:
// file src/06-remove-module.ts
import { SmartrAccount } from "@0xknwn/starknet-modular-account";
import {
classHash,
classNames as sessionkeyClassNames,
} from "@0xknwn/starknet-module-sessionkey";
import { init } from "./06-init";
import { RpcProvider } from "starknet";
const providerURL = "http://127.0.0.1:5050/rpc";
const main = async () => {
const provider = new RpcProvider({ nodeUrl: providerURL });
const { accountAddress, smartrAccountPrivateKey } = await init();
const account = new SmartrAccount(
provider,
accountAddress,
smartrAccountPrivateKey,
undefined,
"1",
"0x3"
);
const { transaction_hash } = await account.removeModule(
classHash(sessionkeyClassNames.SessionKeyValidator)
);
const receipt = await account.waitForTransaction(transaction_hash);
console.log("transaction succeeded", receipt.isSuccess());
const isInstalled = await account.isModule(
classHash(sessionkeyClassNames.SessionKeyValidator)
);
console.log(
"module",
classHash(sessionkeyClassNames.SessionKeyValidator),
"has been removed",
isInstalled
);
};
main()
.then(() => {})
.catch((e) => {
console.warn(e);
});
Transpile and run the script with the commands below:
npx tsc --build
node dist/06-remove-module.js
Modules
Starknet Modular Account requires modules to work properly. There are 2 types of modules:
- Validator Modules, including the Core Validator Module are used to validate transactions
- Executor Modules are not yet available. They enable to change the account behavior: you can create metadata to track the signer complies to some specific rules; you can add checks that are not related to the signature like the fact that an amount allowed to a signer is not already spent.
This section details module development and specific implementations. In particular, the folllowing are explained.
- How to Developing your Own Validator Module
- How the Stark Validator Module Works
- How the Eth Validator Module Works
- How the SessionKey Validator Module Works
If you just plan to use the Starknet Modular Account with those modules, we suggest you check the SDKs sections of the documentation.
Developing your Own Validator Module
A Validator Module can be used to change the way the Modular Account validates
transactions and messages. Validators can be used as the Core Validator, i.e.
they validate the transaction on the account by default. If not, they require
the transaction to be decorated with a prefix call to the __module_validate__
entrypoint.
This section provides directions about how to develop and test your own module. If you are interested to extend the account with Validators, we suggest you contact the project to get some help.
Validator Module
A Validator is a class that implement the following interface:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IValidator<TState> { fn validate(self: @TState, grantor_class: ClassHash, calls: Array<Call>) -> felt252; } }
validate
generated the hash for a transaction and validates the transaction
for the account. Opposite to is_valid_signature
, validate
has access to
the whole transaction and, as such should always be fully implemented by any
validator module.
Note: the grantor class that is passed by the account is the Core Validator class hash registered with the account. It can be used for some specific use. For instance, in the case of the sessionkey validator, it enables the sessionkey validator module to validate the sessionkey authorization with the
is_valid_signature
from the Core Validator of the account.
Core Validator Interface
In addition to the IValidator
interface, Core Validator Modules must implement
the ICoreValidator
interface. That is because the module has to configure
the account public key when the accounted is created the first time
#![allow(unused)] fn main() { #[starknet::interface] pub trait ICoreValidator<TState> { fn is_valid_signature(self: @TState, hash: Array<felt252>, signature: Array<felt252>) -> felt252; fn initialize(ref self: TState, public_key: Array<felt252>); } }
is_valid_signature
, given thehash
that can be a transaction hash or a message hash should returnstarknet::VALIDATED
is the signature is valid and 0 if not. Depending on the case, it can be that the function cannot be implemented. If that is the case, we suggest you return that the transaction is not validinitialize
is used at the installation time of the account to store the first account public key. The reason the public_key is anArray<felt252>
is to be able to initialize scheme where the public key requires more than one felt252.
Management Interface and Mappers
Each Validator Module can provide some management functions to configure the module. The name and implementation of those functions depend on the module.
The management interface cannot be call on a class, they must be triggered on
a contract. To workaround that issue, the account provides 2 entrypoints
execute_on_module
and call_on_module
that can call the management
interface from the account. The execute_on_module
provides some additional
security making sure that only the account signers can initiate those calls.
To allow the remove access between the account calls and the management
interface the validator requires the call
and execute
methods to be
implemented. Those methods map the input and output arguments of the management function. The associated interface looks like:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IConfigure<TState> { fn call(self: @TState, call: Call) -> Array<felt252>; fn execute(ref self: TState, call: Call) -> Array<felt252>; } }
Note: To work the Call should include the following:
selector
must be the selector for the management interface, i.e. thesn_keccak
of the entrypoint nameto
should be the account addresscalldata
should be the call data as defined by the ABI of the class
Version Interface
It is recommended to implement the IVersion
interface in the module and add
them to the mappers as describe above. These interface helps the users to
identify the validators, even if eventually, the class hash remains the only
real identifier:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IVersion<TState> { fn get_version(self: @TState) -> felt252; fn get_name(self: @TState) -> felt252; } }
Other considerations
In addition to the feature above, we suggest a number of good practices to develop modules
- Be very careful with Storage variable names as they can be accessed by another module. A good practice is to prefix those names with a prefix that is uniquely derived from the module.
- Use the account
notify_owner_addition
andnotify_owner_removal
functions that are part of the internal implementation of the account to notify users of the addition/removal of public keys - Add a Typescript/Javascript module SDK to the Modular Account SDK. That would allow applications to easily use your module and help with tests.
- Provide a set of tests to make sure the module is behaving correctly and so that people can understand how it is supposed to work
- Write a documentation both about the module and the SDK.
The Stark Validator Module
The Stark Validator Module is an implementation of a Validator Module for the Starknet Modular Account. It must be used as the Core Validator for the Account. This document explains the features, the configuration and some of the Internals of this module.
Validator Module
A validator is a contract that implement the following interface:
#![allow(unused)] fn main() { #[starknet::interface] trait IValidator<TState> { fn validate(self: @TState, grantor_class: ClassHash, calls: Array<Call>) -> felt252; } }
validate
is used to validate a transaction on the account. It
- gets the hash for the current transaction from the network
- use
is_valid_signature
to check the signature is valid
Note: the grantor class that is passed by the account is the Core Validator class hash registered with the account. In the case of the Stark Validator it is the module class hash. The validator does not use that parameter.
Core Validator Interface
In addition to the IValidator
interface, The Stark Validator Module implements
the ICoreValidator
interface. That is because the Stark Validator can be
installed as a Core Validator Module, i.e. the default Validator for the account.
The interface looks like this:
#![allow(unused)] fn main() { #[starknet::interface] pub trait ICoreValidator<TState> { fn is_valid_signature(self: @TState, hash: Hash<felt252>, signature: Array<felt252>) -> felt252; fn initialize(ref self: TState, public_key: Array<felt252>); } }
In the case of the Stark Validator the 2 functionsare:
is_valid_signature
. It checks a hash of a transaction or a hash of a message matches the account public keys of the current configurationm i.e stored in the account storage:- It checks the elements of the signature are valid considering the public keys registered in the account
- It checks the number of valid signature matches the threshold defines in the account.
initialize
is used at the installation time of the account to store the first account public key. In the case of the Stark Validator, the public key can be managed in a single felt so you can just use an array of one, i.e.array![publicKey]
in cairo or[publicKey]
in Typescript/Javascript.
Note: In the case of the Stark Validator the key is simply stored in the
Account_public_keys
storage. It is also stored in theAccount_public_key
so that we can downgrade the account back to an OpenZeppelin Account.
Management Interface
Each Validator Module can provide some management entrypoint to configure the module. In the case of the Stark Validator, the management methods are:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IPublicKeys<TState> { fn add_public_key(ref self: TState, new_public_key: felt252); fn get_public_keys(self: @TState) -> Array<felt252>; fn get_threshold(self: @TState) -> u8; fn remove_public_key(ref self: TState, old_public_key: felt252); fn set_threshold(ref self: TState, new_threshold: u8); } }
As you can assess by their name:
add_public_key
adds a public key into the account. Be careful that it does not remove the existing key that must be managed separately.remove_public_key
removes a public key from the account.get_public_keys
returns the list of current public keys registered with the accountset_threshold
defines the number of signer that must sign a transaction or message for the signature to be validget_threshold
list the current threshold of the account.
Version Interface
The Stark Validator implements the IVersion
interface below:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IVersion<TState> { fn get_version(self: @TState) -> felt252; fn get_name(self: @TState) -> felt252; } }
get_name()
returnsstark-validator
in a shortStringget_version()
returns the version starting with a v, likev0.1.8
as a short string.
Module Management Interface Mappers
The management interface cannot be call on a class, they must be triggered on
a contract. To workaround that issue, the account provides 2 entrypoints
execute_on_module
and call_on_module
that can call the management
interface from the account. The execute_on_module
provides some additional
security making sure that only the account signers can initiate those calls.
To allow the remove access between the account calls and the management
interface the validator requires the call
and execute
method are implemented
and does map the input and output arguments correctly. That is the following
interface:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IConfigure<TState> { fn call(self: @TState, call: Call) -> Array<felt252>; fn execute(ref self: TState, call: Call) -> Array<felt252>; } }
In the case of the Stark Validator:
call
can takes calls toget_public_keys
,get_name
,get_version
andget_threshold
.execute
execute calls toadd_public_key
,remove_public_keys
andset_threshold
.
Note: To work the Call should include the following:
selector
must be the selector for the management interface, i.e. thesn_keccak
of the entrypoint nameto
should be the account addresscalldata
should be the call data as defined by the ABI of the class
The ETH and P256 Validator Module
The Eth and P256 Validator Modules are implementations of a Validator Module for the Starknet Modular Account. They can both be used as the Core Validator or as a secondary Validator for the Account. This document explains the features, the configuration and some of the Internals of these modules.
Validator Module
A validator is a contract that implement the following interface:
#![allow(unused)] fn main() { #[starknet::interface] trait IValidator<TState> { fn validate(self: @TState, grantor_class: ClassHash, calls: Array<Call>) -> felt252; } }
validate
is used to validate a transaction on the account. It
- gets the hash for the current transaction from the network
- use
is_valid_signature
to check the signature is valid
Note: the grantor class that is passed by the account is the Core Validator class hash registered with the account. In the case of the Eth and P256 Validator it is the module class hash. The validator does not use that parameter for now.
Core Validator Interface
In addition to the IValidator
interface, The Eth and P256 Validator Modules
implement the ICoreValidator
interface. That is because they can be installed
as a Core Validator Modules, i.e. the default Validator for the account.
The interface looks like this:
#![allow(unused)] fn main() { #[starknet::interface] pub trait ICoreValidator<TState> { fn is_valid_signature(self: @TState, hash: Hash<felt252>, signature: Array<felt252>) -> felt252; fn initialize(ref self: TState, public_key: Array<felt252>); } }
In the case of these Validators, the 2 functions are:
is_valid_signature
. It checks a hash of a transaction or a hash of a message matches the account public keys of the current configurationm i.e stored in the account storage. It checks the elements of the signature are valid considering the public keys registered in the accountinitialize
is used at the installation time of the account to store the first account public key. In the case of the Eth and P256 Validator, the public key is managed by an array of 4 felt.
Note: The downgrade from the account back to an OpenZeppelin Account as not been tested.
Management Interface
Each Validator Module can provide some management entrypoint to configure the module. The management methods for the 2 validators are:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IPublicKeys<TState> { fn set_public_key(ref self: TState, new_public_key: Array<felt252>); fn get_public_key(self: @TState) -> Array<felt252>; } }
As you can assess by their name:
set_public_key
changes the public key associated with the account. Be careful that it removes the existing keyget_public_key
returns the elements of current public keys registered with the account
Version Interface
The Validator implements the IVersion
interface below:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IVersion<TState> { fn get_version(self: @TState) -> felt252; fn get_name(self: @TState) -> felt252; } }
get_name()
returnseth-validator
for the ETH validator in a shortString. It returnsp256-validator
for the ETH validator in a shortString.get_version()
returns the version starting with a v, likev0.1.8
as a short string.
Module Management Interface Mappers
The management interface cannot be call on a class, they must be triggered on
a contract. To workaround that issue, the account provides 2 entrypoints
execute_on_module
and call_on_module
that can call the management
interface from the account. The execute_on_module
provides some additional
security making sure that only the account signers can initiate those calls.
To allow the remove access between the account calls and the management
interface the validator requires the call
and execute
method are implemented
and does map the input and output arguments correctly. That is the following
interface:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IConfigure<TState> { fn call(self: @TState, call: Call) -> Array<felt252>; fn execute(ref self: TState, call: Call) -> Array<felt252>; } }
In the case of the Eth Validator:
call
can takes calls toget_public_key
,get_name
andget_version
.execute
can execute calls toset_public_key
.
Note: To work the Call should include the following:
selector
must be the selector for the management interface, i.e. thesn_keccak
of the entrypoint nameto
should be the account addresscalldata
should be the call data as defined by the ABI of the class and in the case of an EthPublicKey, it should be an array of 4 felts.
The SessionKey Validator Module
The SessionKey Validator Module is an implementation of a Validator Module for the Starknet Modular Account. It must be used as a secondary Validator for the Account and requires a Core Validator module to authorize the sessionkey. This document explains the features, the configuration and some of the Internals of this module.
Note: To understand the process of requesting a session key authorization and signing the authorization, you should check the documentation for the Session Key Validator SDK
Validator Module
A validator is a contract that implement the following interface:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IValidator<TState> { fn validate(self: @TState, grantor_class: ClassHash, calls: Array<Call>) -> felt252; } }
In the case of the sessionkey Validator the validate
function checks the
transaction signature is valid by:
- making sure the session key has not been disabled with the module management interface.
- extracting the session key data and making sure they are valid, signed by the core validator and still properly configured in the account.
- check the transaction calls matches the policies associated with the session key by examining the merkle proofs of the signature and recomputing the merkle root with those proofs and the calls
- check the transaction is signed by the private key that has been granted the access by the session key.
Note: the grantor class that is passed by the account is the Core Validator class hash registered with the account. In the case of the SessionKey Validator it is used to check the session key authorization is actually valid with the Core Validator Signature.
Core Validator Interface
This module cannot be used as a Core Validator. That is because the session key
needs to be authorized by the Core Validator Signer to be used. As a result, it
does not implement the ICoreValidator
interface. In addition, a message cannot
be signed by a session key simply because the policy cannot apply to it and
there is no way to check a transaction hash is valid without the calls. That is
why the is_valid_signature
cannot be implemented for that case.
Management Interface
Each Validator Module can provide some management entrypoint to configure the module. In the case of the Stark Validator, the management methods are:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IDisableSessionKey<TState> { fn disable_session_key(ref self: TState, sessionkey: felt252); fn is_disabled_session_key(self: @TState, sessionkey: felt252) -> bool; } }
As you can assess by their name:
disable_session_key
register the hash of the session key in the account to block a future attempt to use it again. Note that disabling is permanent and a session key must be re-emitted if necessaryis_disabled_session_key
list if a session key has been registered with the disabled session keys
Module Management Interface Mappers
The management interface cannot be call on a class, they must be triggered on
a contract. To workaround that issue, the account provides 2 entrypoints
execute_on_module
and call_on_module
that can call the management
interface from the account. The execute_on_module
provides some additional
security making sure that only the account signers can initiate those calls.
To allow the remove access between the account calls and the management
interface the validator requires the call
and execute
method are implemented
and does map the input and output arguments correctly. That is the following
interface:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IConfigure<TState> { fn call(self: @TState, call: Call) -> Array<felt252>; fn execute(ref self: TState, call: Call) -> Array<felt252>; } }
In the case of the Stark Validator:
call
takes calls tois_disabled_session_key
and match the input/output on the account.execute
takes calls todisable_session_key
and match the input/output on the account.
Note: To work the Call should include the following:
selector
must be the selector for the management interface, i.e. thesn_keccak
of the entrypoint nameto
should be the account addresscalldata
should be the call data as defined by the ABI of the class
Version Interface
The Session Key Validator implements the IVersion
interface below:
#![allow(unused)] fn main() { #[starknet::interface] pub trait IVersion<TState> { fn get_version(self: @TState) -> felt252; fn get_name(self: @TState) -> felt252; } }
get_name()
returnssessionkey-validator
in a shortStringget_version()
returns the version starting with a v, likev0.1.8
as a short string.
Roadmap and Known Issues
The starknet modular account is a work-in-progress effort to provide features to dapps and provide better experience to Starknet. As such, there is a lot to work on and this section gives some ideas of the direction the project will take the coming weeks and months.
Roadmap
Ideas to enhance the account are legions. However, we plan to stay focus on a small set of features:
- Add support for the version 3 of the transaction as described in SNIP-8.
- Add an entrypoint to allow
execute_from_outside
with the signature from the core validator and some limitation as described in SNIP-9. This entrypoint would require to manage a user defined Nonce to prevent replay-scenarios. It would also battle test the model. The benefit from that entrypoint besides is that it allows a 3rd party to pay for your transaction. A consolation prize for the paymaster to not be implemented yet. For some implementation details: - Develop a Guarded Validator. This would require it to be a core validators. It would provide more value for the modular account because the implementation requires some different signature depending on the calls so it would validate/invalidate the current set of interfaces.
- Move the Validator Module data from a prefix call to the account signature. This might not be feasible but it makes sense because the validation is supposed to rely on the signature.
- Add support for Executor Modules. Some scenario like the ability to delay the execution of a transaction or the ability to track some data like an amount of ERC20 dedicated to a 3rd party requires those modules to work.
- Ensure the compatibility between session key and offchain signature à la EIP-712 or SNIP-12.
- Check what can be done to support upgrades from OpenZeppelin, Argent and Braavo Accounts
If you need support for another scenario, feel free to open an issue on Github.
Known Issues
In addition to the planned changes above, there are a number of smaller issues that have been identified and need to be addressed. Most of them are implementation details bit some of them might impact your usage. Those issues are the following:
- Version the contracts for both accounts and modules and maintain a compatibility matrix. Actually we should check if we can rely on the boostrap experiment to have a more stable account address stability and introduce a contract that manages versions and allow to initiate some sort of registry.
- Improve the upgrade management with the help of the bootstrap experiment. Right now the upgrade works because it is actually a downgrade and move back. In reality it will not pass a test of an upgrade with an Argent, Braavos or Openzeppelin Account.
- Right now, type conversions are done manually with the
core::traits::Into
package. Improve it so that is it more readable and consistent with how other libraries like openzeppelin does. - Reduce, as much as possible the SDKs API surface to make it simpler to use for developers.
- Associate class versions with class hash in the documentation and version the documentation so that we can keep track of the version history
- It is possible to get a sessionkey that has all accesses on an account. We need to disable that feature to force dapps to requests what they want and for users to review policies.
- For a better understanding of the sessionkey flow in the SDK we should sign
directly the request and not the
SessionKeyModule
- Check when OpenZeppelin uses Poseidon Hash and see if that impacts the Eth module.
- Fees are not computed correctly when a large part comes from the signature and as a result, the deploy_account has a fixed maxFee. Improve the computation and remove that fix value.
- Check existing audits and see if the recommendations apply. If yes, apply them.
SDK Reference Documentation
This section provides the generated reference documentation for the Typescript/Javascript SDKs that comes with the project. For a better understanding of those SDKs, check the Using SDKs section.
The 3 SDKs can be downloaded from npmjs.com.
- @0xknwn/starknet-modular-account
extends the starknet.js
Account
to support multiple signers and manage modules. It also provides theAccountModuleInterface
that should be used by module SDKs. You can check the reference documentation for this SDK here - @0xknwn/starknet-module-sessionkey
provides the
SessionKeyModule
that implements theAccountModuleInterface
as well as tools to configure the sessionkey module, including thePolicyManager
and thePolicyGrantor
classes. You can check the reference documentation for this SDK here - @0xknwn/starknet-module
provides the
EthModule
that implements theAccountModuleInterface
. To sign transaction, you can simply use the Starknet.jsEthSigner
. You can check the reference documentation for this SDK here
In addition, the project provides another SDK called
@0xknwn/starknet-test-helpers that can be used to create helper classes outside of
this repository. That project is used to help building demonstration and/or
tests. In particular in includes the tools to 2 contracts named Counter
and SwapRouter
. You can check the reference documentation for this SDK
here.
@0xknwn/starknet-modular-account
@0xknwn/starknet-modular-account
Classes
Interfaces
@0xknwn/starknet-modular-account
@0xknwn/starknet-modular-account / SmartrAccount
Class: SmartrAccount
Defined in: sdks/account/src/smartr_account.ts:71
Represents a SmartrAccount.
Extends
Account
Constructors
new SmartrAccount()
new SmartrAccount(
providerOrOptions
,address
,pkOrSigner
,module
,cairoVersion
?,transactionVersion
?):SmartrAccount
Defined in: sdks/account/src/smartr_account.ts:76
Parameters
providerOrOptions
ProviderInterface
| ProviderOptions
address
string
pkOrSigner
string
| Uint8Array
| SignerInterface
module
undefined
| AccountModuleInterface
cairoVersion?
CairoVersion
transactionVersion?
any
= RPC.ETransactionVersion.V2
Returns
Overrides
Account.constructor
Properties
address
address:
string
Defined in: node_modules/starknet/dist/index.d.ts:4416
Inherited from
Account.address
cairoVersion
cairoVersion:
CairoVersion
Defined in: node_modules/starknet/dist/index.d.ts:4417
Inherited from
Account.cairoVersion
channel
channel:
RpcChannel
|RpcChannel$1
Defined in: node_modules/starknet/dist/index.d.ts:3386
Inherited from
Account.channel
deploySelf()
deploySelf: (
__namedParameters
,details
?) =>Promise
<DeployContractResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:4448
Parameters
__namedParameters
DeployAccountContractPayload
details?
UniversalDetails
Returns
Promise
<DeployContractResponse
>
Inherited from
Account.deploySelf
getStateUpdate()
getStateUpdate: () =>
Promise
<{block_hash
:never
;old_root
:string
;state_diff
: {declared_classes
:object
[];deployed_contracts
:object
[];deprecated_declared_classes
:string
[];nonces
:object
[];replaced_classes
:object
[];storage_diffs
:object
[]; }; }>(blockIdentifier
) =>Promise
<{block_hash
:never
;old_root
:string
;state_diff
: {declared_classes
:object
[];deployed_contracts
:object
[];deprecated_declared_classes
:string
[];nonces
:object
[];replaced_classes
:object
[];storage_diffs
:object
[]; }; }>(blockIdentifier
) =>Promise
<{block_hash
:string
;new_root
:string
;old_root
:string
;state_diff
: {declared_classes
:object
[];deployed_contracts
:object
[];deprecated_declared_classes
:string
[];nonces
:object
[];replaced_classes
:object
[];storage_diffs
:object
[]; }; }>(blockIdentifier
?) =>Promise
<StateUpdateResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:3423
Gets the state changes in a specific block (result of executing the requested block)
Returns
Promise
<{ block_hash
: never
; old_root
: string
; state_diff
: { declared_classes
: object
[]; deployed_contracts
: object
[]; deprecated_declared_classes
: string
[]; nonces
: object
[]; replaced_classes
: object
[]; storage_diffs
: object
[]; }; }>
Parameters
blockIdentifier
"pending"
Returns
Promise
<{ block_hash
: never
; old_root
: string
; state_diff
: { declared_classes
: object
[]; deployed_contracts
: object
[]; deprecated_declared_classes
: string
[]; nonces
: object
[]; replaced_classes
: object
[]; storage_diffs
: object
[]; }; }>
Parameters
blockIdentifier
"latest"
Returns
Promise
<{ block_hash
: string
; new_root
: string
; old_root
: string
; state_diff
: { declared_classes
: object
[]; deployed_contracts
: object
[]; deprecated_declared_classes
: string
[]; nonces
: object
[]; replaced_classes
: object
[]; storage_diffs
: object
[]; }; }>
Parameters
blockIdentifier?
BlockIdentifier
Returns
Promise
<StateUpdateResponse
>
Param
block identifier
Returns
StateUpdateResponse
Inherited from
Account.getStateUpdate
module
module:
undefined
|AccountModuleInterface
Defined in: sdks/account/src/smartr_account.ts:74
responseParser
responseParser:
RPCResponseParser
Defined in: node_modules/starknet/dist/index.d.ts:3385
Inherited from
Account.responseParser
signer
signer:
SignerInterface
Defined in: node_modules/starknet/dist/index.d.ts:4415
Inherited from
Account.signer
transactionVersion
readonly
transactionVersion:"0x3"
|"0x2"
Defined in: node_modules/starknet/dist/index.d.ts:4418
Inherited from
Account.transactionVersion
Methods
accountInvocationsFactory()
accountInvocationsFactory(
invocations
,details
):Promise
<AccountInvocations
>
Defined in: node_modules/starknet/dist/index.d.ts:4553
Parameters
invocations
Invocations
details
AccountInvocationsFactoryDetails
Returns
Promise
<AccountInvocations
>
Inherited from
Account.accountInvocationsFactory
addModule()
Call Signature
addModule(
class_hash
,execute
?):Promise
<{transaction_hash
:string
; }>
Defined in: sdks/account/src/smartr_account.ts:428
Add a module to an account.
Parameters
class_hash
string
the module to add
execute?
true
If true, the transaction is executed, otherwise it is built and returned so that it can be used in a multicall.
Returns
Promise
<{ transaction_hash
: string
; }>
A promise that resolves to the transaction receipt if executes is true, otherwise it returns the transaction call.
Call Signature
addModule(
class_hash
,execute
):Promise
<Call
[]>
Defined in: sdks/account/src/smartr_account.ts:432
Add a module to an account.
Parameters
class_hash
string
the module to add
execute
false
If true, the transaction is executed, otherwise it is built and returned so that it can be used in a multicall.
Returns
Promise
<Call
[]>
A promise that resolves to the transaction receipt if executes is true, otherwise it returns the transaction call.
buildAccountDeployPayload()
buildAccountDeployPayload(
__namedParameters
,details
):Promise
<DeployAccountContractTransaction
>
Defined in: node_modules/starknet/dist/index.d.ts:4551
Parameters
__namedParameters
DeployAccountContractPayload
details
InvocationsSignerDetails
Returns
Promise
<DeployAccountContractTransaction
>
Inherited from
Account.buildAccountDeployPayload
buildDeclarePayload()
buildDeclarePayload(
payload
,details
):Promise
<DeclareContractTransaction
>
Defined in: node_modules/starknet/dist/index.d.ts:4550
Parameters
payload
DeclareContractPayload
details
InvocationsSignerDetails
Returns
Promise
<DeclareContractTransaction
>
Inherited from
Account.buildDeclarePayload
buildInvocation()
buildInvocation(
call
,details
):Promise
<Invocation
>
Defined in: node_modules/starknet/dist/index.d.ts:4549
Parameters
call
Call
[]
details
InvocationsSignerDetails
Returns
Promise
<Invocation
>
Inherited from
Account.buildInvocation
buildUDCContractPayload()
buildUDCContractPayload(
payload
):Call
[]
Defined in: node_modules/starknet/dist/index.d.ts:4552
Parameters
payload
UniversalDeployerContractPayload
| UniversalDeployerContractPayload
[]
Returns
Call
[]
Inherited from
Account.buildUDCContractPayload
callContract()
callContract(
call
,blockIdentifier
?):Promise
<string
[]>
Defined in: node_modules/starknet/dist/index.d.ts:3477
Calls a function on the Starknet contract.
Parameters
call
Call
transaction to be called
blockIdentifier?
BlockIdentifier
block identifier
Returns
Promise
<string
[]>
the result of the function on the smart contract.
Inherited from
Account.callContract
callOnModule()
callOnModule(
module_class_hash
,module_entrypoint
,calldata
):Promise
<BigNumberish
[]>
Defined in: sdks/account/src/smartr_account.ts:374
Call an entrypoint on the module account.
Parameters
module_class_hash
string
The installed module class_hash.
module_entrypoint
string
The module entrypoint to call.
calldata
string
[]
The entrypoint calldata.
Returns
Promise
<BigNumberish
[]>
A promise that resolves to the call output.
declare()
declare(
payload
,details
?):Promise
<{class_hash
:string
;transaction_hash
:string
; }>
Defined in: node_modules/starknet/dist/index.d.ts:4444
Declares a given compiled contract (json) to starknet
Parameters
payload
DeclareContractPayload
details?
UniversalDetails
Returns
Promise
<{ class_hash
: string
; transaction_hash
: string
; }>
a confirmation of sending a transaction on the starknet contract
Inherited from
Account.declare
declareAndDeploy()
declareAndDeploy(
payload
,details
?):Promise
<DeclareDeployUDCResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:4447
Declares and Deploy a given compiled contract (json) to starknet using UDC Internal wait for L2 transaction, do not support multicall Method will pass even if contract is already declared (internal using DeclareIfNot)
Parameters
payload
DeclareAndDeployContractPayload
contract: compiled contract code
- [casm=cairo1]: CairoAssembly | undefined;
- [compiledClassHash]: string | undefined;
- [classHash]: computed class hash of compiled contract
- [constructorCalldata] contract constructor calldata
- [salt=pseudorandom] deploy address salt
- [unique=true] ensure unique salt
details?
UniversalDetails
InvocationsDetails
Returns
Promise
<DeclareDeployUDCResponse
>
- declare
- transaction_hash
- deploy
- contract_address
- transaction_hash
- address
- deployer
- unique
- classHash
- calldata_len
- calldata
- salt
Inherited from
Account.declareAndDeploy
declareContract()
declareContract(
transaction
,details
):Promise
<DeclaredTransaction
>
Defined in: node_modules/starknet/dist/index.d.ts:3475
Declares a given compiled contract (json) to starknet
Parameters
transaction
DeclareContractTransaction
transaction payload to be deployed containing:
- compiled contract code
- sender address
- signature
details
InvocationsDetailsWithNonce
Invocation Details containing:
- nonce
- optional version
- optional maxFee
Returns
Promise
<DeclaredTransaction
>
a confirmation of sending a transaction on the starknet contract
Inherited from
Account.declareContract
declareIfNot()
declareIfNot(
payload
,transactionsDetail
?):Promise
<{class_hash
:string
;transaction_hash
:string
; }>
Defined in: node_modules/starknet/dist/index.d.ts:4443
First check if contract is already declared, if not declare it If contract already declared returned transaction_hash is ''. Method will pass even if contract is already declared
Parameters
payload
DeclareContractPayload
transactionsDetail?
UniversalDetails
(optional)
Returns
Promise
<{ class_hash
: string
; transaction_hash
: string
; }>
Inherited from
Account.declareIfNot
deploy()
deploy(
payload
,details
?):Promise
<MultiDeployContractResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:4445
Deploys a declared contract to starknet - using Universal Deployer Contract (UDC) support multicall
Parameters
payload
classHash: computed class hash of compiled contract
- [constructorCalldata] contract constructor calldata
- [salt=pseudorandom] deploy address salt
- [unique=true] ensure unique salt
UniversalDeployerContractPayload
| UniversalDeployerContractPayload
[]
details?
UniversalDetails
InvocationsDetails
Returns
Promise
<MultiDeployContractResponse
>
- contract_address[]
- transaction_hash
Inherited from
Account.deploy
deployAccount()
deployAccount(
contractPayload
,details
?):Promise
<DeployContractResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:4449
Deploy the account on Starknet
Parameters
contractPayload
DeployAccountContractPayload
transaction payload to be deployed containing:
- classHash: computed class hash of compiled contract
- optional constructor calldata
- optional address salt
- optional contractAddress
details?
UniversalDetails
Returns
Promise
<DeployContractResponse
>
a confirmation of sending a transaction on the starknet contract
Inherited from
Account.deployAccount
deployAccountContract()
deployAccountContract(
transaction
,details
):Promise
<DeployedAccountTransaction
>
Defined in: node_modules/starknet/dist/index.d.ts:3476
Deploys a given compiled Account contract (json) to starknet
Parameters
transaction
DeployAccountContractTransaction
details
InvocationsDetailsWithNonce
Returns
Promise
<DeployedAccountTransaction
>
a confirmation of sending a transaction on the starknet contract
Inherited from
Account.deployAccountContract
deployContract()
deployContract(
payload
,details
?):Promise
<DeployContractUDCResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:4446
Simplify deploy simulating old DeployContract with same response + UDC specific response Internal wait for L2 transaction, support multicall
Parameters
payload
classHash: computed class hash of compiled contract
- [constructorCalldata] contract constructor calldata
- [salt=pseudorandom] deploy address salt
- [unique=true] ensure unique salt
UniversalDeployerContractPayload
| UniversalDeployerContractPayload
[]
details?
UniversalDetails
InvocationsDetails
Returns
Promise
<DeployContractUDCResponse
>
- contract_address
- transaction_hash
- address
- deployer
- unique
- classHash
- calldata_len
- calldata
- salt
Inherited from
Account.deployContract
estimateAccountDeployFee()
estimateAccountDeployFee(
contractPayload
,details
?):Promise
<EstimateFee
>
Defined in: node_modules/starknet/dist/index.d.ts:4431
Estimate Fee for executing a DEPLOY_ACCOUNT transaction on starknet
Parameters
contractPayload
DeployAccountContractPayload
classHash - the class hash of the compiled contract.
- constructorCalldata? - constructor data;
- contractAddress? - future account contract address. Precalculate for faster execution.
- addressSalt? - salt used for calculation of the contractAddress. Required if contractAddress is provided.
details?
UniversalDetails
Returns
Promise
<EstimateFee
>
response from estimate_fee
Inherited from
Account.estimateAccountDeployFee
estimateDeclareFee()
estimateDeclareFee(
payload
,details
?):Promise
<EstimateFee
>
Defined in: node_modules/starknet/dist/index.d.ts:4430
Estimate Fee for executing a DECLARE transaction on starknet
Parameters
payload
DeclareContractPayload
details?
UniversalDetails
Returns
Promise
<EstimateFee
>
response from estimate_fee
Inherited from
Account.estimateDeclareFee
estimateDeployFee()
estimateDeployFee(
payload
,details
?):Promise
<EstimateFee
>
Defined in: node_modules/starknet/dist/index.d.ts:4432
Estimate Fee for executing a UDC DEPLOY transaction on starknet This is different from the normal DEPLOY transaction as it goes through the Universal Deployer Contract (UDC)
Parameters
payload
UniversalDeployerContractPayload
| UniversalDeployerContractPayload
[]
details?
UniversalDetails
Returns
Promise
<EstimateFee
>
Inherited from
Account.estimateDeployFee
estimateFee()
estimateFee(
calls
,estimateFeeDetails
?):Promise
<EstimateFee
>
Defined in: node_modules/starknet/dist/index.d.ts:4428
Parameters
calls
AllowArray
<Call
>
estimateFeeDetails?
UniversalDetails
Returns
Promise
<EstimateFee
>
Inherited from
Account.estimateFee
estimateFeeBulk()
estimateFeeBulk(
invocations
,details
?):Promise
<EstimateFeeBulk
>
Defined in: node_modules/starknet/dist/index.d.ts:4433
Estimate Fee for executing a list of transactions on starknet Contract must be deployed for fee estimation to be possible
Parameters
invocations
Invocations
array of transaction object containing :
- type - the type of transaction : 'DECLARE' | (multi)'DEPLOY' | (multi)'INVOKE_FUNCTION' | 'DEPLOY_ACCOUNT'
- payload - the payload of the transaction
details?
UniversalDetails
blockIdentifier?
- nonce?
- skipValidate? - default true
- tip? - prioritize order of transactions in the mempool.
- accountDeploymentData? - deploy an account contract (substitution for deploy account transaction)
- paymasterData? - entity other than the transaction sender to pay the transaction fees(EIP-4337)
- nonceDataAvailabilityMode? - allows users to choose their preferred data availability mode (Volition)
- feeDataAvailabilityMode? - allows users to choose their preferred data availability mode (Volition)
- version? - specify ETransactionVersion - V3 Transactions fee is in fri, oldV transactions fee is in wei
Returns
Promise
<EstimateFeeBulk
>
response from estimate_fee
Inherited from
Account.estimateFeeBulk
estimateInvokeFee()
estimateInvokeFee(
calls
,details
?):Promise
<EstimateFee
>
Defined in: node_modules/starknet/dist/index.d.ts:4429
Estimate Fee for executing an INVOKE transaction on starknet
Parameters
calls
AllowArray
<Call
>
the invocation object containing:
- contractAddress - the address of the contract
- entrypoint - the entrypoint of the contract
- calldata? - (defaults to []) the calldata
details?
UniversalDetails
Returns
Promise
<EstimateFee
>
response from estimate_fee
Inherited from
Account.estimateInvokeFee
estimateMessageFee()
estimateMessageFee(
message
,blockIdentifier
?):Promise
<FEE_ESTIMATE
>
Defined in: node_modules/starknet/dist/index.d.ts:3482
NEW: Estimate the fee for a message from L1
Parameters
message
MSG_FROM_L1
Message From L1
blockIdentifier?
BlockIdentifier
Returns
Promise
<FEE_ESTIMATE
>
Inherited from
Account.estimateMessageFee
execute()
Call Signature
execute(
transactions
,transactionsDetail
?):Promise
<{transaction_hash
:string
; }>
Defined in: sdks/account/src/smartr_account.ts:236
Executes a set of transactions on the StarkNet network.
Parameters
transactions
AllowArray
<Call
>
An array of transactions to be executed.
transactionsDetail?
UniversalDetails
Optional object containing additional details for the transactions.
Returns
Promise
<{ transaction_hash
: string
; }>
A Promise that resolves to an InvokeFunctionResponse object representing the result of the execution.
Overrides
Account.execute
Call Signature
execute(
transactions
,abis
?,transactionsDetail
?):Promise
<{transaction_hash
:string
; }>
Defined in: sdks/account/src/smartr_account.ts:249
Executes a set of transactions on the StarkNet network.
Parameters
transactions
AllowArray
<Call
>
An array of transactions to be executed.
abis?
Abi
[]
Optional argument that can be an array of ABIs.
transactionsDetail?
UniversalDetails
Optional object containing additional details for the transactions.
Returns
Promise
<{ transaction_hash
: string
; }>
A Promise that resolves to an InvokeFunctionResponse object representing the result of the execution.
Overrides
Account.execute
executeFromOutside()
executeFromOutside(
outsideTransaction
,opts
?):Promise
<{transaction_hash
:string
; }>
Defined in: node_modules/starknet/dist/index.d.ts:4546
An account B executes a transaction that has been signed by an account A. Fees are paid by B.
Parameters
outsideTransaction
AllowArray
<OutsideTransaction
>
the signed transaction generated by Account.getOutsideTransaction()
.
opts?
UniversalDetails
same options than Account.execute()
.
Returns
Promise
<{ transaction_hash
: string
; }>
same response than Account.execute()
.
Example
const outsideTransaction1: OutsideTransaction = await signerAccount.getOutsideTransaction(callOptions, call1);
const outsideTransaction2: OutsideTransaction = await signerAccount.getOutsideTransaction(callOptions4, call4);
const result = await myAccount.executeFromOutside([
outsideTransaction1,
outsideTransaction2,
]);
// result = { transaction_hash: '0x11233...`}
Inherited from
Account.executeFromOutside
executeMultisig()
executeMultisig(
transactions
,details
,signature
):Promise
<{transaction_hash
:string
; }>
Defined in: sdks/account/src/smartr_account.ts:195
Executes a set of transactions, assuming they have been signed by all parties.
Parameters
transactions
Call
[]
An array of transactions to be executed.
details
InvocationsDetailsWithNonce
Optional object containing additional details for the transactions.
signature
ArraySignatureType
The signature of the transactions.
Returns
Promise
<{ transaction_hash
: string
; }>
A Promise that resolves to the transactions invocation response.
executeOnModule()
Call Signature
executeOnModule(
module_class_hash
,module_entrypoint
,calldata
,execute
?):Promise
<{transaction_hash
:string
; }>
Defined in: sdks/account/src/smartr_account.ts:329
Execute or build a transaction on the module account.
Parameters
module_class_hash
string
The installed module class_hash.
module_entrypoint
string
The module entrypoint to execute.
calldata
string
[]
The entrypoint calldata.
execute?
true
If true, the transaction is executed, otherwise it is built and returned so that it can be used in a multicall.
Returns
Promise
<{ transaction_hash
: string
; }>
A promise that resolves to the transaction receipt if executes is true, otherwise it returns the transaction call.
Call Signature
executeOnModule(
module_class_hash
,module_entrypoint
,calldata
,execute
):Promise
<Call
[]>
Defined in: sdks/account/src/smartr_account.ts:335
Execute or build a transaction on the module account.
Parameters
module_class_hash
string
The installed module class_hash.
module_entrypoint
string
The module entrypoint to execute.
calldata
string
[]
The entrypoint calldata.
execute
false
If true, the transaction is executed, otherwise it is built and returned so that it can be used in a multicall.
Returns
Promise
<Call
[]>
A promise that resolves to the transaction receipt if executes is true, otherwise it returns the transaction call.
fetch()
fetch(
method
,params
?,id
?):Promise
<Response
>
Defined in: node_modules/starknet/dist/index.d.ts:3388
Parameters
method
string
params?
object
id?
string
| number
Returns
Promise
<Response
>
Inherited from
Account.fetch
getAddressFromStarkName()
getAddressFromStarkName(
name
,StarknetIdContract
?):Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:3534
Parameters
name
string
StarknetIdContract?
string
Returns
Promise
<string
>
Inherited from
Account.getAddressFromStarkName
getBlock()
Call Signature
getBlock():
Promise
<PendingBlock
>
Defined in: node_modules/starknet/dist/index.d.ts:3392
Gets the block information
Returns
Promise
<PendingBlock
>
the block object
Inherited from
Account.getBlock
Call Signature
getBlock(
blockIdentifier
):Promise
<PendingBlock
>
Defined in: node_modules/starknet/dist/index.d.ts:3393
Parameters
blockIdentifier
"pending"
Returns
Promise
<PendingBlock
>
Inherited from
Account.getBlock
Call Signature
getBlock(
blockIdentifier
):Promise
<Block$1
>
Defined in: node_modules/starknet/dist/index.d.ts:3394
Parameters
blockIdentifier
"latest"
Returns
Promise
<Block$1
>
Inherited from
Account.getBlock
Call Signature
getBlock(
blockIdentifier
?):Promise
<GetBlockResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:3395
Parameters
blockIdentifier?
BlockIdentifier
Returns
Promise
<GetBlockResponse
>
Inherited from
Account.getBlock
getBlockLatestAccepted()
getBlockLatestAccepted():
Promise
<BlockHashAndNumber
>
Defined in: node_modules/starknet/dist/index.d.ts:3399
Get the most recent accepted block hash and number
Returns
Promise
<BlockHashAndNumber
>
Inherited from
Account.getBlockLatestAccepted
getBlockNumber()
getBlockNumber():
Promise
<number
>
Defined in: node_modules/starknet/dist/index.d.ts:3405
Get the most recent accepted block number redundant use getBlockLatestAccepted();
Returns
Promise
<number
>
Number of the latest block
Inherited from
Account.getBlockNumber
getBlockStateUpdate()
Call Signature
getBlockStateUpdate():
Promise
<{block_hash
:never
;old_root
:string
;state_diff
: {declared_classes
:object
[];deployed_contracts
:object
[];deprecated_declared_classes
:string
[];nonces
:object
[];replaced_classes
:object
[];storage_diffs
:object
[]; }; }>
Defined in: node_modules/starknet/dist/index.d.ts:3429
Returns
Promise
<{ block_hash
: never
; old_root
: string
; state_diff
: { declared_classes
: object
[]; deployed_contracts
: object
[]; deprecated_declared_classes
: string
[]; nonces
: object
[]; replaced_classes
: object
[]; storage_diffs
: object
[]; }; }>
Inherited from
Account.getBlockStateUpdate
Call Signature
getBlockStateUpdate(
blockIdentifier
):Promise
<{block_hash
:never
;old_root
:string
;state_diff
: {declared_classes
:object
[];deployed_contracts
:object
[];deprecated_declared_classes
:string
[];nonces
:object
[];replaced_classes
:object
[];storage_diffs
:object
[]; }; }>
Defined in: node_modules/starknet/dist/index.d.ts:3430
Parameters
blockIdentifier
"pending"
Returns
Promise
<{ block_hash
: never
; old_root
: string
; state_diff
: { declared_classes
: object
[]; deployed_contracts
: object
[]; deprecated_declared_classes
: string
[]; nonces
: object
[]; replaced_classes
: object
[]; storage_diffs
: object
[]; }; }>
Inherited from
Account.getBlockStateUpdate
Call Signature
getBlockStateUpdate(
blockIdentifier
):Promise
<{block_hash
:string
;new_root
:string
;old_root
:string
;state_diff
: {declared_classes
:object
[];deployed_contracts
:object
[];deprecated_declared_classes
:string
[];nonces
:object
[];replaced_classes
:object
[];storage_diffs
:object
[]; }; }>
Defined in: node_modules/starknet/dist/index.d.ts:3431
Parameters
blockIdentifier
"latest"
Returns
Promise
<{ block_hash
: string
; new_root
: string
; old_root
: string
; state_diff
: { declared_classes
: object
[]; deployed_contracts
: object
[]; deprecated_declared_classes
: string
[]; nonces
: object
[]; replaced_classes
: object
[]; storage_diffs
: object
[]; }; }>
Inherited from
Account.getBlockStateUpdate
Call Signature
getBlockStateUpdate(
blockIdentifier
?):Promise
<StateUpdateResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:3432
Parameters
blockIdentifier?
BlockIdentifier
Returns
Promise
<StateUpdateResponse
>
Inherited from
Account.getBlockStateUpdate
getBlockTransactionCount()
getBlockTransactionCount(
blockIdentifier
?):Promise
<number
>
Defined in: node_modules/starknet/dist/index.d.ts:3434
Parameters
blockIdentifier?
BlockIdentifier
Returns
Promise
<number
>
Inherited from
Account.getBlockTransactionCount
getBlockTransactionsTraces()
getBlockTransactionsTraces(
blockIdentifier
?):Promise
<BlockTransactionsTraces
>
Defined in: node_modules/starknet/dist/index.d.ts:3433
Parameters
blockIdentifier?
BlockIdentifier
Returns
Promise
<BlockTransactionsTraces
>
Inherited from
Account.getBlockTransactionsTraces
getBlockWithReceipts()
getBlockWithReceipts(
blockIdentifier
?):Promise
<BlockWithTxReceipts
>
Defined in: node_modules/starknet/dist/index.d.ts:3422
Parameters
blockIdentifier?
BlockIdentifier
Returns
Promise
<BlockWithTxReceipts
>
Inherited from
Account.getBlockWithReceipts
getBlockWithTxHashes()
getBlockWithTxHashes(
blockIdentifier
?):Promise
<BlockWithTxHashes$1
>
Defined in: node_modules/starknet/dist/index.d.ts:3406
Parameters
blockIdentifier?
BlockIdentifier
Returns
Promise
<BlockWithTxHashes$1
>
Inherited from
Account.getBlockWithTxHashes
getBlockWithTxs()
getBlockWithTxs(
blockIdentifier
?):Promise
<BlockWithTxs
>
Defined in: node_modules/starknet/dist/index.d.ts:3407
Parameters
blockIdentifier?
BlockIdentifier
Returns
Promise
<BlockWithTxs
>
Inherited from
Account.getBlockWithTxs
getCairoVersion()
getCairoVersion(
classHash
?):Promise
<CairoVersion
>
Defined in: node_modules/starknet/dist/index.d.ts:4427
Retrieves the Cairo version from the network and sets cairoVersion
if not already set in the constructor.
Parameters
classHash?
string
if provided detects Cairo version from classHash, otherwise from the account address
Returns
Promise
<CairoVersion
>
Inherited from
Account.getCairoVersion
getChainId()
getChainId():
Promise
<StarknetChainId
>
Defined in: node_modules/starknet/dist/index.d.ts:3389
Gets the Starknet chain Id
Returns
Promise
<StarknetChainId
>
the chain Id
Inherited from
Account.getChainId
getClass()
getClass(
classHash
,blockIdentifier
?):Promise
<LegacyContractClass
|Omit
<CompiledSierra
,"sierra_program_debug_info"
>>
Defined in: node_modules/starknet/dist/index.d.ts:3462
Parameters
classHash
BigNumberish
blockIdentifier?
BlockIdentifier
Returns
Promise
<LegacyContractClass
| Omit
<CompiledSierra
, "sierra_program_debug_info"
>>
Inherited from
Account.getClass
getClassAt()
getClassAt(
contractAddress
,blockIdentifier
?):Promise
<LegacyContractClass
|Omit
<CompiledSierra
,"sierra_program_debug_info"
>>
Defined in: node_modules/starknet/dist/index.d.ts:3463
Gets the contract class of the deployed contract.
Parameters
contractAddress
BigNumberish
contract address
blockIdentifier?
BlockIdentifier
block identifier
Returns
Promise
<LegacyContractClass
| Omit
<CompiledSierra
, "sierra_program_debug_info"
>>
Contract class of compiled contract
Inherited from
Account.getClassAt
getClassByHash()
getClassByHash(
classHash
):Promise
<LegacyContractClass
|Omit
<CompiledSierra
,"sierra_program_debug_info"
>>
Defined in: node_modules/starknet/dist/index.d.ts:3461
Returns the contract class deployed under the given class hash.
Parameters
classHash
BigNumberish
class hash
Returns
Promise
<LegacyContractClass
| Omit
<CompiledSierra
, "sierra_program_debug_info"
>>
Contract class of compiled contract
Inherited from
Account.getClassByHash
getClassHashAt()
getClassHashAt(
contractAddress
,blockIdentifier
?):Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:3460
Returns the contract class hash in the given block for the contract deployed at the given address
Parameters
contractAddress
BigNumberish
contract address
blockIdentifier?
BlockIdentifier
block identifier
Returns
Promise
<string
>
Class hash
Inherited from
Account.getClassHashAt
getContractVersion()
Call Signature
getContractVersion(
contractAddress
,classHash
?,options
?):Promise
<ContractVersion
>
Defined in: node_modules/starknet/dist/index.d.ts:3464
Gets the contract version from the provided address
Parameters
contractAddress
BigNumberish
string
classHash?
undefined
undefined
options?
getContractVersionOptions
getContractVersionOptions
- (optional) compiler - (default true) extract compiler version using type tactic from abi
- (optional) blockIdentifier - block identifier
Returns
Promise
<ContractVersion
>
Inherited from
Account.getContractVersion
Call Signature
getContractVersion(
contractAddress
,classHash
,options
?):Promise
<ContractVersion
>
Defined in: node_modules/starknet/dist/index.d.ts:3465
Gets the contract version from the provided address
Parameters
contractAddress
undefined
undefined
classHash
BigNumberish
options?
getContractVersionOptions
getContractVersionOptions
- (optional) compiler - (default true) extract compiler version using type tactic from abi
- (optional) blockIdentifier - block identifier
Returns
Promise
<ContractVersion
>
Inherited from
Account.getContractVersion
getDeclareEstimateFee()
getDeclareEstimateFee(
invocation
,details
,blockIdentifier
?,skipValidate
?):Promise
<EstimateFeeResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:3471
Estimates the fee for a given DECLARE transaction
Parameters
invocation
DeclareContractTransaction
details
InvocationsDetailsWithNonce
optional details containing:
- nonce
- version - optional version
- optional maxFee
blockIdentifier?
BlockIdentifier
(optional) block identifier
skipValidate?
boolean
(optional) skip cairo validate method
Returns
Promise
<EstimateFeeResponse
>
the estimated fee
Inherited from
Account.getDeclareEstimateFee
getDeployAccountEstimateFee()
getDeployAccountEstimateFee(
invocation
,details
,blockIdentifier
?,skipValidate
?):Promise
<EstimateFeeResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:3472
Estimates the fee for a given DEPLOY_ACCOUNT transaction
Parameters
invocation
DeployAccountContractTransaction
details
InvocationsDetailsWithNonce
optional details containing:
- nonce
- version - optional version
- optional maxFee
blockIdentifier?
BlockIdentifier
(optional) block identifier
skipValidate?
boolean
(optional) skip cairo validate method
Returns
Promise
<EstimateFeeResponse
>
the estimated fee
Inherited from
Account.getDeployAccountEstimateFee
getEstimateFee()
getEstimateFee(
invocation
,invocationDetails
,blockIdentifier
?,skipValidate
?):Promise
<EstimateFeeResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:3469
Parameters
invocation
Invocation
invocationDetails
InvocationsDetailsWithNonce
blockIdentifier?
BlockIdentifier
skipValidate?
boolean
Returns
Promise
<EstimateFeeResponse
>
Deprecated
use gettypeEstimateFee (will be refactored based on type after sequencer deprecation)
Inherited from
Account.getEstimateFee
getEstimateFeeBulk()
getEstimateFeeBulk(
invocations
,options
):Promise
<EstimateFeeResponseBulk
>
Defined in: node_modules/starknet/dist/index.d.ts:3473
Estimates the fee for a list of INVOKE transaction
Parameters
invocations
AccountInvocations
AccountInvocations - Complete invocations array with account details
options
getEstimateFeeBulkOptions
getEstimateFeeBulkOptions
- (optional) blockIdentifier - BlockIdentifier
Returns
Promise
<EstimateFeeResponseBulk
>
the estimated fee
Inherited from
Account.getEstimateFeeBulk
getEvents()
getEvents(
eventFilter
):Promise
<EVENTS_CHUNK
>
Defined in: node_modules/starknet/dist/index.d.ts:3492
Returns all events matching the given filter
Parameters
eventFilter
EventFilter
Returns
Promise
<EVENTS_CHUNK
>
events and the pagination of the events
Inherited from
Account.getEvents
getInvokeEstimateFee()
getInvokeEstimateFee(
invocation
,invocationDetails
,blockIdentifier
?,skipValidate
?):Promise
<EstimateFeeResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:3470
Estimates the fee for a given INVOKE transaction
Parameters
invocation
Invocation
the invocation object containing:
- contractAddress - the address of the contract
- entrypoint - the entrypoint of the contract
- calldata - (defaults to []) the calldata
- signature - (defaults to []) the signature
invocationDetails
InvocationsDetailsWithNonce
blockIdentifier?
BlockIdentifier
(optional) block identifier
skipValidate?
boolean
(optional) skip cairo validate method
Returns
Promise
<EstimateFeeResponse
>
the estimated fee
Inherited from
Account.getInvokeEstimateFee
getL1GasPrice()
getL1GasPrice(
blockIdentifier
?):Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:3420
Gets the price of l1 gas in the block
Parameters
blockIdentifier?
BlockIdentifier
block identifier
Returns
Promise
<string
>
gas price of the block
Inherited from
Account.getL1GasPrice
getL1MessageHash()
getL1MessageHash(
l2TxHash
):Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:3421
Get L1 message hash from L2 transaction hash
Parameters
l2TxHash
BigNumberish
L2 transaction hash
Returns
Promise
<string
>
Hex string of L1 message hash
Example
In Sepolia Testnet :
const result = provider.getL1MessageHash('0x28dfc05eb4f261b37ddad451ff22f1d08d4e3c24dc646af0ec69fa20e096819');
// result = '0x55b3f8b6e607fffd9b4d843dfe8f9b5c05822cd94fcad8797deb01d77805532a'
Inherited from
Account.getL1MessageHash
getNonce()
getNonce(
blockIdentifier
?):Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:4421
Gets the nonce of the account with respect to a specific block
Parameters
blockIdentifier?
BlockIdentifier
optional blockIdentifier. Defaults to 'pending'
Returns
Promise
<string
>
nonce of the account
Inherited from
Account.getNonce
getNonceForAddress()
getNonceForAddress(
contractAddress
,blockIdentifier
?):Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:3391
Returns the nonce associated with the given address in the given block
Parameters
contractAddress
BigNumberish
contract address
blockIdentifier?
BlockIdentifier
Returns
Promise
<string
>
the hex nonce
Inherited from
Account.getNonceForAddress
getNonceSafe()
protected
getNonceSafe(nonce
?):Promise
<bigint
>
Defined in: node_modules/starknet/dist/index.d.ts:4422
Parameters
nonce?
BigNumberish
Returns
Promise
<bigint
>
Inherited from
Account.getNonceSafe
getOutsideTransaction()
getOutsideTransaction(
options
,calls
,version
?,nonce
?):Promise
<OutsideTransaction
>
Defined in: node_modules/starknet/dist/index.d.ts:4528
Creates an object containing transaction(s) that can be executed by an other account with Account.executeFromOutside()
, called Outside Transaction.
Parameters
options
OutsideExecutionOptions
Parameters of the transaction(s).
calls
AllowArray
<Call
>
Transaction(s) to execute.
version?
OutsideExecutionVersion
SNIP-9 version of the Account that creates the outside transaction.
nonce?
BigNumberish
Outside Nonce.
Returns
Promise
<OutsideTransaction
>
and object that can be used in Account.executeFromOutside()
Example
const now_seconds = Math.floor(Date.now() / 1000);
const callOptions: OutsideExecutionOptions = {
caller: executorAccount.address, execute_after: now_seconds - 3600, execute_before: now_seconds + 3600 };
const call1: Call = { contractAddress: ethAddress, entrypoint: 'transfer', calldata: {
recipient: recipientAccount.address, amount: cairo.uint256(100) } };
const outsideTransaction1: OutsideTransaction = await signerAccount.getOutsideTransaction(callOptions, call3);
// result = {
// outsideExecution: {
// caller: '0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691',
// nonce: '0x28a612590dbc36927933c8ee0f357eee639c8b22b3d3aa86949eed3ada4ac55',
// execute_after: 1723650229, execute_before: 1723704229, calls: [[Object]] },
// signature: Signature {
// r: 67518627037915514985321278857825384106482999609634873287406612756843916814n,
// s: 737198738569840639192844101690009498983611654458636624293579534560862067709n, recovery: 0 },
// signerAddress: '0x655f8fd7c4013c07cf12a92184aa6c314d181443913e21f7e209a18f0c78492',
// version: '2'
// }
Inherited from
Account.getOutsideTransaction
getPendingTransactions()
getPendingTransactions():
Promise
<TransactionWithHash$1
[]>
Defined in: node_modules/starknet/dist/index.d.ts:3440
Return transactions from pending block
Returns
Promise
<TransactionWithHash$1
[]>
Deprecated
Instead use getBlock(BlockTag.PENDING); (will be removed in next minor version) Utility method, same result can be achieved using getBlockWithTxHashes(BlockTag.pending);
Inherited from
Account.getPendingTransactions
getPreferredVersion()
protected
getPreferredVersion(type12
,type3
):ETransactionVersion
Defined in: node_modules/starknet/dist/index.d.ts:4420
Parameters
type12
ETransactionVersion
type3
ETransactionVersion
Returns
ETransactionVersion
Inherited from
Account.getPreferredVersion
getSimulateTransaction()
getSimulateTransaction(
invocations
,options
?):Promise
<SimulateTransactionResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:3457
Parameters
invocations
AccountInvocations
AccountInvocations
options?
getSimulateTransactionOptions
blockIdentifier and flags to skip validation and fee charge
- blockIdentifier
- skipValidate (default false)
- skipFeeCharge (default true)
Returns
Promise
<SimulateTransactionResponse
>
Inherited from
Account.getSimulateTransaction
getSnip9Nonce()
getSnip9Nonce():
Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:4499
Outside transaction needs a specific SNIP-9 nonce, that we get in this function. A SNIP-9 nonce can be any number not yet used ; no ordering is needed.
Returns
Promise
<string
>
an Hex string of a SNIP-9 nonce.
Example
const result = myAccount.getSnip9Nonce();
// result = "0x28a612590dbc36927933c8ee0f357eee639c8b22b3d3aa86949eed3ada4ac55"
Inherited from
Account.getSnip9Nonce
getSnip9Version()
getSnip9Version():
Promise
<OutsideExecutionVersion
>
Defined in: node_modules/starknet/dist/index.d.ts:4477
Verify if an account is compatible with SNIP-9 outside execution, and with which version of this standard.
Returns
Promise
<OutsideExecutionVersion
>
Not compatible, V1, V2.
Example
const result = myAccount.getSnip9Version();
// result = "V1"
Inherited from
Account.getSnip9Version
getSpecVersion()
getSpecVersion():
Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:3390
Returns
Promise
<string
>
Inherited from
Account.getSpecVersion
getStarkName()
getStarkName(
address
?,StarknetIdContract
?):Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:4554
Parameters
address?
BigNumberish
StarknetIdContract?
string
Returns
Promise
<string
>
Inherited from
Account.getStarkName
getStarkProfile()
getStarkProfile(
address
,StarknetIdContract
?,StarknetIdIdentityContract
?,StarknetIdVerifierContract
?,StarknetIdPfpContract
?,StarknetIdPopContract
?,StarknetIdMulticallContract
?):Promise
<StarkProfile
>
Defined in: node_modules/starknet/dist/index.d.ts:3535
Parameters
address
BigNumberish
StarknetIdContract?
string
StarknetIdIdentityContract?
string
StarknetIdVerifierContract?
string
StarknetIdPfpContract?
string
StarknetIdPopContract?
string
StarknetIdMulticallContract?
string
Returns
Promise
<StarkProfile
>
Inherited from
Account.getStarkProfile
getStorageAt()
getStorageAt(
contractAddress
,key
,blockIdentifier
?):Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:3459
Get the value of the storage (contract's variable) at the given address and key
Parameters
contractAddress
BigNumberish
key
BigNumberish
from getStorageVarAddress('<STORAGE_VARIABLE_NAME>') (WIP)
blockIdentifier?
BlockIdentifier
block identifier
Returns
Promise
<string
>
the value of the storage variable
Inherited from
Account.getStorageAt
getSuggestedFee()
getSuggestedFee(
estimateFeeAction
,details
):Promise
<EstimateFee
>
Defined in: node_modules/starknet/dist/index.d.ts:4548
Gets Suggested Max Fee based on the transaction type
Parameters
estimateFeeAction
EstimateFeeAction
details
UniversalDetails
Returns
Promise
<EstimateFee
>
EstimateFee (...response, resourceBounds, suggestedMaxFee)
Inherited from
Account.getSuggestedFee
getSyncingStats()
getSyncingStats():
Promise
<Syncing
>
Defined in: node_modules/starknet/dist/index.d.ts:3487
Returns an object about the sync status, or false if the node is not synching
Returns
Promise
<Syncing
>
Object with the stats data
Inherited from
Account.getSyncingStats
getTransaction()
getTransaction(
txHash
):Promise
<TransactionWithHash$1
>
Defined in: node_modules/starknet/dist/index.d.ts:3441
Gets the transaction information from a tx id.
Parameters
txHash
BigNumberish
Returns
Promise
<TransactionWithHash$1
>
the transaction object { transaction_id, status, transaction, block_number?, block_number?, transaction_index?, transaction_failure_reason? }
Inherited from
Account.getTransaction
getTransactionByBlockIdAndIndex()
getTransactionByBlockIdAndIndex(
blockIdentifier
,index
):Promise
<TransactionWithHash$1
>
Defined in: node_modules/starknet/dist/index.d.ts:3443
Parameters
blockIdentifier
BlockIdentifier
index
number
Returns
Promise
<TransactionWithHash$1
>
Inherited from
Account.getTransactionByBlockIdAndIndex
getTransactionByHash()
getTransactionByHash(
txHash
):Promise
<TransactionWithHash$1
>
Defined in: node_modules/starknet/dist/index.d.ts:3442
Parameters
txHash
BigNumberish
Returns
Promise
<TransactionWithHash$1
>
Inherited from
Account.getTransactionByHash
getTransactionReceipt()
getTransactionReceipt(
txHash
):Promise
<GetTransactionReceiptResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:3444
Gets the transaction receipt from a tx hash.
Parameters
txHash
BigNumberish
Returns
Promise
<GetTransactionReceiptResponse
>
the transaction receipt object
Inherited from
Account.getTransactionReceipt
getTransactionStatus()
getTransactionStatus(
transactionHash
):Promise
<TransactionStatus$1
>
Defined in: node_modules/starknet/dist/index.d.ts:3449
Get the status of a transaction
Parameters
transactionHash
BigNumberish
Returns
Promise
<TransactionStatus$1
>
Inherited from
Account.getTransactionStatus
getTransactionTrace()
getTransactionTrace(
txHash
):Promise
<TRANSACTION_TRACE
>
Defined in: node_modules/starknet/dist/index.d.ts:3445
Parameters
txHash
BigNumberish
Returns
Promise
<TRANSACTION_TRACE
>
Inherited from
Account.getTransactionTrace
getUniversalSuggestedFee()
protected
getUniversalSuggestedFee(version
,__namedParameters
,details
):Promise
<UniversalSuggestedFee
>
Defined in: node_modules/starknet/dist/index.d.ts:4547
Parameters
version
ETransactionVersion
__namedParameters
EstimateFeeAction
details
UniversalDetails
Returns
Promise
<UniversalSuggestedFee
>
Inherited from
Account.getUniversalSuggestedFee
hashMessage()
hashMessage(
typedData
):Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:4451
Hash a TypedData object with Pedersen hash and return the hash This adds a message prefix so it can't be interchanged with transactions
Parameters
typedData
TypedData
TypedData object to be hashed
Returns
Promise
<string
>
the hash of the TypedData object
Throws
if typedData is not a valid TypedData
Inherited from
Account.hashMessage
invokeFunction()
invokeFunction(
functionInvocation
,details
):Promise
<InvokedTransaction
>
Defined in: node_modules/starknet/dist/index.d.ts:3474
Invokes a function on starknet
Parameters
functionInvocation
Invocation
details
InvocationsDetailsWithNonce
optional details containing:
- nonce - optional nonce
- version - optional version
- maxFee - optional maxFee
Returns
Promise
<InvokedTransaction
>
response from addTransaction
Deprecated
This method won't be supported as soon as fees are mandatory. Should not be used outside of Account class
Inherited from
Account.invokeFunction
isClassDeclared()
isClassDeclared(
contractClassIdentifier
,blockIdentifier
?):Promise
<boolean
>
Defined in: node_modules/starknet/dist/index.d.ts:3522
Test if class is already declared from ContractClassIdentifier Helper method using getClass
Parameters
contractClassIdentifier
ContractClassIdentifier
blockIdentifier?
BlockIdentifier
Returns
Promise
<boolean
>
Inherited from
Account.isClassDeclared
isModule()
isModule(
class_hash
):Promise
<boolean
>
Defined in: sdks/account/src/smartr_account.ts:413
Call an entrypoint on the module account.
Parameters
class_hash
string
the module to test
Returns
Promise
<boolean
>
A promise that is true if the module is installed with the account.
isValidSnip9Nonce()
isValidSnip9Nonce(
nonce
):Promise
<boolean
>
Defined in: node_modules/starknet/dist/index.d.ts:4488
Verify if a SNIP-9 nonce has not yet been used by the account.
Parameters
nonce
BigNumberish
SNIP-9 nonce to test.
Returns
Promise
<boolean
>
true if SNIP-9 nonce not yet used.
Example
const result = myAccount.isValidSnip9Nonce(1234);
// result = true
Inherited from
Account.isValidSnip9Nonce
prepareInvocations()
prepareInvocations(
invocations
):Promise
<Invocations
>
Defined in: node_modules/starknet/dist/index.d.ts:3529
Build bulk invocations with auto-detect declared class
- Test if class is declared if not declare it preventing already declared class error and not declared class errors
- Order declarations first
Parameters
invocations
Invocations
Returns
Promise
<Invocations
>
Inherited from
Account.prepareInvocations
prepareMultisig()
prepareMultisig(
transactions
,transactionsDetail
?):Promise
<InvocationsDetailsWithNonce
>
Defined in: sdks/account/src/smartr_account.ts:105
generates the details, i.e nonce, version, fee, module prefix, to execute transactions on the StarkNet network.
Parameters
transactions
AllowArray
<Call
>
An array of transactions to be executed.
transactionsDetail?
UniversalDetails
Optional object containing additional details for the transactions.
Returns
Promise
<InvocationsDetailsWithNonce
>
A Promise that resolves to the transaction details.
removeModule()
Call Signature
removeModule(
class_hash
,execute
?):Promise
<{transaction_hash
:string
; }>
Defined in: sdks/account/src/smartr_account.ts:454
remove a module from an account.
Parameters
class_hash
string
the module to remove
execute?
true
If true, the transaction is executed, otherwise it is built and returned so that it can be used in a multicall.
Returns
Promise
<{ transaction_hash
: string
; }>
A promise that resolves to the transaction receipt if executes is true, otherwise it returns the transaction call.
Call Signature
removeModule(
class_hash
,execute
):Promise
<Call
[]>
Defined in: sdks/account/src/smartr_account.ts:458
remove a module from an account.
Parameters
class_hash
string
the module to remove
execute
false
If true, the transaction is executed, otherwise it is built and returned so that it can be used in a multicall.
Returns
Promise
<Call
[]>
A promise that resolves to the transaction receipt if executes is true, otherwise it returns the transaction call.
signMessage()
signMessage(
typedData
):Promise
<Signature
>
Defined in: node_modules/starknet/dist/index.d.ts:4450
Signs a TypedData object for off-chain usage with the Starknet private key and returns the signature This adds a message prefix so it can't be interchanged with transactions
Parameters
typedData
TypedData
TypedData object to be signed
Returns
Promise
<Signature
>
the signature of the TypedData object
Throws
if typedData is not a valid TypedData
Inherited from
Account.signMessage
signMultisig()
signMultisig(
transactions
,details
):Promise
<ArraySignatureType
>
Defined in: sdks/account/src/smartr_account.ts:151
Signs a set of transactions to be executed on the StarkNet network.
Parameters
transactions
Call
[]
An array of transactions to be executed.
details
InvocationsDetailsWithNonce
Optional object containing additional details for the transactions.
Returns
Promise
<ArraySignatureType
>
A Promise that resolves to the signature of the transactions.
simulateTransaction()
simulateTransaction(
invocations
,details
?):Promise
<SimulateTransactionResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:4434
Simulates an array of transaction and returns an array of transaction trace and estimated fee.
Parameters
invocations
Invocations
Invocations containing:
- type - transaction type: DECLARE, (multi)DEPLOY, DEPLOY_ACCOUNT, (multi)INVOKE_FUNCTION
details?
SimulateTransactionDetails
SimulateTransactionDetails
Returns
Promise
<SimulateTransactionResponse
>
response from simulate_transaction
Inherited from
Account.simulateTransaction
upgrade()
upgrade(
classHash
):Promise
<{transaction_hash
:string
; }>
Defined in: sdks/account/src/smartr_account.ts:398
Upgrades the SmartrAccount to a new class.
Parameters
classHash
string
The hash of the new class.
Returns
Promise
<{ transaction_hash
: string
; }>
A promise that resolves to the transaction receipt if executes is true, otherwise it returns the transaction call.
verifyMessage()
verifyMessage(
typedData
,signature
,signatureVerificationFunctionName
?,signatureVerificationResponse
?):Promise
<boolean
>
Defined in: node_modules/starknet/dist/index.d.ts:4463
Parameters
typedData
TypedData
signature
Signature
signatureVerificationFunctionName?
string
signatureVerificationResponse?
error
string
[]
nokResponse
string
[]
okResponse
string
[]
Returns
Promise
<boolean
>
Deprecated
To replace by myRpcProvider.verifyMessageInStarknet()
Inherited from
Account.verifyMessage
verifyMessageHash()
verifyMessageHash(
hash
,signature
,signatureVerificationFunctionName
?,signatureVerificationResponse
?):Promise
<boolean
>
Defined in: node_modules/starknet/dist/index.d.ts:4455
Parameters
hash
BigNumberish
signature
Signature
signatureVerificationFunctionName?
string
signatureVerificationResponse?
error
string
[]
nokResponse
string
[]
okResponse
string
[]
Returns
Promise
<boolean
>
Deprecated
To replace by myRpcProvider.verifyMessageInStarknet()
Inherited from
Account.verifyMessageHash
verifyMessageInStarknet()
verifyMessageInStarknet(
message
,signature
,accountAddress
,signatureVerificationFunctionName
?,signatureVerificationResponse
?):Promise
<boolean
>
Defined in: node_modules/starknet/dist/index.d.ts:3511
Verify in Starknet a signature of a TypedData object or of a given hash.
Parameters
message
TypedData object to be verified, or message hash to be verified.
BigNumberish
| TypedData
signature
Signature
signature of the message.
accountAddress
BigNumberish
address of the account that has signed the message.
signatureVerificationFunctionName?
string
if account contract with non standard account verification function name.
signatureVerificationResponse?
if account contract with non standard response of verification function.
error
string
[]
nokResponse
string
[]
okResponse
string
[]
Returns
Promise
<boolean
>
const myTypedMessage: TypedMessage = .... ;
const messageHash = typedData.getMessageHash(myTypedMessage,accountAddress);
const sign: WeierstrassSignatureType = ec.starkCurve.sign(messageHash, privateKey);
const accountAddress = "0x43b7240d227aa2fb8434350b3321c40ac1b88c7067982549e7609870621b535";
const result1 = myRpcProvider.verifyMessageInStarknet(myTypedMessage, sign, accountAddress);
const result2 = myRpcProvider.verifyMessageInStarknet(messageHash, sign, accountAddress);
// result1 = result2 = true
Inherited from
Account.verifyMessageInStarknet
waitForBlock()
waitForBlock(
blockIdentifier
?,retryInterval
?):Promise
<void
>
Defined in: node_modules/starknet/dist/index.d.ts:3419
Pause the execution of the script until a specified block is created.
Parameters
blockIdentifier?
BlockIdentifier
bloc number (BigNumberish) or 'pending' or 'latest'. Use of 'latest" or of a block already created will generate no pause.
retryInterval?
number
number of milliseconds between 2 requests to the node
Returns
Promise
<void
>
Example
await myProvider.waitForBlock();
// wait the creation of the pending block
Inherited from
Account.waitForBlock
waitForTransaction()
waitForTransaction(
txHash
,options
?):Promise
<GetTransactionReceiptResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:3458
Wait for the transaction to be accepted
Parameters
txHash
BigNumberish
transaction hash
options?
waitForTransactionOptions
waitForTransactionOptions
- (optional) retryInterval: number | undefined;
- (optional) successStates: TransactionStatus[] | undefined;
Returns
Promise
<GetTransactionReceiptResponse
>
GetTransactionReceiptResponse
Inherited from
Account.waitForTransaction
getAddressFromStarkName()
static
getAddressFromStarkName(provider
,name
,StarknetIdContract
?):Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:3537
Parameters
provider
ProviderInterface
name
string
StarknetIdContract?
string
Returns
Promise
<string
>
Inherited from
Account.getAddressFromStarkName
getStarkName()
static
getStarkName(provider
,address
,StarknetIdContract
?):Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:3536
Parameters
provider
ProviderInterface
address
BigNumberish
StarknetIdContract?
string
Returns
Promise
<string
>
Inherited from
Account.getStarkName
getStarkProfile()
static
getStarkProfile(provider
,address
,StarknetIdContract
?,StarknetIdIdentityContract
?,StarknetIdVerifierContract
?,StarknetIdPfpContract
?,StarknetIdPopContract
?,StarknetIdMulticallContract
?):Promise
<StarkProfile
>
Defined in: node_modules/starknet/dist/index.d.ts:3538
Parameters
provider
ProviderInterface
address
BigNumberish
StarknetIdContract?
string
StarknetIdIdentityContract?
string
StarknetIdVerifierContract?
string
StarknetIdPfpContract?
string
StarknetIdPopContract?
string
StarknetIdMulticallContract?
string
Returns
Promise
<StarkProfile
>
Inherited from
Account.getStarkProfile
@0xknwn/starknet-modular-account
@0xknwn/starknet-modular-account / AccountModuleInterface
Interface: AccountModuleInterface
Defined in: sdks/account/src/smartr_account.ts:40
Methods
prefix()
prefix(
calls
):Call
Defined in: sdks/account/src/smartr_account.ts:41
Parameters
calls
Call
| Call
[]
Returns
Call
@0xknwn/starknet-module
Classes
@0xknwn/starknet-module / EthModule
Class: EthModule
Defined in: sdks/module/src/eth.ts:6
Implements
AccountModuleInterface
Constructors
new EthModule()
new EthModule(
accountAddress
):EthModule
Defined in: sdks/module/src/eth.ts:8
Parameters
accountAddress
string
Returns
Properties
accountAddress
protected
accountAddress:string
Defined in: sdks/module/src/eth.ts:7
Methods
prefix()
prefix(
calls
):object
Defined in: sdks/module/src/eth.ts:12
Parameters
calls
Call
| Call
[]
Returns
object
calldata
calldata:
string
[]
contractAddress
contractAddress:
string
entrypoint
entrypoint:
string
="__module_validate__"
Implementation of
AccountModuleInterface.prefix
@0xknwn/starknet-module / MultisigModule
Class: MultisigModule
Defined in: sdks/module/src/multisig.ts:6
Implements
AccountModuleInterface
Constructors
new MultisigModule()
new MultisigModule(
accountAddress
):MultisigModule
Defined in: sdks/module/src/multisig.ts:8
Parameters
accountAddress
string
Returns
Properties
accountAddress
protected
accountAddress:string
Defined in: sdks/module/src/multisig.ts:7
Methods
prefix()
prefix(
calls
):object
Defined in: sdks/module/src/multisig.ts:12
Parameters
calls
Call
| Call
[]
Returns
object
calldata
calldata:
string
[]
contractAddress
contractAddress:
string
entrypoint
entrypoint:
string
="__module_validate__"
Implementation of
AccountModuleInterface.prefix
@0xknwn/starknet-module / P256Module
Class: P256Module
Defined in: sdks/module/src/p256.ts:6
Implements
AccountModuleInterface
Constructors
new P256Module()
new P256Module(
accountAddress
):P256Module
Defined in: sdks/module/src/p256.ts:8
Parameters
accountAddress
string
Returns
Properties
accountAddress
protected
accountAddress:string
Defined in: sdks/module/src/p256.ts:7
Methods
prefix()
prefix(
calls
):object
Defined in: sdks/module/src/p256.ts:12
Parameters
calls
Call
| Call
[]
Returns
object
calldata
calldata:
string
[]
contractAddress
contractAddress:
string
entrypoint
entrypoint:
string
="__module_validate__"
Implementation of
AccountModuleInterface.prefix
@0xknwn/starknet-module / P256Signer
Class: P256Signer
Defined in: sdks/module/src/p256signer.ts:36
Signer for accounts using Ethereum signature
Implements
SignerInterface
Constructors
new P256Signer()
new P256Signer(
pk
):P256Signer
Defined in: sdks/module/src/p256signer.ts:39
Parameters
pk
string
| Uint8Array
Returns
Properties
pk
protected
pk:string
Defined in: sdks/module/src/p256signer.ts:37
Methods
formatP256Signature()
protected
formatP256Signature(p256Signature
):ArraySignatureType
Defined in: sdks/module/src/p256signer.ts:195
Serialize the signature in conformity with starknet::eth_signature::Signature
Parameters
p256Signature
RecoveredSignatureType
Returns
ArraySignatureType
an array of felts, representing a Cairo Eth Signature.
getPubKey()
getPubKey():
Promise
<string
>
Defined in: sdks/module/src/p256signer.ts:50
provides the Ethereum full public key (without parity prefix)
Returns
Promise
<string
>
an hex string : 64 first characters are Point X coordinate. 64 last characters are Point Y coordinate.
Implementation of
SignerInterface.getPubKey
signDeclareTransaction()
signDeclareTransaction(
details
):Promise
<Signature
>
Defined in: sdks/module/src/p256signer.ts:155
Signs a DECLARE transaction with the private key and returns the signature
Parameters
details
DeclareSignerDetails
Returns
Promise
<Signature
>
the signature of the transaction to declare a class
Example
const mySigner = new Signer("0x123");
const myDeclare: DeclareSignerDetails = {
version: "0x2", chainId: constants.StarknetChainId.SN_SEPOLIA,
senderAddress: "0x65a822fbee1ae79e898688b5a4282dc79e0042cbed12f6169937fddb4c26641",
classHash: "0x5f3614e8671257aff9ac38e929c74d65b02d460ae966cd826c9f04a7fa8e0d4",
nonce: 45, maxFee: 10 ** 15, tip: 0, paymasterData: [], accountDeploymentData: [],
nonceDataAvailabilityMode: RPC.EDataAvailabilityMode.L1,
feeDataAvailabilityMode: RPC.EDataAvailabilityMode.L1,
resourceBounds: stark.estimateFeeToBounds(constants.ZERO),
}
const result = await mySigner.signDeclareTransaction(myDeclare);
// result = Signature {r: 2432056944313955951711774394836075930010416436707488863728289188289211995670n,
// s: 3407649393310177489888603098175002856596469926897298636282244411990343146307n, recovery: 1}
Implementation of
SignerInterface.signDeclareTransaction
signDeployAccountTransaction()
signDeployAccountTransaction(
details
):Promise
<Signature
>
Defined in: sdks/module/src/p256signer.ts:114
Signs a DEPLOY_ACCOUNT transaction with the private key and returns the signature
Parameters
details
DeployAccountSignerDetails
Returns
Promise
<Signature
>
the signature of the transaction to deploy an account
Example
const mySigner = new Signer("0x123");
const myDeployAcc: DeployAccountSignerDetails = {
contractAddress: "0x65a822fbee1ae79e898688b5a4282dc79e0042cbed12f6169937fddb4c26641",
version: "0x2", chainId: constants.StarknetChainId.SN_SEPOLIA,
classHash: "0x5f3614e8671257aff9ac38e929c74d65b02d460ae966cd826c9f04a7fa8e0d4",
constructorCalldata: [1, 2],addressSalt: 1234,
nonce: 45, maxFee: 10 ** 15, tip: 0, paymasterData: [],accountDeploymentData: [],
nonceDataAvailabilityMode: RPC.EDataAvailabilityMode.L1,
feeDataAvailabilityMode: RPC.EDataAvailabilityMode.L1,
resourceBounds: stark.estimateFeeToBounds(constants.ZERO),
}
const result = await mySigner.signDeployAccountTransaction(myDeployAcc);
// result = Signature {r: 2871311234341436528393212130310036951068553852419934781736214693308640202748n,
// s: 1746271646048888422437132495446973163454853863041370993384284773665861377605n, recovery: 1}
Implementation of
SignerInterface.signDeployAccountTransaction
signMessage()
signMessage(
typedData
,accountAddress
):Promise
<Signature
>
Defined in: sdks/module/src/p256signer.ts:59
Signs a JSON object for off-chain usage with the private key and returns the signature. This adds a message prefix so it can't be interchanged with transactions
Parameters
typedData
TypedData
JSON object to be signed
accountAddress
string
Hex string of the account's address
Returns
Promise
<Signature
>
the signature of the message
Example
const mySigner = new Signer("0x123");
const myTypedData: TypedData = {
domain: {name: "Example DApp",
chainId: constants.StarknetChainId.SN_SEPOLIA,
version: "0.0.3"},
types: {StarkNetDomain: [
{ name: "name", type: "string" },
{ name: "chainId", type: "felt" },
{ name: "version", type: "string" }],
Message: [{ name: "message", type: "felt" }]},
primaryType: "Message", message: {message: "1234"}};
const result = await mySigner.signMessage(myTypedData,"0x5d08a4e9188429da4e993c9bf25aafe5cd491ee2b501505d4d059f0c938f82d");
// result = Signature {r: 684915484701699003335398790608214855489903651271362390249153620883122231253n,
// s: 1399150959912500412309102776989465580949387575375484933432871778355496929189n, recovery: 1}
Implementation of
SignerInterface.signMessage
signTransaction()
signTransaction(
transactions
,details
):Promise
<Signature
>
Defined in: sdks/module/src/p256signer.ts:71
Signs transactions with the private key and returns the signature
Parameters
transactions
Call
[]
array of Call objects
details
InvocationsSignerDetails
Returns
Promise
<Signature
>
the signature of the transaction
Example
const mySigner = new Signer("0x123");
const calls: Call[] = [{
contractAddress: "0x1234567890123456789012345678901234567890",
entrypoint: "functionName",
calldata: [1, 2, 3]
}];
const transactionsDetail: InvocationsSignerDetails = {
walletAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
chainId: constants.StarknetChainId.SN_MAIN,
cairoVersion: "1",
maxFee: '0x1234567890abcdef',
version: "0x0", nonce: 1};
const result = await mySigner.signTransaction(calls, transactionsDetail);
// result = Signature {r: 304910226421970384958146916800275294114105560641204815169249090836676768876n,
// s: 1072798866000813654190523783606274062837012608648308896325315895472901074693n, recovery: 0}
Implementation of
SignerInterface.signTransaction
@0xknwn/starknet-module / StarkModule
Class: StarkModule
Defined in: sdks/module/src/stark.ts:9
Implements
AccountModuleInterface
Constructors
new StarkModule()
new StarkModule(
accountAddress
):StarkModule
Defined in: sdks/module/src/stark.ts:11
Parameters
accountAddress
string
Returns
Properties
accountAddress
protected
accountAddress:string
Defined in: sdks/module/src/stark.ts:10
Methods
prefix()
prefix(
calls
):object
Defined in: sdks/module/src/stark.ts:15
Parameters
calls
Call
| Call
[]
Returns
object
calldata
calldata:
string
[]
contractAddress
contractAddress:
string
entrypoint
entrypoint:
string
="__module_validate__"
Implementation of
AccountModuleInterface.prefix
@0xknwn/starknet-module-sessionkey
@0xknwn/starknet-module-sessionkey
Classes
@0xknwn/starknet-module-sessionkey
@0xknwn/starknet-module-sessionkey / PolicyManager
Class: PolicyManager
Defined in: sdks/module-sessionkey/src/policies.ts:10
Constructors
new PolicyManager()
new PolicyManager(
policies
):PolicyManager
Defined in: sdks/module-sessionkey/src/policies.ts:14
Parameters
policies
Returns
Methods
getProof()
getProof(
policy
):string
[]
Defined in: sdks/module-sessionkey/src/policies.ts:29
Parameters
policy
Returns
string
[]
getRoot()
getRoot():
string
Defined in: sdks/module-sessionkey/src/policies.ts:25
Returns
string
@0xknwn/starknet-module-sessionkey
@0xknwn/starknet-module-sessionkey / SessionKeyGrantor
Class: SessionKeyGrantor
Defined in: sdks/module-sessionkey/src/sessionkey.ts:14
Extends
Signer
Constructors
new SessionKeyGrantor()
new SessionKeyGrantor(
validatorGrantorClass
,privateKey
):SessionKeyGrantor
Defined in: sdks/module-sessionkey/src/sessionkey.ts:16
Parameters
validatorGrantorClass
string
privateKey
string
| Uint8Array
Returns
Overrides
Signer.constructor
Properties
pk
protected
pk:string
|Uint8Array
Defined in: node_modules/starknet/dist/index.d.ts:3691
Inherited from
Signer.pk
validatorGrantorClass
validatorGrantorClass:
string
Defined in: sdks/module-sessionkey/src/sessionkey.ts:15
Methods
getPubKey()
getPubKey():
Promise
<string
>
Defined in: node_modules/starknet/dist/index.d.ts:3693
Method to get the public key of the signer
Returns
Promise
<string
>
hex-string
Example
const mySigner = new Signer("0x123");
const result = await mySigner.getPubKey();
// result = "0x566d69d8c99f62bc71118399bab25c1f03719463eab8d6a444cd11ece131616"
Inherited from
Signer.getPubKey
sign()
sign(
module
):Promise
<ArraySignatureType
>
Defined in: sdks/module-sessionkey/src/sessionkey.ts:21
Parameters
module
Returns
Promise
<ArraySignatureType
>
signDeclareTransaction()
signDeclareTransaction(
details
):Promise
<Signature
>
Defined in: node_modules/starknet/dist/index.d.ts:3697
Signs a DECLARE transaction with the private key and returns the signature
Parameters
details
DeclareSignerDetails
Returns
Promise
<Signature
>
the signature of the transaction to declare a class
Example
const mySigner = new Signer("0x123");
const myDeclare: DeclareSignerDetails = {
version: "0x2", chainId: constants.StarknetChainId.SN_SEPOLIA,
senderAddress: "0x65a822fbee1ae79e898688b5a4282dc79e0042cbed12f6169937fddb4c26641",
classHash: "0x5f3614e8671257aff9ac38e929c74d65b02d460ae966cd826c9f04a7fa8e0d4",
nonce: 45, maxFee: 10 ** 15, tip: 0, paymasterData: [], accountDeploymentData: [],
nonceDataAvailabilityMode: RPC.EDataAvailabilityMode.L1,
feeDataAvailabilityMode: RPC.EDataAvailabilityMode.L1,
resourceBounds: stark.estimateFeeToBounds(constants.ZERO),
}
const result = await mySigner.signDeclareTransaction(myDeclare);
// result = Signature {r: 2432056944313955951711774394836075930010416436707488863728289188289211995670n,
// s: 3407649393310177489888603098175002856596469926897298636282244411990343146307n, recovery: 1}
Inherited from
Signer.signDeclareTransaction
signDeployAccountTransaction()
signDeployAccountTransaction(
details
):Promise
<Signature
>
Defined in: node_modules/starknet/dist/index.d.ts:3696
Signs a DEPLOY_ACCOUNT transaction with the private key and returns the signature
Parameters
details
DeployAccountSignerDetails
Returns
Promise
<Signature
>
the signature of the transaction to deploy an account
Example
const mySigner = new Signer("0x123");
const myDeployAcc: DeployAccountSignerDetails = {
contractAddress: "0x65a822fbee1ae79e898688b5a4282dc79e0042cbed12f6169937fddb4c26641",
version: "0x2", chainId: constants.StarknetChainId.SN_SEPOLIA,
classHash: "0x5f3614e8671257aff9ac38e929c74d65b02d460ae966cd826c9f04a7fa8e0d4",
constructorCalldata: [1, 2],addressSalt: 1234,
nonce: 45, maxFee: 10 ** 15, tip: 0, paymasterData: [],accountDeploymentData: [],
nonceDataAvailabilityMode: RPC.EDataAvailabilityMode.L1,
feeDataAvailabilityMode: RPC.EDataAvailabilityMode.L1,
resourceBounds: stark.estimateFeeToBounds(constants.ZERO),
}
const result = await mySigner.signDeployAccountTransaction(myDeployAcc);
// result = Signature {r: 2871311234341436528393212130310036951068553852419934781736214693308640202748n,
// s: 1746271646048888422437132495446973163454853863041370993384284773665861377605n, recovery: 1}
Inherited from
Signer.signDeployAccountTransaction
signMessage()
signMessage(
typedData
,accountAddress
):Promise
<Signature
>
Defined in: node_modules/starknet/dist/index.d.ts:3694
Signs a JSON object for off-chain usage with the private key and returns the signature. This adds a message prefix so it can't be interchanged with transactions
Parameters
typedData
TypedData
JSON object to be signed
accountAddress
string
Hex string of the account's address
Returns
Promise
<Signature
>
the signature of the message
Example
const mySigner = new Signer("0x123");
const myTypedData: TypedData = {
domain: {name: "Example DApp",
chainId: constants.StarknetChainId.SN_SEPOLIA,
version: "0.0.3"},
types: {StarkNetDomain: [
{ name: "name", type: "string" },
{ name: "chainId", type: "felt" },
{ name: "version", type: "string" }],
Message: [{ name: "message", type: "felt" }]},
primaryType: "Message", message: {message: "1234"}};
const result = await mySigner.signMessage(myTypedData,"0x5d08a4e9188429da4e993c9bf25aafe5cd491ee2b501505d4d059f0c938f82d");
// result = Signature {r: 684915484701699003335398790608214855489903651271362390249153620883122231253n,
// s: 1399150959912500412309102776989465580949387575375484933432871778355496929189n, recovery: 1}
Inherited from
Signer.signMessage
signRaw()
protected
signRaw(msgHash
):Promise
<Signature
>
Defined in: node_modules/starknet/dist/index.d.ts:3698
Parameters
msgHash
string
Returns
Promise
<Signature
>
Inherited from
Signer.signRaw
signTransaction()
signTransaction(
transactions
,details
):Promise
<Signature
>
Defined in: node_modules/starknet/dist/index.d.ts:3695
Signs transactions with the private key and returns the signature
Parameters
transactions
Call
[]
array of Call objects
details
InvocationsSignerDetails
Returns
Promise
<Signature
>
the signature of the transaction
Example
const mySigner = new Signer("0x123");
const calls: Call[] = [{
contractAddress: "0x1234567890123456789012345678901234567890",
entrypoint: "functionName",
calldata: [1, 2, 3]
}];
const transactionsDetail: InvocationsSignerDetails = {
walletAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
chainId: constants.StarknetChainId.SN_MAIN,
cairoVersion: "1",
maxFee: '0x1234567890abcdef',
version: "0x0", nonce: 1};
const result = await mySigner.signTransaction(calls, transactionsDetail);
// result = Signature {r: 304910226421970384958146916800275294114105560641204815169249090836676768876n,
// s: 1072798866000813654190523783606274062837012608648308896325315895472901074693n, recovery: 0}
Inherited from
Signer.signTransaction
@0xknwn/starknet-module-sessionkey
@0xknwn/starknet-module-sessionkey / SessionKeyModule
Class: SessionKeyModule
Defined in: sdks/module-sessionkey/src/sessionkey.ts:33
Implements
AccountModuleInterface
Constructors
new SessionKeyModule()
new SessionKeyModule(
authKey
,accountAddress
,validatorClassHash
,chainId
,expires
,policyManager
?):SessionKeyModule
Defined in: sdks/module-sessionkey/src/sessionkey.ts:37
Parameters
authKey
string
accountAddress
string
validatorClassHash
string
chainId
string
expires
string
policyManager?
Returns
Properties
auth
protected
auth:Authorization
Defined in: sdks/module-sessionkey/src/sessionkey.ts:34
policyManager?
protected
optional
policyManager:PolicyManager
Defined in: sdks/module-sessionkey/src/sessionkey.ts:35
Methods
add_signature()
add_signature(
signature
):Promise
<void
>
Defined in: sdks/module-sessionkey/src/sessionkey.ts:98
Parameters
signature
string
[]
Returns
Promise
<void
>
get_session_key()
get_session_key():
Promise
<string
>
Defined in: sdks/module-sessionkey/src/sessionkey.ts:83
Returns
Promise
<string
>
prefix()
prefix(
calls
):object
Defined in: sdks/module-sessionkey/src/sessionkey.ts:112
Parameters
calls
Call
| Call
[]
Returns
object
calldata
calldata:
string
[]
contractAddress
contractAddress:
string
entrypoint
entrypoint:
string
="__module_validate__"
Implementation of
AccountModuleInterface.prefix
request()
request(
grantorClass
):Promise
<Authorization
>
Defined in: sdks/module-sessionkey/src/sessionkey.ts:62
Parameters
grantorClass
string
Returns
Promise
<Authorization
>
reset()
reset(
signature
):Promise
<void
>
Defined in: sdks/module-sessionkey/src/sessionkey.ts:105
Parameters
signature
string
[]
Returns
Promise
<void
>
@0xknwn/starknet-test-helpers
Classes
@0xknwn/starknet-test-helpers / Counter
Class: Counter
Defined in: sdks/__tests__/helpers/src/counter.ts:52
Represents a Counter contract.
Extends
Contract
Indexable
[key
: string
]: any
Constructors
new Counter()
new Counter(
address
,account
):Counter
Defined in: sdks/__tests__/helpers/src/counter.ts:58
Creates an instance of the Counter contract.
Parameters
address
string
The address of the contract.
account
Account
The account used to interact with the contract.
Returns
Overrides
Contract.constructor
Properties
abi
abi:
Abi
Defined in: node_modules/starknet/dist/index.d.ts:4722
Inherited from
Contract.abi
address
address:
string
Defined in: node_modules/starknet/dist/index.d.ts:4723
Inherited from
Contract.address
callStatic
readonly
callStatic:object
Defined in: node_modules/starknet/dist/index.d.ts:4733
Index Signature
[name
: string
]: AsyncContractFunction
<any
>
Inherited from
Contract.callStatic
deployTransactionHash?
optional
deployTransactionHash:string
Defined in: node_modules/starknet/dist/index.d.ts:4725
Inherited from
Contract.deployTransactionHash
estimateFee
readonly
estimateFee:object
Defined in: node_modules/starknet/dist/index.d.ts:4739
Index Signature
[name
: string
]: ContractFunction
Inherited from
Contract.estimateFee
events
protected
readonly
events:AbiEvents
Defined in: node_modules/starknet/dist/index.d.ts:4729
Inherited from
Contract.events
functions
readonly
functions:object
Defined in: node_modules/starknet/dist/index.d.ts:4730
Index Signature
[name
: string
]: AsyncContractFunction
<any
>
Inherited from
Contract.functions
populateTransaction
readonly
populateTransaction:object
Defined in: node_modules/starknet/dist/index.d.ts:4736
Index Signature
[name
: string
]: ContractFunction
Inherited from
Contract.populateTransaction
providerOrAccount
providerOrAccount:
ProviderInterface
|AccountInterface
Defined in: node_modules/starknet/dist/index.d.ts:4724
Inherited from
Contract.providerOrAccount
structs
protected
readonly
structs:object
Defined in: node_modules/starknet/dist/index.d.ts:4726
Index Signature
[name
: string
]: AbiStruct
Inherited from
Contract.structs
Methods
attach()
attach(
address
):void
Defined in: node_modules/starknet/dist/index.d.ts:4752
Saves the address of the contract deployed on network that will be used for interaction
Parameters
address
string
address of the contract
Returns
void
Inherited from
Contract.attach
call()
call(
method
,args
?,options
?):Promise
<Result
>
Defined in: node_modules/starknet/dist/index.d.ts:4755
Calls a method on a contract
Parameters
method
string
name of the method
args?
ArgsOrCalldata
Array of the arguments for the call
options?
CallOptions
optional blockIdentifier
Returns
Promise
<Result
>
Result of the call as an array with key value pars
Inherited from
Contract.call
connect()
connect(
providerOrAccount
):void
Defined in: node_modules/starknet/dist/index.d.ts:4753
Attaches to new Provider or Account
Parameters
providerOrAccount
new Provider or Account to attach to
ProviderInterface
| AccountInterface
Returns
void
Inherited from
Contract.connect
deployed()
deployed():
Promise
<Contract
>
Defined in: node_modules/starknet/dist/index.d.ts:4754
Resolves when contract is deployed on the network or when no deployment transaction is found
Returns
Promise
<Contract
>
Promise that resolves when contract is deployed on the network or when no deployment transaction is found
Throws
When deployment fails
Inherited from
Contract.deployed
estimate()
estimate(
method
,args
?):Promise
<EstimateFeeResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:4757
Estimates a method on a contract
Parameters
method
string
name of the method
args?
ArgsOrCalldata
Array of the arguments for the call or Calldata
Returns
Promise
<EstimateFeeResponse
>
Inherited from
Contract.estimate
getVersion()
getVersion():
Promise
<ContractVersion
>
Defined in: node_modules/starknet/dist/index.d.ts:4761
Retrieves the version of the contract (cairo version & compiler version)
Returns
Promise
<ContractVersion
>
Inherited from
Contract.getVersion
increment()
increment():
Promise
<{transaction_hash
:string
; }>
Defined in: sdks/__tests__/helpers/src/counter.ts:66
Increments the counter by 1.
Returns
Promise
<{ transaction_hash
: string
; }>
A promise that resolves to the result of the execution.
increment_by()
increment_by(
value
):Promise
<{transaction_hash
:string
; }>
Defined in: sdks/__tests__/helpers/src/counter.ts:77
Increments the counter by the specified value.
Parameters
value
number
The value to increment the counter by.
Returns
Promise
<{ transaction_hash
: string
; }>
A promise that resolves to the result of the execution.
increment_by_array()
increment_by_array(
args
):Promise
<{transaction_hash
:string
; }>
Defined in: sdks/__tests__/helpers/src/counter.ts:106
Increments the counter by an array of numbers using increment_by_array.
Parameters
args
number
[]
The array of values to increment the counter by.
Returns
Promise
<{ transaction_hash
: string
; }>
A promise that resolves to the result of the execution.
increment_by_multicall()
increment_by_multicall(
values
):Promise
<{transaction_hash
:string
; }>
Defined in: sdks/__tests__/helpers/src/counter.ts:88
Increments the counter by an array of numbers using a multicall of increment_by.
Parameters
values
number
[]
The array of values to increment the counter by.
Returns
Promise
<{ transaction_hash
: string
; }>
A promise that resolves to the result of the execution.
invoke()
invoke(
method
,args
?,options
?):Promise
<{transaction_hash
:string
; }>
Defined in: node_modules/starknet/dist/index.d.ts:4756
Invokes a method on a contract
Parameters
method
string
name of the method
args?
ArgsOrCalldata
Array of the arguments for the invoke or Calldata
options?
InvokeOptions
Returns
Promise
<{ transaction_hash
: string
; }>
Add Transaction Response
Inherited from
Contract.invoke
isCairo1()
isCairo1():
boolean
Defined in: node_modules/starknet/dist/index.d.ts:4760
tells if the contract comes from a Cairo 1 contract
Returns
boolean
TRUE if the contract comes from a Cairo1 contract
Example
const isCairo1: boolean = myContract.isCairo1();
Inherited from
Contract.isCairo1
parseEvents()
parseEvents(
receipt
):ParsedEvents
Defined in: node_modules/starknet/dist/index.d.ts:4759
Parse contract events of a GetTransactionReceiptResponse received from waitForTransaction. Based on contract's abi
Parameters
receipt
GetTransactionReceiptResponse
transaction receipt
Returns
ParsedEvents
Events parsed
Inherited from
Contract.parseEvents
populate()
populate(
method
,args
?):Call
Defined in: node_modules/starknet/dist/index.d.ts:4758
Calls a method on a contract
Parameters
method
string
name of the method
args?
RawArgs
Array of the arguments for the call or Calldata
Returns
Call
Invocation object
Inherited from
Contract.populate
reset()
reset():
Promise
<{transaction_hash
:string
; }>
Defined in: sdks/__tests__/helpers/src/counter.ts:120
Resets the counter.
Returns
Promise
<{ transaction_hash
: string
; }>
A promise that resolves to the result of the execution.
Remarks
This function requires the Account used by the Counter to be its owner.
typedv2()
typedv2<
TAbi
>(tAbi
):TypedContractV2
<TAbi
>
Defined in: node_modules/starknet/dist/index.d.ts:4762
Returns a typed instance of ContractV2 based on the supplied ABI.
Type Parameters
• TAbi extends Abi
Parameters
tAbi
TAbi
The ABI (Abstract Binary Interface) of the ContractV2.
Returns
TypedContractV2
<TAbi
>
- A typed instance of ContractV2.
Inherited from
Contract.typedv2
@0xknwn/starknet-test-helpers / SwapRouter
Class: SwapRouter
Defined in: sdks/__tests__/helpers/src/swap_router.ts:56
Represents a Swap contract.
Extends
Contract
Indexable
[key
: string
]: any
Constructors
new SwapRouter()
new SwapRouter(
address
,account
):SwapRouter
Defined in: sdks/__tests__/helpers/src/swap_router.ts:62
Creates an instance of the Counter contract.
Parameters
address
string
The address of the contract.
account
Account
The account used to interact with the contract.
Returns
Overrides
Contract.constructor
Properties
abi
abi:
Abi
Defined in: node_modules/starknet/dist/index.d.ts:4722
Inherited from
Contract.abi
address
address:
string
Defined in: node_modules/starknet/dist/index.d.ts:4723
Inherited from
Contract.address
callStatic
readonly
callStatic:object
Defined in: node_modules/starknet/dist/index.d.ts:4733
Index Signature
[name
: string
]: AsyncContractFunction
<any
>
Inherited from
Contract.callStatic
deployTransactionHash?
optional
deployTransactionHash:string
Defined in: node_modules/starknet/dist/index.d.ts:4725
Inherited from
Contract.deployTransactionHash
estimateFee
readonly
estimateFee:object
Defined in: node_modules/starknet/dist/index.d.ts:4739
Index Signature
[name
: string
]: ContractFunction
Inherited from
Contract.estimateFee
events
protected
readonly
events:AbiEvents
Defined in: node_modules/starknet/dist/index.d.ts:4729
Inherited from
Contract.events
functions
readonly
functions:object
Defined in: node_modules/starknet/dist/index.d.ts:4730
Index Signature
[name
: string
]: AsyncContractFunction
<any
>
Inherited from
Contract.functions
populateTransaction
readonly
populateTransaction:object
Defined in: node_modules/starknet/dist/index.d.ts:4736
Index Signature
[name
: string
]: ContractFunction
Inherited from
Contract.populateTransaction
providerOrAccount
providerOrAccount:
ProviderInterface
|AccountInterface
Defined in: node_modules/starknet/dist/index.d.ts:4724
Inherited from
Contract.providerOrAccount
structs
protected
readonly
structs:object
Defined in: node_modules/starknet/dist/index.d.ts:4726
Index Signature
[name
: string
]: AbiStruct
Inherited from
Contract.structs
Methods
attach()
attach(
address
):void
Defined in: node_modules/starknet/dist/index.d.ts:4752
Saves the address of the contract deployed on network that will be used for interaction
Parameters
address
string
address of the contract
Returns
void
Inherited from
Contract.attach
call()
call(
method
,args
?,options
?):Promise
<Result
>
Defined in: node_modules/starknet/dist/index.d.ts:4755
Calls a method on a contract
Parameters
method
string
name of the method
args?
ArgsOrCalldata
Array of the arguments for the call
options?
CallOptions
optional blockIdentifier
Returns
Promise
<Result
>
Result of the call as an array with key value pars
Inherited from
Contract.call
connect()
connect(
providerOrAccount
):void
Defined in: node_modules/starknet/dist/index.d.ts:4753
Attaches to new Provider or Account
Parameters
providerOrAccount
new Provider or Account to attach to
ProviderInterface
| AccountInterface
Returns
void
Inherited from
Contract.connect
deployed()
deployed():
Promise
<Contract
>
Defined in: node_modules/starknet/dist/index.d.ts:4754
Resolves when contract is deployed on the network or when no deployment transaction is found
Returns
Promise
<Contract
>
Promise that resolves when contract is deployed on the network or when no deployment transaction is found
Throws
When deployment fails
Inherited from
Contract.deployed
estimate()
estimate(
method
,args
?):Promise
<EstimateFeeResponse
>
Defined in: node_modules/starknet/dist/index.d.ts:4757
Estimates a method on a contract
Parameters
method
string
name of the method
args?
ArgsOrCalldata
Array of the arguments for the call or Calldata
Returns
Promise
<EstimateFeeResponse
>
Inherited from
Contract.estimate
faucet()
faucet(
amount
):Promise
<GetTransactionReceiptResponse
>
Defined in: sdks/__tests__/helpers/src/swap_router.ts:71
Sends a request to the faucet to receive a specified amount of Token A.
Parameters
amount
Uint256
The amount of tokens to request from the faucet.
Returns
Promise
<GetTransactionReceiptResponse
>
A promise that resolves to the transaction receipt once the transfer is complete.
get_conversion_rate()
get_conversion_rate():
Promise
<bigint
>
Defined in: sdks/__tests__/helpers/src/swap_router.ts:115
Retrieves the conversion rate.
Returns
Promise
<bigint
>
A promise that resolves to a bigint representing the conversion rate.
getVersion()
getVersion():
Promise
<ContractVersion
>
Defined in: node_modules/starknet/dist/index.d.ts:4761
Retrieves the version of the contract (cairo version & compiler version)
Returns
Promise
<ContractVersion
>
Inherited from
Contract.getVersion
invoke()
invoke(
method
,args
?,options
?):Promise
<{transaction_hash
:string
; }>
Defined in: node_modules/starknet/dist/index.d.ts:4756
Invokes a method on a contract
Parameters
method
string
name of the method
args?
ArgsOrCalldata
Array of the arguments for the invoke or Calldata
options?
InvokeOptions
Returns
Promise
<{ transaction_hash
: string
; }>
Add Transaction Response
Inherited from
Contract.invoke
isCairo1()
isCairo1():
boolean
Defined in: node_modules/starknet/dist/index.d.ts:4760
tells if the contract comes from a Cairo 1 contract
Returns
boolean
TRUE if the contract comes from a Cairo1 contract
Example
const isCairo1: boolean = myContract.isCairo1();
Inherited from
Contract.isCairo1
parseEvents()
parseEvents(
receipt
):ParsedEvents
Defined in: node_modules/starknet/dist/index.d.ts:4759
Parse contract events of a GetTransactionReceiptResponse received from waitForTransaction. Based on contract's abi
Parameters
receipt
GetTransactionReceiptResponse
transaction receipt
Returns
ParsedEvents
Events parsed
Inherited from
Contract.parseEvents
populate()
populate(
method
,args
?):Call
Defined in: node_modules/starknet/dist/index.d.ts:4758
Calls a method on a contract
Parameters
method
string
name of the method
args?
RawArgs
Array of the arguments for the call or Calldata
Returns
Call
Invocation object
Inherited from
Contract.populate
set_conversion_rate()
set_conversion_rate(
rate
):Promise
<GetTransactionReceiptResponse
>
Defined in: sdks/__tests__/helpers/src/swap_router.ts:102
Sets the conversion rate for the swap router.
Parameters
rate
string
The conversion rate to be set.
Returns
Promise
<GetTransactionReceiptResponse
>
A promise that resolves to the transaction receipt once the conversion rate is set.
set_tokens()
set_tokens(
tokenAAddress
,tokenBAddress
):Promise
<GetTransactionReceiptResponse
>
Defined in: sdks/__tests__/helpers/src/swap_router.ts:87
Sets the token addresses for tokenA and tokenB.
Parameters
tokenAAddress
string
The address of tokenA.
tokenBAddress
string
The address of tokenB.
Returns
Promise
<GetTransactionReceiptResponse
>
A promise that resolves to the transaction receipt once the transaction is confirmed.
swap()
swap(
tokenAAddress
,amount
):Promise
<GetTransactionReceiptResponse
>
Defined in: sdks/__tests__/helpers/src/swap_router.ts:125
Swaps a specified amount of a token for another token.
Parameters
tokenAAddress
string
The address of the token to be swapped.
amount
Uint256
The amount of the token to be swapped.
Returns
Promise
<GetTransactionReceiptResponse
>
A promise that resolves to the transaction receipt once the swap is completed.
swap_maximum_at()
swap_maximum_at(
tokenAAddress
,rate
,amount
):Promise
<GetTransactionReceiptResponse
>
Defined in: sdks/__tests__/helpers/src/swap_router.ts:178
Swaps the maximum amount of tokens at a given rate.
Parameters
tokenAAddress
string
The address of the token A.
rate
string
The rate at which to swap the tokens.
amount
Uint256
The amount of tokens to swap.
Returns
Promise
<GetTransactionReceiptResponse
>
A promise that resolves to the transaction receipt of the swap.
swap_minimum_at()
swap_minimum_at(
tokenAAddress
,rate
,amount
):Promise
<GetTransactionReceiptResponse
>
Defined in: sdks/__tests__/helpers/src/swap_router.ts:152
Executes a swap with a minimum rate and amount.
Parameters
tokenAAddress
string
The address of the token A.
rate
string
The rate of the swap.
amount
Uint256
The amount to swap.
Returns
Promise
<GetTransactionReceiptResponse
>
A promise that resolves to the transaction receipt of the swap.
typedv2()
typedv2<
TAbi
>(tAbi
):TypedContractV2
<TAbi
>
Defined in: node_modules/starknet/dist/index.d.ts:4762
Returns a typed instance of ContractV2 based on the supplied ABI.
Type Parameters
• TAbi extends Abi
Parameters
tAbi
TAbi
The ABI (Abstract Binary Interface) of the ContractV2.
Returns
TypedContractV2
<TAbi
>
- A typed instance of ContractV2.
Inherited from
Contract.typedv2
How to Contribute
We really appreciate and value contributions to the starknet-modular-account. There are different ways to be involved so do not hesitate to do it.
Development Guidelines
Before starting development, please create an issue to open the discussion, validate that the PR is wanted and coordinate with the team.
Please check this document to make sure your contributions are merged as soon as possible.
Table of Contents
Pull Requests (PRs)
As a contributor, you are expected to fork this repository, work on your own fork and then submit pull requests. The pull requests will be reviewed and eventually merged into the repository. See "Fork-a-Repo" for how to work.
The typical PR includes:
- a branch associated with the PR should be rebased to the head of
develop
- documentation that complies with the PR template. Make sure every section of it is properly fullfilled
Code Reviews
Maintainers will review your code and ask for changes if necessary you code can be merged into the repository. Be mindful and forgiving as we might not be available right on.
Many of the issues we are facing during the code review are due to a lack of context. Do not hesitate to explain why you are doing what you are doing.
IMPORTANT Pay attention to the maintainer's feedback and do the changes accordingly.
Environment
The project uses a number of tools. Make sure you have installed them in order to develop and test:
- scarb.
- starknet foundry
- node 20+ and npm
- mitmproxy
- starknet-devnet-rs
- rust and cargo
- starknet.js
- abi-wan-kanabi
- git and github.com
Once you have forked/cloned the repository, you should create a
.env.devnet.json
file at the root of the project. You can simply copy the
content of .env.template.json
if you plan to use the devnet on the default
port and with the account associated with the seed 0
!
Useful Commands
- (1) fetch the project cairo dependencies
scarb fetch
- (2) build the project artifacts
scarb build
- (3) execute starknet foundry tests
# you can also use: snforge test
scarb test
- (4) start starknet-devnet on the default port
starknet-devnet --seed=0
- (5) create a configuration file to run the project against the devnet
cp .env.template.json .env.devnet.json
- (6) install node dependencies
npm install
- (7) execute the npm
test
scripts with a specific test suite
npm run test -- simple_account.test.ts
- (8) start mitmproxy with the browser UI on port 8080 and redirect all the
requests to the default starknet. You can then change the url in
.env.devnet.json
so that you run the tests throughhttp://localhost:8080
mitmweb --mode reverse:http://localhost:5050
Getting ETH on Sepolia
To get ETH on Starknet Sepolia
- there is a Faucet on blast.io
- the starknet book explains how to bridge ETH with Starknet
Learning Starknet/Cairo
If you are not used to Starknet and Cairo yet, a good starting point is to learn is the list of ressources below:
- Awesome Starknet
- Cairo Compiler
- Cairo Book
- Cairo By Example
- Starknet Documentation and the
- Starknet Developer Portal, the
- Starknet Community Portal.
- Starknet Book
- Starknet Improvement Proposals
- Starknet By Example
- Starklings Cairo
Do not hesitate to join the Starkware Discord and some Telegram Developer Groups...
More questions
If you have any questions, feel free to post them as an issues.
Thanks for your time and code!
Starknet Accounts and Network Updates
There are already numerous implementations of Abstract Accounts, many of which provide awesome features:
- the Guarded WebWallet Account from Argent
- the passkey/webauthn controller from Cartridge. You can test the connection flow on their connection page
- the Starksign Multisig from Equilibrium, the WebWallet from Braavos and the WebWallet from Argent
- the MIT-licensed well documented and reusable Abstract Account from OpenZeppelin
- Starkware provides a nice workshop about Abstract Account development and the Starknet Book has several sections about accounts and multisig.
- The Cairo v0 Plugin Account provides some implementation ideas
The 2024 Starknet roadmap provides some details about the expected features. In particular, see Mattéo Georges answer in Dec 2023 that suggests the Paymaster is not yet prioritized.
Proposals, Whitepapers and Docs for Ethereum Accounts
- Adam Egyed (@adamegyed), Fangting Liu (@trinity-0111), Jay Paik (@jaypaik), Yoav Weiss (@yoavw), Huawei Gu (@huaweigu), Daniel Lim (@dlim-circle), Zhiyu Zhang (@ZhiyuCircle), "ERC-6900: Modular Smart Contract Accounts and Plugins [DRAFT]," Ethereum Improvement Proposals, no. 6900, April 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6900.
- zeroknots (@zeroknots), Konrad Kopp (@kopy-kat), Taek Lee (@leekt), Fil Makarov (@filmakarov), Elim Poon (@yaonam), Lyu Min (@rockmin216), "ERC-7579: Minimal Modular Smart Accounts [DRAFT]," Ethereum Improvement Proposals, no. 7579, December 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7579.
- Safe Module system that supports an alpha implementation of the Safe{Core} Protocol
- The Kernel Smart Contract
- The RIP 7560 that is a proposal to make ERC 4337 mandatory for rollups.
Module Registry
- Konrad Kopp (@kopy-kat), zeroknots (@zeroknots), "ERC-7484: Registry Extension for ERC-7579 [DRAFT]," Ethereum Improvement Proposals, no. 7484, August 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7484.
Module Use-Cases
Modules help to address advanced scenarios. They are often classified in 4 groups:
- Roles: e.g. spending limits, associated token: vTokens, NFT(id) or Oracle, multiple factor for administrative privileges, recurring payments, alternative signatures including faceid or fingerprints, zkproof, Merkle tree for Offchain ACL
- Recovery: e.g. social recovery, custodial recovery, physical devices, other secrets validation
- Protection: e.g. allow/deny list, freeze account, external whitelisting, Oracle MEV protection
- Modifiers: e.g. time-lock, cooldown/grace period, bonds
To dig deeper into some of the many scenarios associated with Module, you can have a look at existing implementations like:
Other resources
You might also want to check:
Contributor Covenant Code of Conduct
Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at 0xknwn at gmail dot com. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
1. Correction
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
2. Warning
Community Impact: A violation through a single incident or series of actions.
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
3. Temporary Ban
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
4. Permanent Ban
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
Consequence: A permanent ban from any sort of public interaction within the community.
Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
Community Impact Guidelines were inspired by Mozilla's code of conduct enforcement ladder.
For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
MIT License
Copyright (c) 2025 0xknwn
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.