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.