-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #79 from ethereum-optimism/02-23-feat_add_tx-sende…
…r_class_and_fix_private_key_deployments_causing_replacement_tx feat: add tx-sender class and fix private key deployments causing replacement tx
- Loading branch information
Showing
8 changed files
with
195 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@eth-optimism/super-cli": patch | ||
--- | ||
|
||
fixed private key deployments causing replacement tx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
export interface AsyncQueueItem<T, R> { | ||
item: T; | ||
resolve: (value: R) => void; | ||
reject: (error: any) => void; | ||
} | ||
|
||
// Simple queue that processes items sequentially | ||
export class AsyncQueue<T, R> { | ||
private queue: AsyncQueueItem<T, R>[] = []; | ||
private processing = false; | ||
private processFn: (item: T) => Promise<R>; | ||
|
||
constructor(processFn: (item: T) => Promise<R>) { | ||
this.processFn = processFn; | ||
} | ||
|
||
public enqueue(item: T): Promise<R> { | ||
return new Promise((resolve, reject) => { | ||
this.queue.push({item, resolve, reject}); | ||
this.processQueue(); | ||
}); | ||
} | ||
|
||
private async processQueue(): Promise<void> { | ||
if (this.processing) { | ||
return; | ||
} | ||
this.processing = true; | ||
|
||
try { | ||
while (this.queue.length > 0) { | ||
const queueItem = this.queue.shift(); | ||
if (!queueItem) continue; | ||
try { | ||
const result = await this.processFn(queueItem.item); | ||
queueItem.resolve(result); | ||
} catch (error) { | ||
queueItem.reject(error); | ||
} | ||
} | ||
} finally { | ||
this.processing = false; | ||
if (this.queue.length > 0) { | ||
this.processQueue(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import {AsyncQueue} from '@/util/AsyncQueue'; | ||
import {chainById} from '@/util/chains/chains'; | ||
import {TransactionTask} from '@/util/transactionTask'; | ||
import {sendTransaction, waitForTransactionReceipt} from '@wagmi/core'; | ||
import { | ||
createWalletClient, | ||
Hash, | ||
http, | ||
PrivateKeyAccount, | ||
zeroAddress, | ||
} from 'viem'; | ||
import {Config} from 'wagmi'; | ||
|
||
type WalletRpcUrlFactory = (chainId: number) => string; | ||
|
||
type TxSenderTx = TransactionTask; | ||
|
||
export interface TxSender { | ||
sendTx: (tx: TxSenderTx) => Promise<Hash>; | ||
} | ||
|
||
export const createTxSenderFromPrivateKeyAccount = ( | ||
config: Config, | ||
account: PrivateKeyAccount, | ||
): TxSender => { | ||
const queueByChainId = {} as Record<number, AsyncQueue<TxSenderTx, Hash>>; | ||
|
||
config.chains.forEach(chain => { | ||
queueByChainId[chain.id] = new AsyncQueue<TxSenderTx, Hash>(async tx => { | ||
const hash = await sendTransaction(config, { | ||
chainId: tx.chainId, | ||
to: tx.to, | ||
data: tx.data, | ||
account, | ||
}); | ||
// Prevent replacement tx. | ||
await waitForTransactionReceipt(config, { | ||
hash, | ||
chainId: tx.chainId, | ||
pollingInterval: 1000, | ||
}); | ||
|
||
return hash; | ||
}); | ||
}); | ||
|
||
return { | ||
sendTx: async tx => { | ||
if (!queueByChainId[tx.chainId]) { | ||
throw new Error(`Chain tx queue for ${tx.chainId} not found`); | ||
} | ||
return await queueByChainId[tx.chainId]!.enqueue(tx); | ||
}, | ||
}; | ||
}; | ||
|
||
export const createTxSenderFromCustomWalletRpc = ( | ||
getRpcUrl: WalletRpcUrlFactory, | ||
): TxSender => { | ||
return { | ||
sendTx: async tx => { | ||
const chain = chainById[tx.chainId]; | ||
if (!chain) { | ||
throw new Error(`Chain not found for ${tx.chainId}`); | ||
} | ||
const walletClient = createWalletClient({ | ||
transport: http(getRpcUrl(tx.chainId)), | ||
}); | ||
return await walletClient.sendTransaction({ | ||
to: tx.to, | ||
data: tx.data, | ||
account: zeroAddress, // will be ignored | ||
chain: chain, | ||
}); | ||
}, | ||
}; | ||
}; |