Skip to content

Commit

Permalink
feat: sequential EVM signer
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed Feb 13, 2025
1 parent e76e74c commit 9681289
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 2 deletions.
5 changes: 5 additions & 0 deletions lib/db/models/PendingEthereumTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class PendingEthereumTransaction
implements PendingEthereumTransactionType
{
public hash!: string;
public etherAmount!: bigint;
public nonce!: number;

public static load = (sequelize: Sequelize): void => {
Expand All @@ -20,6 +21,10 @@ class PendingEthereumTransaction
primaryKey: true,
allowNull: false,
},
etherAmount: {
type: new DataTypes.DECIMAL(),
allowNull: false,
},
nonce: {
type: new DataTypes.INTEGER(),
unique: true,
Expand Down
10 changes: 10 additions & 0 deletions lib/db/repositories/PendingEthereumTransactionRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,22 @@ class PendingEthereumTransactionRepository {
return nonce + 1;
};

public static getTotalSent = async (): Promise<bigint> => {
return BigInt(
(await PendingEthereumTransaction.sum<number, PendingEthereumTransaction>(
'etherAmount',
)) ?? 0,
);
};

public static addTransaction = (
hash: string,
etherAmount: bigint,
nonce: number,
): Promise<PendingEthereumTransaction> => {
return PendingEthereumTransaction.create({
hash,
etherAmount,
nonce,
});
};
Expand Down
5 changes: 4 additions & 1 deletion lib/wallet/ethereum/EthereumManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import ConsolidatedEventHandler from './ConsolidatedEventHandler';
import EthereumTransactionTracker from './EthereumTransactionTracker';
import { Ethereum, NetworkDetails, Rsk } from './EvmNetworks';
import InjectedProvider from './InjectedProvider';
import SequentialSigner from './SequentialSigner';
import Contracts from './contracts/Contracts';

type Network = {
Expand Down Expand Up @@ -81,7 +82,9 @@ class EthereumManager {
name: this.config.networkName || network.name,
};

this.signer = EthersWallet.fromPhrase(mnemonic).connect(this.provider);
this.signer = new SequentialSigner(
EthersWallet.fromPhrase(mnemonic),
).connect(this.provider);
this.address = await this.signer.getAddress();

this.logger.verbose(
Expand Down
61 changes: 61 additions & 0 deletions lib/wallet/ethereum/SequentialSigner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import AsyncLock from 'async-lock';
import {
AbstractSigner,
Provider,
Signer,
TransactionRequest,
TypedDataDomain,
TypedDataField,
} from 'ethers';
import PendingEthereumTransactionRepository from '../../db/repositories/PendingEthereumTransactionRepository';

class SequentialSigner extends AbstractSigner {
private static readonly txLock = 'txLock';

private readonly lock = new AsyncLock();

constructor(private signer: AbstractSigner) {
super();
}

public getAddress = (): Promise<string> => this.signer.getAddress();

public connect = (provider: null | Provider): Signer => {
this.signer = this.signer.connect(provider);
return this;
};

public signTransaction = async (tx: TransactionRequest): Promise<string> => {
return await this.lock.acquire(SequentialSigner.txLock, async () => {
if (tx.value !== undefined && tx.value !== null) {
const [ourBalance, pendingTxsValue] = await Promise.all([
this.signer.provider!.getBalance(await this.getAddress()),
PendingEthereumTransactionRepository.getTotalSent(),
]);

if (ourBalance - pendingTxsValue < BigInt(tx.value)) {
throw new Error('insufficient balance');
}
}

if (tx.nonce === undefined || tx.nonce === null) {
tx.nonce = await this.signer.provider!.getTransactionCount(
await this.getAddress(),
);
}

return await this.signer.signTransaction(tx);
});
};

public signMessage = (message: string | Uint8Array): Promise<string> =>
this.signer.signMessage(message);

public signTypedData = (
domain: TypedDataDomain,
types: Record<string, Array<TypedDataField>>,
value: Record<string, any>,
): Promise<string> => this.signer.signTypedData(domain, types, value);
}

export default SequentialSigner;
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,40 @@ describe('PendingEthereumTransactionRepository', () => {
});

test('should get highest nonce when there are pending transactions', async () => {
await PendingEthereumTransactionRepository.addTransaction('txHash', 20);
await PendingEthereumTransactionRepository.addTransaction(
'txHash',
1n,
20,
);

await expect(
PendingEthereumTransactionRepository.getHighestNonce(),
).resolves.toEqual(21);
});
});

describe('getTotalSent', () => {
test('should get total sent when there are no pending transactions', async () => {
await expect(
PendingEthereumTransactionRepository.getTotalSent(),
).resolves.toEqual(BigInt(0));
});

test('should get total sent when there are pending transactions', async () => {
await PendingEthereumTransactionRepository.addTransaction(
'txHash',
21n,
20,
);
await PendingEthereumTransactionRepository.addTransaction(
'txHash2',
21n,
21,
);

await expect(
PendingEthereumTransactionRepository.getTotalSent(),
).resolves.toEqual(BigInt(42));
});
});
});

0 comments on commit 9681289

Please sign in to comment.