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