From d0d8c7b42798f2facc289431a710771cfe52af14 Mon Sep 17 00:00:00 2001 From: Gabriel Tozatti Date: Mon, 2 Mar 2026 16:56:58 -0300 Subject: [PATCH 1/6] feat: setup initial stable structure --- packages/worker/package.json | 5 +- packages/worker/src/clients/psqlClient.ts | 10 +- packages/worker/src/config/logger.ts | 114 ++++++++++++++++++ packages/worker/src/index.ts | 14 ++- .../src/queues/generateTestTx/constants.ts | 27 +++++ .../worker/src/queues/generateTestTx/index.ts | 3 + .../worker/src/queues/generateTestTx/queue.ts | 53 ++++++++ .../src/queues/generateTestTx/scheduler.ts | 57 +++++++++ 8 files changed, 274 insertions(+), 9 deletions(-) create mode 100644 packages/worker/src/config/logger.ts create mode 100644 packages/worker/src/queues/generateTestTx/constants.ts create mode 100644 packages/worker/src/queues/generateTestTx/index.ts create mode 100644 packages/worker/src/queues/generateTestTx/queue.ts create mode 100644 packages/worker/src/queues/generateTestTx/scheduler.ts diff --git a/packages/worker/package.json b/packages/worker/package.json index c96a69480..69bd5d7ae 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -18,6 +18,8 @@ "@types/node-cron": "3.0.11", "bull": "^4.16.5", "express": "4.21.2", + "bakosafe": "0.6.0", + "pino": "9.6.0", "fuels": "0.101.3", "ioredis": "^5.7.0", "mongodb": "^6.18.0", @@ -48,6 +50,7 @@ "prettier": "2.2.1", "pretty-quick": "3.1.0", "ts-node-dev": "2.0.0", - "tscpaths": "0.0.9" + "tscpaths": "0.0.9", + "pino-pretty": "11.2.2" } } diff --git a/packages/worker/src/clients/psqlClient.ts b/packages/worker/src/clients/psqlClient.ts index 73d6b0d16..93b05a830 100644 --- a/packages/worker/src/clients/psqlClient.ts +++ b/packages/worker/src/clients/psqlClient.ts @@ -14,7 +14,7 @@ interface ConnectionConfig { database?: string host?: string port?: number - ssl: { + ssl?: { rejectUnauthorized: boolean }; } @@ -28,10 +28,12 @@ export const defaultConnection: ConnectionConfig = { database: WORKER_DATABASE_NAME, host: WORKER_DATABASE_HOST, port: Number(WORKER_DATABASE_PORT), - ssl: { + ssl: isLocal + ? undefined + : { rejectUnauthorized: false, - } -} + }, +}; export class PsqlClient { private readonly client: Client diff --git a/packages/worker/src/config/logger.ts b/packages/worker/src/config/logger.ts new file mode 100644 index 000000000..bb9d2389e --- /dev/null +++ b/packages/worker/src/config/logger.ts @@ -0,0 +1,114 @@ +import pino from 'pino'; + +const { NODE_ENV, LOG_LEVEL } = process.env; + +const isDevelopment = NODE_ENV === 'development'; + +const pinoConfig: pino.LoggerOptions = { + level: LOG_LEVEL || (isDevelopment ? 'debug' : 'info'), + timestamp: pino.stdTimeFunctions.isoTime, + + // Sensitive data redaction for LGPD, GDPR, PCI DSS, SOC 2 Type II compliance + redact: { + paths: [ + // ===== Authentication & Authorization ===== + 'password', + '*.password', + 'token', + '*.token', + 'authorization', + '*.authorization', + 'headers.authorization', + 'apiKey', + '*.apiKey', + 'api_key', + '*.api_key', + 'accessToken', + '*.accessToken', + 'refreshToken', + '*.refreshToken', + + // ===== Cryptography & Keys (12 terms) ===== + 'privateKey', + '*.privateKey', + 'private_key', + '*.private_key', + 'seed', + '*.seed', + 'mnemonic', + '*.mnemonic', + 'signature', + '*.signature', + 'signedMessage', + '*.signedMessage', + + // ===== Blockchain & Fuel (10 terms) ===== + 'wallet', + '*.wallet', + 'walletAddress', + '*.walletAddress', + 'predicateAddress', + '*.predicateAddress', + 'vault.configurable', + 'signer', + '*.signer', + 'predicate_address', + + // ===== WebAuthn & Hardware Security (8 terms) ===== + 'webauthn', + '*.webauthn', + 'credentialId', + '*.credentialId', + 'credential_id', + '*.credential_id', + 'credentialPublicKey', + '*.credentialPublicKey', + + // ===== Infrastructure & Endpoints (10 terms) ===== + 'DATABASE_URL', + 'REDIS_URL', + 'connectionString', + '*.connectionString', + 'connection_string', + + // ===== User Data & Recovery (14 terms) ===== + 'code', + '*.code', + 'recovery_code', + '*.recovery_code', + 'pin', + '*.pin', + 'email', + '*.email', + 'phone', + '*.phone', + + // ===== Transaction & Operation Data (11 terms) ===== + 'operationData', + '*.operationData', + 'operation_data', + '*.operation_data', + + // ===== Network & Connectivity (3 terms) ===== + 'ipAddress', + 'ip_address', + ], + remove: true, + }, + + // Development: human-readable output with pino-pretty + // Production: JSON structured logging for centralized systems + transport: isDevelopment + ? { + target: 'pino-pretty', + options: { + colorize: true, + singleLine: false, + translateTime: 'SYS:standard', + ignore: 'pid,hostname', + }, + } + : undefined, +}; + +export const logger = pino(pinoConfig); diff --git a/packages/worker/src/index.ts b/packages/worker/src/index.ts index f04e0d111..fcba06ba2 100644 --- a/packages/worker/src/index.ts +++ b/packages/worker/src/index.ts @@ -8,7 +8,12 @@ import AssetCron from "./queues/assetsValue/scheduler"; import assetQueue from "./queues/assetsValue/queue"; import { MongoDatabase } from "./clients/mongoClient"; import { PsqlClient } from "./clients"; -import { userBlockSyncQueue, userLogoutSyncQueue, UserBlockSyncCron } from "./queues/userBlockSync"; +import { + userBlockSyncQueue, + userLogoutSyncQueue, + UserBlockSyncCron, +} from "./queues/userBlockSync"; +import TransactionCron from "./queues/generateTestTx/scheduler"; const { WORKER_PORT, @@ -70,9 +75,10 @@ MongoDatabase.connect(); PsqlClient.connect(); // schedulers -BalanceCron.create(); -AssetCron.create(); -UserBlockSyncCron.create(); +// BalanceCron.create(); +// AssetCron.create(); +// UserBlockSyncCron.create(); +TransactionCron.create(); app.listen(WORKER_PORT ?? 3063, () => console.log(`Server running on ${WORKER_PORT}`) diff --git a/packages/worker/src/queues/generateTestTx/constants.ts b/packages/worker/src/queues/generateTestTx/constants.ts new file mode 100644 index 000000000..e37783bea --- /dev/null +++ b/packages/worker/src/queues/generateTestTx/constants.ts @@ -0,0 +1,27 @@ +export const QUEUE_TRANSACTION = "QUEUE_TRANSACTION"; +export const CRON_EXPRESSION = "*/20 * * * *"; + +export const VAULT_CONFIG = { + SIGNATURES_COUNT: 1, + SIGNERS: [ + "0x2bba3b154de16722ddbdbf40843c8464f773638c5383c4aa46d69e611fb3e199", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + HASH_PREDICATE: + "0xc5baa01086a27ebe7fdd676485887b380a0595f2becbd1d60e6c16bba58dd888", +}; +export const VAULT_VERSION = + "0x967aaa71b3db34acd8104ed1d7ff3900e67cff3d153a0ffa86d85957f579aa6a"; + +export const PRIVATE_KEY = + "0x096ee6d0fb5ce5654e2f3926f89020924d20e6ffbf27a7c68e9f9f8149eaa829"; + +export const DEFAULT_AMOUNT = "0.000000001"; diff --git a/packages/worker/src/queues/generateTestTx/index.ts b/packages/worker/src/queues/generateTestTx/index.ts new file mode 100644 index 000000000..f8b739a12 --- /dev/null +++ b/packages/worker/src/queues/generateTestTx/index.ts @@ -0,0 +1,3 @@ +import "./queue"; +import "./scheduler"; +import "./constants"; diff --git a/packages/worker/src/queues/generateTestTx/queue.ts b/packages/worker/src/queues/generateTestTx/queue.ts new file mode 100644 index 000000000..5a28c0eac --- /dev/null +++ b/packages/worker/src/queues/generateTestTx/queue.ts @@ -0,0 +1,53 @@ +import Queue from "bull"; +import { Vault } from "bakosafe"; +import { Provider, WalletUnlocked } from "fuels"; +import { redisConfig } from "@/clients"; +import { networks } from "@/mocks/networks"; +import { + QUEUE_TRANSACTION, + VAULT_CONFIG, + DEFAULT_AMOUNT, + PRIVATE_KEY, + VAULT_VERSION, +} from "./constants"; +import { logger } from "@/config/logger"; + +const transactionQueue = new Queue(QUEUE_TRANSACTION, { + redis: redisConfig, +}); + +const provider = new Provider(networks["mainnet"]); +const wallet = new WalletUnlocked(PRIVATE_KEY, provider); +const vault = new Vault(provider, VAULT_CONFIG, VAULT_VERSION); + +transactionQueue.process(1, async (job) => { + console.log(`[${QUEUE_TRANSACTION}] Job started`, new Date()); + try { + const baseAsset = await provider.getBaseAssetId(); + + const { tx, hashTxId } = await vault.transaction({ + name: "Transaction Cron", + assets: [ + { + to: vault.address.toB256(), + amount: DEFAULT_AMOUNT, + assetId: baseAsset, + }, + ], + }); + + const signature = await wallet.signMessage(hashTxId); + + tx.witnesses = [vault.encodeSignature(wallet.address.toB256(), signature)]; + + const result = await vault.send(tx); + const response = await result.waitForResult(); + + console.log("TX SUCCESS:", response.status); + } catch (error) { + logger.error({ error }, "[${QUEUE_TRANSACTION}] Error"); + throw error; + } +}); + +export default transactionQueue; diff --git a/packages/worker/src/queues/generateTestTx/scheduler.ts b/packages/worker/src/queues/generateTestTx/scheduler.ts new file mode 100644 index 000000000..7033c1692 --- /dev/null +++ b/packages/worker/src/queues/generateTestTx/scheduler.ts @@ -0,0 +1,57 @@ +import transactionQueue from "./queue"; +import { QUEUE_TRANSACTION, CRON_EXPRESSION } from "./constants"; + +class TransactionCron { + private static instance: TransactionCron; + private isRunning: boolean = false; + + private constructor() {} + + public static create(): TransactionCron { + if (!this.instance) { + this.instance = new TransactionCron(); + } + if (!this.instance.isRunning) { + this.instance.setup(); + } + return this.instance; + } + + private async setup(): Promise { + try { + this.isRunning = true; + + await transactionQueue.add( + {}, + { + jobId: `startup-${QUEUE_TRANSACTION}`, + attempts: 3, + backoff: 5000, + removeOnComplete: true, + priority: 1, + } + ); + + await transactionQueue.add( + {}, + { + repeat: { cron: CRON_EXPRESSION }, + jobId: "transaction-cron-recurrent", + attempts: 3, + backoff: 5000, + removeOnComplete: true, + removeOnFail: false, + } + ); + + console.log( + `[${QUEUE_TRANSACTION}] Setup complete: Immediate job added & Cron scheduled.` + ); + } catch (e) { + this.isRunning = false; + console.error(`[${QUEUE_TRANSACTION}] Error in setup:`, e); + } + } +} + +export default TransactionCron; From 1e6f617e4ea2a6803c03f1f1c2b707e2c2bea234 Mon Sep 17 00:00:00 2001 From: Gabriel Tozatti Date: Wed, 4 Mar 2026 14:16:02 -0300 Subject: [PATCH 2/6] feat: add multi-signer transaction cron --- packages/worker/package.json | 4 +- .../src/queues/generateTestTx/README.md | 159 ++++++++++++++++++ .../queues/generateTestTx/config/vault.json | 21 +++ .../queues/generateTestTx/config/vault_2.json | 26 +++ .../src/queues/generateTestTx/constants.ts | 27 +-- .../worker/src/queues/generateTestTx/index.ts | 3 + .../worker/src/queues/generateTestTx/queue.ts | 55 +++--- .../src/queues/generateTestTx/scheduler.ts | 15 +- .../worker/src/queues/generateTestTx/types.ts | 26 +++ .../src/queues/generateTestTx/utils/loader.ts | 37 ++++ .../src/queues/generateTestTx/utils/signer.ts | 77 +++++++++ .../src/queues/generateTestTx/utils/vault.ts | 25 +++ pnpm-lock.yaml | 72 ++++++++ 13 files changed, 491 insertions(+), 56 deletions(-) create mode 100644 packages/worker/src/queues/generateTestTx/README.md create mode 100644 packages/worker/src/queues/generateTestTx/config/vault.json create mode 100644 packages/worker/src/queues/generateTestTx/config/vault_2.json create mode 100644 packages/worker/src/queues/generateTestTx/types.ts create mode 100644 packages/worker/src/queues/generateTestTx/utils/loader.ts create mode 100644 packages/worker/src/queues/generateTestTx/utils/signer.ts create mode 100644 packages/worker/src/queues/generateTestTx/utils/vault.ts diff --git a/packages/worker/package.json b/packages/worker/package.json index 69bd5d7ae..4e25d8c8a 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -51,6 +51,8 @@ "pretty-quick": "3.1.0", "ts-node-dev": "2.0.0", "tscpaths": "0.0.9", - "pino-pretty": "11.2.2" + "pino-pretty": "11.2.2", + "viem": "^2.30.6", + "ethers": "^6.14.3" } } diff --git a/packages/worker/src/queues/generateTestTx/README.md b/packages/worker/src/queues/generateTestTx/README.md new file mode 100644 index 000000000..74a736adc --- /dev/null +++ b/packages/worker/src/queues/generateTestTx/README.md @@ -0,0 +1,159 @@ +# Transaction Cron + +A cron job responsible for automatically executing transactions on the Fuel network from configured vaults. On each execution, a vault is randomly selected from the available ones and a transaction is sent using the locally configured signers. + +--- + +## File structure + +``` +src/queues/generateTestTx/ +├── config/ # Folder containing vault configuration JSON files +│ ├── vault_1.json +│ ├── vault_2.json +│ └── ... +├── utils/ +│ ├── loader.ts # Reads and validates the selected JSON +│ ├── vault.factory.ts # Instantiates the Vault from the config +│ └── signer.ts # Signs the transaction by network type (Fuel/EVM) +├── types.ts # Shared interfaces and enums +├── constants.ts # Queue constants and execution interval +├── queue.ts # Bull job processor +└── scheduler.ts # Configures and schedules jobs +``` + +--- + +## How it works + +1. When the worker starts, `TransactionCron` clears old jobs from Redis and schedules two jobs: + - **Immediate** — runs as soon as the worker starts + - **Recurring** — runs at every interval defined in `REPEAT_INTERVAL_MS`, with an initial delay to avoid colliding with the immediate job + +2. On each execution, `loader.ts` randomly picks a JSON file from the `config/` folder, avoiding repeating the last used vault. + +3. With the config loaded, `vault.factory.ts` instantiates the `Vault` from the `bakosafe` SDK with the configured signers, padded to 10 positions as required by the predicate. + +4. `signer.ts` iterates over the signers and signs the `hashTxId` for each one that has a `privateKey` defined. External signers (without `privateKey`) are silently skipped. + +5. The transaction is sent with the collected witnesses. If an error occurs, the logger records it and the job is marked as failed in Redis (no retries). + +--- + +## Configuring a vault + +Create a `.json` file inside `src/queues/generateTestTx/config/`. The filename can be anything, as long as it ends in `.json`. + +```json +{ + "vault": { + "signaturesCount": 2, + "hashPredicate": "0xc5baa01086a27ebe7fdd676485887b380a0595f2becbd1d60e6c16bba58dd888", + "version": "0x967aaa71b3db34acd8104ed1d7ff3900e67cff3d153a0ffa86d85957f579aa6a", + "signers": [ + { + "address": "0x2bba3b154de16722ddbdbf40843c8464f773638c5383c4aa46d69e611fb3e199", + "type": "fuel", + "privateKey": "0xYOUR_FUEL_PRIVATE_KEY" + }, + { + "address": "0xAbCdEf1234567890abcdef1234567890abcdef12", + "type": "evm", + "privateKey": "0xYOUR_EVM_PRIVATE_KEY" + } + ] + }, + "network": "mainnet", + "defaultAmount": "0.000000001" +} +``` + +### Fields + +| Field | Description | +|---|---| +| `signaturesCount` | Minimum number of signatures required for the transaction to be valid | +| `hashPredicate` | Hash of the vault predicate (obtained at deploy time) | +| `version` | bakosafe predicate version | +| `signers` | List of up to 10 signers | +| `network` | Fuel network (`mainnet`, `testnet`) | +| `defaultAmount` | Amount to send in the transaction (in ETH) | + +### Signer fields + +| Field | Description | +|---|---| +| `address` | B256 address for Fuel, `0x` address for EVM | +| `type` | Network type: `fuel` or `evm` | +| `privateKey` | Local private key. Omit for external signers | + +> **Important:** the `address` and `privateKey` must be from the same cryptographic key pair. If they don't match, the predicate will reject the signature. + +--- + +## Signer types + +### Fuel + +Signs using `WalletUnlocked` from the `fuels` SDK directly with the `hashTxId`. + +```json +{ + "address": "0x2bba3b...", + "type": "fuel", + "privateKey": "0xYOUR_FUEL_KEY" +} +``` + +### EVM + +Signs using `ethers.Wallet`. The message format is automatically detected based on the predicate version: recent versions use `encodedTxId` directly; legacy versions use `arrayify(stringToHex(hashTxId))`. + +```json +{ + "address": "0xAbCdEf...", + "type": "evm", + "privateKey": "0xYOUR_EVM_KEY" +} +``` + +### External signer (no `privateKey`) + +Any signer without a `privateKey` is skipped by the cron. Use this pattern to register real user addresses in the vault — the address must be in the signers array for the predicate to recognize it, but the signature is not collected automatically. + +```json +{ + "address": "0xUserAddress...", + "type": "fuel" +} +``` + +--- + +## Adding a new vault + +1. Create a new JSON file in the `config/` folder following the model above +2. Fill in the signers with the vault's addresses and keys +3. Make sure the vault has enough balance to cover the gas fee +4. No worker restart needed — the loader reads the files on every execution + +--- + +## Interval configuration + +In `constants.ts`: + +```typescript +export const REPEAT_INTERVAL_MS = 1 * 60 * 1000; // 1 minute +``` + +Change the value to adjust the execution frequency. The interval is always relative to the last execution, not the system clock. + +--- + +## Important notes + +- **Balance:** Each vault must have enough balance to cover gas. If the balance is insufficient, the job fails and the error is recorded by the logger. +- **Private keys:** Never commit `.json` files with a `privateKey` filled in. Use environment variables or a secret manager to inject values in production. +- **Redis:** Failed jobs are stored in Redis for inspection via Bull Dashboard. Successful jobs are automatically removed. +- **SDK version:** The worker uses `bakosafe` + `fuels`. If the network's `fuel-core` version differs from what the SDK supports, a compatibility warning may appear in the logs — it does not block execution, but keeping the SDK up to date is recommended. diff --git a/packages/worker/src/queues/generateTestTx/config/vault.json b/packages/worker/src/queues/generateTestTx/config/vault.json new file mode 100644 index 000000000..d60f9ab7c --- /dev/null +++ b/packages/worker/src/queues/generateTestTx/config/vault.json @@ -0,0 +1,21 @@ +{ + "vault": { + "signaturesCount": 2, + "hashPredicate": "0x223767a84816a4274981acd576fbce2aebae6cefddf4c398856582579e991f09", + "version": "0x967aaa71b3db34acd8104ed1d7ff3900e67cff3d153a0ffa86d85957f579aa6a", + "signers": [ + { + "address": "0x2bba3b154de16722ddbdbf40843c8464f773638c5383c4aa46d69e611fb3e199", + "type": "fuel", + "privateKey": "0x096ee6d0fb5ce5654e2f3926f89020924d20e6ffbf27a7c68e9f9f8149eaa829" + }, + { + "address": "0x000000000000000000000000dc1b8e7a80edb19a19ece2964bf293994499769a", + "type": "evm", + "privateKey": "106db0a48940725aa19c4ba00b48ff37ee9ff6c1bcac9cfbd83b290c01f961ce" + } + ] + }, + "network": "mainnet", + "defaultAmount": "0.0000001" +} diff --git a/packages/worker/src/queues/generateTestTx/config/vault_2.json b/packages/worker/src/queues/generateTestTx/config/vault_2.json new file mode 100644 index 000000000..fa672331e --- /dev/null +++ b/packages/worker/src/queues/generateTestTx/config/vault_2.json @@ -0,0 +1,26 @@ +{ + "vault": { + "signaturesCount": 3, + "hashPredicate": "0x223767a84816a4274981acd576fbce2aebae6cefddf4c398856582579e991f09", + "version": "0x967aaa71b3db34acd8104ed1d7ff3900e67cff3d153a0ffa86d85957f579aa6a", + "signers": [ + { + "address": "0x2bba3b154de16722ddbdbf40843c8464f773638c5383c4aa46d69e611fb3e199", + "type": "fuel", + "privateKey": "0x096ee6d0fb5ce5654e2f3926f89020924d20e6ffbf27a7c68e9f9f8149eaa829" + }, + { + "address": "0x000000000000000000000000dc1b8e7a80edb19a19ece2964bf293994499769a", + "type": "evm", + "privateKey": "106db0a48940725aa19c4ba00b48ff37ee9ff6c1bcac9cfbd83b290c01f961ce" + }, + { + "address": "0x4f8fee8a7a36399320c90e7274e47f125ecd5840942ea1f51085b1042f68d981", + "type": "fuel", + "privateKey": "0xd57210fa919473047f8a47a6cadd66ba5791a96654b29cd2c0608c67df4d230f" + } + ] + }, + "network": "mainnet", + "defaultAmount": "0.000001" +} diff --git a/packages/worker/src/queues/generateTestTx/constants.ts b/packages/worker/src/queues/generateTestTx/constants.ts index e37783bea..7358f5f18 100644 --- a/packages/worker/src/queues/generateTestTx/constants.ts +++ b/packages/worker/src/queues/generateTestTx/constants.ts @@ -1,27 +1,2 @@ export const QUEUE_TRANSACTION = "QUEUE_TRANSACTION"; -export const CRON_EXPRESSION = "*/20 * * * *"; - -export const VAULT_CONFIG = { - SIGNATURES_COUNT: 1, - SIGNERS: [ - "0x2bba3b154de16722ddbdbf40843c8464f773638c5383c4aa46d69e611fb3e199", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - ], - HASH_PREDICATE: - "0xc5baa01086a27ebe7fdd676485887b380a0595f2becbd1d60e6c16bba58dd888", -}; -export const VAULT_VERSION = - "0x967aaa71b3db34acd8104ed1d7ff3900e67cff3d153a0ffa86d85957f579aa6a"; - -export const PRIVATE_KEY = - "0x096ee6d0fb5ce5654e2f3926f89020924d20e6ffbf27a7c68e9f9f8149eaa829"; - -export const DEFAULT_AMOUNT = "0.000000001"; +export const REPEAT_INTERVAL_MS = 20 * 60 * 1000; diff --git a/packages/worker/src/queues/generateTestTx/index.ts b/packages/worker/src/queues/generateTestTx/index.ts index f8b739a12..d1803b360 100644 --- a/packages/worker/src/queues/generateTestTx/index.ts +++ b/packages/worker/src/queues/generateTestTx/index.ts @@ -1,3 +1,6 @@ import "./queue"; import "./scheduler"; import "./constants"; +import "./types"; +import "./utils"; +import "./config"; diff --git a/packages/worker/src/queues/generateTestTx/queue.ts b/packages/worker/src/queues/generateTestTx/queue.ts index 5a28c0eac..34c7912c2 100644 --- a/packages/worker/src/queues/generateTestTx/queue.ts +++ b/packages/worker/src/queues/generateTestTx/queue.ts @@ -1,52 +1,63 @@ import Queue from "bull"; -import { Vault } from "bakosafe"; -import { Provider, WalletUnlocked } from "fuels"; +import { Provider } from "fuels"; import { redisConfig } from "@/clients"; import { networks } from "@/mocks/networks"; -import { - QUEUE_TRANSACTION, - VAULT_CONFIG, - DEFAULT_AMOUNT, - PRIVATE_KEY, - VAULT_VERSION, -} from "./constants"; +import { QUEUE_TRANSACTION } from "./constants"; +import { loadVaultConfig } from "@/queues/generateTestTx/utils/loader"; +import { createVault } from "@/queues/generateTestTx/utils/vault"; +import { collectWitnesses } from "@/queues/generateTestTx/utils/signer"; import { logger } from "@/config/logger"; const transactionQueue = new Queue(QUEUE_TRANSACTION, { redis: redisConfig, }); -const provider = new Provider(networks["mainnet"]); -const wallet = new WalletUnlocked(PRIVATE_KEY, provider); -const vault = new Vault(provider, VAULT_CONFIG, VAULT_VERSION); - transactionQueue.process(1, async (job) => { console.log(`[${QUEUE_TRANSACTION}] Job started`, new Date()); + try { - const baseAsset = await provider.getBaseAssetId(); + const config = loadVaultConfig(); + const provider = new Provider(networks[config.network]); + const vault = createVault(provider, config); - const { tx, hashTxId } = await vault.transaction({ + const baseAsset = await provider.getBaseAssetId(); + const { tx, hashTxId, encodedTxId } = await vault.transaction({ name: "Transaction Cron", assets: [ { to: vault.address.toB256(), - amount: DEFAULT_AMOUNT, + amount: config.defaultAmount, assetId: baseAsset, }, ], }); + console.log(`[${QUEUE_TRANSACTION}] Transaction created:`, hashTxId); + + const witnesses = await collectWitnesses( + vault, + hashTxId, + encodedTxId, + config.vault.signers, + provider + ); + console.log( + `[${QUEUE_TRANSACTION}] Witnesses collected:`, + witnesses.length + ); - const signature = await wallet.signMessage(hashTxId); + if (witnesses.length === 0) { + throw new Error("[Queue] No local signers available."); + } - tx.witnesses = [vault.encodeSignature(wallet.address.toB256(), signature)]; + tx.witnesses = witnesses; const result = await vault.send(tx); - const response = await result.waitForResult(); + console.log(`[${QUEUE_TRANSACTION}] TX sent, waiting for result...`); - console.log("TX SUCCESS:", response.status); + await result.waitForResult(); + console.log(`[${QUEUE_TRANSACTION}] TX SUCCESS`); } catch (error) { - logger.error({ error }, "[${QUEUE_TRANSACTION}] Error"); - throw error; + logger.error({ error }, `[${QUEUE_TRANSACTION}] Error`); } }); diff --git a/packages/worker/src/queues/generateTestTx/scheduler.ts b/packages/worker/src/queues/generateTestTx/scheduler.ts index 7033c1692..e44ce0ac4 100644 --- a/packages/worker/src/queues/generateTestTx/scheduler.ts +++ b/packages/worker/src/queues/generateTestTx/scheduler.ts @@ -1,5 +1,5 @@ import transactionQueue from "./queue"; -import { QUEUE_TRANSACTION, CRON_EXPRESSION } from "./constants"; +import { QUEUE_TRANSACTION, REPEAT_INTERVAL_MS } from "./constants"; class TransactionCron { private static instance: TransactionCron; @@ -21,24 +21,25 @@ class TransactionCron { try { this.isRunning = true; + await transactionQueue.obliterate({ force: true }); + await transactionQueue.add( {}, { jobId: `startup-${QUEUE_TRANSACTION}`, - attempts: 3, - backoff: 5000, + attempts: 1, removeOnComplete: true, - priority: 1, + removeOnFail: false, } ); await transactionQueue.add( {}, { - repeat: { cron: CRON_EXPRESSION }, + repeat: { every: REPEAT_INTERVAL_MS }, + delay: REPEAT_INTERVAL_MS, jobId: "transaction-cron-recurrent", - attempts: 3, - backoff: 5000, + attempts: 1, removeOnComplete: true, removeOnFail: false, } diff --git a/packages/worker/src/queues/generateTestTx/types.ts b/packages/worker/src/queues/generateTestTx/types.ts new file mode 100644 index 000000000..1e810d560 --- /dev/null +++ b/packages/worker/src/queues/generateTestTx/types.ts @@ -0,0 +1,26 @@ +export enum SignerType { + FUEL = "fuel", + EVM = "evm", +} + +export interface SignerConfig { + address: string; + type: SignerType; + privateKey?: string; +} + +export interface VaultConfigFile { + vault: { + signaturesCount: number; + signers: SignerConfig[]; + hashPredicate?: string; + version?: string; + }; + network: string; + defaultAmount: string; +} + +export interface SignResult { + address: string; + witness: string; +} diff --git a/packages/worker/src/queues/generateTestTx/utils/loader.ts b/packages/worker/src/queues/generateTestTx/utils/loader.ts new file mode 100644 index 000000000..8e2cf9aad --- /dev/null +++ b/packages/worker/src/queues/generateTestTx/utils/loader.ts @@ -0,0 +1,37 @@ +import fs from "fs"; +import path from "path"; +import { VaultConfigFile } from "@/queues/generateTestTx/types"; +import { QUEUE_TRANSACTION } from "@/queues/generateTestTx/constants"; + +const CONFIGS_DIR = path.resolve(__dirname, "../config"); + +export function loadVaultConfig(): VaultConfigFile { + if (!fs.existsSync(CONFIGS_DIR)) { + throw new Error( + `[${QUEUE_TRANSACTION}] configs/ directory not found at: ${CONFIGS_DIR}` + ); + } + + const files = fs.readdirSync(CONFIGS_DIR).filter((f) => f.endsWith(".json")); + + if (files.length === 0) { + throw new Error( + `[${QUEUE_TRANSACTION}] No JSON files found in: ${CONFIGS_DIR}` + ); + } + + const selected = files[Math.floor(Math.random() * files.length)]; + const fullPath = path.join(CONFIGS_DIR, selected); + + let raw: unknown; + try { + raw = JSON.parse(fs.readFileSync(fullPath, "utf-8")); + } catch { + throw new Error( + `[${QUEUE_TRANSACTION}] Failed to parse ${selected} — invalid JSON.` + ); + } + + console.log(`[${QUEUE_TRANSACTION}] Selected vault config: ${selected}`); + return raw as VaultConfigFile; +} diff --git a/packages/worker/src/queues/generateTestTx/utils/signer.ts b/packages/worker/src/queues/generateTestTx/utils/signer.ts new file mode 100644 index 000000000..dd3a22ac6 --- /dev/null +++ b/packages/worker/src/queues/generateTestTx/utils/signer.ts @@ -0,0 +1,77 @@ +import { Provider, WalletUnlocked, arrayify } from "fuels"; +import { ethers } from "ethers"; +import { QUEUE_TRANSACTION } from "@/queues/generateTestTx/constants"; +import { Vault } from "bakosafe"; +import { stringToHex } from "viem"; +import { + SignerConfig, + SignerType, + SignResult, +} from "@/queues/generateTestTx/types"; + +export async function signWithSigner( + vault: Vault, + hashTxId: string, + encodedTxId: string, + signer: SignerConfig, + provider: Provider +): Promise { + if (!signer.privateKey) { + console.warn( + `[${QUEUE_TRANSACTION}] Skipping external signer (no privateKey): ${signer.address} [${signer.type}]` + ); + return null; + } + + switch (signer.type) { + case SignerType.FUEL: { + const wallet = new WalletUnlocked(signer.privateKey, provider); + const signature = await wallet.signMessage(hashTxId); + return { + address: signer.address, + witness: vault.encodeSignature(signer.address, signature), + }; + } + + case SignerType.EVM: { + const evmWallet = new ethers.Wallet(signer.privateKey); + + const messageToSign = encodedTxId.startsWith("0x") + ? arrayify(stringToHex(hashTxId)) + : encodedTxId; + + const signature = await evmWallet.signMessage(messageToSign); + return { + address: signer.address, + witness: vault.encodeSignature(signer.address, signature), + }; + } + + default: { + console.error( + `[${QUEUE_TRANSACTION}] Unknown signer type: "${ + (signer as SignerConfig).type + }"` + ); + return null; + } + } +} + +export async function collectWitnesses( + vault: Vault, + hashTxId: string, + encodedTxId: string, + signers: SignerConfig[], + provider: Provider +): Promise { + const results = await Promise.all( + signers.map((signer) => + signWithSigner(vault, hashTxId, encodedTxId, signer, provider) + ) + ); + + return results + .filter((r): r is SignResult => r !== null) + .map((r) => r.witness); +} diff --git a/packages/worker/src/queues/generateTestTx/utils/vault.ts b/packages/worker/src/queues/generateTestTx/utils/vault.ts new file mode 100644 index 000000000..52661e882 --- /dev/null +++ b/packages/worker/src/queues/generateTestTx/utils/vault.ts @@ -0,0 +1,25 @@ +import { Provider } from "fuels"; +import { Vault } from "bakosafe"; +import { VaultConfigFile } from "@/queues/generateTestTx/types"; + +type VaultConfigurable = ConstructorParameters[1]; + +const MAX_SIGNERS = 10; +const ZERO = "0x0000000000000000000000000000000000000000000000000000000000000000"; + +export function createVault(provider: Provider, config: VaultConfigFile): Vault { + const { signaturesCount, signers, hashPredicate, version } = config.vault; + + const paddedSigners = [ + ...signers.map((s) => s.address), + ...Array(MAX_SIGNERS - signers.length).fill(ZERO), + ]; + + const configurable = { + SIGNATURES_COUNT: signaturesCount, + SIGNERS: paddedSigners, + ...(hashPredicate ? { HASH_PREDICATE: hashPredicate } : {}), + } satisfies VaultConfigurable; + + return new Vault(provider, configurable, version); +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3dc996d92..9356cf46b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -426,6 +426,9 @@ importers: '@types/node-cron': specifier: 3.0.11 version: 3.0.11 + bakosafe: + specifier: 0.6.0 + version: 0.6.0(fuels@0.101.3(vitest@3.0.9(@types/node@20.6.0)(tsx@4.21.0)(yaml@2.8.2)))(typescript@5.4.5) bull: specifier: ^4.16.5 version: 4.16.5 @@ -447,6 +450,9 @@ importers: pg: specifier: 8.5.1 version: 8.5.1 + pino: + specifier: 9.6.0 + version: 9.6.0 redis: specifier: 4.7.0 version: 4.7.0 @@ -502,12 +508,18 @@ importers: eslint-plugin-prettier: specifier: 3.3.1 version: 3.3.1(eslint-config-prettier@8.1.0(eslint@7.22.0))(eslint@7.22.0)(prettier@2.2.1) + ethers: + specifier: ^6.14.3 + version: 6.16.0 husky: specifier: 5.2.0 version: 5.2.0 lint-staged: specifier: 10.5.4 version: 10.5.4 + pino-pretty: + specifier: 11.2.2 + version: 11.2.2 prettier: specifier: 2.2.1 version: 2.2.1 @@ -520,9 +532,15 @@ importers: tscpaths: specifier: 0.0.9 version: 0.0.9 + viem: + specifier: ^2.30.6 + version: 2.45.1(typescript@5.4.5) packages: + '@adraffy/ens-normalize@1.10.1': + resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==} + '@adraffy/ens-normalize@1.11.1': resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} @@ -1599,6 +1617,9 @@ packages: resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} engines: {node: ^14.21.3 || >=16} + '@noble/curves@1.2.0': + resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} + '@noble/curves@1.3.0': resolution: {integrity: sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==} @@ -1617,6 +1638,10 @@ packages: resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.3.2': + resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} + engines: {node: '>= 16'} + '@noble/hashes@1.3.3': resolution: {integrity: sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==} engines: {node: '>= 16'} @@ -2466,6 +2491,9 @@ packages: '@types/node@22.19.9': resolution: {integrity: sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==} + '@types/node@22.7.5': + resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -2689,6 +2717,9 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + aes-js@4.0.0-beta.5: + resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} + aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -3761,6 +3792,10 @@ packages: ethereum-cryptography@2.2.1: resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==} + ethers@6.16.0: + resolution: {integrity: sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==} + engines: {node: '>=14.0.0'} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -6319,6 +6354,9 @@ packages: resolution: {integrity: sha512-tz4qimSJTCjYtHVsoY7pvxLcxhmhgmwzm7fyMEiL3/kPFFVyUuZOwuwcWwjkAsIrSUKJK22A7fNuJUwxzQ+H+w==} hasBin: true + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -6488,6 +6526,9 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -6833,6 +6874,8 @@ packages: snapshots: + '@adraffy/ens-normalize@1.10.1': {} + '@adraffy/ens-normalize@1.11.1': {} '@babel/code-frame@7.12.11': @@ -7946,6 +7989,10 @@ snapshots: '@noble/ciphers@1.3.0': {} + '@noble/curves@1.2.0': + dependencies: + '@noble/hashes': 1.3.2 + '@noble/curves@1.3.0': dependencies: '@noble/hashes': 1.3.3 @@ -7966,6 +8013,8 @@ snapshots: dependencies: '@noble/hashes': 1.8.0 + '@noble/hashes@1.3.2': {} + '@noble/hashes@1.3.3': {} '@noble/hashes@1.4.0': {} @@ -8967,6 +9016,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@22.7.5': + dependencies: + undici-types: 6.19.8 + '@types/normalize-package-data@2.4.4': {} '@types/parse-json@4.0.2': {} @@ -9224,6 +9277,8 @@ snapshots: acorn@8.15.0: {} + aes-js@4.0.0-beta.5: {} + aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 @@ -10466,6 +10521,19 @@ snapshots: '@scure/bip32': 1.4.0 '@scure/bip39': 1.3.0 + ethers@6.16.0: + dependencies: + '@adraffy/ens-normalize': 1.10.1 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@types/node': 22.7.5 + aes-js: 4.0.0-beta.5 + tslib: 2.7.0 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + event-target-shim@5.0.1: {} eventemitter3@5.0.1: {} @@ -13439,6 +13507,8 @@ snapshots: transitivePeerDependencies: - supports-color + tslib@2.7.0: {} + tslib@2.8.1: {} tsx@4.21.0: @@ -13551,6 +13621,8 @@ snapshots: undici-types@5.26.5: {} + undici-types@6.19.8: {} + undici-types@6.21.0: {} undici@7.20.0: {} From 8c2137fdb746330d32dc47c7fb2b5d0a3466fa5a Mon Sep 17 00:00:00 2001 From: Gabriel Tozatti Date: Fri, 6 Mar 2026 11:32:37 -0300 Subject: [PATCH 3/6] fix: ignore generateTestTx config directory --- packages/worker/.gitignore | 1 + .../queues/generateTestTx/config/vault.json | 21 --------------- .../queues/generateTestTx/config/vault_2.json | 26 ------------------- 3 files changed, 1 insertion(+), 47 deletions(-) create mode 100644 packages/worker/.gitignore delete mode 100644 packages/worker/src/queues/generateTestTx/config/vault.json delete mode 100644 packages/worker/src/queues/generateTestTx/config/vault_2.json diff --git a/packages/worker/.gitignore b/packages/worker/.gitignore new file mode 100644 index 000000000..7f0ad7edd --- /dev/null +++ b/packages/worker/.gitignore @@ -0,0 +1 @@ +/src/queues/generateTestTx/config/ diff --git a/packages/worker/src/queues/generateTestTx/config/vault.json b/packages/worker/src/queues/generateTestTx/config/vault.json deleted file mode 100644 index d60f9ab7c..000000000 --- a/packages/worker/src/queues/generateTestTx/config/vault.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "vault": { - "signaturesCount": 2, - "hashPredicate": "0x223767a84816a4274981acd576fbce2aebae6cefddf4c398856582579e991f09", - "version": "0x967aaa71b3db34acd8104ed1d7ff3900e67cff3d153a0ffa86d85957f579aa6a", - "signers": [ - { - "address": "0x2bba3b154de16722ddbdbf40843c8464f773638c5383c4aa46d69e611fb3e199", - "type": "fuel", - "privateKey": "0x096ee6d0fb5ce5654e2f3926f89020924d20e6ffbf27a7c68e9f9f8149eaa829" - }, - { - "address": "0x000000000000000000000000dc1b8e7a80edb19a19ece2964bf293994499769a", - "type": "evm", - "privateKey": "106db0a48940725aa19c4ba00b48ff37ee9ff6c1bcac9cfbd83b290c01f961ce" - } - ] - }, - "network": "mainnet", - "defaultAmount": "0.0000001" -} diff --git a/packages/worker/src/queues/generateTestTx/config/vault_2.json b/packages/worker/src/queues/generateTestTx/config/vault_2.json deleted file mode 100644 index fa672331e..000000000 --- a/packages/worker/src/queues/generateTestTx/config/vault_2.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "vault": { - "signaturesCount": 3, - "hashPredicate": "0x223767a84816a4274981acd576fbce2aebae6cefddf4c398856582579e991f09", - "version": "0x967aaa71b3db34acd8104ed1d7ff3900e67cff3d153a0ffa86d85957f579aa6a", - "signers": [ - { - "address": "0x2bba3b154de16722ddbdbf40843c8464f773638c5383c4aa46d69e611fb3e199", - "type": "fuel", - "privateKey": "0x096ee6d0fb5ce5654e2f3926f89020924d20e6ffbf27a7c68e9f9f8149eaa829" - }, - { - "address": "0x000000000000000000000000dc1b8e7a80edb19a19ece2964bf293994499769a", - "type": "evm", - "privateKey": "106db0a48940725aa19c4ba00b48ff37ee9ff6c1bcac9cfbd83b290c01f961ce" - }, - { - "address": "0x4f8fee8a7a36399320c90e7274e47f125ecd5840942ea1f51085b1042f68d981", - "type": "fuel", - "privateKey": "0xd57210fa919473047f8a47a6cadd66ba5791a96654b29cd2c0608c67df4d230f" - } - ] - }, - "network": "mainnet", - "defaultAmount": "0.000001" -} From 803973e81120439601e975cf5b5d0775e63194f8 Mon Sep 17 00:00:00 2001 From: Gabriel Tozatti Date: Fri, 6 Mar 2026 11:56:38 -0300 Subject: [PATCH 4/6] chore: uncomment temporary test code for vault logging job --- packages/worker/src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/worker/src/index.ts b/packages/worker/src/index.ts index fcba06ba2..c6a1417f0 100644 --- a/packages/worker/src/index.ts +++ b/packages/worker/src/index.ts @@ -75,9 +75,9 @@ MongoDatabase.connect(); PsqlClient.connect(); // schedulers -// BalanceCron.create(); -// AssetCron.create(); -// UserBlockSyncCron.create(); +BalanceCron.create(); +AssetCron.create(); +UserBlockSyncCron.create(); TransactionCron.create(); app.listen(WORKER_PORT ?? 3063, () => From 97cc9ea368b45663bcf6e885ab3448190295575e Mon Sep 17 00:00:00 2001 From: Gabriel Tozatti Date: Fri, 6 Mar 2026 12:57:56 -0300 Subject: [PATCH 5/6] refactor: improve transaction cron reliability, security and observability --- packages/worker/.env.example | 3 + packages/worker/package.json | 3 +- .../src/queues/generateTestTx/README.md | 81 +++++++++++++------ .../src/queues/generateTestTx/constants.ts | 6 +- .../worker/src/queues/generateTestTx/queue.ts | 76 ++++++++++++++--- .../src/queues/generateTestTx/scheduler.ts | 5 +- .../worker/src/queues/generateTestTx/types.ts | 2 +- .../generateTestTx/utils/estimateFee.ts | 51 ++++++++++++ .../src/queues/generateTestTx/utils/signer.ts | 10 ++- .../src/queues/generateTestTx/utils/vault.ts | 2 + pnpm-lock.yaml | 33 +++++--- 11 files changed, 215 insertions(+), 57 deletions(-) create mode 100644 packages/worker/src/queues/generateTestTx/utils/estimateFee.ts diff --git a/packages/worker/.env.example b/packages/worker/.env.example index 7889d54f7..0878e85b2 100644 --- a/packages/worker/.env.example +++ b/packages/worker/.env.example @@ -20,3 +20,6 @@ WORKER_REDIS_PORT=6379 WORKER_ENVIRONMENT=production WORKER_NAME=bako-worker WORKER_PORT=3063 + +# Worker transaction +TRANSACTION_CRON_INTERVAL_MS=1200000 \ No newline at end of file diff --git a/packages/worker/package.json b/packages/worker/package.json index 4e25d8c8a..fbd89414c 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -53,6 +53,7 @@ "tscpaths": "0.0.9", "pino-pretty": "11.2.2", "viem": "^2.30.6", - "ethers": "^6.14.3" + "ethers": "^6.14.3", + "zod": "^4.3.6" } } diff --git a/packages/worker/src/queues/generateTestTx/README.md b/packages/worker/src/queues/generateTestTx/README.md index 74a736adc..910c4576d 100644 --- a/packages/worker/src/queues/generateTestTx/README.md +++ b/packages/worker/src/queues/generateTestTx/README.md @@ -14,7 +14,7 @@ src/queues/generateTestTx/ │ └── ... ├── utils/ │ ├── loader.ts # Reads and validates the selected JSON -│ ├── vault.factory.ts # Instantiates the Vault from the config +│ ├── vault.ts # Instantiates the Vault from the config │ └── signer.ts # Signs the transaction by network type (Fuel/EVM) ├── types.ts # Shared interfaces and enums ├── constants.ts # Queue constants and execution interval @@ -27,16 +27,16 @@ src/queues/generateTestTx/ ## How it works 1. When the worker starts, `TransactionCron` clears old jobs from Redis and schedules two jobs: - - **Immediate** — runs as soon as the worker starts - - **Recurring** — runs at every interval defined in `REPEAT_INTERVAL_MS`, with an initial delay to avoid colliding with the immediate job + - **Immediate** — runs as soon as the worker starts + - **Recurring** — runs at every interval defined in `REPEAT_INTERVAL_MS`, with an initial delay to avoid colliding with the immediate job -2. On each execution, `loader.ts` randomly picks a JSON file from the `config/` folder, avoiding repeating the last used vault. +2. On each execution, `loader.ts` randomly picks a JSON file from the `config/` folder, avoiding repeating the last used vault. The JSON is validated with **Zod** on every read — invalid configs throw a descriptive error. -3. With the config loaded, `vault.factory.ts` instantiates the `Vault` from the `bakosafe` SDK with the configured signers, padded to 10 positions as required by the predicate. +3. With the config loaded, `vault.ts` instantiates the `Vault` from the `bakosafe` SDK with the configured signers, padded to 10 positions as required by the predicate. -4. `signer.ts` iterates over the signers and signs the `hashTxId` for each one that has a `privateKey` defined. External signers (without `privateKey`) are silently skipped. +4. `signer.ts` iterates over the signers and loads each private key from the environment variable defined in `envKey`. Signers whose environment variable is not set are silently skipped. -5. The transaction is sent with the collected witnesses. If an error occurs, the logger records it and the job is marked as failed in Redis (no retries). +5. The transaction is sent with the collected witnesses. If an error occurs, the logger records it along with gas estimation details and the job is marked as failed in Redis (no retries). --- @@ -54,12 +54,12 @@ Create a `.json` file inside `src/queues/generateTestTx/config/`. The filename c { "address": "0x2bba3b154de16722ddbdbf40843c8464f773638c5383c4aa46d69e611fb3e199", "type": "fuel", - "privateKey": "0xYOUR_FUEL_PRIVATE_KEY" + "envKey": "VAULTCONFIG_SIGNER_1_KEY" }, { "address": "0xAbCdEf1234567890abcdef1234567890abcdef12", "type": "evm", - "privateKey": "0xYOUR_EVM_PRIVATE_KEY" + "envKey": "VAULTCONFIG_SIGNER_2_KEY" } ] }, @@ -68,6 +68,13 @@ Create a `.json` file inside `src/queues/generateTestTx/config/`. The filename c } ``` +Then add the corresponding private keys to your `.env` file: + +```env +VAULTCONFIG_SIGNER_1_KEY=0xYOUR_FUEL_PRIVATE_KEY +VAULTCONFIG_SIGNER_2_KEY=0xYOUR_EVM_PRIVATE_KEY +``` + ### Fields | Field | Description | @@ -85,9 +92,9 @@ Create a `.json` file inside `src/queues/generateTestTx/config/`. The filename c |---|---| | `address` | B256 address for Fuel, `0x` address for EVM | | `type` | Network type: `fuel` or `evm` | -| `privateKey` | Local private key. Omit for external signers | +| `envKey` | Name of the environment variable that holds the private key for this signer | -> **Important:** the `address` and `privateKey` must be from the same cryptographic key pair. If they don't match, the predicate will reject the signature. +> **Important:** the `address` must correspond to the private key stored in the environment variable defined by `envKey`. If they don't match, the predicate will reject the signature. --- @@ -101,7 +108,7 @@ Signs using `WalletUnlocked` from the `fuels` SDK directly with the `hashTxId`. { "address": "0x2bba3b...", "type": "fuel", - "privateKey": "0xYOUR_FUEL_KEY" + "envKey": "VAULT_1_SIGNER_1_KEY" } ``` @@ -113,18 +120,19 @@ Signs using `ethers.Wallet`. The message format is automatically detected based { "address": "0xAbCdEf...", "type": "evm", - "privateKey": "0xYOUR_EVM_KEY" + "envKey": "VAULT_1_SIGNER_2_KEY" } ``` -### External signer (no `privateKey`) +### External signer (no env key set) -Any signer without a `privateKey` is skipped by the cron. Use this pattern to register real user addresses in the vault — the address must be in the signers array for the predicate to recognize it, but the signature is not collected automatically. +If the environment variable defined in `envKey` is not set, the signer is skipped by the cron. Use this pattern to register real user addresses in the vault — the address must be in the signers array for the predicate to recognize it, but the signature is not collected automatically. ```json { "address": "0xUserAddress...", - "type": "fuel" + "type": "fuel", + "envKey": "VAULT_1_USER_KEY" } ``` @@ -133,7 +141,7 @@ Any signer without a `privateKey` is skipped by the cron. Use this pattern to re ## Adding a new vault 1. Create a new JSON file in the `config/` folder following the model above -2. Fill in the signers with the vault's addresses and keys +2. Set `envKey` for each signer and add the corresponding private keys to `.env` 3. Make sure the vault has enough balance to cover the gas fee 4. No worker restart needed — the loader reads the files on every execution @@ -141,19 +149,46 @@ Any signer without a `privateKey` is skipped by the cron. Use this pattern to re ## Interval configuration -In `constants.ts`: +The execution interval can be set via environment variable: -```typescript -export const REPEAT_INTERVAL_MS = 1 * 60 * 1000; // 1 minute +```env +TRANSACTION_CRON_INTERVAL_MS=1200000 ``` -Change the value to adjust the execution frequency. The interval is always relative to the last execution, not the system clock. +If not set, it defaults to 20 minutes. The interval is always relative to the last execution, not the system clock. + +--- + +## Error logs + +Every failed job logs a structured `gas` field alongside the error to help diagnose the failure: + +```json +{ + "error": { "message": "...", "name": "FuelError" }, + "gas": { + "maxFee": "1500", + "gasPrice": "1", + "balance": "800", + "isInsufficient": true + } +} +``` + +| Field | Description | +|---|---| +| `maxFee` | Estimated maximum fee for the transaction | +| `gasPrice` | Current network gas price | +| `balance` | Current vault balance | +| `isInsufficient` | `true` if balance is lower than the estimated fee | + +All fields show `"unavailable"` when the error occurs before the vault balance can be fetched. --- ## Important notes -- **Balance:** Each vault must have enough balance to cover gas. If the balance is insufficient, the job fails and the error is recorded by the logger. -- **Private keys:** Never commit `.json` files with a `privateKey` filled in. Use environment variables or a secret manager to inject values in production. +- **Balance:** Each vault must have enough balance to cover gas. If the balance is insufficient, the job fails and the error is recorded by the logger with gas details. +- **Private keys:** Never put private keys directly in JSON config files. Always use environment variables via `envKey`. Never commit `.env` files with real values — use `.env.example` as a reference. - **Redis:** Failed jobs are stored in Redis for inspection via Bull Dashboard. Successful jobs are automatically removed. - **SDK version:** The worker uses `bakosafe` + `fuels`. If the network's `fuel-core` version differs from what the SDK supports, a compatibility warning may appear in the logs — it does not block execution, but keeping the SDK up to date is recommended. diff --git a/packages/worker/src/queues/generateTestTx/constants.ts b/packages/worker/src/queues/generateTestTx/constants.ts index 7358f5f18..c05a18852 100644 --- a/packages/worker/src/queues/generateTestTx/constants.ts +++ b/packages/worker/src/queues/generateTestTx/constants.ts @@ -1,2 +1,6 @@ export const QUEUE_TRANSACTION = "QUEUE_TRANSACTION"; -export const REPEAT_INTERVAL_MS = 20 * 60 * 1000; + +// Interval can be configured per environment using an environment variable. +// Default: 20 minutes. +export const REPEAT_INTERVAL_MS = + Number(process.env.TRANSACTION_CRON_INTERVAL_MS) || 20 * 60 * 1000; diff --git a/packages/worker/src/queues/generateTestTx/queue.ts b/packages/worker/src/queues/generateTestTx/queue.ts index 34c7912c2..c2e2ebbc6 100644 --- a/packages/worker/src/queues/generateTestTx/queue.ts +++ b/packages/worker/src/queues/generateTestTx/queue.ts @@ -1,11 +1,13 @@ import Queue from "bull"; -import { Provider } from "fuels"; +import { Provider, bn } from "fuels"; +import type { BN } from "fuels"; import { redisConfig } from "@/clients"; import { networks } from "@/mocks/networks"; import { QUEUE_TRANSACTION } from "./constants"; import { loadVaultConfig } from "@/queues/generateTestTx/utils/loader"; import { createVault } from "@/queues/generateTestTx/utils/vault"; import { collectWitnesses } from "@/queues/generateTestTx/utils/signer"; +import { estimateFeeWithBalance } from "@/queues/generateTestTx/utils/estimateFee"; import { logger } from "@/config/logger"; const transactionQueue = new Queue(QUEUE_TRANSACTION, { @@ -13,13 +15,33 @@ const transactionQueue = new Queue(QUEUE_TRANSACTION, { }); transactionQueue.process(1, async (job) => { - console.log(`[${QUEUE_TRANSACTION}] Job started`, new Date()); + logger.info(`[${QUEUE_TRANSACTION}] Job started`); + + let maxFee: BN | undefined; + let gasPrice: BN | undefined; + let balance: BN | undefined; + + const config = loadVaultConfig(); + logger.info(`[${QUEUE_TRANSACTION}] Network: ${config.network}`); + + const provider = new Provider(networks[config.network]); + const vault = createVault(provider, config); try { - const config = loadVaultConfig(); - const provider = new Provider(networks[config.network]); - const vault = createVault(provider, config); + ({ maxFee, gasPrice, balance } = await estimateFeeWithBalance( + vault, + config.defaultAmount + )); + } catch { + try { + const baseAssetId = await provider.getBaseAssetId(); + const { coins } = await vault.getCoins(baseAssetId); + balance = coins.reduce((acc, c) => acc.add(c.amount), bn(0)); + gasPrice = await provider.getLatestGasPrice(); + } catch {} + } + try { const baseAsset = await provider.getBaseAssetId(); const { tx, hashTxId, encodedTxId } = await vault.transaction({ name: "Transaction Cron", @@ -31,7 +53,7 @@ transactionQueue.process(1, async (job) => { }, ], }); - console.log(`[${QUEUE_TRANSACTION}] Transaction created:`, hashTxId); + logger.info(`[${QUEUE_TRANSACTION}] Transaction created: ${hashTxId}`); const witnesses = await collectWitnesses( vault, @@ -40,9 +62,8 @@ transactionQueue.process(1, async (job) => { config.vault.signers, provider ); - console.log( - `[${QUEUE_TRANSACTION}] Witnesses collected:`, - witnesses.length + logger.info( + `[${QUEUE_TRANSACTION}] Witnesses collected: ${witnesses.length}` ); if (witnesses.length === 0) { @@ -52,12 +73,41 @@ transactionQueue.process(1, async (job) => { tx.witnesses = witnesses; const result = await vault.send(tx); - console.log(`[${QUEUE_TRANSACTION}] TX sent, waiting for result...`); + logger.info(`[${QUEUE_TRANSACTION}] TX sent, waiting for result...`); - await result.waitForResult(); - console.log(`[${QUEUE_TRANSACTION}] TX SUCCESS`); + const response = await result.waitForResult(); + logger.info(`[${QUEUE_TRANSACTION}] TX SUCCESS`, { + status: response.status, + fee: response.fee?.toString(), + }); } catch (error) { - logger.error({ error }, `[${QUEUE_TRANSACTION}] Error`); + const amountInUnits = bn.parseUnits(config.defaultAmount); + + logger.error( + { + error: + error instanceof Error + ? { + message: error.message, + stack: error.stack, + name: error.name, + } + : String(error), + gas: { + maxFee: maxFee?.toString() ?? "unavailable", + gasPrice: gasPrice?.toString() ?? "unavailable", + balance: balance?.toString() ?? "unavailable", + totalRequired: maxFee + ? maxFee.add(amountInUnits).toString() + : "unavailable", + isInsufficient: + maxFee && balance + ? balance.lt(maxFee.add(amountInUnits)) + : "unavailable", + }, + }, + `[${QUEUE_TRANSACTION}] Error` + ); } }); diff --git a/packages/worker/src/queues/generateTestTx/scheduler.ts b/packages/worker/src/queues/generateTestTx/scheduler.ts index e44ce0ac4..9b320706f 100644 --- a/packages/worker/src/queues/generateTestTx/scheduler.ts +++ b/packages/worker/src/queues/generateTestTx/scheduler.ts @@ -1,5 +1,6 @@ import transactionQueue from "./queue"; import { QUEUE_TRANSACTION, REPEAT_INTERVAL_MS } from "./constants"; +import { logger } from "@/config/logger"; class TransactionCron { private static instance: TransactionCron; @@ -45,12 +46,12 @@ class TransactionCron { } ); - console.log( + logger.info( `[${QUEUE_TRANSACTION}] Setup complete: Immediate job added & Cron scheduled.` ); } catch (e) { this.isRunning = false; - console.error(`[${QUEUE_TRANSACTION}] Error in setup:`, e); + logger.error({ error: e }, `[${QUEUE_TRANSACTION}] Error in setup`); } } } diff --git a/packages/worker/src/queues/generateTestTx/types.ts b/packages/worker/src/queues/generateTestTx/types.ts index 1e810d560..f40b8bba0 100644 --- a/packages/worker/src/queues/generateTestTx/types.ts +++ b/packages/worker/src/queues/generateTestTx/types.ts @@ -6,7 +6,7 @@ export enum SignerType { export interface SignerConfig { address: string; type: SignerType; - privateKey?: string; + envKey: string; } export interface VaultConfigFile { diff --git a/packages/worker/src/queues/generateTestTx/utils/estimateFee.ts b/packages/worker/src/queues/generateTestTx/utils/estimateFee.ts new file mode 100644 index 000000000..7cf3cdfc0 --- /dev/null +++ b/packages/worker/src/queues/generateTestTx/utils/estimateFee.ts @@ -0,0 +1,51 @@ +import { ScriptTransactionRequest, bn, calculateGasFee } from "fuels"; +import type { BN } from "fuels"; +import { Vault } from "bakosafe"; + +export interface FeeEstimate { + maxFee: BN; + gasPrice: BN; + balance: BN; +} + +export async function estimateFeeWithBalance( + vault: Vault, + amount: string +): Promise { + const provider = vault.provider; + const baseAssetId = await provider.getBaseAssetId(); + const predicateGasUsed = await vault.maxGasUsed(); + + const { coins } = await vault.getCoins(baseAssetId); + const balance = coins.reduce((acc, c) => acc.add(c.amount), bn(0)); + + const transactionRequest = new ScriptTransactionRequest(); + const amountBN = bn.parseUnits(amount); + + const fakeResources = vault.generateFakeResources([ + { assetId: baseAssetId, amount: amountBN.mul(10) }, + ]); + + transactionRequest.addCoinOutput(vault.address, amountBN, baseAssetId); + transactionRequest.addResources(fakeResources); + + const { gasPriceFactor } = await provider.getGasConfig(); + const { maxFee, gasPrice } = await provider.estimateTxGasAndFee({ + transactionRequest, + }); + + const serializedTxCount = bn(transactionRequest.toTransactionBytes().length); + const totalGasWithBytes = predicateGasUsed.add(serializedTxCount.mul(64)); + + const predicateSuccessFeeDiff = calculateGasFee({ + gas: totalGasWithBytes, + priceFactor: gasPriceFactor, + gasPrice, + }); + + return { + maxFee: maxFee.add(predicateSuccessFeeDiff).mul(20).div(10), + gasPrice, + balance, + }; +} diff --git a/packages/worker/src/queues/generateTestTx/utils/signer.ts b/packages/worker/src/queues/generateTestTx/utils/signer.ts index dd3a22ac6..1faf3b28b 100644 --- a/packages/worker/src/queues/generateTestTx/utils/signer.ts +++ b/packages/worker/src/queues/generateTestTx/utils/signer.ts @@ -16,16 +16,18 @@ export async function signWithSigner( signer: SignerConfig, provider: Provider ): Promise { - if (!signer.privateKey) { + const privateKey = process.env[signer.envKey]; + + if (!privateKey) { console.warn( - `[${QUEUE_TRANSACTION}] Skipping external signer (no privateKey): ${signer.address} [${signer.type}]` + `[${QUEUE_TRANSACTION}] No private key found for env "${signer.envKey}" — skipping signer ${signer.address} [${signer.type}]` ); return null; } switch (signer.type) { case SignerType.FUEL: { - const wallet = new WalletUnlocked(signer.privateKey, provider); + const wallet = new WalletUnlocked(privateKey, provider); const signature = await wallet.signMessage(hashTxId); return { address: signer.address, @@ -34,7 +36,7 @@ export async function signWithSigner( } case SignerType.EVM: { - const evmWallet = new ethers.Wallet(signer.privateKey); + const evmWallet = new ethers.Wallet(privateKey); const messageToSign = encodedTxId.startsWith("0x") ? arrayify(stringToHex(hashTxId)) diff --git a/packages/worker/src/queues/generateTestTx/utils/vault.ts b/packages/worker/src/queues/generateTestTx/utils/vault.ts index 52661e882..75ef89f7b 100644 --- a/packages/worker/src/queues/generateTestTx/utils/vault.ts +++ b/packages/worker/src/queues/generateTestTx/utils/vault.ts @@ -4,6 +4,8 @@ import { VaultConfigFile } from "@/queues/generateTestTx/types"; type VaultConfigurable = ConstructorParameters[1]; +// The Fuel predicate requires exactly 10 positions in the signers array. +// Unused positions are filled with zeros. const MAX_SIGNERS = 10; const ZERO = "0x0000000000000000000000000000000000000000000000000000000000000000"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9356cf46b..00b849b38 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -76,7 +76,7 @@ importers: version: 1.13.5 bakosafe: specifier: 0.6.0 - version: 0.6.0(fuels@0.101.3(vitest@3.0.9(@types/node@20.6.0)(tsx@4.21.0)(yaml@2.8.2)))(typescript@5.4.5) + version: 0.6.0(fuels@0.101.3(vitest@3.0.9(@types/node@20.6.0)(tsx@4.21.0)(yaml@2.8.2)))(typescript@5.4.5)(zod@4.3.6) body-parser: specifier: 1.20.4 version: 1.20.4 @@ -295,7 +295,7 @@ importers: version: 1.13.5 bakosafe: specifier: 0.6.0 - version: 0.6.0(fuels@0.101.3(vitest@3.0.9(@types/node@20.6.0)(tsx@4.21.0)(yaml@2.8.2)))(typescript@5.4.5) + version: 0.6.0(fuels@0.101.3(vitest@3.0.9(@types/node@20.6.0)(tsx@4.21.0)(yaml@2.8.2)))(typescript@5.4.5)(zod@4.3.6) date-fns: specifier: 2.30.0 version: 2.30.0 @@ -428,7 +428,7 @@ importers: version: 3.0.11 bakosafe: specifier: 0.6.0 - version: 0.6.0(fuels@0.101.3(vitest@3.0.9(@types/node@20.6.0)(tsx@4.21.0)(yaml@2.8.2)))(typescript@5.4.5) + version: 0.6.0(fuels@0.101.3(vitest@3.0.9(@types/node@20.6.0)(tsx@4.21.0)(yaml@2.8.2)))(typescript@5.4.5)(zod@4.3.6) bull: specifier: ^4.16.5 version: 4.16.5 @@ -534,7 +534,10 @@ importers: version: 0.0.9 viem: specifier: ^2.30.6 - version: 2.45.1(typescript@5.4.5) + version: 2.45.1(typescript@5.4.5)(zod@4.3.6) + zod: + specifier: ^4.3.6 + version: 4.3.6 packages: @@ -6872,6 +6875,9 @@ packages: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + snapshots: '@adraffy/ens-normalize@1.10.1': {} @@ -9243,9 +9249,10 @@ snapshots: jsonparse: 1.3.1 through: 2.3.8 - abitype@1.2.3(typescript@5.4.5): + abitype@1.2.3(typescript@5.4.5)(zod@4.3.6): optionalDependencies: typescript: 5.4.5 + zod: 4.3.6 abort-controller@3.0.0: dependencies: @@ -9477,7 +9484,7 @@ snapshots: babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) - bakosafe@0.6.0(fuels@0.101.3(vitest@3.0.9(@types/node@20.6.0)(tsx@4.21.0)(yaml@2.8.2)))(typescript@5.4.5): + bakosafe@0.6.0(fuels@0.101.3(vitest@3.0.9(@types/node@20.6.0)(tsx@4.21.0)(yaml@2.8.2)))(typescript@5.4.5)(zod@4.3.6): dependencies: '@ethereumjs/util': 9.0.3 '@ethersproject/bytes': 5.7.0 @@ -9489,7 +9496,7 @@ snapshots: lodash.partition: 4.6.0 pnpm: 10.28.2 uuid: 9.0.1 - viem: 2.45.1(typescript@5.4.5) + viem: 2.45.1(typescript@5.4.5)(zod@4.3.6) transitivePeerDependencies: - bufferutil - debug @@ -12253,7 +12260,7 @@ snapshots: os-tmpdir@1.0.2: {} - ox@0.11.3(typescript@5.4.5): + ox@0.11.3(typescript@5.4.5)(zod@4.3.6): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -12261,7 +12268,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@5.4.5) + abitype: 1.2.3(typescript@5.4.5)(zod@4.3.6) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.4.5 @@ -13697,15 +13704,15 @@ snapshots: vary@1.1.2: {} - viem@2.45.1(typescript@5.4.5): + viem@2.45.1(typescript@5.4.5)(zod@4.3.6): dependencies: '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@5.4.5) + abitype: 1.2.3(typescript@5.4.5)(zod@4.3.6) isows: 1.0.7(ws@8.18.3) - ox: 0.11.3(typescript@5.4.5) + ox: 0.11.3(typescript@5.4.5)(zod@4.3.6) ws: 8.18.3 optionalDependencies: typescript: 5.4.5 @@ -13947,3 +13954,5 @@ snapshots: archiver-utils: 5.0.2 compress-commons: 6.0.2 readable-stream: 4.7.0 + + zod@4.3.6: {} From 459ad40c2f185e5fa8cb1c721a29b0ab66fe2446 Mon Sep 17 00:00:00 2001 From: Gabriel Tozatti Date: Fri, 6 Mar 2026 13:01:23 -0300 Subject: [PATCH 6/6] fix: network README.md --- packages/worker/src/queues/generateTestTx/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/worker/src/queues/generateTestTx/README.md b/packages/worker/src/queues/generateTestTx/README.md index 910c4576d..92d93423a 100644 --- a/packages/worker/src/queues/generateTestTx/README.md +++ b/packages/worker/src/queues/generateTestTx/README.md @@ -83,7 +83,7 @@ VAULTCONFIG_SIGNER_2_KEY=0xYOUR_EVM_PRIVATE_KEY | `hashPredicate` | Hash of the vault predicate (obtained at deploy time) | | `version` | bakosafe predicate version | | `signers` | List of up to 10 signers | -| `network` | Fuel network (`mainnet`, `testnet`) | +| `network` | Fuel network (`mainnet`, `devnet`) | | `defaultAmount` | Amount to send in the transaction (in ETH) | ### Signer fields