Skip to content

Commit 57430f2

Browse files
feat(txm): transactions with value
1 parent 69f4e43 commit 57430f2

11 files changed

Lines changed: 373 additions & 237 deletions

File tree

bun.lock

Lines changed: 287 additions & 219 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contracts/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@
2525
"account-abstraction": "https://github.com/eth-infinitism/account-abstraction#v0.7.0",
2626
"forge-std": "https://github.com/foundry-rs/forge-std.git#v1.9.6",
2727
"kernel": "https://github.com/zerodevapp/kernel#v3.1",
28+
"node-jq": "^6.0.1",
2829
"solady": "0.0.237"
2930
},
3031
"devDependencies": {
31-
"@happy.tech/happybuild": "workspace:0.1.1",
3232
"@happy.tech/configs": "workspace:0.1.0",
33+
"@happy.tech/happybuild": "workspace:0.1.1",
3334
"@openzeppelin/upgrades-core": "^1.36.0",
3435
"@types/node": "^22.0.2",
35-
"node-jq": "^6.0.1",
3636
"permissionless": "^0.2.0",
3737
"solhint": "^5.0.5",
3838
"viem": "^2.21.53"

packages/txm/lib/GasEstimator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class DefaultGasLimitEstimator implements GasEstimator {
7373
account: transactionManager.viemWallet.account,
7474
to: transaction.address,
7575
data: transaction.calldata,
76-
value: 0n,
76+
value: transaction.value,
7777
})
7878

7979
if (gasResult.isErr()) {

packages/txm/lib/Transaction.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type Hex, type UUID, bigIntReplacer, bigIntReviver, createUUID } from "@happy.tech/common"
1+
import { type Hex, type UUID, bigIntReplacer, bigIntReviver, bigIntToZeroPadded, createUUID } from "@happy.tech/common"
22
import { context, trace } from "@opentelemetry/api"
33
import type { Insertable, Selectable } from "kysely"
44
import { type Address, type ContractFunctionArgs, type Hash, encodeFunctionData } from "viem"
@@ -67,6 +67,11 @@ interface TransactionConstructorBaseConfig {
6767
* The address of the contract that will be called
6868
*/
6969
address: Address
70+
/**
71+
* The value of the transaction in wei
72+
* Defaults to 0n
73+
*/
74+
value?: bigint
7075
/**
7176
* The deadline of the transaction in seconds (optional)
7277
* This is used to try to cancel the transaction if it is not included in a block after the deadline to save gas
@@ -111,6 +116,8 @@ export class Transaction {
111116

112117
readonly calldata: Hex
113118

119+
readonly value: bigint
120+
114121
readonly deadline: number | undefined
115122

116123
status: TransactionStatus
@@ -158,6 +165,7 @@ export class Transaction {
158165
this.from = config.from
159166
this.chainId = config.chainId
160167
this.address = config.address
168+
this.value = config.value ?? 0n
161169
this.deadline = config.deadline
162170
this.status = config.status ?? TransactionStatus.Pending
163171
this.attempts = config.attempts ?? []
@@ -270,6 +278,7 @@ export class Transaction {
270278
address: this.address,
271279
functionName: this.functionName,
272280
contractName: this.contractName,
281+
value: bigIntToZeroPadded(this.value), // We convert the bigint value to a zero-padded string because 'value' can exceed the numeric limits of Number
273282
calldata: this.calldata,
274283
args: this.args ? JSON.stringify(this.args, bigIntReplacer) : undefined,
275284
deadline: this.deadline,
@@ -286,6 +295,7 @@ export class Transaction {
286295
return new Transaction(
287296
{
288297
...row,
298+
value: BigInt(row.value),
289299
args: row.args ? JSON.parse(row.args, bigIntReviver) : undefined,
290300
attempts: JSON.parse(row.attempts, bigIntReviver),
291301
collectionBlock: row.collectionBlock ? BigInt(row.collectionBlock) : undefined,

packages/txm/lib/TransactionManager.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export type TransactionManagerConfig = {
122122
/** The private key of the account used for signing transactions. */
123123
privateKey: Hex
124124

125-
gas: {
125+
gas?: {
126126
/** Optional EIP-1559 parameters. If not provided, defaults to the OP stack's stock parameters. */
127127
eip1559?: EIP1559Parameters
128128
/**
@@ -179,7 +179,7 @@ export type TransactionManagerConfig = {
179179
* This is a record of aliases to ABIs. The aliases are used to reference the ABIs in the
180180
* transactions.
181181
*/
182-
abis: Record<string, Abi>
182+
abis?: Record<string, Abi>
183183

184184
/**
185185
* The expected interval (in seconds) for the creation of a new block on the blockchain.
@@ -402,14 +402,14 @@ export class TransactionManager {
402402
this.rpcLivenessMonitor = new RpcLivenessMonitor(this)
403403

404404
this.chainId = _config.chainId
405-
this.eip1559 = _config.gas.eip1559 ?? opStackDefaultEIP1559Parameters
406-
this.abiManager = new ABIManager(_config.abis)
407-
408-
this.baseFeeMargin = _config.gas.baseFeePercentageMargin ?? 20n
409-
this.maxPriorityFeePerGas = _config.gas.maxPriorityFeePerGas
410-
this.minPriorityFeePerGas = _config.gas.minPriorityFeePerGas
411-
this.priorityFeeTargetPercentile = _config.gas.priorityFeeTargetPercentile ?? 50
412-
this.priorityFeeAnalysisBlocks = _config.gas.priorityFeeAnalysisBlocks ?? 2
405+
this.eip1559 = _config.gas?.eip1559 ?? opStackDefaultEIP1559Parameters
406+
this.abiManager = new ABIManager(_config.abis ?? {})
407+
408+
this.baseFeeMargin = _config.gas?.baseFeePercentageMargin ?? 20n
409+
this.maxPriorityFeePerGas = _config.gas?.maxPriorityFeePerGas
410+
this.minPriorityFeePerGas = _config.gas?.minPriorityFeePerGas
411+
this.priorityFeeTargetPercentile = _config.gas?.priorityFeeTargetPercentile ?? 50
412+
this.priorityFeeAnalysisBlocks = _config.gas?.priorityFeeAnalysisBlocks ?? 2
413413

414414
this.rpcAllowDebug = _config.rpc.allowDebug ?? false
415415
this.blockTime = _config.blockTime ?? 2n

packages/txm/lib/TransactionSubmitter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export class TransactionSubmitter {
128128
from: this.txmgr.viemWallet.account.address,
129129
to: transaction.address,
130130
data: transaction.calldata,
131-
value: 0n,
131+
value: transaction.value,
132132
nonce: attempt.nonce,
133133
maxFeePerGas: attempt.maxFeePerGas,
134134
maxPriorityFeePerGas: attempt.maxPriorityFeePerGas,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Kysely } from "kysely"
2+
import type { Database } from "../types"
3+
4+
export async function up(db: Kysely<Database>) {
5+
await db.schema.alterTable("transaction").addColumn("value", "text").execute()
6+
}
7+
8+
export const migration20250421120000 = { up }

packages/txm/lib/db/migrations/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { migration20241111223000 } from "./Migration20241111223000"
33
import { migration20241205104400 } from "./Migration20241205104400"
44
import { migration20250121110600 } from "./Migration20250121110600"
55
import { migration20250410123000 } from "./Migration20250410123000"
6+
import { migration20250421120000 } from "./Migration20250421120000"
67

78
export const migrations = {
89
"20241111163800": migration20241111163800,
910
"20241111223000": migration20241111223000,
1011
"20241205104400": migration20241205104400,
1112
"20250121110600": migration20250121110600,
1213
"20250410123000": migration20250410123000,
14+
"20250421120000": migration20250421120000,
1315
}

packages/txm/lib/db/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface TransactionTable {
77
from: Address
88
chainId: number
99
address: Address
10+
value: string
1011
functionName: string | undefined
1112
args: string | undefined
1213
contractName: string | undefined

packages/txm/test/txm.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
import { deployMockContracts } from "./utils/contracts"
3636
import { assertIsDefined, assertIsOk, assertReceiptReverted, assertReceiptSuccess } from "./utils/customAsserts"
3737
import { cleanDB, getPersistedTransaction } from "./utils/db"
38+
import { bigIntToZeroPadded } from "@happy.tech/common"
3839

3940
const retryManager = new TestRetryManager()
4041

@@ -612,6 +613,42 @@ test("Transaction cancelled due to deadline passing", async () => {
612613
})
613614
})
614615

616+
test("Execute a transaction with value", async () => {
617+
const value = BigInt(1000)
618+
const transactionToSend = await txm.createTransaction({
619+
address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
620+
calldata: "0x",
621+
value,
622+
})
623+
624+
transactionQueue.push(transactionToSend)
625+
626+
await mineBlock(2)
627+
628+
const transactionExecuted = await txm.getTransaction(transactionToSend.intentId)
629+
630+
if (!assertIsOk(transactionExecuted)) return
631+
632+
const transactionExecutedValue = transactionExecuted.value
633+
634+
if (!assertIsDefined(transactionExecutedValue)) return
635+
636+
const blockchainTransaction = await directBlockchainClient.getTransaction({
637+
hash: transactionExecutedValue.attempts[0].hash,
638+
})
639+
640+
const blockchainTransactionReceipt = await directBlockchainClient.getTransactionReceipt({
641+
hash: blockchainTransaction.hash,
642+
})
643+
644+
const persistedTransaction = await getPersistedTransaction(transactionToSend.intentId)
645+
646+
expect(blockchainTransactionReceipt?.status).toBe("success")
647+
expect(blockchainTransaction.value).toBe(value)
648+
expect(persistedTransaction).toBeDefined()
649+
expect(persistedTransaction?.value).toBe(bigIntToZeroPadded(value))
650+
})
651+
615652
test("Correctly calculates baseFeePerGas after a block with high gas usage", async () => {
616653
const transactionBurner = await txm.createTransaction({
617654
address: deployment.MockGasBurner,
@@ -885,4 +922,4 @@ test("RPC liveness monitor works correctly", async () => {
885922
value: previousLivenessWindow,
886923
configurable: true,
887924
})
888-
})
925+
})

0 commit comments

Comments
 (0)