Create an account
Since there are no Externally Owned Accounts (EOA) in Starknet, all Accounts in Starknet are contracts.
Unlike in Ethereum where a wallet is created with a public and private key pair, Starknet Accounts are the only way to sign transactions and messages and verify signatures. Therefore an Account - Contract interface is needed.
Account contracts on Starknet cannot be deployed without paying a fee. Creating an account is a bit tricky; you have several steps:
- Decide on your account type (OpenZeppelin, ArgentX, Braavos, ...).
- Compute the address of your future account.
- Send funds to this pre-computed address. The funds will be used to pay for the account contract deployment and remains will fund the new account.
- Actual deployment of the Account
Create an OZ (Open Zeppelin) account​
Level: easy.
Here, we will create a wallet with the Open Zeppelin smart contract v0.5.1. The contract class is already implemented in Testnet.
This contract is coded in Cairo 0, so it will not survive the upcoming re-genesis of Starknet.
import { Account, constants, ec, json, stark, Provider, hash, CallData } from 'starknet';
compute address​
// connect provider
const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_GOERLI } });
// new Open Zeppelin account v0.5.1
// Generate public and private key pair.
const privateKey = stark.randomAddress();
console.log('New OZ account:\nprivateKey=', privateKey);
const starkKeyPub = ec.starkCurve.getStarkKey(privateKey);
console.log('publicKey=', starkKeyPub);
const OZaccountClassHash = '0x2794ce20e5f2ff0d40e632cb53845b9f4e526ebd8471983f7dbd355b721d5a';
// Calculate future address of the account
const OZaccountConstructorCallData = CallData.compile({ publicKey: starkKeyPub });
const OZcontractAddress = hash.calculateContractAddressFromHash(
starkKeyPub,
OZaccountClassHash,
OZaccountConstructorCallData,
0
);
console.log('Precalculated account address=', OZcontractAddress);
If you want a specific private key, replace stark.randomAddress
()` with your choice.
Then you have to fund this address!
How to proceed is out of the scope of this guide, but you can for example:
- Transfer ETH from another wallet.
- Bridge ETH to this Starknet address.
- Use a faucet. (https://faucet.goerli.starknet.io/)
- Mint ETH on starknet-devnet, like so:
curl -X POST http://127.0.0.1:5050/mint -d '{"address":"0x04a093c37ab61065d001550089b1089922212c60b34e662bb14f2f91faee2979","amount":50000000000000000000,"lite":true}' -H "Content-Type:application/json"
// {"new_balance":50000000000000000000,"tx_hash":null,"unit":"wei"}
deployment of the new account​
If you have sent enough funds to this new address, you can go forward to the final step:
const OZaccount = new Account(provider, OZcontractAddress, privateKey);
const { transaction_hash, contract_address } = await OZaccount.deployAccount({
classHash: OZaccountClassHash,
constructorCalldata: OZaccountConstructorCallData,
addressSalt: starkKeyPub,
});
await provider.waitForTransaction(transaction_hash);
console.log('✅ New OpenZeppelin account created.\n address =', contract_address);
IMPORTANT: If this account is based on a Cairo v2 contract (for example OpenZeppelin account 0.7.0 or later), do not forget to add the parameter "1" after the privateKey parameter:
const OZaccount = new Account(provider, OZcontractAddress, privateKey, '1');
Take care that this added parameter is a string, NOT a number.
Create an Argent account​
Level: medium.
Here, we will create a wallet with the Argent smart contract v0.2.3. This case is more complicated because we will have the account behind a proxy contract (this way, the wallet contract can be updated). The contract classes of both contracts are already implemented in Testnet.
If necessary OZ contracts can also be created with a proxy.
import { Account, ec, json, stark, Provider, hash, CallData } from 'starknet';
compute address​
// connect provider
const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_GOERLI } });
//new Argent X account v0.2.3
const argentXproxyClassHash = '0x25ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918';
const argentXaccountClassHash =
'0x033434ad846cdd5f23eb73ff09fe6fddd568284a0fb7d1be20ee482f044dabe2';
// Generate public and private key pair.
const privateKeyAX = stark.randomAddress();
console.log('AX_ACCOUNT_PRIVATE_KEY=', privateKeyAX);
const starkKeyPubAX = ec.starkCurve.getStarkKey(privateKeyAX);
console.log('AX_ACCOUNT_PUBLIC_KEY=', starkKeyPubAX);
// Calculate future address of the ArgentX account
const AXproxyConstructorCallData = CallData.compile({
implementation: argentXaccountClassHash,
selector: hash.getSelectorFromName('initialize'),
calldata: CallData.compile({ signer: starkKeyPubAX, guardian: '0' }),
});
const AXcontractAddress = hash.calculateContractAddressFromHash(
starkKeyPubAX,
argentXproxyClassHash,
AXproxyConstructorCallData,
0
);
console.log('Precalculated account address=', AXcontractAddress);
If you want a specific private key, replace stark.randomAddress
()` with your choice.
Then you have to fund this address.
deployment of the new account​
If you have sent enough funds to this new address, you can go forward to the final step:
const accountAX = new Account(provider, AXcontractAddress, privateKeyAX);
const deployAccountPayload = {
classHash: argentXproxyClassHash,
constructorCalldata: AXproxyConstructorCallData,
contractAddress: AXcontractAddress,
addressSalt: starkKeyPubAX,
};
const { transaction_hash: AXdAth, contract_address: AXcontractFinalAddress } =
await accountAX.deployAccount(deployAccountPayload);
console.log('✅ ArgentX wallet deployed at:', AXcontractFinalAddress);
Create a Braavos account​
Level: hard.
Even more complicated, a Braavos account needs also a proxy but needs in addition a specific signature. Starknet.js is handling only Starknet standard signatures; so we need extra code to handle this specific signature for account creation. These nearly 200 lines of code are not displayed here but are available in a module here.
We will deploy hereunder a Braavos account in devnet. So launch starknet-devnet with these parameters:
starknet-devnet --seed 0 --fork-network alpha-goerli
Initialization:
import { Provider, Account, num, stark } from 'starknet';
import {
calculateAddressBraavos,
deployBraavosAccount,
estimateBraavosAccountDeployFee,
} from './deployBraavos';
import axios from 'axios';
If you want to create the private key, for example with a random number:
const privateKeyBraavos = stark.randomAddress();
If you want to use a private key generated by your browser wallet, create a new account (without deploying it), then copy/paste the account private key (it's useless to copy the public key).
const privateKeyBraavos = '0x02e8....e12';
Compute address​
// initialize Provider
const providerDevnet = new Provider({ sequencer: { baseUrl: 'http://127.0.0.1:5050' } });
// address
const BraavosProxyAddress = calculateAddressBraavos(privateKeyBraavos);
console.log('Calculated account address=', BraavosProxyAddress);
Estimate fees​
// estimate fees
const estimatedFee = await estimateBraavosAccountDeployFee(privateKeyBraavos, providerDevnet);
console.log('calculated fee =', estimatedFee);
Deploy account​
// fund account address before account creation (easy in devnet)
const { data: answer } = await axios.post(
'http://127.0.0.1:5050/mint',
{
address: BraavosProxyAddress,
amount: 10_000_000_000_000_000_000,
lite: true,
},
{ headers: { 'Content-Type': 'application/json' } }
);
console.log('Answer mint =', answer); // 10 ETH
// deploy Braavos account
const { transaction_hash, contract_address: BraavosAccountFinalAddress } =
await deployBraavosAccount(privateKeyBraavos, providerDevnet, estimatedFee);
// estimatedFee is optional
console.log('Transaction hash =', transaction_hash);
await providerDevnet.waitForTransaction(transaction_hash);
console.log('✅ Braavos wallet deployed at', BraavosAccountFinalAddress);
The computed address has been funded automatically by minting a new dummy ETH in Starknet devnet!
Create your account abstraction​
You are not limited to these 3 contracts. You can create your own contract for the wallet. It's the concept of Account Abstraction.
You can customize entirely the wallet - for example:
use a different concept of keys.
add a guardian to save your account.
have the possibility to transfer ownership of the wallet.
add some administrators or a super-administrator.
whitelist of addresses for transfer.
multisig.
delayed withdraw.
The only limitation is your imagination...
Here is an example of a customized wallet, including super administrator management, on a local starknet-devnet:
launch
starknet-devnet --seed 0
before using this script
import { Account, ec, json, stark, Provider, hash, CallData } from 'starknet';
import fs from 'fs';
import axios from 'axios';
// connect provider
const provider = new Provider({ sequencer: { network: 'http://127.0.0.1:5050' } });
// initialize existing predeployed account 0 of Devnet
const privateKey0 = '0xe3e70682c2094cac629f6fbed82c07cd';
const accountAddress0 = '0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a';
const account0 = new Account(provider, accountAddress0, privateKey0);
// add ,"1" after privateKey0 if this account is not a Cairo 0 contract
// new account abstraction
// Generate public and private key pair.
const AAprivateKey = stark.randomAddress();
console.log('New account:\nprivateKey=', AAprivateKey);
const AAstarkKeyPub = ec.starkCurve.getStarkKey(AAprivateKey);
console.log('publicKey=', AAstarkKeyPub);
// declare the contract
const compiledAAaccount = json.parse(
fs.readFileSync('./compiled_contracts/myAccountAbstraction.json').toString('ascii')
);
const { transaction_hash: declTH, class_hash: decCH } = await account0.declare({
contract: compiledAAaccount,
});
console.log('Customized account class hash =', decCH);
await provider.waitForTransaction(declTH);
// Calculate future address of the account
const AAaccountConstructorCallData = CallData.compile({
super_admin_address: account0.address,
publicKey: AAstarkKeyPub,
});
const AAcontractAddress = hash.calculateContractAddressFromHash(
AAstarkKeyPub,
AAaccountClassHash,
AAaccountConstructorCallData,
0
);
console.log('Precalculated account address=', AAcontractAddress);
// fund account address before account creation
const { data: answer } = await axios.post(
'http://127.0.0.1:5050/mint',
{ address: AAcontractAddress, amount: 50_000_000_000_000_000_000, lite: true },
{ headers: { 'Content-Type': 'application/json' } }
);
console.log('Answer mint =', answer);
// deploy account
const AAaccount = new Account(provider, AAcontractAddress, AAprivateKey);
// add ,"1" after AAprivateKey if this account is not a Cairo 0 contract
const { transaction_hash, contract_address } = await AAaccount.deployAccount({
classHash: AAaccountClassHash,
constructorCalldata: AAaccountConstructorCallData,
addressSalt: AAstarkKeyPub,
});
await provider.waitForTransaction(transaction_hash);
console.log('✅ New customized account created.\n address =', contract_address);
Account update​
For ArgentX and Braavos wallets, if you have created the private key inside the browser wallet, necessary upgrades will be automatically managed in the wallet.
However, if you have created the private key by yourself, it becomes your responsibility to update the account implementation class when it's necessary. It can be done with the upgrade
function of the implementation class.