SmartAccount

Managing and signing transactions with a SmartAccount in ZKsync.

A SmartAccount is a signer which can be configured to sign various payloads using a provided secret. The secret can be in any form, allowing for flexibility when working with different account implementations. The SmartAccount is bound to a specific address and provides the ability to define custom method for populating transactions and custom signing method used for signing messages, typed data, and transactions. It is compatible with ethers.ContractFactory for deploying contracts/accounts, as well as with ethers.Contract for interacting with contracts/accounts using provided ABI along with custom transaction signing logic.

connect

Creates a new instance of SmartAccount connected to a provider or detached from any provider if null is provided.

Inputs

ParameterTypeDescription
providerProvider or nullThe provider to connect the SmartAccount to. If null, the SmartAccount will be detached from any provider.
connect(provider: null | Provider): SmartAccount

Example

import { Wallet, Provider, types } from "zksync-ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const sepoliaProvider = Provider.getDefaultProvider(types.Network.Sepolia);
const sepoliaAccount = new SmartAccount({ address: ADDRESS, secret: PRIVATE_KEY }, sepoliaProvider);

const mainnetProvider = Provider.getDefaultProvider(types.Network.Mainnet);
const mainnetAccount = sepoliaAccount.connect(mainnetProvider);

constructor

Creates a SmartAccount instance with provided signer and provider. By default, uses signPayloadWithECDSA and populateTransactionECDSA.

Inputs

ParameterTypeDescription
signerSmartAccountSignerContains necessary properties for signing payloads.
provider?Provider or nullThe provider to connect to. Can be null for offline usage.
constructor(signer: SmartAccountSigner, provider?: null | Provider)

Example

import { SmartAccount, Provider, types } from "zksync-ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount({ address: ADDRESS, secret: PRIVATE_KEY }, provider);

getAddress

Returns the address of the account.

getAddress(): Promise<string>

Example

import { SmartAccount, Provider, types } from "zksync-ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount({ address: ADDRESS, secret: PRIVATE_KEY }, provider);

const address = await account.getAddress();

getAllBalances

Returns all token balances of the account.

async getAllBalances(): Promise<BalancesMap>

Example

import { SmartAccount, Provider, types } from "zksync-ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount({ address: ADDRESS, secret: PRIVATE_KEY }, provider);

const balances = await account.getAllBalances();

getBalance

Returns the balance of the account.

Inputs

ParameterTypeDescription
token?AddressThe token address to query balance for. Defaults to the native token.
blockTagBlockTagThe block tag to get the balance at. Defaults to committed.
async getBalance(token?: Address, blockTag: BlockTag = 'committed'): Promise<bigint>

Example

import { SmartAccount, Provider, types } from "zksync-ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount({ address: ADDRESS, secret: PRIVATE_KEY }, provider);

const balance = await account.getBalance();

getDeploymentNonce

Returns the deployment nonce of the account.

async getDeploymentNonce(): Promise<bigint>

Example

import { SmartAccount, Provider, types } from "zksync-ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount({ address: ADDRESS, secret: PRIVATE_KEY }, provider);

const nonce = await account.getDeploymentNonce();

populateTransaction

Populates the transaction tx using the provided TransactionBuilder function. If tx.from is not set, it sets the value from the getAddress method which can be utilized in the TransactionBuilder function.

Inputs

ParameterTypeDescription
txTransactionRequestThe transaction that needs to be populated.
async populateTransaction(tx: TransactionRequest): Promise<TransactionLike>

Example

import { SmartAccount, Provider, types, utils } from "zksync-ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount({ address: ADDRESS, secret: PRIVATE_KEY }, provider);

const populatedTx = await account.populateTransaction({
  type: utils.EIP712_TX_TYPE,
  to: "<RECEIVER>",
  value: 7_000_000_000,
});

sendTransaction

Sends tx to the Network. The signTransaction is called first to ensure transaction is properly signed.

Inputs

ParameterTypeDescription
txTransactionRequestThe transaction that needs to be sent.
async sendTransaction(tx: TransactionRequest): Promise<TransactionResponse>

Example

import { SmartAccount, Provider, types } from "zksync-ethers";
import { ethers } from "ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount({ address: ADDRESS, secret: PRIVATE_KEY }, provider);

const signedTx = await account.sendTransaction({
  to: "<RECEIVER>",
  value: ethers.parseEther("1"),
});

signMessage

Signs a message using the provided PayloadSigner function.

Inputs

ParameterTypeDescription
messagestring or Uint8ArrayThe message that needs to be signed.
signMessage(message: string | Uint8Array): Promise<string>

Example

import { SmartAccount, Provider, types } from "zksync-ethers";
import { ethers } from "ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount({ address: ADDRESS, secret: PRIVATE_KEY }, provider);

const signedMessage = await account.signMessage("Hello World!");

signTransaction

Signs the transaction tx using the provided PayloadSigner function, returning the fully signed transaction. The populateTransaction method is called first to ensure that all necessary properties for the transaction to be valid have been populated.

Inputs

ParameterTypeDescription
txTransactionRequestThe transaction that needs to be signed.
async signTransaction(tx: TransactionRequest): Promise<string>

Example

import { SmartAccount, Provider, types } from "zksync-ethers";
import { ethers } from "ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount({ address: ADDRESS, secret: PRIVATE_KEY }, provider);

const signedTx = await account.signTransaction({
  to: "<RECEIVER>",
  value: ethers.parseEther("1"),
});

signTypedData

Signs a typed data using the provided PayloadSigner function.

Inputs

ParameterTypeDescription
domainethers.TypedDataDomainThe domain data.
typesRecord<string, ethers.TypedDataField[]>A map of records pointing from field name to field type.
valueRecord<string, any>A single record value.
async signTypedData(
  domain: ethers.TypedDataDomain,
  types: Record<string, ethers.TypedDataField[]>,
  value: Record<string, any>
): Promise<string>

Example

import { SmartAccount, Provider, types } from "zksync-ethers";
import { ethers } from "ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount({ address: ADDRESS, secret: PRIVATE_KEY }, provider);

const signedTypedData = await account.signTypedData(
  { name: "Example", version: "1", chainId: 270 },
  {
    Person: [
      { name: "name", type: "string" },
      { name: "age", type: "uint8" },
    ],
  },
  { name: "John", age: 30 }
);

transfer

Transfer ETH or any ERC20 token within the same interface.

Inputs

ParameterTypeDescription
transaction.toAddressThe address of the recipient.
transaction.amountBigNumberishThe amount of the token to transfer.
transaction.token?AddressThe address of the token. ETH by default.
transaction.paymasterParams?PaymasterParamsPaymaster parameters.
transaction.overrides?ethers.OverridesTransaction's overrides which may be used to pass l2 gasLimit,
gasPrice, value, etc.
async transfer(transaction: {
  to: Address;
  amount: BigNumberish;
  token ? : Address;
  paymasterParams?: PaymasterParams;
  overrides ? : ethers.Overrides;
}): Promise<ethers.ContractTransaction>

Examples

Transfer ETH.

import { SmartAccount, Wallet, Provider, types } from "zksync-ethers";
import { ethers } from "ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount(
  {address: ADDRESS, secret: PRIVATE_KEY},
  provider
);

const transferTx = await account.transfer({
  token: utils.ETH_ADDRESS,
  to: Wallet.createRandom().address,
  amount: ethers.parseEther("0.01"),
});

const receipt = await transferTx.wait();

console.log(`The sum of ${receipt.value} ETH was transferred to ${receipt.to}`);

Transfer ETH using paymaster to facilitate fee payment with an ERC20 token.

import { SmartAccount, Wallet, Provider, utils } from "zksync-ethers";
import { ethers } from "ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const token = "0x927488F48ffbc32112F1fF721759649A89721F8F"; // Crown token which can be minted for free
const paymaster = "0x13D0D8550769f59aa241a41897D4859c87f7Dd46"; // Paymaster for Crown token

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount(
  {address: ADDRESS, secret: PRIVATE_KEY},
  provider
);

const transferTx = await account.transfer({
  to: Wallet.createRandom().address,
  amount: ethers.parseEther("0.01"),
  paymasterParams: utils.getPaymasterParams(paymaster, {
    type: "ApprovalBased",
    token: token,
    minimalAllowance: 1,
    innerInput: new Uint8Array(),
  }),
});

const receipt = await transferTx.wait();

console.log(`The sum of ${receipt.value} ETH was transferred to ${receipt.to}`);

withdraw

Initiates the withdrawal process which withdraws ETH or any ERC20 token from the associated account on L2 network to the target account on L1 network.

Inputs

ParameterTypeDescription
transaction.tokenAddressThe address of the token. ETH by default.
transaction.amountBigNumberishThe amount of the token to withdraw.
transaction.to?AddressThe address of the recipient on L1.
transaction.bridgeAddress?AddressThe address of the bridge contract to be used.
transaction.paymasterParams?PaymasterParamsPaymaster parameters.
transaction.overrides?ethers.OverridesTransaction's overrides which may be used to pass l2 gasLimit, gasPrice, value, etc.
async withdraw(transaction: {
  token: Address;
  amount: BigNumberish;
  to?: Address;
  bridgeAddress?: Address;
  paymasterParams?: PaymasterParams;
  overrides?: ethers.Overrides;
}): Promise<TransactionResponse>

Examples

Withdraw ETH.

import { SmartAccount, Provider, types, utils } from "zksync-ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount(
  {address: ADDRESS, secret: PRIVATE_KEY},
  provider
);

const withdrawTx = await account.withdraw({
  token: utils.ETH_ADDRESS,
  amount: 10_000_000n,
});

Withdraw ETH using paymaster to facilitate fee payment with an ERC20 token.

import { SmartAccount, Provider, types, utils } from "zksync-ethers";

const ADDRESS = "<ADDRESS>";
const PRIVATE_KEY = "<PRIVATE_KEY>";

const token = "0x927488F48ffbc32112F1fF721759649A89721F8F"; // Crown token which can be minted for free
const paymaster = "0x13D0D8550769f59aa241a41897D4859c87f7Dd46"; // Paymaster for Crown token

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
const account = new SmartAccount(
  {address: ADDRESS, secret: PRIVATE_KEY},
  provider
);

const withdrawTx = await account.withdraw({
  token: utils.ETH_ADDRESS,
  amount: 10_000_000n,
  paymasterParams: utils.getPaymasterParams(paymaster, {
    type: "ApprovalBased",
    token: token,
    minimalAllowance: 1,
    innerInput: new Uint8Array(),
  }),
});

Made with ❤️ by the ZKsync Community