Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Transaction Types #48

Merged
merged 26 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ jobs:
cache: yarn
- run: yarn install
- run: yarn build
- run: export PRIVATE_KEY=${{secrets.PRIVATE_KEY}} && yarn test:integration
- run: export PRIVATE_KEY=${{secrets.PRIVATE_KEY}}
- run: yarn test:unit
- run: yarn test test/integration/rpc test/integration/mainnet test/integration/zksync test/integration/utils
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
node-version: 20
cache: yarn
- run: yarn install
- run: npx typedoc src/index.ts
- run: yarn build:docs
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
Expand Down
74 changes: 38 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,47 @@ Use [Web3.js](https://web3js.org/) to interact with the [ZKsync Era](https://zks

Please refer to the comprehensive
[API documentation](https://chainsafe.github.io/web3-plugin-zksync/) for a complete overview of this
plugin's capabilities. Usage documentation that includes explanations and code samples is coming
soon to the [official ZKsync SDK docs](https://sdk.zksync.io/).
plugin's capabilities. Usage documentation that includes explanations and code samples is available
as part of the [official Web3.js plugin for ZKsync SDK docs](https://sdk.zksync.io/js/web3js).

## Plugin Components

- RPC methods:
The[`RpcMethods` class](https://chainsafe.github.io/web3-plugin-zksync/classes/RpcMethods.html)
implements the [ZKsync JSON-RPC API](https://docs.zksync.io/build/api-reference/zks-rpc).
- Constants: The
[`constants` package](https://chainsafe.github.io/web3-plugin-zksync/modules/constants.html)
includes well-known addresses, such as the address of the L1 ETH token.
- Types: The [`types` package](https://chainsafe.github.io/web3-plugin-zksync/modules/types.html)
defines enums, interfaces, and types that are used for interacting with the ZKsync Era network.
- Utilities: The
[`utils` package](https://chainsafe.github.io/web3-plugin-zksync/modules/utils.html) exposes
helpful functions and contract definitions that can be used with the Web3.js plugin for ZKsync.
- Wallet: The
[`ZKSyncWallet` class](https://chainsafe.github.io/web3-plugin-zksync/classes/ZKSyncWallet.html)
allows developers to create, manage, and use ZKsync accounts.
- Paymasters: The plugin includes a number of helpful utilities for working with
[ZKsync paymasters](https://docs.zksync.io/build/developer-reference/account-abstraction/paymasters),
including a
[`getPaymasterParams` function](https://chainsafe.github.io/web3-plugin-zksync/functions/getPaymasterParams.html)
for generating paymaster parameters to add to a transaction.
- Smart contracts: The
[`ContractFactory` class](https://chainsafe.github.io/web3-plugin-zksync/classes/ContractFactory.html)
can be used to deploy smart contracts to the ZKsync Era network. The return type of the
[`ContractFactory.deploy` method](https://chainsafe.github.io/web3-plugin-zksync/classes/ContractFactory.html#deploy)
is the standard
[Web3.js Contract class](https://docs.web3js.org/api/web3-eth-contract/class/Contract/).
- Smart accounts: The
[`SmartAccount` class](https://chainsafe.github.io/web3-plugin-zksync/classes/SmartAccount.html)
can be used to create
[ZKsync smart accounts](https://docs.zksync.io/build/developer-reference/account-abstraction/)
with custom logic for building and signing transactions. There are factory functions for
creating
[ECDSA smart accounts](https://chainsafe.github.io/web3-plugin-zksync/classes/ECDSASmartAccount.html#create)
and
[multi-signature ECDSA smart accounts](https://chainsafe.github.io/web3-plugin-zksync/classes/MultisigECDSASmartAccount.html#create).
- [RPC methods](https://sdk.zksync.io/js/web3js/rpc):
The[`RpcMethods` class](https://chainsafe.github.io/web3-plugin-zksync/classes/RpcMethods.html)
implements the [ZKsync JSON-RPC API](https://docs.zksync.io/build/api-reference/zks-rpc).
- [Constants](https://sdk.zksync.io/js/web3js/constants-types-utilities#constants): The
[`constants` package](https://chainsafe.github.io/web3-plugin-zksync/modules/constants.html)
includes well-known addresses, such as the address of the L1 ETH token.
- [Types](https://sdk.zksync.io/js/web3js/constants-types-utilities#types): The
[`types` package](https://chainsafe.github.io/web3-plugin-zksync/modules/types.html) defines
enums, interfaces, and types that are used for interacting with the ZKsync Era network.
- [Utilities](https://sdk.zksync.io/js/web3js/constants-types-utilities#utilities): The
[`utils` package](https://chainsafe.github.io/web3-plugin-zksync/modules/utils.html) exposes
helpful functions and contract definitions that can be used with the Web3.js plugin for ZKsync.
- [Wallet](https://sdk.zksync.io/js/web3js/wallet): The
[`ZKSyncWallet` class](https://chainsafe.github.io/web3-plugin-zksync/classes/ZKSyncWallet.html)
allows developers to create, manage, and use ZKsync accounts.
- [Paymasters](https://sdk.zksync.io/js/web3js/paymasters): The plugin includes a number of
helpful utilities for working with
[ZKsync paymasters](https://docs.zksync.io/build/developer-reference/account-abstraction/paymasters),
including a
[`getPaymasterParams` function](https://chainsafe.github.io/web3-plugin-zksync/functions/getPaymasterParams.html)
for generating paymaster parameters to add to a transaction.
- [Smart contracts](https://sdk.zksync.io/js/web3js/contracts): The
[`ContractFactory` class](https://chainsafe.github.io/web3-plugin-zksync/classes/ContractFactory.html)
can be used to deploy smart contracts to the ZKsync Era network. The return type of the
[`ContractFactory.deploy` method](https://chainsafe.github.io/web3-plugin-zksync/classes/ContractFactory.html#deploy)
is the standard
[Web3.js Contract class](https://docs.web3js.org/api/web3-eth-contract/class/Contract/).
- [Smart accounts](https://sdk.zksync.io/js/web3js/smart-accounts): The
[`SmartAccount` class](https://chainsafe.github.io/web3-plugin-zksync/classes/SmartAccount.html)
can be used to create
[ZKsync smart accounts](https://docs.zksync.io/build/developer-reference/account-abstraction/)
with custom logic for building and signing transactions. There are factory functions for
creating
[ECDSA smart accounts](https://chainsafe.github.io/web3-plugin-zksync/classes/ECDSASmartAccount.html#create)
and
[multi-signature ECDSA smart accounts](https://chainsafe.github.io/web3-plugin-zksync/classes/MultisigECDSASmartAccount.html#create).

## Contributing

Expand Down
23 changes: 13 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web3-plugin-zksync",
"version": "1.0.0-alpha.0",
"version": "1.0.6",
"description": "web3.js plugin for ZkSync",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand All @@ -12,9 +12,12 @@
"lint": "eslint '{src,test}/**/*.ts'",
"lint:fix": "eslint '{src,test}/**/*.ts' --fix",
"build": "tsc --project tsconfig.build.json",
"build:docs": "npx -y typedoc src/index.ts",
"test": "jest --config=./test/jest.config.js",
"test:unit": "jest --config=./test/jest.config.js test/unit",
"test:local": "jest --config=./test/jest.config.js --runInBand --maxConcurrency=1 test/local",
"test:integration": "jest --config=./test/jest.config.js test/integration"
"test:integration": "jest --config=./test/jest.config.js --runInBand --maxConcurrency=1 test/integration",
"test:all": "jest --config=./test/jest.config.js --runInBand --maxConcurrency=1"
},
"contributors": [
"ChainSafe <[email protected]>"
Expand All @@ -27,20 +30,20 @@
"dependencies": {
"ethereum-cryptography": "^2.1.3",
"hardhat": "^2.19.4",
"web3": "^4.11.1"
"web3": "^4.12.1"
},
"devDependencies": {
"@chainsafe/eslint-config": "^2.1.1",
"@types/jest": "^29.5.11",
"@types/node": "^20.11.10",
"@chainsafe/eslint-config": "^2.2.4",
"@types/jest": "^29.5.12",
"@types/node": "^22.5.0",
"eslint": "8.56.0",
"jest": "^29.7.0",
"jest-extended": "^4.0.2",
"ts-jest": "^29.1.2",
"ts-jest": "^29.2.4",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
"typescript": "^5.5.4"
},
"peerDependencies": {
"web3": ">= 4.0.3"
"web3": ">= 4.12.0"
}
}
}
133 changes: 12 additions & 121 deletions src/Eip712.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import { bytesToHex, toBigInt, toHex } from 'web3-utils';
import type { Bytes, Eip712TypedData, Numbers } from 'web3-types';
import type { Bytes, Eip712TypedData } from 'web3-types';
import * as web3Abi from 'web3-eth-abi';
import * as web3Utils from 'web3-utils';
import type * as web3Accounts from 'web3-eth-accounts';
import {
BaseTransaction,
bigIntToUint8Array,
signMessageWithPrivateKey,
toUint8Array,
} from 'web3-eth-accounts';
import { bigIntToUint8Array, signMessageWithPrivateKey } from 'web3-eth-accounts';
import { RLP } from '@ethereumjs/rlp';
import type { Address } from 'web3';
import {
Expand All @@ -17,12 +12,13 @@ import {
EIP712_TYPES,
ZERO_ADDRESS,
} from './constants';
import type {
import {
Eip712Meta,
Eip712SignedInput,
Eip712TxData,
EthereumSignature,
PaymasterParams,
TransactionRequest,
} from './types';
import { SignatureLike } from './utils';
import { concat, hashBytecode, SignatureObject, toBytes } from './utils';
Expand Down Expand Up @@ -94,14 +90,11 @@ export class EIP712 {
factoryDeps:
transaction.customData?.factoryDeps?.map((dep: Bytes) => hashBytecode(dep)) || [],
paymasterInput: transaction.customData?.paymasterParams?.paymasterInput || '0x',
customData:
transaction.customData && Object.keys(transaction.customData).length > 0
? transaction.customData
: undefined,
};
}

static txTypedData(transaction: Eip712TxData): Eip712TypedData {
const signInput = EIP712.getSignInput(transaction);
return {
types: EIP712_TYPES,
primaryType: 'Transaction',
Expand All @@ -110,7 +103,7 @@ export class EIP712 {
version: '2',
chainId: Number(transaction.chainId),
},
message: EIP712.getSignInput(transaction),
message: signInput,
};
}
/**
Expand Down Expand Up @@ -241,7 +234,7 @@ export class EIP712 {

return new Uint8Array([...r, ...s, v]);
}
static raw(transaction: Eip712TxData, signature?: SignatureLike) {
static raw(transaction: TransactionRequest, signature?: SignatureLike) {
if (!transaction.chainId) {
throw Error("Transaction chainId isn't set!");
}
Expand All @@ -252,7 +245,7 @@ export class EIP712 {
);
}
const from = transaction.from;
const meta: Eip712Meta = transaction.customData ?? {};
const meta: Eip712Meta = (transaction.customData ?? {}) as Eip712Meta;
const maxFeePerGas = toHex(transaction.maxFeePerGas || transaction.gasPrice || 0);
const maxPriorityFeePerGas = toHex(transaction.maxPriorityFeePerGas || maxFeePerGas);

Expand Down Expand Up @@ -311,16 +304,13 @@ export class EIP712 {
}
return fields;
}
static serialize(transaction: Eip712TxData, signature?: SignatureLike): string {
static serialize(transaction: TransactionRequest, signature?: SignatureLike): string {
const fields = EIP712.raw(transaction, signature);
return concat([new Uint8Array([EIP712_TX_TYPE]), RLP.encode(fields)]);
}

static sign(hash: string, privateKey: string) {
return new EIP712Transaction({}).ecsign(
toUint8Array(web3Utils.keccak256(hash)),
toUint8Array(privateKey),
);
return signMessageWithPrivateKey(web3Utils.keccak256(hash), privateKey);
}
}

Expand Down Expand Up @@ -351,12 +341,12 @@ export class EIP712Signer {
*
* @throws {Error} If `transaction.chainId` is not set.
*/
static getSignedDigest(transaction: Eip712TxData): Bytes {
static getSignedDigest(transaction: TransactionRequest): Bytes {
if (!transaction.chainId) {
throw Error("Transaction chainId isn't set!");
}

return web3Abi.getEncodedEip712Data(EIP712.txTypedData(transaction), true);
return web3Abi.getEncodedEip712Data(EIP712.txTypedData(transaction as Eip712TxData), true);
}

/**
Expand All @@ -366,102 +356,3 @@ export class EIP712Signer {
return this.eip712Domain;
}
}
export class EIP712Transaction extends BaseTransaction<EIP712Transaction> {
private txData: Eip712TxData;
private signature?: SignatureObject;
constructor(txData: Eip712TxData) {
super(txData, {} as web3Accounts.TxOptions);
const { v, r, s, ...data } = txData;

if (r && s) {
this.signature = new SignatureObject(toUint8Array(r), toUint8Array(s), toBigInt(v));
}

this.txData = data;
}
public getSignature(): SignatureObject | undefined {
return this.signature;
}
public getMessageToSign(isHash = false): Uint8Array {
const typedDataStruct = EIP712.txTypedData(this.txData);
const message = web3Abi.getEncodedEip712Data(typedDataStruct, isHash);
return web3Utils.hexToBytes(message);
}
_processSignature(
v: Numbers,
r: EthereumSignature['r'],
s: EthereumSignature['s'],
): EIP712Transaction {
const signature = new SignatureObject(toUint8Array(r), toUint8Array(s), toBigInt(v));
return new EIP712Transaction({
...this.txData,
v: toBigInt(signature.v),
r: toHex(signature.r),
s: toHex(signature.s),
});
}
public ecsign(msgHash: Uint8Array, privateKey: Uint8Array, chainId?: bigint) {
const { s, r, v } = this._ecsign(msgHash, privateKey, chainId);
this.signature = new SignatureObject(toUint8Array(r), toUint8Array(s), toBigInt(v));
return this.signature;
}

protected _errorMsg(msg: string): string {
return `${msg} (${this.errorStr()})`;
}

public static fromTxData(txData: Eip712TxData, _ = {}) {
return new EIP712Transaction(txData);
}

errorStr(): string {
return '';
}

getMessageToVerifySignature(): Uint8Array {
return this.getMessageToSign();
}

getSenderPublicKey(): Uint8Array {
// @TODO: implement recover transaction here
return new Uint8Array();
}

getUpfrontCost(): bigint {
return 0n;
}

hash(): Uint8Array {
return toUint8Array(EIP712.txHash(this.txData));
}
raw(): web3Accounts.TxValuesArray {
return EIP712.raw(this.txData) as unknown as web3Accounts.TxValuesArray;
}

serialize(): Uint8Array {
return toUint8Array(EIP712.serialize(this.txData));
}

toJSON(): web3Accounts.JsonTx {
const data = EIP712.getSignInput(this.txData);
return {
to: data.to && toHex(data.to),
gasLimit: toHex(data.gasLimit),
// @ts-ignore-next-line
gasPerPubdataByteLimit: data.gasPerPubdataByteLimit,
customData: data.customData,
maxFeePerGas: toHex(data.maxFeePerGas),
maxPriorityFeePerGas: toHex(data.maxPriorityFeePerGas),
paymaster: data.paymaster,
nonce: toHex(data.nonce),
value: toHex(data.value),
data: toHex(data.data),
factoryDeps: data.factoryDeps,
paymasterInput: data.paymasterInput,
type: toHex(data.txType),
v: this.signature?.v ? toHex(this.signature.v) : undefined,
r: this.signature?.r ? toHex(this.signature?.r) : undefined,
s: this.signature?.s ? toHex(this.signature?.s) : undefined,
};
}
}
Loading