import { BigNumber, BigNumberish, ethers } from 'ethers'; import { EthMessageSigner } from './eth-message-signer'; import { SyncProvider } from './provider-interface'; import { Create2WalletSigner, Signer, unableToSign } from './signer'; import { BatchBuilderInternalTx } from './batch-builder'; import { Address, ChangePubKey, ChangePubKeyCREATE2, ChangePubKeyECDSA, ChangePubKeyOnchain, ChangePubkeyTypes, Create2Data, EthSignerType, ForcedExit, MintNFT, NFT, Nonce, Order, PubKeyHash, Ratio, SignedTransaction, Swap, TokenLike, Transfer, TxEthSignature, Withdraw, WithdrawNFT, TokenRatio, WeiRatio } from './types'; import { getChangePubkeyLegacyMessage, getChangePubkeyMessage, MAX_TIMESTAMP, isNFT } from './utils'; import { Transaction, submitSignedTransaction } from './operations'; import { AbstractWallet } from './abstract-wallet'; export { Transaction, ETHOperation, submitSignedTransaction, submitSignedTransactionsBatch } from './operations'; export class Wallet extends AbstractWallet { protected constructor( public _ethSigner: ethers.Signer, private _ethMessageSigner: EthMessageSigner, cachedAddress: Address, public signer?: Signer, accountId?: number, public ethSignerType?: EthSignerType ) { super(cachedAddress, accountId); } // ************ // Constructors // static async fromEthSigner( ethWallet: ethers.Signer, provider: SyncProvider, signer?: Signer, accountId?: number, ethSignerType?: EthSignerType ): Promise { if (signer == null) { const signerResult = await Signer.fromETHSignature(ethWallet); signer = signerResult.signer; ethSignerType = ethSignerType || signerResult.ethSignatureType; } else if (ethSignerType == null) { throw new Error('If you passed signer, you must also pass ethSignerType.'); } const ethMessageSigner = new EthMessageSigner(ethWallet, ethSignerType); const wallet = new Wallet( ethWallet, ethMessageSigner, await ethWallet.getAddress(), signer, accountId, ethSignerType ); wallet.connect(provider); await wallet.verifyNetworks(); return wallet; } static async fromCreate2Data( syncSigner: Signer, provider: SyncProvider, create2Data: Create2Data, accountId?: number ): Promise { const create2Signer = new Create2WalletSigner(await syncSigner.pubKeyHash(), create2Data); return await Wallet.fromEthSigner(create2Signer, provider, syncSigner, accountId, { verificationMethod: 'ERC-1271', isSignedMsgPrefixed: true }); } static async fromEthSignerNoKeys( ethWallet: ethers.Signer, provider: SyncProvider, accountId?: number, ethSignerType?: EthSignerType ): Promise { const ethMessageSigner = new EthMessageSigner(ethWallet, ethSignerType); const wallet = new Wallet( ethWallet, ethMessageSigner, await ethWallet.getAddress(), undefined, accountId, ethSignerType ); wallet.connect(provider); await wallet.verifyNetworks(); return wallet; } static async fromSyncSigner( ethWallet: ethers.Signer, syncSigner: Signer, provider: SyncProvider, accountId?: number ) { return await Wallet.fromEthSigner(ethWallet, provider, syncSigner, accountId, { verificationMethod: 'ERC-1271', isSignedMsgPrefixed: true }); } // **************** // Abstract getters // override ethSigner(): ethers.Signer { return this._ethSigner; } override ethMessageSigner(): EthMessageSigner { return this._ethMessageSigner; } override syncSignerConnected(): boolean { return this.signer != null; } override async syncSignerPubKeyHash(): Promise { return await this.signer.pubKeyHash(); } // ********************* // Batch builder methods // override async processBatchBuilderTransactions( startNonce: Nonce, txs: BatchBuilderInternalTx[] ): Promise<{ txs: SignedTransaction[]; signature?: TxEthSignature }> { const processedTxs: SignedTransaction[] = []; let messages: string[] = []; let nonce: number = await this.getNonce(startNonce); const batchNonce = nonce; for (const tx of txs) { tx.tx.nonce = nonce++; switch (tx.type) { case 'Withdraw': messages.push(this.getWithdrawEthMessagePart(tx.tx)); const withdraw = { tx: await this.getWithdrawFromSyncToEthereum(tx.tx) }; processedTxs.push(withdraw); break; case 'Transfer': messages.push(await this.getTransferEthMessagePart(tx.tx)); const transfer = { tx: await this.getTransfer(tx.tx) }; processedTxs.push(transfer); break; case 'ChangePubKey': // ChangePubKey requires its own Ethereum signature, we either expect // it to be signed already or do it here. const changePubKey: ChangePubKey = tx.alreadySigned ? tx.tx : (await this.signSetSigningKey(tx.tx)).tx; const currentPubKeyHash = await this.getCurrentPubKeyHash(); if (currentPubKeyHash === changePubKey.newPkHash) { throw new Error('Current signing key is already set'); } messages.push( this.getChangePubKeyEthMessagePart({ pubKeyHash: changePubKey.newPkHash, feeToken: tx.token, fee: changePubKey.fee }) ); processedTxs.push({ tx: changePubKey }); break; case 'ForcedExit': messages.push(this.getForcedExitEthMessagePart(tx.tx)); const forcedExit = { tx: await this.getForcedExit(tx.tx) }; processedTxs.push(forcedExit); break; case 'MintNFT': messages.push(this.getMintNFTEthMessagePart(tx.tx)); const mintNft = { tx: await this.getMintNFT(tx.tx) }; processedTxs.push(mintNft); break; case 'Swap': messages.push(this.getSwapEthSignMessagePart(tx.tx)); const swap = { tx: await this.getSwap(tx.tx), ethereumSignature: [ null, tx.tx.orders[0].ethSignature || null, tx.tx.orders[1].ethSignature || null ] }; processedTxs.push(swap); break; case 'WithdrawNFT': messages.push(this.getWithdrawNFTEthMessagePart(tx.tx)); const withdrawNft = { tx: await this.getWithdrawNFT(tx.tx) }; processedTxs.push(withdrawNft); break; } } messages.push(`Nonce: ${batchNonce}`); const message = messages.filter((part) => part.length != 0).join('\n'); const signature = await this.ethMessageSigner().getEthMessageSignature(message); return { txs: processedTxs, signature }; } // ************** // L2 operations // override async signSyncTransfer(transfer: { to: Address; token: TokenLike; amount: BigNumberish; fee: BigNumberish; nonce: number; validFrom?: number; validUntil?: number; }): Promise { transfer.validFrom = transfer.validFrom || 0; transfer.validUntil = transfer.validUntil || MAX_TIMESTAMP; const signedTransferTransaction = await this.getTransfer(transfer as any); const stringAmount = BigNumber.from(transfer.amount).isZero() ? null : this.provider.tokenSet.formatToken(transfer.token, transfer.amount); const stringFee = BigNumber.from(transfer.fee).isZero() ? null : this.provider.tokenSet.formatToken(transfer.token, transfer.fee); const stringToken = this.provider.tokenSet.resolveTokenSymbol(transfer.token); const ethereumSignature = unableToSign(this.ethSigner()) ? null : await this.ethMessageSigner().ethSignTransfer({ stringAmount, stringFee, stringToken, to: transfer.to, nonce: transfer.nonce, accountId: this.accountId }); return { tx: signedTransferTransaction, ethereumSignature }; } override async syncTransfer(transfer: { to: Address; token: TokenLike; amount: BigNumberish; fee?: BigNumberish; nonce?: Nonce; validFrom?: number; validUntil?: number; }): Promise { transfer.nonce = transfer.nonce != null ? await this.getNonce(transfer.nonce) : await this.getNonce(); if (transfer.fee == null) { const fullFee = await this.provider.getTransactionFee('Transfer', transfer.to, transfer.token); transfer.fee = fullFee.totalFee; } const signedTransferTransaction = await this.signSyncTransfer(transfer as any); return submitSignedTransaction(signedTransferTransaction, this.provider); } // ChangePubKey part override async signSetSigningKey(changePubKey: { feeToken: TokenLike; fee: BigNumberish; nonce: number; ethAuthType: ChangePubkeyTypes; batchHash?: string; validFrom?: number; validUntil?: number; }): Promise { const newPubKeyHash = await this.signer.pubKeyHash(); let ethAuthData; let ethSignature; if (changePubKey.ethAuthType === 'Onchain') { ethAuthData = { type: 'Onchain' }; } else if (changePubKey.ethAuthType === 'ECDSA') { await this.setRequiredAccountIdFromServer('ChangePubKey authorized by ECDSA.'); const changePubKeyMessage = getChangePubkeyMessage( newPubKeyHash, changePubKey.nonce, this.accountId, changePubKey.batchHash ); const ethSignature = (await this.ethMessageSigner().getEthMessageSignature(changePubKeyMessage)).signature; ethAuthData = { type: 'ECDSA', ethSignature, batchHash: changePubKey.batchHash }; } else if (changePubKey.ethAuthType === 'CREATE2') { const ethSigner = this.ethSigner(); if (ethSigner instanceof Create2WalletSigner) { const create2data = ethSigner.create2WalletData; ethAuthData = { type: 'CREATE2', creatorAddress: create2data.creatorAddress, saltArg: create2data.saltArg, codeHash: create2data.codeHash }; } else { throw new Error('CREATE2 wallet authentication is only available for CREATE2 wallets'); } } else if (changePubKey.ethAuthType === 'ECDSALegacyMessage') { await this.setRequiredAccountIdFromServer('ChangePubKey authorized by ECDSALegacyMessage.'); const changePubKeyMessage = getChangePubkeyLegacyMessage(newPubKeyHash, changePubKey.nonce, this.accountId); ethSignature = (await this.ethMessageSigner().getEthMessageSignature(changePubKeyMessage)).signature; } else { throw new Error('Unsupported SetSigningKey type'); } const changePubkeyTxUnsigned = Object.assign(changePubKey, { ethAuthData, ethSignature }); changePubkeyTxUnsigned.validFrom = changePubKey.validFrom || 0; changePubkeyTxUnsigned.validUntil = changePubKey.validUntil || MAX_TIMESTAMP; const changePubKeyTx = await this.getChangePubKey(changePubkeyTxUnsigned as any); return { tx: changePubKeyTx }; } override async setSigningKey(changePubKey: { feeToken: TokenLike; ethAuthType: ChangePubkeyTypes; fee?: BigNumberish; nonce?: Nonce; validFrom?: number; validUntil?: number; }): Promise { changePubKey.nonce = changePubKey.nonce != null ? await this.getNonce(changePubKey.nonce) : await this.getNonce(); if (changePubKey.fee == null) { changePubKey.fee = 0; if (changePubKey.ethAuthType === 'ECDSALegacyMessage') { const feeType = { ChangePubKey: { onchainPubkeyAuth: false } }; const fullFee = await this.provider.getTransactionFee(feeType, this.address(), changePubKey.feeToken); changePubKey.fee = fullFee.totalFee; } else { const feeType = { ChangePubKey: changePubKey.ethAuthType }; const fullFee = await this.provider.getTransactionFee(feeType, this.address(), changePubKey.feeToken); changePubKey.fee = fullFee.totalFee; } } const txData = await this.signSetSigningKey(changePubKey as any); const currentPubKeyHash = await this.getCurrentPubKeyHash(); if (currentPubKeyHash === (txData.tx as ChangePubKey).newPkHash) { throw new Error('Current signing key is already set'); } return submitSignedTransaction(txData, this.provider); } // Withdraw part override async signWithdrawFromSyncToEthereum(withdraw: { ethAddress: string; token: TokenLike; amount: BigNumberish; fee: BigNumberish; nonce: number; validFrom?: number; validUntil?: number; }): Promise { withdraw.validFrom = withdraw.validFrom || 0; withdraw.validUntil = withdraw.validUntil || MAX_TIMESTAMP; const signedWithdrawTransaction = await this.getWithdrawFromSyncToEthereum(withdraw as any); const stringAmount = BigNumber.from(withdraw.amount).isZero() ? null : this.provider.tokenSet.formatToken(withdraw.token, withdraw.amount); const stringFee = BigNumber.from(withdraw.fee).isZero() ? null : this.provider.tokenSet.formatToken(withdraw.token, withdraw.fee); const stringToken = this.provider.tokenSet.resolveTokenSymbol(withdraw.token); const ethereumSignature = unableToSign(this.ethSigner()) ? null : await this.ethMessageSigner().ethSignWithdraw({ stringAmount, stringFee, stringToken, ethAddress: withdraw.ethAddress, nonce: withdraw.nonce, accountId: this.accountId }); return { tx: signedWithdrawTransaction, ethereumSignature }; } override async withdrawFromSyncToEthereum(withdraw: { ethAddress: string; token: TokenLike; amount: BigNumberish; fee?: BigNumberish; nonce?: Nonce; fastProcessing?: boolean; validFrom?: number; validUntil?: number; }): Promise { withdraw.nonce = withdraw.nonce != null ? await this.getNonce(withdraw.nonce) : await this.getNonce(); if (withdraw.fee == null) { const feeType = withdraw.fastProcessing === true ? 'FastWithdraw' : 'Withdraw'; const fullFee = await this.provider.getTransactionFee(feeType, withdraw.ethAddress, withdraw.token); withdraw.fee = fullFee.totalFee; } const signedWithdrawTransaction = await this.signWithdrawFromSyncToEthereum(withdraw as any); return submitSignedTransaction(signedWithdrawTransaction, this.provider, withdraw.fastProcessing); } // Forced exit part override async signSyncForcedExit(forcedExit: { target: Address; token: TokenLike; fee: BigNumberish; nonce: number; validFrom?: number; validUntil?: number; }): Promise { const signedForcedExitTransaction = await this.getForcedExit(forcedExit); const stringFee = BigNumber.from(forcedExit.fee).isZero() ? null : this.provider.tokenSet.formatToken(forcedExit.token, forcedExit.fee); const stringToken = this.provider.tokenSet.resolveTokenSymbol(forcedExit.token); const ethereumSignature = unableToSign(this.ethSigner()) ? null : await this.ethMessageSigner().ethSignForcedExit({ stringToken, stringFee, target: forcedExit.target, nonce: forcedExit.nonce }); return { tx: signedForcedExitTransaction, ethereumSignature }; } override async syncForcedExit(forcedExit: { target: Address; token: TokenLike; fee?: BigNumberish; nonce?: Nonce; validFrom?: number; validUntil?: number; }): Promise { forcedExit.nonce = forcedExit.nonce != null ? await this.getNonce(forcedExit.nonce) : await this.getNonce(); if (forcedExit.fee == null) { const fullFee = await this.provider.getTransactionFee('ForcedExit', forcedExit.target, forcedExit.token); forcedExit.fee = fullFee.totalFee; } const signedForcedExitTransaction = await this.signSyncForcedExit(forcedExit as any); return submitSignedTransaction(signedForcedExitTransaction, this.provider); } // Swap part override async signOrder(orderData: { tokenSell: TokenLike; tokenBuy: TokenLike; ratio: TokenRatio | WeiRatio; amount: BigNumberish; recipient?: Address; nonce?: Nonce; validFrom?: number; validUntil?: number; }): Promise { const order = await this.getPartialOrder(orderData); const stringAmount = BigNumber.from(order.amount).isZero() ? null : this.provider.tokenSet.formatToken(order.tokenSell, order.amount); const stringTokenSell = await this.provider.getTokenSymbol(order.tokenSell); const stringTokenBuy = await this.provider.getTokenSymbol(order.tokenBuy); const ethereumSignature = unableToSign(this.ethSigner()) ? null : await this.ethMessageSigner().ethSignOrder({ amount: stringAmount, tokenSell: stringTokenSell, tokenBuy: stringTokenBuy, nonce: order.nonce, recipient: order.recipient, ratio: order.ratio }); order.ethSignature = ethereumSignature; return order; } override async signSyncSwap(swap: { orders: [Order, Order]; feeToken: number; amounts: [BigNumberish, BigNumberish]; nonce: number; fee: BigNumberish; }): Promise { const signedSwapTransaction = await this.getSwap(swap); const stringFee = BigNumber.from(swap.fee).isZero() ? null : this.provider.tokenSet.formatToken(swap.feeToken, swap.fee); const stringToken = this.provider.tokenSet.resolveTokenSymbol(swap.feeToken); const ethereumSignature = unableToSign(this.ethSigner()) ? null : await this.ethMessageSigner().ethSignSwap({ fee: stringFee, feeToken: stringToken, nonce: swap.nonce }); return { tx: signedSwapTransaction, ethereumSignature: [ ethereumSignature, swap.orders[0].ethSignature || null, swap.orders[1].ethSignature || null ] }; } override async syncSwap(swap: { orders: [Order, Order]; feeToken: TokenLike; amounts?: [BigNumberish, BigNumberish]; nonce?: number; fee?: BigNumberish; }): Promise { swap.nonce = swap.nonce != null ? await this.getNonce(swap.nonce) : await this.getNonce(); if (swap.fee == null) { const fullFee = await this.provider.getTransactionFee('Swap', this.address(), swap.feeToken); swap.fee = fullFee.totalFee; } if (swap.amounts == null) { let amount0 = BigNumber.from(swap.orders[0].amount); let amount1 = BigNumber.from(swap.orders[1].amount); if (!amount0.eq(0) && !amount1.eq(0)) { swap.amounts = [amount0, amount1]; } else { throw new Error('If amounts in orders are implicit, you must specify them during submission'); } } const signedSwapTransaction = await this.signSyncSwap(swap as any); return submitSignedTransaction(signedSwapTransaction, this.provider); } // Mint NFT part override async signMintNFT(mintNFT: { recipient: string; contentHash: string; feeToken: TokenLike; fee: BigNumberish; nonce: number; }): Promise { const signedMintNFTTransaction = await this.getMintNFT(mintNFT as any); const stringFee = BigNumber.from(mintNFT.fee).isZero() ? null : this.provider.tokenSet.formatToken(mintNFT.feeToken, mintNFT.fee); const stringFeeToken = this.provider.tokenSet.resolveTokenSymbol(mintNFT.feeToken); const ethereumSignature = unableToSign(this.ethSigner()) ? null : await this.ethMessageSigner().ethSignMintNFT({ stringFeeToken, stringFee, recipient: mintNFT.recipient, contentHash: mintNFT.contentHash, nonce: mintNFT.nonce }); return { tx: signedMintNFTTransaction, ethereumSignature }; } override async mintNFT(mintNFT: { recipient: Address; contentHash: ethers.BytesLike; feeToken: TokenLike; fee?: BigNumberish; nonce?: Nonce; }): Promise { mintNFT.nonce = mintNFT.nonce != null ? await this.getNonce(mintNFT.nonce) : await this.getNonce(); mintNFT.contentHash = ethers.utils.hexlify(mintNFT.contentHash); if (mintNFT.fee == null) { const fullFee = await this.provider.getTransactionFee('MintNFT', mintNFT.recipient, mintNFT.feeToken); mintNFT.fee = fullFee.totalFee; } const signedMintNFTTransaction = await this.signMintNFT(mintNFT as any); return submitSignedTransaction(signedMintNFTTransaction, this.provider, false); } // Withdraw NFT part override async signWithdrawNFT(withdrawNFT: { to: string; token: number; feeToken: TokenLike; fee: BigNumberish; nonce: number; validFrom?: number; validUntil?: number; }): Promise { withdrawNFT.validFrom = withdrawNFT.validFrom || 0; withdrawNFT.validUntil = withdrawNFT.validUntil || MAX_TIMESTAMP; const signedWithdrawNFTTransaction = await this.getWithdrawNFT(withdrawNFT as any); const stringFee = BigNumber.from(withdrawNFT.fee).isZero() ? null : this.provider.tokenSet.formatToken(withdrawNFT.feeToken, withdrawNFT.fee); const stringFeeToken = this.provider.tokenSet.resolveTokenSymbol(withdrawNFT.feeToken); const ethereumSignature = unableToSign(this.ethSigner()) ? null : await this.ethMessageSigner().ethSignWithdrawNFT({ token: withdrawNFT.token, to: withdrawNFT.to, stringFee, stringFeeToken, nonce: withdrawNFT.nonce }); return { tx: signedWithdrawNFTTransaction, ethereumSignature }; } override async withdrawNFT(withdrawNFT: { to: string; token: number; feeToken: TokenLike; fee?: BigNumberish; nonce?: Nonce; fastProcessing?: boolean; validFrom?: number; validUntil?: number; }): Promise { withdrawNFT.nonce = withdrawNFT.nonce != null ? await this.getNonce(withdrawNFT.nonce) : await this.getNonce(); if (!isNFT(withdrawNFT.token)) { throw new Error('This token ID does not correspond to an NFT'); } if (withdrawNFT.fee == null) { const feeType = withdrawNFT.fastProcessing === true ? 'FastWithdrawNFT' : 'WithdrawNFT'; const fullFee = await this.provider.getTransactionFee(feeType, withdrawNFT.to, withdrawNFT.feeToken); withdrawNFT.fee = fullFee.totalFee; } const signedWithdrawNFTTransaction = await this.signWithdrawNFT(withdrawNFT as any); return submitSignedTransaction(signedWithdrawNFTTransaction, this.provider, withdrawNFT.fastProcessing); } // Transfer NFT part override async syncTransferNFT(transfer: { to: Address; token: NFT; feeToken: TokenLike; fee?: BigNumberish; nonce?: Nonce; validFrom?: number; validUntil?: number; }): Promise { transfer.nonce = transfer.nonce != null ? await this.getNonce(transfer.nonce) : await this.getNonce(); let fee: BigNumberish; if (transfer.fee == null) { fee = await this.provider.getTransactionsBatchFee( ['Transfer', 'Transfer'], [transfer.to, this.address()], transfer.feeToken ); } else { fee = transfer.fee; } const txNFT = { to: transfer.to, token: transfer.token.id, amount: 1, fee: 0 }; const txFee = { to: this.address(), token: transfer.feeToken, amount: 0, fee }; return await this.syncMultiTransfer([txNFT, txFee]); } // Multi-transfer part // Note: this method signature requires to specify fee in each transaction. // For details, see the comment on this method in `AbstractWallet` class. override async syncMultiTransfer( transfers: { to: Address; token: TokenLike; amount: BigNumberish; fee: BigNumberish; nonce?: Nonce; validFrom?: number; validUntil?: number; }[] ): Promise { if (!this.signer) { throw new Error('ZKSync signer is required for sending zksync transactions.'); } if (transfers.length == 0) return []; await this.setRequiredAccountIdFromServer('Transfer funds'); let batch = []; let messages: string[] = []; let nextNonce = transfers[0].nonce != null ? await this.getNonce(transfers[0].nonce) : await this.getNonce(); const batchNonce = nextNonce; for (let i = 0; i < transfers.length; i++) { const transfer = transfers[i]; const nonce = nextNonce; nextNonce += 1; const tx: Transfer = await this.getTransfer({ to: transfer.to, token: transfer.token, amount: transfer.amount, fee: transfer.fee, nonce, validFrom: transfer.validFrom || 0, validUntil: transfer.validUntil || MAX_TIMESTAMP }); const message = await this.getTransferEthMessagePart(transfer); messages.push(message); batch.push({ tx, signature: null }); } messages.push(`Nonce: ${batchNonce}`); const message = messages.filter((part) => part.length != 0).join('\n'); const ethSignatures = unableToSign(this.ethSigner()) ? [] : [await this.ethMessageSigner().getEthMessageSignature(message)]; const transactionHashes = await this.provider.submitTxsBatch(batch, ethSignatures); return transactionHashes.map((txHash, idx) => new Transaction(batch[idx], txHash, this.provider)); } // **************** // Internal methods // protected async getTransfer(transfer: { to: Address; token: TokenLike; amount: BigNumberish; fee: BigNumberish; nonce: number; validFrom: number; validUntil: number; }): Promise { if (!this.signer) { throw new Error('ZKSync signer is required for sending zksync transactions.'); } await this.setRequiredAccountIdFromServer('Transfer funds'); const tokenId = this.provider.tokenSet.resolveTokenId(transfer.token); const transactionData = { accountId: this.accountId, from: this.address(), to: transfer.to, tokenId, amount: transfer.amount, fee: transfer.fee, nonce: transfer.nonce, validFrom: transfer.validFrom, validUntil: transfer.validUntil }; return this.signer.signSyncTransfer(transactionData); } protected async getChangePubKey(changePubKey: { feeToken: TokenLike; fee: BigNumberish; nonce: number; ethAuthData?: ChangePubKeyOnchain | ChangePubKeyECDSA | ChangePubKeyCREATE2; ethSignature?: string; validFrom: number; validUntil: number; }): Promise { if (!this.signer) { throw new Error('ZKSync signer is required for current pubkey calculation.'); } const feeTokenId = this.provider.tokenSet.resolveTokenId(changePubKey.feeToken); const newPkHash = await this.signer.pubKeyHash(); await this.setRequiredAccountIdFromServer('Set Signing Key'); const changePubKeyTx: ChangePubKey = await this.signer.signSyncChangePubKey({ accountId: this.accountId, account: this.address(), newPkHash, nonce: changePubKey.nonce, feeTokenId, fee: BigNumber.from(changePubKey.fee).toString(), ethAuthData: changePubKey.ethAuthData, ethSignature: changePubKey.ethSignature, validFrom: changePubKey.validFrom, validUntil: changePubKey.validUntil }); return changePubKeyTx; } protected async getWithdrawFromSyncToEthereum(withdraw: { ethAddress: string; token: TokenLike; amount: BigNumberish; fee: BigNumberish; nonce: number; validFrom: number; validUntil: number; }): Promise { if (!this.signer) { throw new Error('ZKSync signer is required for sending zksync transactions.'); } await this.setRequiredAccountIdFromServer('Withdraw funds'); const tokenId = this.provider.tokenSet.resolveTokenId(withdraw.token); const transactionData = { accountId: this.accountId, from: this.address(), ethAddress: withdraw.ethAddress, tokenId, amount: withdraw.amount, fee: withdraw.fee, nonce: withdraw.nonce, validFrom: withdraw.validFrom, validUntil: withdraw.validUntil }; return await this.signer.signSyncWithdraw(transactionData); } protected async getForcedExit(forcedExit: { target: Address; token: TokenLike; fee: BigNumberish; nonce: number; validFrom?: number; validUntil?: number; }): Promise { if (!this.signer) { throw new Error('ZKSync signer is required for sending zksync transactions.'); } await this.setRequiredAccountIdFromServer('perform a Forced Exit'); const tokenId = this.provider.tokenSet.resolveTokenId(forcedExit.token); const transactionData = { initiatorAccountId: this.accountId, target: forcedExit.target, tokenId, fee: forcedExit.fee, nonce: forcedExit.nonce, validFrom: forcedExit.validFrom || 0, validUntil: forcedExit.validUntil || MAX_TIMESTAMP }; return await this.signer.signSyncForcedExit(transactionData); } protected async getSwap(swap: { orders: [Order, Order]; feeToken: number; amounts: [BigNumberish, BigNumberish]; nonce: number; fee: BigNumberish; }): Promise { if (!this.signer) { throw new Error('zkSync signer is required for swapping funds'); } await this.setRequiredAccountIdFromServer('Swap submission'); const feeToken = this.provider.tokenSet.resolveTokenId(swap.feeToken); return this.signer.signSyncSwap({ ...swap, submitterId: await this.getAccountId(), submitterAddress: this.address(), feeToken }); } protected async getMintNFT(mintNFT: { recipient: string; contentHash: string; feeToken: TokenLike; fee: BigNumberish; nonce: number; }): Promise { if (!this.signer) { throw new Error('ZKSync signer is required for sending zksync transactions.'); } await this.setRequiredAccountIdFromServer('MintNFT'); const feeTokenId = this.provider.tokenSet.resolveTokenId(mintNFT.feeToken); const transactionData = { creatorId: this.accountId, creatorAddress: this.address(), recipient: mintNFT.recipient, contentHash: mintNFT.contentHash, feeTokenId, fee: mintNFT.fee, nonce: mintNFT.nonce }; return await this.signer.signMintNFT(transactionData); } protected async getWithdrawNFT(withdrawNFT: { to: string; token: TokenLike; feeToken: TokenLike; fee: BigNumberish; nonce: number; validFrom: number; validUntil: number; }): Promise { if (!this.signer) { throw new Error('ZKSync signer is required for sending zksync transactions.'); } await this.setRequiredAccountIdFromServer('WithdrawNFT'); const tokenId = this.provider.tokenSet.resolveTokenId(withdrawNFT.token); const feeTokenId = this.provider.tokenSet.resolveTokenId(withdrawNFT.feeToken); const transactionData = { accountId: this.accountId, from: this.address(), to: withdrawNFT.to, tokenId, feeTokenId, fee: withdrawNFT.fee, nonce: withdrawNFT.nonce, validFrom: withdrawNFT.validFrom, validUntil: withdrawNFT.validUntil }; return await this.signer.signWithdrawNFT(transactionData); } getWithdrawNFTEthMessagePart(withdrawNFT: { to: string; token: number; feeToken: TokenLike; fee: BigNumberish; }): string { const stringFee = BigNumber.from(withdrawNFT.fee).isZero() ? null : this.provider.tokenSet.formatToken(withdrawNFT.feeToken, withdrawNFT.fee); const stringFeeToken = this.provider.tokenSet.resolveTokenSymbol(withdrawNFT.feeToken); return this.ethMessageSigner().getWithdrawNFTEthMessagePart({ token: withdrawNFT.token, to: withdrawNFT.to, stringFee, stringFeeToken }); } // The following methods are needed in case user decided to build // a message for the batch himself (e.g. in case of multi-authors batch). // It might seem that these belong to ethMessageSigner, however, we have // to resolve the token and format amount/fee before constructing the // transaction. async getTransferEthMessagePart(transfer: { to: Address; token: TokenLike; amount: BigNumberish; fee: BigNumberish; }): Promise { const stringAmount = BigNumber.from(transfer.amount).isZero() ? null : this.provider.tokenSet.formatToken(transfer.token, transfer.amount); const stringFee = BigNumber.from(transfer.fee).isZero() ? null : this.provider.tokenSet.formatToken(transfer.token, transfer.fee); const stringToken = await this.provider.getTokenSymbol(transfer.token); return this.ethMessageSigner().getTransferEthMessagePart({ stringAmount, stringFee, stringToken, to: transfer.to }); } getWithdrawEthMessagePart(withdraw: { ethAddress: string; token: TokenLike; amount: BigNumberish; fee: BigNumberish; }): string { const stringAmount = BigNumber.from(withdraw.amount).isZero() ? null : this.provider.tokenSet.formatToken(withdraw.token, withdraw.amount); const stringFee = BigNumber.from(withdraw.fee).isZero() ? null : this.provider.tokenSet.formatToken(withdraw.token, withdraw.fee); const stringToken = this.provider.tokenSet.resolveTokenSymbol(withdraw.token); return this.ethMessageSigner().getWithdrawEthMessagePart({ stringAmount, stringFee, stringToken, ethAddress: withdraw.ethAddress }); } getChangePubKeyEthMessagePart(changePubKey: { pubKeyHash: string; feeToken: TokenLike; fee: BigNumberish; }): string { const stringFee = BigNumber.from(changePubKey.fee).isZero() ? null : this.provider.tokenSet.formatToken(changePubKey.feeToken, changePubKey.fee); const stringToken = this.provider.tokenSet.resolveTokenSymbol(changePubKey.feeToken); return this.ethMessageSigner().getChangePubKeyEthMessagePart({ pubKeyHash: changePubKey.pubKeyHash, stringToken, stringFee }); } getMintNFTEthMessagePart(mintNFT: { recipient: string; contentHash: string; feeToken: TokenLike; fee: BigNumberish; }): string { const stringFee = BigNumber.from(mintNFT.fee).isZero() ? null : this.provider.tokenSet.formatToken(mintNFT.feeToken, mintNFT.fee); const stringFeeToken = this.provider.tokenSet.resolveTokenSymbol(mintNFT.feeToken); return this.ethMessageSigner().getMintNFTEthMessagePart({ stringFeeToken, stringFee, recipient: mintNFT.recipient, contentHash: mintNFT.contentHash }); } getSwapEthSignMessagePart(swap: { fee: BigNumberish; feeToken: TokenLike }): string { const stringFee = BigNumber.from(swap.fee).isZero() ? null : this.provider.tokenSet.formatToken(swap.feeToken, swap.fee); const stringToken = this.provider.tokenSet.resolveTokenSymbol(swap.feeToken); return this.ethMessageSigner().getSwapEthSignMessagePart({ fee: stringFee, feeToken: stringToken }); } getForcedExitEthMessagePart(forcedExit: { target: Address; token: TokenLike; fee: BigNumberish }): string { const stringFee = BigNumber.from(forcedExit.fee).isZero() ? null : this.provider.tokenSet.formatToken(forcedExit.token, forcedExit.fee); const stringToken = this.provider.tokenSet.resolveTokenSymbol(forcedExit.token); return this.ethMessageSigner().getForcedExitEthMessagePart({ stringToken, stringFee, target: forcedExit.target }); } async getPartialOrder(order: { tokenSell: TokenLike; tokenBuy: TokenLike; ratio: TokenRatio | WeiRatio; amount: BigNumberish; recipient?: Address; nonce?: Nonce; validFrom?: number; validUntil?: number; }): Promise { if (!this.signer) { throw new Error('zkSync signer is required for signing an order'); } await this.setRequiredAccountIdFromServer('Swap order'); const nonce = order.nonce != null ? await this.getNonce(order.nonce) : await this.getNonce(); const recipient = order.recipient || this.address(); let ratio: Ratio; const sell = order.tokenSell; const buy = order.tokenBuy; if (!order.ratio[sell] || !order.ratio[buy]) { throw new Error(`Wrong tokens in the ratio object: should be ${sell} and ${buy}`); } if (order.ratio.type == 'Wei') { ratio = [order.ratio[sell], order.ratio[buy]]; } else if (order.ratio.type == 'Token') { ratio = [ this.provider.tokenSet.parseToken(sell, order.ratio[sell].toString()), this.provider.tokenSet.parseToken(buy, order.ratio[buy].toString()) ]; } const partialOrder = await this.signer.signSyncOrder({ accountId: this.accountId, recipient, nonce, amount: order.amount || BigNumber.from(0), tokenSell: this.provider.tokenSet.resolveTokenId(order.tokenSell), tokenBuy: this.provider.tokenSet.resolveTokenId(order.tokenBuy), validFrom: order.validFrom || 0, validUntil: order.validUntil || MAX_TIMESTAMP, ratio }); return partialOrder; } }