-
Notifications
You must be signed in to change notification settings - Fork 105
transfer route AA fixes + experimental mine worker polling interval env vars #897
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -11,9 +11,13 @@ import { | |||||
| import { transfer as transferERC20 } from "thirdweb/extensions/erc20"; | ||||||
| import { isContractDeployed, resolvePromisedValue } from "thirdweb/utils"; | ||||||
| import { getChain } from "../../../shared/utils/chain"; | ||||||
| import { normalizeAddress } from "../../../shared/utils/primitive-types"; | ||||||
| import { | ||||||
| getChecksumAddress, | ||||||
| normalizeAddress, | ||||||
| } from "../../../shared/utils/primitive-types"; | ||||||
| import { thirdwebClient } from "../../../shared/utils/sdk"; | ||||||
| import { insertTransaction } from "../../../shared/utils/transaction/insert-transaction"; | ||||||
| import { queueTransaction } from "../../../shared/utils/transaction/queue-transation"; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Import path typo breaks compilation
-import { queueTransaction } from "../../../shared/utils/transaction/queue-transation";
+import { queueTransaction } from "../../../shared/utils/transaction/queue-transaction";📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| import type { InsertedTransaction } from "../../../shared/utils/transaction/types"; | ||||||
| import { createCustomError } from "../../middleware/error"; | ||||||
| import { AddressSchema } from "../../schemas/address"; | ||||||
|
|
@@ -25,7 +29,7 @@ import { | |||||
| } from "../../schemas/shared-api-schemas"; | ||||||
| import { txOverridesWithValueSchema } from "../../schemas/tx-overrides"; | ||||||
| import { | ||||||
| walletHeaderSchema, | ||||||
| walletWithAAHeaderSchema, | ||||||
| walletWithAddressParamSchema, | ||||||
| } from "../../schemas/wallet"; | ||||||
| import { getChainIdFromChain } from "../../utils/chain"; | ||||||
|
|
@@ -70,7 +74,7 @@ export async function transfer(fastify: FastifyInstance) { | |||||
| operationId: "transfer", | ||||||
| params: requestSchema, | ||||||
| body: requestBodySchema, | ||||||
| headers: walletHeaderSchema, | ||||||
| headers: walletWithAAHeaderSchema, | ||||||
| querystring: requestQuerystringSchema, | ||||||
| response: { | ||||||
| ...standardResponseSchema, | ||||||
|
|
@@ -88,31 +92,50 @@ export async function transfer(fastify: FastifyInstance) { | |||||
| const { | ||||||
| "x-backend-wallet-address": walletAddress, | ||||||
| "x-idempotency-key": idempotencyKey, | ||||||
| "x-account-address": accountAddress, | ||||||
| "x-account-factory-address": accountFactoryAddress, | ||||||
| "x-account-salt": accountSalt, | ||||||
| "x-transaction-mode": transactionMode, | ||||||
| } = request.headers as Static<typeof walletHeaderSchema>; | ||||||
| } = request.headers as Static<typeof walletWithAAHeaderSchema>; | ||||||
| const { simulateTx: shouldSimulate } = request.query; | ||||||
|
|
||||||
| // Resolve inputs. | ||||||
| const currencyAddress = normalizeAddress(_currencyAddress); | ||||||
| const chainId = await getChainIdFromChain(chain); | ||||||
|
|
||||||
| let insertedTransaction: InsertedTransaction; | ||||||
| let queueId: string; | ||||||
| if ( | ||||||
| currencyAddress === ZERO_ADDRESS || | ||||||
| currencyAddress === NATIVE_TOKEN_ADDRESS | ||||||
| ) { | ||||||
| insertedTransaction = { | ||||||
| isUserOp: false, | ||||||
| // Native token transfer - use insertTransaction directly | ||||||
| const insertedTransaction: InsertedTransaction = { | ||||||
| chainId, | ||||||
| from: walletAddress as Address, | ||||||
| to: to as Address, | ||||||
| data: "0x", | ||||||
| value: toWei(amount), | ||||||
| extension: "none", | ||||||
| functionName: "transfer", | ||||||
| transactionMode, | ||||||
| ...parseTransactionOverrides(txOverrides), | ||||||
| ...(accountAddress | ||||||
| ? { | ||||||
| isUserOp: true, | ||||||
| accountAddress: getChecksumAddress(accountAddress), | ||||||
| signerAddress: getChecksumAddress(walletAddress), | ||||||
| target: getChecksumAddress(to), | ||||||
| accountFactoryAddress: getChecksumAddress( | ||||||
| accountFactoryAddress, | ||||||
| ), | ||||||
| accountSalt, | ||||||
| } | ||||||
| : { isUserOp: false }), | ||||||
| }; | ||||||
|
|
||||||
| queueId = await insertTransaction({ | ||||||
| insertedTransaction, | ||||||
| idempotencyKey, | ||||||
| shouldSimulate, | ||||||
| }); | ||||||
| } else { | ||||||
| const contract = getContract({ | ||||||
| client: thirdwebClient, | ||||||
|
|
@@ -131,31 +154,25 @@ export async function transfer(fastify: FastifyInstance) { | |||||
| ); | ||||||
| } | ||||||
|
|
||||||
| // ERC20 token transfer - use queueTransaction with PreparedTransaction | ||||||
| const transaction = transferERC20({ contract, to, amount }); | ||||||
|
|
||||||
| insertedTransaction = { | ||||||
| isUserOp: false, | ||||||
| chainId, | ||||||
| from: walletAddress as Address, | ||||||
| to: (await resolvePromisedValue(transaction.to)) as | ||||||
| | Address | ||||||
| | undefined, | ||||||
| data: await resolvePromisedValue(transaction.data), | ||||||
| value: 0n, | ||||||
| extension: "erc20", | ||||||
| queueId = await queueTransaction({ | ||||||
| transaction, | ||||||
| fromAddress: getChecksumAddress(walletAddress), | ||||||
| toAddress: getChecksumAddress(to), | ||||||
| accountAddress: getChecksumAddress(accountAddress), | ||||||
| accountFactoryAddress: getChecksumAddress(accountFactoryAddress), | ||||||
| accountSalt, | ||||||
| txOverrides, | ||||||
| idempotencyKey, | ||||||
| shouldSimulate, | ||||||
| functionName: "transfer", | ||||||
| functionArgs: [to, amount, currencyAddress], | ||||||
| extension: "erc20", | ||||||
| transactionMode, | ||||||
| ...parseTransactionOverrides(txOverrides), | ||||||
| }; | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
| const queueId = await insertTransaction({ | ||||||
| insertedTransaction, | ||||||
| idempotencyKey, | ||||||
| shouldSimulate, | ||||||
| }); | ||||||
|
|
||||||
| reply.status(StatusCodes.OK).send({ | ||||||
| result: { | ||||||
| queueId, | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -103,6 +103,16 @@ export const env = createEnv({ | |||||||||||||||||||||||||||||
| .default(30 * 60), | ||||||||||||||||||||||||||||||
| // Sets the max gas price for a transaction attempt. Most RPCs reject transactions above a certain gas price. Default: 10^18 wei. | ||||||||||||||||||||||||||||||
| EXPERIMENTAL__MAX_GAS_PRICE_WEI: z.coerce.number().default(10 ** 18), | ||||||||||||||||||||||||||||||
| EXPERIMENTAL__MINE_WORKER_BASE_POLL_INTERVAL_SECONDS: z.coerce | ||||||||||||||||||||||||||||||
| .number() | ||||||||||||||||||||||||||||||
| .default(2), | ||||||||||||||||||||||||||||||
| EXPERIMENTAL__MINE_WORKER_MAX_POLL_INTERVAL_SECONDS: z.coerce | ||||||||||||||||||||||||||||||
| .number() | ||||||||||||||||||||||||||||||
| .default(20), | ||||||||||||||||||||||||||||||
| EXPERIMENTAL__MINE_WORKER_POLL_INTERVAL_SCALING_FACTOR: z.coerce | ||||||||||||||||||||||||||||||
| .number() | ||||||||||||||||||||||||||||||
| .gt(0.0, "scaling factor must be greater than 0") | ||||||||||||||||||||||||||||||
| .default(1.0), | ||||||||||||||||||||||||||||||
|
Comment on lines
+106
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Validate base ≤ max & keep variables coherent The schema allows a configuration where EXPERIMENTAL__MINE_WORKER_MAX_POLL_INTERVAL_SECONDS: z.coerce
.number()
.default(20),
EXPERIMENTAL__MINE_WORKER_POLL_INTERVAL_SCALING_FACTOR: z.coerce
.number()
.gt(0.0, "scaling factor must be greater than 0")
.default(1.0),
+}).refine(
+ (e) =>
+ e.EXPERIMENTAL__MINE_WORKER_BASE_POLL_INTERVAL_SECONDS <=
+ e.EXPERIMENTAL__MINE_WORKER_MAX_POLL_INTERVAL_SECONDS,
+ {
+ message: "BASE_POLL_INTERVAL_SECONDS must not exceed MAX_POLL_INTERVAL_SECONDS",
+ path: [
+ "EXPERIMENTAL__MINE_WORKER_BASE_POLL_INTERVAL_SECONDS",
+ "EXPERIMENTAL__MINE_WORKER_MAX_POLL_INTERVAL_SECONDS",
+ ],
+ },
)🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. base can be greater than max, combined with a low scaling factor this actually makes sense There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @d4mr Thank you for the clarification! You're absolutely right - that's actually a clever configuration pattern. With a low scaling factor, having I appreciate you explaining the intended design - it makes perfect sense now! ʕ •ᴥ•ʔ Thanks for teaching me something new! ✏️ Learnings added
|
||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| clientPrefix: "NEVER_USED", | ||||||||||||||||||||||||||||||
| client: {}, | ||||||||||||||||||||||||||||||
|
|
@@ -151,6 +161,12 @@ export const env = createEnv({ | |||||||||||||||||||||||||||||
| CUSTOM_HMAC_AUTH_CLIENT_ID: process.env.CUSTOM_HMAC_AUTH_CLIENT_ID, | ||||||||||||||||||||||||||||||
| CUSTOM_HMAC_AUTH_CLIENT_SECRET: process.env.CUSTOM_HMAC_AUTH_CLIENT_SECRET, | ||||||||||||||||||||||||||||||
| ACCOUNT_CACHE_SIZE: process.env.ACCOUNT_CAHCE_SIZE, | ||||||||||||||||||||||||||||||
| EXPERIMENTAL__MINE_WORKER_BASE_POLL_INTERVAL_SECONDS: | ||||||||||||||||||||||||||||||
| process.env.EXPERIMENTAL__MINE_WORKER_BASE_POLL_INTERVAL_SECONDS, | ||||||||||||||||||||||||||||||
| EXPERIMENTAL__MINE_WORKER_MAX_POLL_INTERVAL_SECONDS: | ||||||||||||||||||||||||||||||
| process.env.EXPERIMENTAL__MINE_WORKER_MAX_POLL_INTERVAL_SECONDS, | ||||||||||||||||||||||||||||||
| EXPERIMENTAL__MINE_WORKER_POLL_INTERVAL_SCALING_FACTOR: | ||||||||||||||||||||||||||||||
| process.env.EXPERIMENTAL__MINE_WORKER_POLL_INTERVAL_SCALING_FACTOR, | ||||||||||||||||||||||||||||||
|
Comment on lines
163
to
+169
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo in runtimeEnvStrict key prevents variable exposure
- ACCOUNT_CACHE_SIZE: process.env.ACCOUNT_CAHCE_SIZE,
+ ACCOUNT_CACHE_SIZE: process.env.ACCOUNT_CACHE_SIZE,📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| onValidationError: (error: ZodError) => { | ||||||||||||||||||||||||||||||
| console.error( | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard optional AA headers before checksum conversion
accountFactoryAddresscan beundefined, yetgetChecksumAddressis called unconditionally whenaccountAddressis present, causing a runtime error.Apply the same check in the ERC20 branch below.
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@d4mr Thank you for showing the implementation! You're absolutely right -
getChecksumAddresssafely handlesundefinedvalues by returningundefinedwhen the input is falsy. The original concern about runtime errors was based on an incorrect assumption about the function's behavior.The function signature
val?: string | Address | nulland the implementationreturn val ? checksumAddress(val) : undefinedclearly show it's designed to handle optional values gracefully.✏️ Learnings added