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
SmartrAccountclass has been instantiated in thesmartrAccountvariable as shown in Using the modular account from the SDK. It also assumes theCountercontract that comes with the project has been deploys to thecounterAddressand theCounterABIclass is available. The05-setup.tsscript 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 
Accountas 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 
EthModulemodule from the SDK - to use the 
EthSignerprovided by Starknet.js with the Private Key - to instantiate the 
SmartrAccountwith 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 
SmartrAccountwith 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 
EthModuleto 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.