[{"content":"Where ideas become words (maybe). I write about blockchain protocols, Web3 infrastructure, and production systems more broadly — plus whatever else I run into building software day to day.\n","date":"June 22, 2026","externalUrl":null,"permalink":"/","section":"Antomor","summary":"Where ideas become words (maybe). I write about blockchain protocols, Web3 infrastructure, and production systems more broadly — plus whatever else I run into building software day to day.\n","title":"Antomor","type":"page"},{"content":"","date":"June 22, 2026","externalUrl":null,"permalink":"/tags/bitcoin/","section":"Tags","summary":"","title":"Bitcoin","type":"tags"},{"content":"","date":"June 22, 2026","externalUrl":null,"permalink":"/categories/blockchain/","section":"Categories","summary":"","title":"Blockchain","type":"categories"},{"content":"","date":"June 22, 2026","externalUrl":null,"permalink":"/tags/blockchain/","section":"Tags","summary":"","title":"Blockchain","type":"tags"},{"content":"","date":"June 22, 2026","externalUrl":null,"permalink":"/blog/","section":"Blog","summary":"","title":"Blog","type":"blog"},{"content":"","date":"June 22, 2026","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"June 22, 2026","externalUrl":null,"permalink":"/tags/ethereum/","section":"Tags","summary":"","title":"Ethereum","type":"tags"},{"content":"","date":"June 22, 2026","externalUrl":null,"permalink":"/tags/event-driven-architecture/","section":"Tags","summary":"","title":"Event-Driven-Architecture","type":"tags"},{"content":"","date":"June 22, 2026","externalUrl":null,"permalink":"/tags/reliability/","section":"Tags","summary":"","title":"Reliability","type":"tags"},{"content":"I worked on a production off-chain service: a system that watches Bitcoin activity and coordinates downstream operations on Ethereum for a cross-chain interoperability system, with more than one independent operator watching and acting on the same chain activity. It\u0026rsquo;s a monitoring and coordination role, not custody or key management: the service observes Bitcoin, decides what that activity means, and triggers the corresponding action elsewhere. Getting that decision right, without producing duplicate downstream effects, in the face of a chain that can change its mind, turns out to be most of the engineering problem.\nWhy Bitcoin monitoring is hard # If you\u0026rsquo;re used to Ethereum, Bitcoin\u0026rsquo;s quirks are easy to underestimate. Three things make chain-watching harder than it looks:\nReorgs are a normal outcome, not an attack. A block your service already acted on can be replaced by a different block at the same height. This isn\u0026rsquo;t a rare edge case reserved for 51%-attack scenarios; short reorgs (one or two blocks) happen as an ordinary consequence of how proof-of-work chains resolve competing miners, and a service that isn\u0026rsquo;t built to expect them will eventually act on a block that stops existing. There\u0026rsquo;s no finality below N confirmations. Bitcoin doesn\u0026rsquo;t have an on-chain finality gadget the way some proof-of-stake chains do. \u0026ldquo;Final enough to act on\u0026rdquo; is a threshold you choose, not a guarantee the protocol gives you, and choosing it is a real trade-off between how long you wait and how much reorg risk you\u0026rsquo;re willing to eat. The same event can be observed more than once. During a reorg, a transaction can appear in a block, disappear when that block gets orphaned, and reappear in a later block once the chain resolves, sometimes with a different set of surrounding transactions. This isn\u0026rsquo;t unique to Bitcoin\u0026rsquo;s UTXO model, any chain re-observes activity across a reorg, but it\u0026rsquo;s the case this kind of service has to be built around. From the watcher\u0026rsquo;s point of view, that\u0026rsquo;s the same real-world deposit surfacing as multiple, slightly different observations over time. None of these are solved by watching harder. They\u0026rsquo;re solved by designing for the chain telling you something, then later telling you something else.\nStart with the invariants # Before getting into specific mechanisms, it\u0026rsquo;s worth naming what actually has to hold, independent of implementation detail. A handful of invariants define what \u0026ldquo;correct\u0026rdquo; means for this kind of system:\nOne canonical Bitcoin observation must not produce the same downstream effect twice. State derived from an orphaned block must be reversible. Restarting a process must not change the outcome. Adding another service instance must not change the logical outcome. One logical consumer processing an event must not prevent another logical consumer from independently processing it. Everything below is one of these invariants, made concrete.\nFour failure modes, four patterns # Chain reorganizations # The direct fix is a confirmation-depth threshold: don\u0026rsquo;t treat an observation as final until it\u0026rsquo;s buried under N additional blocks. But depth alone isn\u0026rsquo;t enough, because you still need to detect when a reorg happens within that window. In this design, the canonical-chain check runs before advancing the persisted tip: before doing anything else on a given tick, compare the block hash your service has stored at a given height against the chain\u0026rsquo;s actual hash at that height right now. A mismatch means the block you built state on is no longer canonical, and everything derived from it has to be rolled back before you continue.\ngraph TD A[Start tick] --\u003e B[\"Compare stored hash atheight H vs. chain'scurrent hash at H\"] B --\u003e|match| C[Proceed: fetch newblocks since last tip] B --\u003e|mismatch: reorg| D[\"Roll back state derivedfrom the orphaned block(s)\"] D --\u003e C Running that check before advancing the tip matters: check for a reorg only after fetching new blocks, and you can end up building new state on top of a branch that\u0026rsquo;s already been abandoned. A single-height comparison catches the common case (the reorg replaced the block right at your last checkpoint), but it\u0026rsquo;s a simplification: a deeper reorg needs the same idea applied over a window of recent heights, walking back until you find a height where the stored and current hashes actually agree, and rolling back everything after that common ancestor.\nRetries # Any call to an upstream data source, a node, an indexing API, can fail transiently: timeouts, rate limits, a dropped connection. Retrying a read is cheap, since nothing has happened yet that a duplicate read could compound. Bounded retries at the client level, a small retry budget with backoff, handle the common transient-failure case without changing application semantics. The failure mode that actually needs design is different: a retry issues the same operation again without knowing whether the first attempt\u0026rsquo;s side effect, recording an observation, triggering a downstream action, already landed on the far side before the client timed out or crashed. That\u0026rsquo;s not a retry problem anymore, it\u0026rsquo;s a duplicate-processing problem, and it needs its own answer.\nDuplicate processing # The same on-chain event can also reach your service more than once for reasons that have nothing to do with retries: once before a reorg, again after, or because two polling windows briefly overlap. Either way, the fix is the same: idempotency, not making duplicates impossible, but making them harmless. Give every observation a deterministic identity, protocol-specific rather than generic: for a Bitcoin output, that\u0026rsquo;s (txid, vout); for an EVM log, it\u0026rsquo;s (transaction hash, log index). Enforce uniqueness on that identity at the database level: it prevents duplicate observation records and gives the downstream path a stable deduplication boundary to build on, provided recording the observation and deciding to trigger the downstream effect stay correctly coupled.\nRace conditions and consistency # This is the failure mode that\u0026rsquo;s easiest to get wrong, because the instinct is to reach for a distributed lock or leader election: pick one instance to be authoritative, and have everyone else defer to it. That works, but it\u0026rsquo;s more machinery than the problem needs, and it also runs two different situations together that deserve separate answers.\nDifferent logical consumers watching the same activity. One component might react to a Bitcoin deposit by updating a balance; another might react to the same deposit by feeding an entirely separate downstream process. Both need to see the same event independently. The pattern here is called Idempotent Consumer. Every domain event carries an idempotency key, enforced unique at the database level exactly as above. Each consumer then keeps its own dedup record for that event, keyed on the pair (consumer, event) rather than on the event alone, so whether one consumer has processed something is invisible to every other consumer. A minimal version of this is just an existence check; a fuller implementation tracks pending, done, or failed on top of it to support retries and observability. Either way, consistency between different consumers comes from that isolation, each one owning its own delivery record, not from making them take turns.\nMultiple replicas of the same logical consumer. Running several instances of one consumer for throughput or availability is a different problem. They share the same work stream, and per-consumer isolation doesn\u0026rsquo;t separate them from each other, since they\u0026rsquo;re the same consumer. Two replicas can still race to dequeue the same pending row. Solving that takes a narrow, row-level claim at dequeue time, a SELECT ... FOR UPDATE SKIP LOCKED-style lock on one row, not the system-wide lock the earlier instinct reaches for, layered on top of the pattern above rather than something it gives you for free.\ngraph TD E[\"Domain event(idempotency key)\"] --\u003e U{\"Unique constraint onidempotency key\"} U --\u003e|duplicate| R[Rejected: no-op] U --\u003e|new| S[Event stored] S --\u003e I1[\"Consumer A's dedup record(own delivery state)\"] S --\u003e I2[\"Consumer B's dedup record(own delivery state)\"] I1 --\u003e H1[\"Consumer A's handler(idempotent)\"] I2 --\u003e H2[\"Consumer B's handler(idempotent)\"] One more thing both cases force you to confront: handlers have to be idempotent even beyond the uniqueness check, because delivery is at-least-once. If a handler finishes its work but the process crashes before its delivery record is marked done, that event gets redelivered. What makes redelivery safe isn\u0026rsquo;t any single technique: an upsert is the common shape when the effect is itself a piece of state (a balance, a status), but a conditional state transition (\u0026ldquo;only advance from pending to confirmed, never from confirmed itself\u0026rdquo;) or a separate idempotency record written atomically alongside the real state change both work too. The requirement is that reapplying the same operation lands in the same externally relevant business effect as applying it once, not that every handler looks like an upsert or that every last timestamp, audit record, or metric is byte-identical across replays.\nEthereum-side ingestion with Ponder # The same principle applies on the Ethereum side: canonical chain data should become durable, queryable state rather than being reconstructed ad hoc during every decision. In this system I used Ponder for EVM indexing, feeding the same persistent store the Bitcoin-side decision logic reads from, so \u0026ldquo;did this already happen\u0026rdquo; is always a database read, not a re-derivation from raw events. I\u0026rsquo;ve written a deeper, code-driven walkthrough of how that indexing layer works, including backfills, reorg handling, and crash recovery on the Ethereum side specifically.\nTakeaway # Correctness in a chain-watching service is dominated by handling the chain changing its mind, not by the happy-path event-processing logic. Reorgs, retries, and duplicate observations aren\u0026rsquo;t corner cases to patch in later. A confirmation-depth threshold with reorg rollback, protocol-specific idempotency keys enforced at the database level, and per-consumer delivery state layered with per-replica claims where they\u0026rsquo;re actually needed are what keep the system\u0026rsquo;s behavior predictable under reorgs, retries, duplicate delivery, process crashes, and concurrent consumers, rather than merely correct on the happy path.\nResources # Bitcoin Developer Guide: block chain, forks, and orphan blocks Ponder Idempotent Consumer pattern (microservices.io) ","date":"June 22, 2026","externalUrl":null,"permalink":"/blog/bitcoin-watchdog-reorgs-race-conditions/","section":"Blog","summary":"I worked on a production off-chain service: a system that watches Bitcoin activity and coordinates downstream operations on Ethereum for a cross-chain interoperability system, with more than one independent operator watching and acting on the same chain activity. It’s a monitoring and coordination role, not custody or key management: the service observes Bitcoin, decides what that activity means, and triggers the corresponding action elsewhere. Getting that decision right, without producing duplicate downstream effects, in the face of a chain that can change its mind, turns out to be most of the engineering problem.\n","title":"Reorgs, Retries, and Race Conditions: Building a Bitcoin-Watching Off-Chain Service","type":"blog"},{"content":"","date":"June 22, 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"A chain doesn\u0026rsquo;t hand you application state, it hands you a stream of events with a footnote attached: this stream can be revised. Ponder is one good vehicle for turning that stream into state you can actually query and trust, but the tool is secondary to the problem. This post is mostly about the problem.\nThe problem # A block your service already processed can stop being canonical. An RPC node can tell you two different things about the same height five minutes apart. An application built to read \u0026ldquo;current state\u0026rdquo; directly off the chain on every request has no principled way to handle any of that; it just re-derives an answer each time and hopes the chain agrees with itself in the meantime.\nThe actual engineering problem isn\u0026rsquo;t \u0026ldquo;how do I read events off a contract.\u0026rdquo; It\u0026rsquo;s: how do I turn a stream of events that can be reorganized, replayed, and re-observed into application state I can trust enough to build a product on. Everything below, backfills, reorg handling, crash recovery, reindexing, is really one problem viewed from different angles: keeping derived state honest about the event history it\u0026rsquo;s built from.\nRaw events vs derived state # Two different things live in an indexer, and conflating them is where most confusion starts.\ntransfer_event: the durable historical record. One row per canonical Transfer log in the indexed history. Rows don\u0026rsquo;t change shape once written; a reorg is the one thing that adds or removes them. account: a derived projection. Today\u0026rsquo;s balance per address, computed from the event history, not observed directly from the chain. The second table exists because recomputing \u0026ldquo;current balance\u0026rdquo; by replaying the full transfer history on every read doesn\u0026rsquo;t scale. You materialize it once and keep it incrementally up to date instead. That relationship, projection derived from durable events, is where the interesting properties come from:\nBackfill is possible because account can be thrown away and rebuilt by replaying every row in transfer_event in order: it\u0026rsquo;s a deterministic function of that history and nothing else. Deterministic replay matters because recovery can cause historical events to be processed again after their previous effects have been rolled back first. Given the same event and the same preceding database state, the handler needs to produce the same result. That\u0026rsquo;s a related property to idempotency, but not the same one: idempotency is about safely reapplying an operation on top of unknown prior state; deterministic replay is about Ponder rolling state back to a known point before reprocessing, so the handler never has to reason about \u0026ldquo;did this already run.\u0026rdquo; Reorg rollback is tractable because there\u0026rsquo;s no special \u0026ldquo;undo\u0026rdquo;: if a block turns out not to be canonical, you roll the event history back to before that block and replay forward again, and the projection lands wherever the corrected history says it should. None of that guarantees the projection is complete, though. That depends entirely on how much of the real history you actually observed, which is the subject of its own section below.\nBuilding the indexer with Ponder # A Ponder project has three parts: a config file declaring which contracts and chains to index, a schema file declaring the Postgres tables, and indexing functions that run once per matching event.\nConfig: declare the contract and the chain it lives on:\n// ponder.config.ts import { createConfig } from \u0026#34;ponder\u0026#34;; import { erc20Abi } from \u0026#34;./abis/erc20Abi\u0026#34;; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1, }, }, contracts: { Token: { abi: erc20Abi, chain: \u0026#34;mainnet\u0026#34;, address: \u0026#34;0xYourTokenAddressHere\u0026#34;, startBlock: 18000000, }, }, }); Schema: one table for the durable event history, one for the derived projection:\n// ponder.schema.ts import { index, onchainTable } from \u0026#34;ponder\u0026#34;; export const account = onchainTable(\u0026#34;account\u0026#34;, (t) =\u0026gt; ({ address: t.hex().primaryKey(), balance: t.bigint().notNull().default(0n), })); export const transferEvent = onchainTable( \u0026#34;transfer_event\u0026#34;, (t) =\u0026gt; ({ id: t.text().primaryKey(), from: t.hex().notNull(), to: t.hex().notNull(), amount: t.bigint().notNull(), blockNumber: t.bigint().notNull(), }), (table) =\u0026gt; ({ blockNumberIdx: index().on(table.blockNumber), }), ); Indexing function: runs once per Transfer log, appends to the history, and updates the projection:\n// src/index.ts import { ponder } from \u0026#34;ponder:registry\u0026#34;; import { account, transferEvent } from \u0026#34;../ponder.schema\u0026#34;; const ZERO_ADDRESS = \u0026#34;0x0000000000000000000000000000000000000000\u0026#34;; ponder.on(\u0026#34;Token:Transfer\u0026#34;, async ({ event, context }) =\u0026gt; { const { from, to, value } = event.args; await context.db.insert(transferEvent).values({ id: event.id, from, to, amount: value, blockNumber: event.block.number, }); if (to !== ZERO_ADDRESS) { await context.db .insert(account) .values({ address: to, balance: value }) .onConflictDoUpdate((row) =\u0026gt; ({ balance: row.balance + value })); } if (from !== ZERO_ADDRESS) { await context.db .insert(account) .values({ address: from, balance: -value }) .onConflictDoUpdate((row) =\u0026gt; ({ balance: row.balance - value })); } }); event.id is Ponder\u0026rsquo;s own globally unique identifier for the event, so there\u0026rsquo;s no need to build one by hand from the transaction hash and log index. The zero address gets excluded on both sides: from == 0x0 is a mint, to == 0x0 is a burn, and neither makes the zero address a real holder worth a row in account. The -value seed on the sender side is deliberate, not a bug: if a sender has no prior row, that\u0026rsquo;s a real address transacting outside your observation window, and a negative balance is the honest signal of that, not something to paper over. The next section is about exactly when and why that happens.\nThat\u0026rsquo;s the core of the indexer, not a complete deployable system: it still needs a real ABI, a real token address, RPC and database configuration, and dependencies installed. What it does demonstrate completely is the durable-event/derived-projection split from the previous section, in actual code.\nA start block is part of your data model # startBlock looks like a config knob. It\u0026rsquo;s actually a claim about how much history your projection can see, and that claim shapes what the data means.\nStart indexing at the token\u0026rsquo;s deployment block, and you\u0026rsquo;ve observed every transfer that ever happened. account.balance is a full reconstruction: replay the complete transfer_event history and you get every address\u0026rsquo;s real balance, because there\u0026rsquo;s nothing before your observation window that could be missing.\nStart later, and transfer_event is still correct: every row in it accurately reflects a real event you observed. It\u0026rsquo;s no longer complete. An address that already held a balance before your startBlock shows up in account reflecting only what it did after that point, not its real balance. It can go negative: an address that, from your indexer\u0026rsquo;s point of view, only ever sends, because it received its tokens before you started watching, looks like it\u0026rsquo;s spending money it never had.\nThat distinction is worth sitting with, because it\u0026rsquo;s easy to blur: the indexer can be entirely internally consistent, every event stored once, every projection update correctly derived from the events it saw, while still producing application state that\u0026rsquo;s semantically incomplete, because the events it saw were never the whole story. Correctness of the mechanism and completeness of the data are different properties. A startBlock picked for convenience (\u0026ldquo;whenever we started this project\u0026rdquo;) rather than for correctness (the contract\u0026rsquo;s real deployment block, or a point where you independently know the true starting balances) trades one for the other without saying so.\nReorg handling # This is where the durable-events-plus-projection split earns its keep. Ponder maintains its own transaction log, independent of any table you declare, recording every write your indexing functions make. When its sync engine detects a reorg, the chain no longer agreeing with what it reported before at some height, it walks that log back to the common ancestor, the last block both the old and new chain agree on, discards the recorded changes after that point, and reprocesses the new canonical chain by re-running your indexing functions against it.\nThe handler above has no reorg logic in it, because none belongs there. The mechanism for \u0026ldquo;what happens when the chain changes its mind\u0026rdquo; lives in Ponder\u0026rsquo;s sync engine, not in application code, which is exactly what makes it safe to write indexing functions as if the chain were append-only, even though it isn\u0026rsquo;t.\nOne thing worth being precise about: this rollback runs against Ponder\u0026rsquo;s own internal log, not against whatever indexes you declare on your own tables. The blockNumberIdx on transfer_event above is for the queries you\u0026rsquo;d actually run, \u0026ldquo;transfers in this block range\u0026rdquo;, not for reorg performance; reorg recovery works identically without it.\nCrash recovery and deterministic replay # Reorg recovery and crash recovery look similar, both roll something back and reprocess, but they answer different questions.\nA reorg means the chain itself changed: some of what you indexed is no longer canonical, so Ponder rolls back to the common ancestor and processes the replacement canonical history. A crash is different: Ponder does not try to preserve the partially indexed unfinalized region. Ponder tracks a content hash of your config, schema, and indexing-function code, called the build_id, to recognize whether a restart is the same app resuming or something new. On recovery with the same build_id, it rolls back unfinalized changes and resumes from finalized state. That\u0026rsquo;s deliberately broader than a reorg rollback, which only needs to unwind history after the common ancestor.\nBoth recovery paths lean on the same property: indexing functions are meant to be deterministic. The same event against the same prior state produces the same result every time, which is what makes \u0026ldquo;discard some writes and reprocess\u0026rdquo; a safe recovery strategy instead of a guess. A handler that depended on wall-clock time, external randomness, or state outside the event and the database would break that guarantee, and neither recovery path would be trustworthy anymore.\nWhat happens when indexing logic changes # Changing your schema or your indexing-function code doesn\u0026rsquo;t resume the current build, it starts a new one. A different build_id means the old derived state might not match what the new code would have produced from the same history, so Ponder backfills fresh rather than resuming.\n\u0026ldquo;Fresh backfill\u0026rdquo; doesn\u0026rsquo;t mean \u0026ldquo;refetch everything from your RPC provider,\u0026rdquo; and that distinction is the actual point here. Ponder caches the raw chain data it fetches, logs, blocks, transactions, independently of build_id, and that cache persists across restarts and rebuilds. A logic change replays your new handlers against data Ponder already has; it doesn\u0026rsquo;t re-download it. Recomputing derived state and refetching raw data are two different costs, and keeping them separate is what makes iterating on indexing logic against a large history practical instead of something you avoid. That separation is useful well beyond blockchain indexing: keeping source data reusable while treating projections as disposable makes changes to application logic much cheaper.\nPonder vs The Graph, and when self-hosted indexing makes sense # I\u0026rsquo;ve written before about the other end of this trade-off: indexing with The Graph. Both solve the same underlying problem, turning chain events into queryable state, with a different shape:\nThe Graph: chain → mapping functions → a subgraph\u0026rsquo;s own entity store → GraphQL API, executed by a network of indexers you don\u0026rsquo;t operate. Ponder: chain → TypeScript indexing functions → Postgres tables you define → SQL, GraphQL, or direct access from the rest of your backend, executed by a service you do operate. This isn\u0026rsquo;t \u0026ldquo;one has a fixed shape and the other doesn\u0026rsquo;t\u0026rdquo;; a subgraph defines its own schema too. What actually differs is who runs the indexing and where the result lives. Ponder is worth the operational cost when the indexed state needs to live in the same database as the rest of your backend, when other services already read and write Postgres and a second, differently-shaped data source to reconcile against is real cost, or when the indexing logic needs to be tightly coupled to other backend code rather than sitting behind a separate GraphQL boundary. A hosted or decentralized indexer is worth it when what you actually want is to operate less infrastructure, and a GraphQL API in front of someone else\u0026rsquo;s indexing pipeline is enough.\nTakeaway # The tool underneath this post could change; the properties it needs don\u0026rsquo;t. Turning a chain\u0026rsquo;s event stream into application state worth trusting means separating source events from derived projections, being explicit about how much history the system has observed, and treating reorgs, crashes, and logic changes as normal operating conditions rather than exceptional cases. Ponder provides one practical way to implement that model on EVM chains; the underlying design principles carry well beyond Ponder.\nResources # Ponder Ponder on GitHub Ponder: Indexing overview (reorg handling) Ponder: Database reference (build cache, RPC cache) Ponder: Write to the database ","date":"April 14, 2026","externalUrl":null,"permalink":"/blog/indexing-onchain-events-with-ponder/","section":"Blog","summary":"A chain doesn’t hand you application state, it hands you a stream of events with a footnote attached: this stream can be revised. Ponder is one good vehicle for turning that stream into state you can actually query and trust, but the tool is secondary to the problem. This post is mostly about the problem.\nThe problem # A block your service already processed can stop being canonical. An RPC node can tell you two different things about the same height five minutes apart. An application built to read “current state” directly off the chain on every request has no principled way to handle any of that; it just re-derives an answer each time and hopes the chain agrees with itself in the meantime.\n","title":"From EVM Events to Reliable Application State with Ponder","type":"blog"},{"content":"","date":"April 14, 2026","externalUrl":null,"permalink":"/tags/indexing/","section":"Tags","summary":"","title":"Indexing","type":"tags"},{"content":"","date":"April 14, 2026","externalUrl":null,"permalink":"/tags/ponder/","section":"Tags","summary":"","title":"Ponder","type":"tags"},{"content":"","date":"April 14, 2026","externalUrl":null,"permalink":"/tags/reorgs/","section":"Tags","summary":"","title":"Reorgs","type":"tags"},{"content":"","date":"April 14, 2026","externalUrl":null,"permalink":"/tags/typescript/","section":"Tags","summary":"","title":"Typescript","type":"tags"},{"content":"","date":"August 20, 2025","externalUrl":null,"permalink":"/tags/defi/","section":"Tags","summary":"","title":"Defi","type":"tags"},{"content":"","date":"August 20, 2025","externalUrl":null,"permalink":"/tags/erc-4626/","section":"Tags","summary":"","title":"Erc-4626","type":"tags"},{"content":"","date":"August 20, 2025","externalUrl":null,"permalink":"/tags/smart-contracts/","section":"Tags","summary":"","title":"Smart-Contracts","type":"tags"},{"content":"","date":"August 20, 2025","externalUrl":null,"permalink":"/tags/solidity/","section":"Tags","summary":"","title":"Solidity","type":"tags"},{"content":"While researching vault standards to inform the design of a planned vault product on Rootstock, it became clear pretty quickly that \u0026ldquo;use ERC-4626\u0026rdquo; wasn\u0026rsquo;t the whole answer. The base standard covers a lot, but it also assumes a specific shape (one share token, one asset, everything settling synchronously) that doesn\u0026rsquo;t fit every vault a real product might need. I put together an internal walkthrough for the team, covering ERC-4626 and the standards that extend it, for exactly the cases where the base spec runs out. This post is that walkthrough.\nDefinitions, first # Three terms are worth pinning down before anything else, since the rest of this only makes sense once they\u0026rsquo;re precise:\nAsset: the underlying token a user deposits (e.g. an ERC-20 stablecoin or the vault\u0026rsquo;s target token). Share: the vault\u0026rsquo;s own token, minted to a depositor in exchange for assets, representing their proportional claim on everything the vault holds. Fee: value the vault or its operator takes on entry, exit, or over time; the standards below don\u0026rsquo;t mandate a specific fee mechanism, just leave room for one. ERC-4626: the base standard # ERC-4626 standardizes the tokenized vault pattern as an extension of ERC-20: asset()/totalAssets() to inspect the vault, deposit()/mint() to go in, withdraw()/redeem() to go out, a max*/preview* variant of each so a caller can check limits and expected outcomes before committing, and convertToShares()/convertToAssets() for the underlying exchange rate. That exchange rate is the core of the whole standard, and it comes down to one ratio: a vault holding totalAssets backing totalSupply shares prices one share at totalAssets / totalSupply assets. Going from assets to shares, which is what deposit() does, divides by that price instead of multiplying it: depositing a assets mints a * totalSupply / totalAssets shares. Going the other way, which is what redeem() does, applies the price directly: redeeming s shares returns s * totalAssets / totalSupply assets. Every deposit/mint/redeem/withdraw call is one of these two directions.\nOne consequence of this formula is worth understanding early, because it\u0026rsquo;s the standard\u0026rsquo;s best-known failure mode: the inflation attack. Since the exchange rate is a ratio over the vault\u0026rsquo;s actual balances, an attacker can deposit a trivial amount to mint the first share, then donate assets directly to the vault (bypassing deposit() entirely) to distort totalAssets before any real user shows up. The next honest depositor\u0026rsquo;s shares get computed against that distorted ratio, and rounding in the vault\u0026rsquo;s favor (or the attacker\u0026rsquo;s) can be enough to siphon value from them. This is why the spec is explicit that preview functions must reflect what execution will actually do as closely as possible: an approximate preview is exactly the gap this attack lives in. The standard mitigation you\u0026rsquo;ll see in production implementations is a decimal offset (effectively minting the vault some unclaimable \u0026ldquo;virtual\u0026rdquo; shares up front), which makes the same manipulation prohibitively expensive rather than free.\nWhy one flavor of vault isn\u0026rsquo;t enough # ERC-4626 assumes two things by default: one share token backed by exactly one asset, and a deposit/withdraw that settles fully within a single transaction. Two situations break each of those assumptions:\nA vault design where several entry points, each in a different asset, should all mint the same share (for example, a vault that accepts multiple stablecoins and treats them as fungible for accounting purposes). A vault whose underlying strategy can\u0026rsquo;t settle atomically: it needs to wait on something external (an oracle price, a batch auction, unwinding a real-world position) before it knows what a deposit or redemption is actually worth. Neither is solved by ERC-4626 alone. Both got their own standard.\nERC-7575: multi-asset vaults # ERC-7575 solves the multi-asset case with an idea it calls share externalization. In plain ERC-4626, the vault contract is the ERC-20 share token; they\u0026rsquo;re the same address. ERC-7575 breaks that coupling: a vault exposes a share() method that can point to an external token contract instead of itself, and that external share token can optionally point back with a vault() lookup.\nOnce share and vault are separate, nothing stops multiple vaults (each denominated in a different asset) from pointing at the same share token. A user can deposit asset A into vault A or asset B into vault B and receive the same fungible share either way. Converting between a given asset and the shared token happens through what the standard calls a pipe, which can be one-directional (deposit/mint only) or bidirectional. Because a vault is no longer guaranteed to be ERC-20 itself, the standard requires ERC-165 support so callers can safely detect what they\u0026rsquo;re actually talking to.\nERC-7540: async vaults # ERC-7540 solves the second case (a vault that can\u0026rsquo;t settle a deposit or redemption within one transaction) by replacing the instant conversion with a three-state request lifecycle:\nstateDiagram-v2 [*] --\u003e Pending: requestDeposit / requestRedeem Pending --\u003e Claimable: vault processes the request internally Claimable --\u003e Claimed: deposit / redeem (claim) Claimed --\u003e [*] A user calls requestDeposit() or requestRedeem() to move into Pending. Once the vault has done whatever it needs to do off the critical path (settle a batch, get a price, unwind a position), the request becomes Claimable, and the user then calls the ordinary ERC-4626 deposit()/mint() or redeem()/withdraw() as a separate transaction to actually claim the result and move to Claimed. Two roles come with this: a controller owns the request and is entitled to claim it, and an operator is an account the controller can approve to act on their behalf, which is useful for automation that shouldn\u0026rsquo;t need the controller\u0026rsquo;s private key on every claim.\nWorth flagging an asymmetry the deck itself flags as an open question: there\u0026rsquo;s no requestMint() or requestWithdraw(). Only requestDeposit() (by asset amount) and requestRedeem() (by share amount) exist on the request side. The spec\u0026rsquo;s own reasoning is that an async vault can only act with certainty on the quantity that\u0026rsquo;s actually known at request time: how many assets you\u0026rsquo;re putting in, or how many shares you\u0026rsquo;re giving up. Requesting the opposite quantity (\u0026ldquo;give me exactly N shares\u0026rdquo; before the vault has processed anything) would require guessing an exchange rate that hasn\u0026rsquo;t been fixed yet. The claim side doesn\u0026rsquo;t have this restriction: once a request is Claimable, both deposit() and mint() can claim a deposit request (by asset amount or share amount respectively), and both redeem() and withdraw() can claim a redemption request. The uncertainty is gone by then, so either quantity works.\nA few details worth knowing if you\u0026rsquo;re actually implementing this: a vault can be fully or only partially async. Synchronous deposits paired with async redemptions, for example, is a common shape for anything backed by a real-world or illiquid position, where getting money in is easy but getting it out requires unwinding something first. And the preview* functions the base standard leans on for a reliable quote don\u0026rsquo;t have a sane answer for a request that hasn\u0026rsquo;t been processed yet, so the standard has them revert for the async legs rather than return a number that isn\u0026rsquo;t real.\nThe three side by side # ERC-4626 ERC-7575 ERC-7540 Share is itself ERC-20 Yes (the vault contract is the token) No (externalized to its own contract) Not specified by this standard Share-to-asset shape One share, one asset One share, potentially many assets/vaults via pipes n/a (orthogonal to this; a 7540 vault can also be 7575) Settlement Synchronous Synchronous Asynchronous (request then claim) Request side n/a n/a Only requestDeposit/requestRedeem (no requestMint/requestWithdraw) New roles n/a n/a Controller, operator Two more worth knowing, briefly # Two adjacent standards came up in the same research pass, without needing a full section each:\nEIP-2612 (permit): a plain approve() is its own transaction before the action a user actually wants to take, which for a vault means \u0026ldquo;approve, then deposit\u0026rdquo; as two separate round trips. Permit lets a user sign an off-chain message the vault (or the token) can consume as an approval inside the same transaction as the deposit, cutting that extra step out of the flow entirely. EIP-7579 (modular smart accounts, still Draft status) and EIP-5115 (the SY/Standardized Yield token, also still Draft status) both surfaced as adjacent standards worth tracking rather than immediately relevant. 7579 standardizes how a smart-contract wallet installs and manages modules (validators, executors, fallback handlers, hooks) at runtime, and 5115 does for yield-bearing tokens roughly what ERC-4626 does for vaults, wrapping deposit/redeem behind one common interface. Neither changes anything about the vault standards above; they\u0026rsquo;re worth knowing exist for adjacent design decisions. Takeaway # None of this is about picking a winner. ERC-4626, ERC-7575, and ERC-7540 solve different problems, and a real vault product might only need the base standard, or might need exactly one of the two extensions, depending on whether it has to support multiple assets or can\u0026rsquo;t settle atomically. Going through all three before writing any vault code was about knowing which shape the product actually needed before picking a specific implementation to build it with. That implementation choice (comparing how OpenZeppelin, Solmate, and Solady actually build out the base ERC-4626 standard, and what each costs in gas) was part of the same evaluation, and is written up as its own post rather than folded into this one.\nResources # ERC-4626: Tokenized Vault Standard ERC-7575: Multi-Asset ERC-4626 Vaults ERC-7540: Asynchronous ERC-4626 Tokenized Vaults EIP-2612: Permit Extension for EIP-20 ","date":"August 20, 2025","externalUrl":null,"permalink":"/blog/understanding-vault-standards-eip-4626-and-beyond/","section":"Blog","summary":"While researching vault standards to inform the design of a planned vault product on Rootstock, it became clear pretty quickly that “use ERC-4626” wasn’t the whole answer. The base standard covers a lot, but it also assumes a specific shape (one share token, one asset, everything settling synchronously) that doesn’t fit every vault a real product might need. I put together an internal walkthrough for the team, covering ERC-4626 and the standards that extend it, for exactly the cases where the base spec runs out. This post is that walkthrough.\n","title":"Understanding Vault Standards: EIP-4626 and Beyond","type":"blog"},{"content":"","date":"March 12, 2025","externalUrl":null,"permalink":"/tags/architecture/","section":"Tags","summary":"","title":"Architecture","type":"tags"},{"content":"At RootstockLabs, one of the products I owned the architecture for was the RootstockCollective dApp (frontend source) — a UI that needs to answer questions like \u0026ldquo;what\u0026rsquo;s this user\u0026rsquo;s balance,\u0026rdquo; \u0026ldquo;what has this contract emitted since block X,\u0026rdquo; and \u0026ldquo;show me the history of this account\u0026rdquo; on every page load. This is the story of how that UI went from asking the chain those questions directly, every time, to asking a decentralized indexing network instead — and why the process mattered as much as the destination.\nWhere the time was actually going # The first version worked the obvious way: the React app called the chain directly — eth_call for current state, eth_getLogs for history — and did the rest itself. Reconstructing a user\u0026rsquo;s position, deriving history, combining data from several contracts: all of that logic lived on the client, in the React app\u0026rsquo;s own state layer. It re-fetched and re-derived the same things on every view, every user, every refresh. That\u0026rsquo;s a perfectly reasonable way to ship a first version — it only becomes a problem once real traffic exposes what scales and what doesn\u0026rsquo;t.\ngraph TD U[User] --\u003e UI[\"React App(client-side aggregation)\"] UI --\u003e|\"eth_call (state) +eth_getLogs (re-scan block range)\"| RPC[RPC Node] RPC --\u003e Chain[(Rootstock Chain)] UI --\u003e|\"derive balances,reconstruct history,join across contracts\"| Store[\"Client-side State\"] Store --\u003e UI Before writing a single line of indexing code, the first step was just watching where that fell over — pulling call volumes from the RPC provider\u0026rsquo;s dashboard and attributing them per page and per call type, rather than guessing: which calls were repeated on nearly every page, which ones scaled with the number of users rather than staying flat, and which \u0026ldquo;derive this from raw events\u0026rdquo; logic in the React store was really doing database work by hand, in the browser, on every load. Log-range rescans for account history and repeated recomputation of positions across contracts were the two that dominated everything else.\nDesigning the data model first # The instinct once you\u0026rsquo;ve found the expensive calls is to go straight to indexing them. The more useful step in between was designing the data model those calls actually needed — before writing any indexing logic. That meant working backward from the UI\u0026rsquo;s real questions (\u0026ldquo;this user\u0026rsquo;s current position across several contracts,\u0026rdquo; \u0026ldquo;this account\u0026rsquo;s activity over time\u0026rdquo;) rather than forward from the contracts\u0026rsquo; storage layout. A schema that just mirrors Solidity structs one-to-one still leaves the application joining and deriving data itself; a schema shaped around the actual queries doesn\u0026rsquo;t.\nThe actual entities are specific to this dApp\u0026rsquo;s contracts, but the shape that came out of that exercise looked roughly like this — accounts holding positions across several contracts, with a single activity timeline joining events from all of them, instead of one table per contract mirroring its storage layout one-to-one:\nerDiagram ACCOUNT ||--o{ POSITION : holds ACCOUNT ||--o{ ACTIVITY_EVENT : \"has history of\" POSITION }o--|| CONTRACT : \"tracked across\" ACTIVITY_EVENT }o--|| CONTRACT : \"emitted by\" A per-contract mirror of Solidity storage would have forced the client back into the same joins and derivations it had before — this shape lets \u0026ldquo;this user\u0026rsquo;s position\u0026rdquo; and \u0026ldquo;this account\u0026rsquo;s history\u0026rdquo; be single queries instead.\nIndexing with The Graph, plus a sync layer on top # With the data model settled, the indexing side ended up as two layers, not one. Raw contract events are indexed by a subgraph on The Graph: it declares which events to watch and how to map each one into entities matching that schema, queryable over GraphQL. On top of that sits a purpose-built state-sync service — a Node.js/TypeScript service that continuously syncs the subgraph\u0026rsquo;s data (plus a few things watched directly from the chain that the subgraph doesn\u0026rsquo;t cover, and a couple of external data sources) into its own PostgreSQL database, with tables shaped for exactly what the frontend needs to query.\nThe choice to index via The Graph rather than a self-hosted indexer wasn\u0026rsquo;t only about not wanting to run and operate that piece of infrastructure ourselves. The Graph is a decentralized protocol: the raw event-indexing work is done by a network of independent Indexers who stake to back the service they provide, rather than by a single node or provider one team depends on. For a dApp built on the premise that the chain itself isn\u0026rsquo;t controlled by any single party, indexing that chain\u0026rsquo;s events through infrastructure we alone operated would have quietly reintroduced exactly the kind of single point of control the rest of the architecture was designed to avoid.\nThe sync service on top is something we do operate, so this isn\u0026rsquo;t decentralized end to end — that\u0026rsquo;s a deliberate, narrower trade rather than an inconsistency. The part that has to watch and interpret raw chain events at scale is backed by a decentralized network; the service sitting above it is a thin, replaceable layer that combines already-indexed data with a few other sources and reshapes it for the frontend — not the thing doing the indexing itself.\ngraph TD U[User] --\u003e UI[\"React App(thin query layer)\"] UI --\u003e|\"query\"| Sync[\"State-Sync Service(Node.js + PostgreSQL)\"] Sync --\u003e|\"sync subgraph data\"| TG[\"The Graph(decentralized network)\"] subgraph TG_Network [\" \"] I1[Indexer] I2[Indexer] I3[Indexer] end TG --- I1 TG --- I2 TG --- I3 I1 --\u003e|watches events| RPC[RPC Node] I2 --\u003e|watches events| RPC I3 --\u003e|watches events| RPC Sync --\u003e|\"watches a few eventsdirectly + external sources\"| RPC RPC --\u003e Chain[(Rootstock Chain)] Switching the UI over # None of this replaced anything until it was validated against the existing client-side logic — running the subgraph and sync service alongside the old code path and comparing results before trusting either as the source of truth. Once that held up, the cutover was mechanical: the React app\u0026rsquo;s queries were pointed at the sync service\u0026rsquo;s API instead of at RPC calls, one query at a time.\nThe effect on the frontend was bigger than just fewer network calls. Most of the aggregation, derivation, and cross-contract joining that used to be hand-rolled logic inside the React store simply went away — the sync service now returns data shaped to match what a component needs, instead of raw events the client had to turn into that shape itself. The UI code got measurably simpler as a direct result, not just faster.\nWhat it doesn\u0026rsquo;t solve # An indexing layer is a read-path optimization, and it\u0026rsquo;s worth being honest about where that stops. A subgraph is a few blocks behind the chain head by construction — it has to wait for a block, process it, and handle the possibility that block gets reorganized out before treating it as final. If a UI needs the absolute latest pending state, the indexer isn\u0026rsquo;t the source for that. And none of this touches the write path: submitting a transaction still means talking to an RPC node directly, no matter how good your read-side indexing is. Cutting RPC usage by 90% here specifically meant 90% of read traffic — the piece that scales with every user\u0026rsquo;s every page view, which is exactly the piece that was dominating the bill and the latency. That figure comes from comparing RPC provider call-volume metrics before and after the cutover, not an estimate; the p95 end-to-end latency figure below is the browser-measured time for those same queries after the cutover.\nTakeaway # 90% fewer RPC calls, p95 end-to-end query latency under 400ms, and a simpler frontend — but the process is the part worth repeating on the next project: observe where the cost actually is before designing anything, design the data model around the application\u0026rsquo;s real questions before indexing a single event, validate the new path against the old one before cutting over, and — for a decentralized application — be deliberate about which parts of the data path stay decentralized and which don\u0026rsquo;t, rather than letting convenience decide by default.\nResources # RootstockCollective dApp Frontend source State-sync service source The Graph docs ","date":"March 12, 2025","externalUrl":null,"permalink":"/blog/onchain-indexing-layer-rpc-reduction/","section":"Blog","summary":"At RootstockLabs, one of the products I owned the architecture for was the RootstockCollective dApp (frontend source) — a UI that needs to answer questions like “what’s this user’s balance,” “what has this contract emitted since block X,” and “show me the history of this account” on every page load. This is the story of how that UI went from asking the chain those questions directly, every time, to asking a decentralized indexing network instead — and why the process mattered as much as the destination.\n","title":"Cutting RPC Calls by 90%: Building an On-Chain Indexing Layer","type":"blog"},{"content":"","date":"March 12, 2025","externalUrl":null,"permalink":"/tags/dapp/","section":"Tags","summary":"","title":"Dapp","type":"tags"},{"content":"","date":"March 12, 2025","externalUrl":null,"permalink":"/tags/evm/","section":"Tags","summary":"","title":"Evm","type":"tags"},{"content":"","date":"March 12, 2025","externalUrl":null,"permalink":"/tags/performance/","section":"Tags","summary":"","title":"Performance","type":"tags"},{"content":"","date":"March 12, 2025","externalUrl":null,"permalink":"/tags/rootstock/","section":"Tags","summary":"","title":"Rootstock","type":"tags"},{"content":"","date":"March 12, 2025","externalUrl":null,"permalink":"/tags/the-graph/","section":"Tags","summary":"","title":"The-Graph","type":"tags"},{"content":"A new user shows up with an address and no RBTC in it. Every transaction, including the very first one, needs gas, and gas on Rootstock is paid in RBTC. RIF Relay removes that requirement: a user pays with a token they already hold, or gets the transaction sponsored outright, while a separate account submits it and pays the actual gas.\nAt RootstockLabs, I led the team that extended and productionized RIF Relay, including the smart-contract changes required to support Boltz\u0026rsquo;s Rootstock integration, a non-custodial Bitcoin and Lightning swap service. This post covers how RIF Relay\u0026rsquo;s relay flow and smart-wallet architecture work, and what changed when a real integration stopped fitting the generic model.\nThe relay mechanism # RIF Relay is a fork-turned-redesign of the Gas Station Network (GSN), adapted for Rootstock. Five roles matter:\nRequester: the end user\u0026rsquo;s EOA. They sign a request; they don\u0026rsquo;t submit a transaction. Smart Wallet: a per-user contract account, owned by the Requester, that executes the call and pays the relay fee. Relay Verifier: checks, off-chain and before anything happens on-chain, that the Requester\u0026rsquo;s fee token is accepted and their balance covers it. Relay Server: an off-chain daemon with a Relay Manager (a staked EOA) and one or more Relay Workers (EOAs that submit transactions and pay gas in RBTC). Relay Hub: the on-chain entry point. It verifies the submitting worker belongs to a staked manager, then hands the request to the Smart Wallet. A signed, gas-less request becomes a normal, gas-paid transaction by passing through this pipeline. RIF Relay\u0026rsquo;s own docs call the wrapping transaction an \u0026ldquo;envelope.\u0026rdquo;\nsequenceDiagram participant U as Requester (EOA) participant C as Relay Client participant S as Relay Server participant V as Relay Verifier participant H as Relay Hub participant W as Smart Wallet participant D as Destination Contract U-\u003e\u003eC: sign relay request C-\u003e\u003eS: HTTP: relay request S-\u003e\u003eV: verify token accepted + balance (off-chain) S-\u003e\u003eS: wrap in envelope,sign with Relay Worker key S-\u003e\u003eH: relayCall(envelope) H-\u003e\u003eH: verify Worker belongs tostaked Manager H-\u003e\u003eW: execute(request, sig) W-\u003e\u003eW: verify signature + nonce W-\u003e\u003eD: call(data) W-\u003e\u003eS: pay fee (token or RBTC) The Verifier check happens off-chain, in the client and again in the relay server, before submission: it\u0026rsquo;s how the server avoids paying gas for a request that would just revert.\nThe Smart Wallet is what makes this safe without touching the destination contract: because the wallet, not the Relay Worker, is msg.sender, ordinary contracts work under RIF Relay unmodified. RIF Relay\u0026rsquo;s own v0.1, itself a GSN fork, instead relied on a _msgSender()/_msgData() pattern, which meant contracts had to unwrap the real sender from calldata themselves. Routing execution through a real per-user contract account sidesteps that; v0.2 replaced the _msgSender() dependency with the counterfactual smart-wallet design described next.\nSmart wallets, deployed cheaply and on demand # A Smart Wallet\u0026rsquo;s address can be deterministically derived via CREATE2 before the wallet itself is ever deployed. Both the client and the relay server compute it off-chain, and the wallet can receive funds at that address before it exists as a contract; it only needs to actually deploy once its owner wants to do something.\nDeployment itself is a proxy, not a full contract: the factory\u0026rsquo;s getCreationBytecode() returns a short constructor plus a fixed runtime stub wrapping a shared implementation address:\n602D3D8160093D39F3 363D3D373D3D3D3D363D73 \u0026lt;masterCopy address\u0026gt; 5AF43D923D90803E602B57FD5BF3 That runtime is the same idea as EIP-1167\u0026rsquo;s minimal proxy (the 5af4 at the end is DELEGATECALL), close to but not byte-identical to the canonical EIP-1167 encoding. Every user\u0026rsquo;s Smart Wallet is a thin delegate-call shim onto one shared logic contract, not a copy of the whole implementation. That significantly reduces the cost of maintaining a per-user wallet model.\nThe project\u0026rsquo;s own gas measurements show what the v0.1-to-v0.2 redesign bought in practice:\nVersion Smart Wallet template Deployment overhead (gas) v0.1 SmartWallet 172,400 v0.2 SmartWallet 97,695 v0.2 CustomSmartWallet 98,070 Roughly a 43% cut, though not from the proxy pattern alone: v0.2 also removed hardcoded gas-overhead validations from the Relay Hub and moved the old Paymaster\u0026rsquo;s pre-transaction checks off-chain into the Verifier, so less of the relay\u0026rsquo;s own bookkeeping happens on-chain per call. (Figures from RIF Relay\u0026rsquo;s own gas-costs documentation; v1\u0026rsquo;s numbers weren\u0026rsquo;t published at the time.)\nEach piece in this pipeline owns a distinct responsibility, with some checks deliberately repeated across trust boundaries. The Relay Server decides whether it\u0026rsquo;s willing to relay a request at all and avoids paying gas for one likely to fail. The Relay Hub verifies that the submitting Relay Worker belongs to a staked Relay Manager, then routes execution onward. The Smart Wallet is where a user\u0026rsquo;s actual authorization and replay protection live, and where the destination call and fee payment happen. The Verifier does integration-specific validation before any of this reaches the chain, so a request that would fail gets rejected for free instead of on-chain for a fee. That division of responsibility is what Boltz\u0026rsquo;s integration ended up testing.\nWhere the standard wallet\u0026rsquo;s assumptions break: Boltz # The interesting part of the integration wasn\u0026rsquo;t adding another destination contract. Boltz violated two assumptions built into the standard relay flow: the user might have no fee token at all, and the destination protocol already had its own authorization mechanism.\nBoltz runs non-custodial swaps across Bitcoin, Lightning, and Rootstock. Onboarding a new Rootstock user through it hit the same problem RIF Relay exists to solve in the first place, one level down: to claim RBTC from a swap, a user needed RBTC to pay for the claim transaction. Rootstock\u0026rsquo;s own writeup on the integration calls this exactly what it is, a chicken-and-egg problem. The Rootstock leg of a swap is a hashlock-and-timelock contract (RIF Relay\u0026rsquo;s own interface for it is called NativeSwap; Boltz\u0026rsquo;s underlying implementation, shared across the chains it supports, is EtherSwap): claiming requires supplying a preimage whose hash matches the one committed when the swap was opened, the same primitive as a Lightning HTLC. Two things follow. First, the funds being claimed are native RBTC, so there\u0026rsquo;s no ERC-20 sitting around to pay a relay fee from. Second, and more interesting: the swap contract has already, independently, verified that whoever calls claim() knows the right preimage. A second, general-purpose signature check on top of that would just be re-checking something the protocol already guarantees.\nThat\u0026rsquo;s the actual engineering question behind this integration: what is the minimum authorization the wallet needs to enforce, without duplicating guarantees the swap protocol already provides?\nRIF Relay\u0026rsquo;s contracts answer it with two wallets, at two different points on that spectrum:\nBoltzSmartWallet keeps the full signed-forward-request model, still checking the owner\u0026rsquo;s signature and nonce exactly like the standard wallet, and only extends fee payment to accept native RBTC directly alongside the existing ERC-20 path. This is the conservative fix: it solves the \u0026ldquo;no fee token\u0026rdquo; half of the problem without touching authorization at all. MinimalBoltzSmartWallet is the more significant change, but not in the sense of dropping signature verification from the flow. The wallet contract itself exposes no execute() path at all (it isn\u0026rsquo;t even a BaseSmartWallet), and its own MinimalBoltzRelayVerifier unconditionally rejects any ordinary relay call. The entire lifecycle is deploy-and-claim in a single transaction: MinimalBoltzSmartWalletFactory still verifies the requester\u0026rsquo;s EIP-712 signature on the deploy request exactly like the standard flow, and only then calls initialize(), which runs the claim. What the wallet adds on top of that already-verified request is a narrower constraint: BoltzUtils.validateClaimSignature checks that the calldata being executed matches one of Boltz\u0026rsquo;s two known claim() selectors, so even a validly-signed deploy request can\u0026rsquo;t make the wallet call anything else. The paired MinimalBoltzDeployVerifier adds a third check, off-chain and before submission, confirming against the real NativeSwap contract that a swap with those exact parameters actually exists. Authorization isn\u0026rsquo;t removed here, it\u0026rsquo;s split across four boundaries: the factory verifies who\u0026rsquo;s asking, the wallet constrains what they can execute, the Verifier checks whether the request is worth submitting, and NativeSwap remains the actual source of truth for whether the claim itself, hashlock and timelock included, is valid. graph TD subgraph Standard[\"Standard flow\"] direction TB A1[\"User signsEIP-712 forward request\"] --\u003e A2[\"SmartWalletverifies signature + nonce\"] A2 --\u003e A3[\"Destination contractexecutes\"] end subgraph Boltz[\"Boltz claim flow\"] direction TB V[\"Deploy Verifier: checks swapexists on NativeSwap(off-chain, before submission)\"] B1[\"User signsdeploy request\"] --\u003e B2[\"Factory verifiessignature\"] B2 --\u003e B3[\"MinimalBoltzSmartWalletrestricts execution toknown claim() selectors\"] B3 --\u003e B4[\"NativeSwap contract enforceshashlock + timelock,releases RBTC\"] V -.-\u003e|\"must pass beforerelay server submits\"| B2 end The minimal wallet wasn\u0026rsquo;t the only option the team weighed. Keeping Boltz on the standard signed-forward-request wallet, and sponsoring the claim transaction outright, were both considered and rejected. The minimal design won on several grounds: it reduces execution overhead for the relayed claim, running through a smaller contract with no execute() path, no domain separator, and no signature-recovery logic to pay for, rather than the overhead of a general-purpose implementation built to support arbitrary future calls; it presents a narrower surface for auditors to review than the standard wallet\u0026rsquo;s full signature-and-nonce logic; it avoided modifying a contract Boltz already had deployed and in live use; and it avoids requiring anyone to sponsor claims outright, which wasn\u0026rsquo;t going to scale economically as swap volume grew.\nThis is a live deployment, not a hypothetical. Public on-chain records place the four Boltz-specific Verifier contracts in the same block on Rootstock mainnet, on September 16, 2024 (verifiable directly on the Rootstock explorer); Boltz\u0026rsquo;s and Rootstock\u0026rsquo;s own posts about the integration describe its public launch about two months later, in November 2024. It runs as a paid service, a small percentage fee on each relayed claim, not fully sponsored. A user finishing a Bitcoin-to-Rootstock swap through Boltz claims their RBTC and pays the relay fee out of the exact funds being claimed, without ever having held RBTC beforehand, which is the whole point of gas-relaying, applied to a case the original design didn\u0026rsquo;t anticipate.\nTakeaway # Gas-relaying removes a real onboarding constraint: users can interact with an application before they own the chain\u0026rsquo;s native gas token. Doing that safely takes more than moving gas payment to another account, though. It\u0026rsquo;s a system spanning off-chain infrastructure, relayer economics, smart-wallet execution, replay protection, fee collection, and application-level authorization, and the boundaries between those pieces are where the design decisions actually live.\nThe Boltz integration is a case where the generic model\u0026rsquo;s assumptions stopped fitting. The standard wallet assumed authorization came from a signed forward request and that fees were paid in an ERC-20 token, and neither held for a swap where the asset being claimed was native RBTC and the destination protocol already enforced its own claim conditions. Rather than forcing the generic model onto that case, the wallet was specialized around the guarantees the swap protocol already provided: constrain what the wallet can execute, validate the request before paying to relay it, and leave the swap contract responsible for its own claim conditions.\nThe broader lesson generalizes past this one integration: when two protocols meet, security mechanisms shouldn\u0026rsquo;t be duplicated across both by default. Identify which component already owns a given guarantee, preserve that boundary, and add only the checks the integration actually needs on top of it.\nResources # RIF Relay: umbrella repo and docs RIF Relay Contracts: Smart Wallet, factory, and Verifier implementations RIF Relay Server RIF Relay Client Boltz Boltz\u0026rsquo;s EtherSwap contract (boltz-core): the underlying swap implementation RIF Relay\u0026rsquo;s NativeSwap interface targets \u0026ldquo;Boltz Integrates RIF Relay to Simplify Bitcoin Layer Swaps\u0026rdquo; (Rootstock blog) \u0026ldquo;Hello, Rootstock Swaps!\u0026rdquo; (Boltz blog) EIP-1167: Minimal Proxy Contract ","date":"November 20, 2024","externalUrl":null,"permalink":"/blog/engineering-gasless-transactions-boltz-integration/","section":"Blog","summary":"A new user shows up with an address and no RBTC in it. Every transaction, including the very first one, needs gas, and gas on Rootstock is paid in RBTC. RIF Relay removes that requirement: a user pays with a token they already hold, or gets the transaction sponsored outright, while a separate account submits it and pays the actual gas.\nAt RootstockLabs, I led the team that extended and productionized RIF Relay, including the smart-contract changes required to support Boltz’s Rootstock integration, a non-custodial Bitcoin and Lightning swap service. This post covers how RIF Relay’s relay flow and smart-wallet architecture work, and what changed when a real integration stopped fitting the generic model.\n","title":"Engineering Gasless Transactions: Smart Wallets, Meta-Transactions and the Boltz Integration","type":"blog"},{"content":"","date":"November 20, 2024","externalUrl":null,"permalink":"/tags/gas-optimization/","section":"Tags","summary":"","title":"Gas-Optimization","type":"tags"},{"content":"","date":"November 20, 2024","externalUrl":null,"permalink":"/tags/meta-transactions/","section":"Tags","summary":"","title":"Meta-Transactions","type":"tags"},{"content":"","date":"November 20, 2024","externalUrl":null,"permalink":"/tags/rif-relay/","section":"Tags","summary":"","title":"Rif-Relay","type":"tags"},{"content":"","date":"October 25, 2020","externalUrl":null,"permalink":"/tags/blog/","section":"Tags","summary":"","title":"Blog","type":"tags"},{"content":"","date":"October 25, 2020","externalUrl":null,"permalink":"/categories/development/","section":"Categories","summary":"","title":"Development","type":"categories"},{"content":"This post aims to describe the basic mechanisms behind iterators and generators.\nIterator protocol # As in many programming languages, Python allows you to iterate over a collection. The iteration mechanism is often useful when we need to scan a sequence, an operation that is very common in programming. In Python, the iterator protocol involves two components: an iterable and an iterator.\nIterable # The iterable is the container through which we want to iterate. It is the object that needs to be scanned to retrieve all the elements (or some of them). Some well-known iterables are lists, tuples, dictionaries, and ranges. In the iterator protocol, the iterable exposes an __iter__ method that returns an iterator object.\nIterator # The iterator is the data structure that allows scanning through the container. It could seem like a complication, but actually, with the separation of concerns, it lets the developer separate the concept of the container from the concept of iteration. The container object doesn\u0026rsquo;t need to keep the state of an iteration, and furthermore, on the same object, many iterations can take place at the same time, so keeping the iteration state in a different object is a must. The container is a collection of elements, while the iterator is a kind of handler for the container: it exposes the same elements (owned by the container) one by one, in a specific order. In the iterator protocol, the iterator exposes two methods: __iter__ and __next__. While the first one returns the object itself (which allows the use of both the container and the iterator in for and in statements), the latter returns the next item from the container. What makes the iterator end the iteration? The StopIteration exception.\nIterable and Iterator Examples # Below is an example of an iterator protocol implementation:\nAnd its usage:\nGenerator # Generators are methods with yield statements. The yield statement has the power to suspend the function\u0026rsquo;s execution and store its state, so that it can be resumed. Behind the scenes, Python returns control to the function\u0026rsquo;s caller and saves the function\u0026rsquo;s state; this way, at the next execution, the function will start where it left off, without the developer needing to worry about the function\u0026rsquo;s state. Generators ARE iterators, but not vice versa.\nHere is an example of a generator:\nand its usage:\nAsync generator # Like generators, they are async functions with a yield statement.\nConclusions # Iterators are iterables. Iterators are objects that implement the iterator protocol, consisting of implementing both __iter__ and __next__. Iterator iterations stop when StopIteration is raised. Generators are methods with yield statements. Async generators are async methods with yield statements. Whenever possible, generators should be the preferred method due to their simplicity, while the protocol implementation gives much more control. Resources # Iterator types Generator types Yield expression Generators and iterators ","date":"October 25, 2020","externalUrl":null,"permalink":"/blog/python-iterators-generators/","section":"Blog","summary":"This post aims to describe the basic mechanisms behind iterators and generators.\nIterator protocol # As in many programming languages, Python allows you to iterate over a collection. The iteration mechanism is often useful when we need to scan a sequence, an operation that is very common in programming. In Python, the iterator protocol involves two components: an iterable and an iterator.\n","title":"Iterators \u0026 Generators in Python","type":"blog"},{"content":"","date":"October 25, 2020","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"","date":"April 11, 2020","externalUrl":null,"permalink":"/tags/angular/","section":"Tags","summary":"","title":"Angular","type":"tags"},{"content":"In many cases we need to change the content of a component dynamically — for instance, to allow the user to change view, or to let the children render data retrieved and processed by its parent component. Below, we are going to show some techniques for creating a component without deciding in advance how the data will be shown. According to the application\u0026rsquo;s needs, each technique has its own strengths, but all of them encourage component reusability. We are going to describe:\nContent projection Dynamic templates Dynamic component creation All the code is available on github and Stackblitz.\nProjection # This is the simplest technique, and it allows the component user to decide what to show within the component. This could be useful, for example, if we want a container component to decide the main content structure without forcing what will be rendered in a certain section.\nFirst of all, we need to use the ng-content tag in the dynamic component. Then, when we use the component, we could include all the content we want within the new component tag. A more flexible way to use content projection can be achieved by using the class selector. In this case, the dynamic content specifies, using a class selector, where the content will be injected.\nNow, in the component user, we could include two elements that will be injected in the component. Dynamic Template # Dynamic template is very easy to use, but it comes with some problems in terms of flexibility, so it could be a good choice for simple mechanisms to be implemented. A typical example could be rendering one of two elements according to a condition.\nIt could also be very useful for avoiding repetition of template parts, by writing the HTML part once and using the template context to pass data to the template. Dynamic Component creation # Dynamic component creation is by far the most flexible technique to use, because it allows you to choose the instant at which the component will be created. On the other hand, it is the most complicated to write, even if, after the first few times, everything becomes clearer and easier to repeat.\nTo implement this technique we need:\na container tag directive, used to reference the container from within the parent component, to tag where the new child component will be rendered. from the parent component, we need to reference the container using the @ViewChild annotation. in the parent component, we use the ComponentFactoryResolver to create an instance of the component to be created Conclusions # As in many other cases in programming, there are different ways to accomplish the same task, but it often depends on what the software requirements are and what result we want to achieve. This article isn\u0026rsquo;t intended to be a detailed guide to these techniques, but only a general description of some of the possibilities Angular offers for dynamically rendering content within a component.\nResources # ng-content ng-template, ng-container and ng-template-outlet Dynamic Components ","date":"April 11, 2020","externalUrl":null,"permalink":"/blog/angular-dynamic-content/","section":"Blog","summary":"In many cases we need to change the content of a component dynamically — for instance, to allow the user to change view, or to let the children render data retrieved and processed by its parent component. Below, we are going to show some techniques for creating a component without deciding in advance how the data will be shown. According to the application’s needs, each technique has its own strengths, but all of them encourage component reusability. We are going to describe:\n","title":"Angular Dynamic Content","type":"blog"},{"content":"","date":"April 11, 2020","externalUrl":null,"permalink":"/tags/dev/","section":"Tags","summary":"","title":"Dev","type":"tags"},{"content":"","date":"April 11, 2020","externalUrl":null,"permalink":"/tags/tutorial/","section":"Tags","summary":"","title":"Tutorial","type":"tags"},{"content":" What is CSP # CSP stands for Content Security Policy and it is a security mechanism that helps to protect or mitigate some common attacks such as XSS (Cross-site scripting). It can be set by means of a Content-Security-Policy HTTP header or using an HTML meta tag.\nHTTP header:\nContent-Security-Policy: policy HTML Meta tag:\n\u0026lt;meta http-equiv=\u0026#34;Content-Security-Policy\u0026#34; content=\u0026#34;policy\u0026#34;\u0026gt; A policy describes a set of directives, each composed of the area in which the rule is applied and the rule itself. The policy directive default-src 'self' says to load all content from the site\u0026rsquo;s origin, while if we want to load content from the site and from another trusted domain, the policy would be Content-Security-Policy: default-src 'self' *.trusted.com.\nWhat is CORS # CORS stands for Cross-Origin Resource Sharing and it is a mechanism that permits a web application running at an origin to access resources served from a different origin. What is an origin? The origin is identified by domain, protocol and port. It uses a set of HTTP headers to declare which domains are entitled to access the resource. Let\u0026rsquo;s have a look at the following scenario:\nWe have a web application running on domain-a.com We have a web service running on domain-b.com domain-a.com web app wants to access the service published on domain-b.com The service on domain-b.com should enable CORS by properly setting HTTP Headers. The headers involved in CORS are:\nAccess-Control-Allow-Origin - Origin can load the resource Access-Control-Allow-Methods - Methods can be used to access the resource Access-Control-Allow-Headers - Request headers allowed Access-Control-Max-Age - Value in seconds describing for how long the pre-flight response is cached (wait, pre-what?) Pre-flight request # When the browser encounters a cross-domain request, it performs a so-called pre-flight request. In practice it performs an HTTP request using the OPTIONS method to verify if CORS is enabled. If and only if the response is successful (response code 204 - No Content) and the previously mentioned HTTP headers are properly set, the browser will send the real request.\nRelation # So, what is the relation between CSP and CORS?\nLet\u0026rsquo;s have a look at the previous scenario again.\nWe have a web application running on domain-a.com We have a web service running on domain-b.com domain-a.com web app wants to access the service published on domain-b.com As we have previously said, the web service exposed on domain-b.com must enable CORS, but that might not be enough. If we have configured CSP on the domain-a.com web application, we also need to relax the connect-src policy directive to allow https://domain-b.com. So, to properly run the previous scenario:\nEnable CSP on domain-a.com Enable CORS on domain-b.com, by allowing domain-a.com using the Access-Control-Allow-Origin header. Relax the CSP (header or HTML tag), by setting the connect-src policy directive to allow the domain-b.com connection. Very important notes # Please, setting CSP to be able to connect to everything (*) is a BAD CSP configuration, unless you know what you are doing. Please, setting CORS to return always the Access-Control-Allow-Origin header set with * is a BAD CORS configuration, unless you know what you are doing. Resources # W3c CSP2 specification CSP HTTP Header W3c CORS specification MDN CSP MDN CORS ","date":"April 11, 2020","externalUrl":null,"permalink":"/blog/csp-and-cors/","section":"Blog","summary":"What is CSP # CSP stands for Content Security Policy and it is a security mechanism that helps to protect or mitigate some common attacks such as XSS (Cross-site scripting). It can be set by means of a Content-Security-Policy HTTP header or using an HTML meta tag.\nHTTP header:\nContent-Security-Policy: policy HTML Meta tag:\n\u003cmeta http-equiv=\"Content-Security-Policy\" content=\"policy\"\u003e A policy describes a set of directives, each composed of the area in which the rule is applied and the rule itself. The policy directive default-src 'self' says to load all content from the site’s origin, while if we want to load content from the site and from another trusted domain, the policy would be Content-Security-Policy: default-src 'self' *.trusted.com.\n","title":"CSP and CORS","type":"blog"},{"content":"","date":"April 11, 2020","externalUrl":null,"permalink":"/tags/sec/","section":"Tags","summary":"","title":"Sec","type":"tags"},{"content":"","date":"April 11, 2020","externalUrl":null,"permalink":"/categories/security/","section":"Categories","summary":"","title":"Security","type":"categories"},{"content":"Any modern web application needs, sooner or later, to perform some http requests to retrieve data. Below, I\u0026rsquo;ll describe some common scenarios and how to perform such requests using RxJS.\nSingle request # The most common scenario, no special rxjs handling needed, since Angular provides an Http service that returns an Observable.\nservices.getItems().subscribe(); Is that easy? Yes. In the particular example, I assumed the http service call was inside the service.getItems method, but it could be a fetch or anything else returning an observable.\nParallel requests # In this scenario instead, we want to perform multiple parallel requests. A common example could be:\nWe perform an http request that returns a list of items For each item, we need to perform another request to fill some item detail No — if you are thinking of applying the solution described in point 2 word-for-word, it is not the correct one, even if it can seem logically feasible.\n!!Bad code!! responses = [] items.forEach((item, index) =\u0026gt; { service.get(item.id).subscribe( (response) =\u0026gt; { responses[index] = response; }); });\nWhy not? Because we cannot control the requests, we can\u0026rsquo;t know when all of them will be resolved and what is their state.\nOur goal can be achieved by using the forkJoin operator. It is often compared to Promises.all(), but many of you may never have used it, so let\u0026rsquo;s do an example.\nOur friend is watching a race on TV, but we are not fans, so we are not interested in who wins; the only thing we want to know is when the race is finished, nothing else (we don\u0026rsquo;t watch TV, we want to have a beer). With forkJoin we wait for all the runners to finish. In practice, it receives an array of observables, and we can then subscribe to an array of results having the same array index as the requests.\nHere is the resulting code:\nitemRequests = items.map((item) =\u0026gt; service.get(item.id)); forkJoin(itemRequests).subscribe((results) =\u0026gt; { // do something with results }) But \u0026hellip;\nThis leads to a common mistake. What if a runner is disqualified? The race is still valid for the remaining runners, right? Well, by default, forkJoin succeeds only if all the runners successfully finish their run, and this isn\u0026rsquo;t what we usually want.\nHere is the forkJoin code snippet that succeeds even if one of the requests fails:\nitemRequests = items.map((item) =\u0026gt; { return service.get(item.id).pipe( // not the best way to handle the error, // maybe we could retry, or set some placeholder instead catchError((err) =\u0026gt; of(undefined)); ) }); forkJoin(itemRequests).subscribe((results) =\u0026gt; { // do something with results }) In that way, the results are filled differently according to the responses\u0026rsquo; status, and if a request fails, we have undefined in the corresponding item among the results. So if request[1] fails, results[1] will be undefined.\nSequential requests # For sequential requests there are many operators that can be used to achieve almost the same result, but they have slightly different behaviours.\nconcatMap mergeMap switchMap Assuming we want to perform 2 requests:\nmergeMap: the second request starts as soon as possible, without waiting for the end of the first one, so the order of the subscriptions isn\u0026rsquo;t guaranteed (if important, it must be handled manually in some way) concatMap: the second request starts only after the end of the first one and the emitted results will maintain the order of the subscriptions. So the first result corresponds to the first subscription and so on. switchMap: it is slightly different, because it executes the inner subscription only after the first one, but, in particular, only one request at a time will be active. One big advantage of using this operator is that on each emission, it can cancel existing requests. Single request followed by parallel requests # After analysing both single and multiple requests, we can compose them.\n!!Bad code, don\u0026rsquo;t use it // get the items service.getItems().subscribe((items) =\u0026gt; { itemRequests = items.map((item) =\u0026gt; { return service.get(item.id).pipe( // not the best way to handle the error, // maybe we could retry, or set some placeholder instead catchError((err) =\u0026gt; of(undefined)); ); }); forkJoin(itemRequests).subscribe((results) =\u0026gt; { // do something with results }) });\nAs you may already know, performing a subscription within a subscribe is very bad practice and MUST be absolutely avoided. This looks very similar to the callback hell problem, nowadays fortunately almost forgotten.\nWhat is the right way to compose them? By using our wonderful switchMap operator.\n// get the items service.getItems().pipe( switchMap((items) =\u0026gt; { // it must return an observable itemRequests = items.map((item) =\u0026gt; service.get(item.id).pipe( // not the best way to handle the error, // maybe we could retry, or set some placeholder instead catchError((err) =\u0026gt; of(undefined)); )); return forkJoin(itemRequests); }), tap((itemRequestsResponses) =\u0026gt; { // here we have the array of responses returned from forkJoin }) ); If we wanted to compose the response using both the first response and the responses coming from forkJoin: // get the items service.getItems().pipe( switchMap((items) =\u0026gt; { // it must return an observable itemRequests = items.map((item) =\u0026gt; service.get(item.id).pipe( // not the best way to handle the error, // maybe we could retry, or set some placeholder instead catchError((err) =\u0026gt; of(undefined)); )); return forkJoin(itemRequests).pipe( map((itemRequests) =\u0026gt; { // compose the response in some way using both itemRequests and items }) ); }), tap((ourComposedResponse) =\u0026gt; { // here we have the object composed in the inner map of the forkJoin }) );\nConclusions # Take Away # forkJoin performs parallel requests, but each failure should be handled individually If we have a request that uses the response of another request, it\u0026rsquo;s likely that we need switchMap NO Subscribe-in-Subscribe Resources # RxJS LearnRxJS Angular HTTP guide ","date":"April 9, 2020","externalUrl":null,"permalink":"/blog/angular-rxjs-http-requests/","section":"Blog","summary":"Any modern web application needs, sooner or later, to perform some http requests to retrieve data. Below, I’ll describe some common scenarios and how to perform such requests using RxJS.\nSingle request # The most common scenario, no special rxjs handling needed, since Angular provides an Http service that returns an Observable.\nservices.getItems().subscribe(); Is that easy? Yes. In the particular example, I assumed the http service call was inside the service.getItems method, but it could be a fetch or anything else returning an observable.\n","title":"Angular and Rxjs Http Requests: Common scenarios","type":"blog"},{"content":"","date":"April 9, 2020","externalUrl":null,"permalink":"/tags/rxjs/","section":"Tags","summary":"","title":"Rxjs","type":"tags"},{"content":"","date":"March 14, 2020","externalUrl":null,"permalink":"/tags/git/","section":"Tags","summary":"","title":"Git","type":"tags"},{"content":"Some time ago we needed to create a new product that shares many features with the existing one. The first idea was to extract some of the code into a library, to be then imported and used independently in the two projects.\nAfter creating the repository for the library, the first thing I would have done was copy the files from one repo to another. Well, it works like a charm, it\u0026rsquo;s fast, and it doesn\u0026rsquo;t require any particular expertise — a normal drag-and-drop operation. What\u0026rsquo;s more? It could be a very effective approach, but \u0026hellip;\nDuring a PR review, I learnt there was a clever way to perform this operation, and I wasn\u0026rsquo;t even aware such a thing was possible before:\nMoving files from one git repository to another retaining the history.\nMoving a single directory # From the source repository:\ngit clone \u0026lt;src_repository\u0026gt; \u0026lt;src_repository\u0026gt;_clone # clone the src repository in new folder cd \u0026lt;src_repository\u0026gt;_clone git remote rm origin # remove the origin to avoid disasters in case of error git filter-branch --subdirectory-filter \u0026lt;directory\u0026gt; -- --all # the magic, rewrite the git history mkdir \u0026lt;directory\u0026gt; mv * \u0026lt;directory\u0026gt; # create the directory and move everything inside In the destination repository:\ngit remote add \u0026lt;src_repository\u0026gt; \u0026lt;git repository A directory\u0026gt; # clone the repository, if not previously done git pull repo-A master --allow-unrelated-histories # Import the src repository code into the dst repository git mv \u0026lt;directory_1\u0026gt; \u0026lt;desired_position\u0026gt; # Optionally, move the just imported files, into the desired position in the new repository Moving code file-by-file # The operations required to move many files at once are conceptually the same. The extraction of the files to be moved will be performed by means of the following script (from Tom Hacohen\u0026rsquo;s blog article):\nAfter changing the script to include the files to be imported, it must be executed just before the git filter-branch ... command; this will create a single folder in the repository. So, to recap:\nDownload the script, and change it according to the files to be copied.\nFrom the source repository:\ngit clone \u0026lt;src_repository\u0026gt; \u0026lt;src_repository\u0026gt;_clone # clone the src repository in new folder cd \u0026lt;src_repository\u0026gt;_clone git remote rm origin # remove the origin to avoid disasters in case of error Execute the script previously modified here, so assuming the script has been saved in the parent folder:\nbash $(pwd)/../git-move.sh At this point, the repository contains only the folder and the files specified in the script.\ngit filter-branch --subdirectory-filter \u0026lt;directory\u0026gt; -- --all # the magic, rewrite the git history mkdir \u0026lt;directory\u0026gt; mv * \u0026lt;directory\u0026gt; # create the directory and move everything inside In the destination repository:\ngit remote add \u0026lt;src_repository\u0026gt; \u0026lt;git repository A directory\u0026gt; # clone the repository, if not previously done git pull repo-A master --allow-unrelated-histories # Import the src repository code into the dst repository git mv \u0026lt;directory_1\u0026gt; \u0026lt;desired_position\u0026gt; # Optionally, move the just imported files, into the desired position in the new repository Conclusions # That easy? Yep. Despite the many commands to be run, the advantage of maintaining the history is priceless — especially when we need to investigate some issue, file history can be very useful.\nTip: The process could seem counterintuitive, so I suggest giving it a try with a test repository. After the first time, everything will be much clearer.\nResources # A useful guide from @ayushya on Medium Tom Hacohen explains how to git move single files ","date":"March 14, 2020","externalUrl":null,"permalink":"/blog/git-move-file-with-history/","section":"Blog","summary":"Some time ago we needed to create a new product that shares many features with the existing one. The first idea was to extract some of the code into a library, to be then imported and used independently in the two projects.\nAfter creating the repository for the library, the first thing I would have done was copy the files from one repo to another. Well, it works like a charm, it’s fast, and it doesn’t require any particular expertise — a normal drag-and-drop operation. What’s more? It could be a very effective approach, but …\n","title":"Git: Move Files Retaining History","type":"blog"},{"content":"","date":"December 28, 2019","externalUrl":null,"permalink":"/tags/mongodb/","section":"Tags","summary":"","title":"Mongodb","type":"tags"},{"content":"Some months ago, I was creating a web page to show some aggregated data, but I soon noticed the API used to retrieve the data was very slow. After investigating the possible issue, we discovered the bottleneck: the database. The solution was to restructure the data to make it consumable from a web page.\nAlthough I had sometimes used the SQL Server profiler, I had no experience with the MongoDB profiler, so here are the steps involved in analysing a MongoDB query.\nIt doesn\u0026rsquo;t apply to our case, but in many situations performance problems are related to missing or wrongly implemented MongoDB Indexes.\nEnable MongoDB profiler # By default, MongoDB doesn\u0026rsquo;t profile any query. It is possible to set the profiling level executing:\ndb.setProfilingLevel(\u0026lt;profile_level\u0026gt;) where profile_level can be:\n0 - the profiler is off, so it doesn\u0026rsquo;t collect any data. 1 - the profiler collects data only if the operation takes more than slowms (a configurable threshold). 2 - the profiler collects data for all operations. IMPORTANT: At the end of the analysis, please remember to disable profiling with: db.setProfilingLevel(0), since it can affect MongoDB performance.\nRetrieve last query profile # Once profiling is enabled, it is possible to retrieve the last profiled query (meaning that the query must be executed after setting the profiling level) with the command:\ndb.system.profile.find().limit(1).sort({ts:-1}).pretty() Explain # The explain command comes in handy, since it provides information on many common operations such as aggregate, count, distinct, find, findAndModify, delete and update. Nevertheless, a method with the same name is also available on collection and cursor.\nSo if we want to analyse the information associated with an aggregate operation we can execute:\ndb.\u0026lt;collection_name\u0026gt;.explain(\u0026lt;verbosity\u0026gt;).aggregate(...) where:\n\u0026lt;collection_name\u0026gt; is the name of the collection on which the aggregation is performed \u0026lt;verbosity\u0026gt; is the level of verbosity we want to extract. Explain verbosity # The explain command accepts one of the following verbosity levels:\nqueryPlanner - It returns the queryPlanner information about the winning plan evaluation. executionStats - It returns queryPlanner and the executionStats information, but rejected plans are not included in the latter. allPlansExecution - It returns queryPlanner and the executionStats information about all the evaluated plans (including also rejected plans). Conclusion # Only by understanding how the query was executed by MongoDB were we able to improve its performance.\nNot our case, but it\u0026rsquo;s worth remembering that performance problems are often associated with missing or wrongly implemented indexes. If so, please have a look at MongoDB Indexes.\nResources # MongoDB database profiler Explain results MongoDB Indexes ","date":"December 28, 2019","externalUrl":null,"permalink":"/blog/mongodb-query-profiler/","section":"Blog","summary":"Some months ago, I was creating a web page to show some aggregated data, but I soon noticed the API used to retrieve the data was very slow. After investigating the possible issue, we discovered the bottleneck: the database. The solution was to restructure the data to make it consumable from a web page.\nAlthough I had sometimes used the SQL Server profiler, I had no experience with the MongoDB profiler, so here are the steps involved in analysing a MongoDB query.\n","title":"Mongodb Query Profiler","type":"blog"},{"content":"","date":"December 28, 2019","externalUrl":null,"permalink":"/tags/nosql/","section":"Tags","summary":"","title":"Nosql","type":"tags"},{"content":"When I started this blog, I evaluated many options:\nblogging platform vs static-generated website self-hosted vs hosted solutions costs and many more. I ended up trying to build something very simple, consisting of a static website hosted on GitHub Pages. With this solution, I have been able to cut the cost of any hosting solution, since, being a dev, I am quite comfortable writing in a text editor. But soon some problems arose.\nI discovered it was impossible to harden the server to customize the response headers. Why should someone customize the response headers? The response to that question is:\n\u0026ldquo;When she/he encounters securityheaders.com and she/he gets an F\u0026rdquo;.\nI believe that many people do not apply the required security constraints due to the barriers they encounter in understanding the reasoning behind them. On the contrary, securityheaders.com did a very good job of letting people understand what is wrong with their security headers, and why it is necessary to change them accordingly.\nWhy # Although there is a misleading conception that static websites are intrinsically secure, web server misconfiguration represents one of the attack vectors shared among dynamic and static websites.\nWeb server misconfiguration can lead to well-known vulnerabilities such as:\nReferrer leakage\nWith the Referrer-Policy, it is possible to instruct the browser not to send the referrer header along with requests. XSS (Cross-site scripting)\nUsing Content-Security-Policy instructs the browser about what content will be rendered on a page Along with the CSP security header, it would be useful to set the X-XSS-Protection header to instruct old browsers not to render the page if an attack is detected. Click-jacking\nThe X-Frame-Options header can be used to instruct the browser whether it is allowed to render content in a frame. Tab nabbing\nAlong with other mechanisms that can be used to mitigate this attack, setting the Referrer-Policy header can also be useful. and many more.\nFor a much more detailed description of the risks associated with HTML5 pages, please have a look at the OWASP HTML5 Security Cheat Sheet.\nHow # So, back to this blog: how is it possible to reach an A+ report while running a static website on GitHub Pages? No, you don\u0026rsquo;t need to pay anything to improve your website\u0026rsquo;s security.\nA free plan on Cloudflare is enough 😎.\nWe are going to customize the response headers by using the Cloudflare Workers service provided by the Cloudflare free tier.\nBelow is the script to be used. It can also be found on GitHub.\nlet securityHeaders = { \u0026#34;Content-Security-Policy\u0026#34; : \u0026#34;upgrade-insecure-requests\u0026#34;, \u0026#34;Strict-Transport-Security\u0026#34; : \u0026#34;max-age=2592000\u0026#34;, \u0026#34;X-Xss-Protection\u0026#34; : \u0026#34;1; mode=block\u0026#34;, \u0026#34;X-Frame-Options\u0026#34; : \u0026#34;DENY\u0026#34;, \u0026#34;X-Content-Type-Options\u0026#34; : \u0026#34;nosniff\u0026#34;, \u0026#34;Referrer-Policy\u0026#34; : \u0026#34;strict-origin-when-cross-origin\u0026#34;, \u0026#34;Feature-Policy\u0026#34;: \u0026#34;accelerometer \u0026#39;none\u0026#39;; camera \u0026#39;none\u0026#39;; geolocation \u0026#39;none\u0026#39;; gyroscope \u0026#39;none\u0026#39;; magnetometer \u0026#39;none\u0026#39;; microphone \u0026#39;none\u0026#39;; payment \u0026#39;none\u0026#39;; usb \u0026#39;none\u0026#39;\u0026#34; } let sanitiseHeaders = { \u0026#34;Server\u0026#34; : \u0026#34;My New Server Header!!!\u0026#34;, } let removeHeaders = [ \u0026#34;Public-Key-Pins\u0026#34;, \u0026#34;X-Powered-By\u0026#34;, \u0026#34;X-AspNet-Version\u0026#34;, ] addEventListener(\u0026#39;fetch\u0026#39;, event =\u0026gt; { event.respondWith(addHeaders(event.request)) }) async function addHeaders(req) { let response = await fetch(req) let newHdrs = new Headers(response.headers) if (newHdrs.has(\u0026#34;Content-Type\u0026#34;) \u0026amp;\u0026amp; !newHdrs.get(\u0026#34;Content-Type\u0026#34;).includes(\u0026#34;text/html\u0026#34;)) { return new Response(response.body , { status: response.status, statusText: response.statusText, headers: newHdrs }) } let setHeaders = Object.assign({}, securityHeaders, sanitiseHeaders) Object.keys(setHeaders).forEach(name =\u0026gt; { newHdrs.set(name, setHeaders[name]); }) removeHeaders.forEach(name =\u0026gt; { newHdrs.delete(name) }) return new Response(response.body , { status: response.status, statusText: response.statusText, headers: newHdrs }) } I took the script from the creator of securityheaders.com, from his post, where he describes exactly what each header is used for. I strongly recommend having a look at the rest of his blog for a detailed description of many security headers.\nResources # Original Scott Helme post securityheaders.com GitHub Pages OWASP HTML5 Security Cheat Sheet HTTP Headers on MDN ","date":"December 26, 2019","externalUrl":null,"permalink":"/blog/security-headers-on-static-websites/","section":"Blog","summary":"When I started this blog, I evaluated many options:\nblogging platform vs static-generated website self-hosted vs hosted solutions costs and many more. I ended up trying to build something very simple, consisting of a static website hosted on GitHub Pages. With this solution, I have been able to cut the cost of any hosting solution, since, being a dev, I am quite comfortable writing in a text editor. But soon some problems arose.\n","title":"Security Headers on Static Websites","type":"blog"},{"content":"","date":"December 13, 2019","externalUrl":null,"permalink":"/tags/ci/cd/","section":"Tags","summary":"","title":"Ci/Cd","type":"tags"},{"content":"","date":"December 13, 2019","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"","date":"December 13, 2019","externalUrl":null,"permalink":"/tags/gitlab/","section":"Tags","summary":"","title":"Gitlab","type":"tags"},{"content":"","date":"December 13, 2019","externalUrl":null,"permalink":"/tags/nginx/","section":"Tags","summary":"","title":"Nginx","type":"tags"},{"content":"In the last few projects, I started using GitLab, not only as a git repository server, but also as a DevOps platform. So here I am going to describe a very simple architecture for deploying a single-page application using Docker, docker-compose, and nginx. In this specific project I also used dotnet-core for the back-end API and VueJS as the front-end framework, but it is language-agnostic, meaning that you can replace whatever back-end or front-end you prefer.\nFirst of all, following the separation of concerns, let\u0026rsquo;s consider each part on its own. I have created 3 repositories:\nFront-end Back-end Deploy I considered using a single repo to maintain all the codebase, but I ended up with this structure for the following reasons:\nOften the teams working on back-end and front-end are not the same, so this way they are completely independent from each other This way the project structure can be reused with any framework/language combination We are almost separating the local development phase from the DevOps phase (apart from the inclusion of the gitlab-ci.yml files). Front-end static files generation # I used VueJS and the related vue-cli tools to generate the static files, but it doesn\u0026rsquo;t really matter for the whole application; the only important thing is to generate static files.\nIn our case the gitlab-ci.yml file will look like:\nbuild site: image: node:latest stage: build script: - npm ci - npm run build artifacts: paths: - dist It creates a job named build site It executes the script in a docker image node:latest It creates a build stage to execute the following script in the image aforementioned: npm ci npm run build It stores the artifacts created in the dist directory. Back-end API build # Since we make use of dotnet-core, we need to build the project, so this step could be unnecessary if you choose a language that doesn\u0026rsquo;t require compilation (e.g. Node.js, Python, etc\u0026hellip;).\nOur gitlab-ci.yml:\nbuild: image: microsoft/dotnet:sdk stage: build script: - dotnet restore - dotnet publish -c Release -o out artifacts: paths: - out As you can see, the file looks very similar to the previous one.\nIt creates a build job It uses a different docker image to build the project microsoft/dotnet:sdk It creates a build stage to execute the following scripts: - dotnet restore # it restores the dependencies - dotnet publish -c Release -o out # it builds the project It stores the artifacts created in the out directory. So at the moment, we have 2 projects that produce their own artifacts. It\u0026rsquo;s time to let them talk!\nDeploy project # Project structure # |__ api | |__ Dockerfile |__ nginx | |__ nginx.conf |__ gitlab-ci.yml |__ docker-compose.yml This project performs the following steps:\nRetrieve files generated from the front-end Retrieve files generated from the back-end Set-up nginx docker-compose.yml:\nversion: \u0026#39;3\u0026#39; services: nginx: image: nginx:latest volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf - ./dist/:/var/www/html/ ports: - 80:80 - 443:443 networks: - proxy-net web: build: ./api/ networks: - proxy-net networks: proxy-net: From the docker-compose file it is important to notice two folders:\n./dist/ used to map the static file folder to the nginx container ./api/ the folder containing the Dockerfile used to set up the dotnet APIs container. For completeness, here is the content of the Dockerfile used to run the dotnet APIs.\n# Build runtime image FROM microsoft/dotnet:aspnetcore-runtime WORKDIR /app COPY . . ENTRYPOINT [\u0026#34;dotnet\u0026#34;, \u0026#34;Api.dll\u0026#34;] Where Api is the name of the dotnet API project.\nnginx.conf:\nevents { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; server { server_name \u0026lt;name_of_the_server\u0026gt;; # TODO: to be replaced location / { # This would be the directory where your SPA static files are stored at root /var/www/html/; try_files $uri $uri/ /index.html; } location /api { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://web; proxy_ssl_session_reuse off; proxy_set_header Host $http_host; proxy_cache_bypass $http_upgrade; proxy_redirect off; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; } } } The previous nginx configuration is intended as an example only. For nginx production configuration, please refer to the following resources:\nSsl-config generator by Mozilla Security Headers We are assuming this project is deployed by an agent running on a machine with docker-compose installed.\nLet\u0026rsquo;s have a look at gitlab-ci.yml.\ndeploy: stage: deploy only: - \u0026#34;master\u0026#34; environment: production script: - \u0026#34;curl -L --header \\\u0026#34;PRIVATE-TOKEN: $TOKEN\\\u0026#34; \\\u0026#34;https://gitlab.com/api/v4/projects/14072554/jobs/artifacts/master/download?job=build+site\\\u0026#34; --output artifacts.zip\u0026#34; - unzip artifacts.zip - \u0026#34;curl -L --header \\\u0026#34;PRIVATE-TOKEN: $TOKEN\\\u0026#34; \\\u0026#34;https://gitlab.com/api/v4/projects/10080203/jobs/artifacts/master/download?job=build\\\u0026#34; --output api.zip\u0026#34; - unzip api.zip - cp -R out/* api/ - sh down.sh - sh up.sh It creates a deploy job It creates a deploy stage with the following script section: - \u0026#34;curl -L --header \\\u0026#34;PRIVATE-TOKEN: $TOKEN\\\u0026#34; \\\u0026#34;https://gitlab.com/api/v4/projects/14072554/jobs/artifacts/master/download?job=build+site\\\u0026#34; --output artifacts.zip\u0026#34; - unzip artifacts.zip - \u0026#34;curl -L --header \\\u0026#34;PRIVATE-TOKEN: $TOKEN\\\u0026#34; \\\u0026#34;https://gitlab.com/api/v4/projects/10080203/jobs/artifacts/master/download?job=build\\\u0026#34; --output api.zip\u0026#34; - unzip api.zip - cp -R out/* api/ - docker-compose up --build -d So let\u0026rsquo;s dive deeper into the script commands:\nRetrieve the front-end artifacts by using the GitLab API (for non-enterprise users) It also requires setting the PRIVATE-TOKEN variable with a personal access token, which can be generated from User Settings -\u0026gt; Access Tokens Unzip the front-end artifacts Retrieve the back-end artifacts Unzip the back-end artifacts Run docker-compose Conclusion # I have tried to describe only the steps necessary to deploy a single-page application using Docker and GitLab, without considering the initial steps of repository creation and GitLab runner setup.\nWhile the setup of the GitLab runner used to deploy the application is required, the front-end and back-end builds make use of the shared runners available in GitLab.\nFor further details, please don\u0026rsquo;t hesitate to get in touch!\n","date":"December 13, 2019","externalUrl":null,"permalink":"/blog/gitlab-cd/","section":"Blog","summary":"In the last few projects, I started using GitLab, not only as a git repository server, but also as a DevOps platform. So here I am going to describe a very simple architecture for deploying a single-page application using Docker, docker-compose, and nginx. In this specific project I also used dotnet-core for the back-end API and VueJS as the front-end framework, but it is language-agnostic, meaning that you can replace whatever back-end or front-end you prefer.\n","title":"SPA deployment with GitLab","type":"blog"},{"content":"Web extensions or browser plugins can be used to improve browser functionalities. The following examples summarize the different goals they can have:\nno-script - to block script served from not-trusted domains ublock Origin - it is \u0026ldquo;a wide-spectrum blocker\u0026rdquo; dark-mode - it changes the appearance of web pages React Developer Tools - to inspect react-specific properties of a page that makes use of React. Anatomy of an extension # The main components of a browser extension are:\nManifest.json — it is the only mandatory file, and it describes the extension itself. It acts as an entry point for the browser, declaring which files/resources/permissions will be used.\nBackground scripts — they contain the logic that is not strictly related to a single web page. They are loaded when the extension is loaded and remain active until the extension is disabled or uninstalled.\nContent scripts — unlike background scripts, they are loaded into web pages, so they can manipulate the DOM exactly like a normal script can.\nUI components — they include all the parts that allow a user to interact with the extension, by means of an HTML document. There are three different UI components:\nsidebar popup options page Web-accessible resources — they include all the resources that are made available to the scripts (e.g. HTML, images, JS, CSS)\nExtension pages - They are additional pages used to handle specific user interactions.\nWeb-extension toolbox # As with many browser APIs, the web extension APIs can also behave differently according to the browser on which they are executed. In case of cross-browser development, the web-extension polyfill developed by Mozilla could be handy.\nFurthermore, to ease the development process, there is also the web-extension toolbox and its related yeoman generator. Among other things, it provides the following functionalities:\nCompiles the extension via webpack to dist/\u0026lt;vendor\u0026gt; Watches all extension files and re-compiles on demand Reloads the extension or extension page as soon as something changes After answering some questions, it generates a ready-to-run web extension.\nDevelopment # You only need to run npm run dev \u0026lt;vendor_name\u0026gt;, where \u0026lt;vendor_name\u0026gt; can be chrome, firefox, opera or edge; now you can load the generated extension in the browser of choice.\nBuild # As easy as running npm run build \u0026lt;vendor_name\u0026gt;.\nConclusions # So, what are you waiting for? Are you ready to develop your own browser plugin? I\u0026rsquo;ve already started mine ;-)\nResources # MDN Browser Extensions webextension-toolbox Mozilla web-ext ","date":"November 20, 2019","externalUrl":null,"permalink":"/blog/browser-extension/","section":"Blog","summary":"Web extensions or browser plugins can be used to improve browser functionalities. The following examples summarize the different goals they can have:\nno-script - to block script served from not-trusted domains ublock Origin - it is “a wide-spectrum blocker” dark-mode - it changes the appearance of web pages React Developer Tools - to inspect react-specific properties of a page that makes use of React. Anatomy of an extension # The main components of a browser extension are:\n","title":"Browser Extension Development","type":"blog"},{"content":"","date":"November 20, 2019","externalUrl":null,"permalink":"/tags/browser-plugin/","section":"Tags","summary":"","title":"Browser Plugin","type":"tags"},{"content":"","date":"November 20, 2019","externalUrl":null,"permalink":"/tags/web-extension/","section":"Tags","summary":"","title":"Web-Extension","type":"tags"},{"content":" Preamble # This article is not intended to cover the whole testing process due to its vastness. Having a glance at its Wikipedia page could give an idea of it. I am going to cover only the tip of the iceberg, but it is important to remember that bugs cannot be entirely eliminated from software — instead, they must be discovered as soon as possible.\nThe higher the time passed since the bug came into the system, the higher the cost to fix it.\nSo any techniques that could be useful to identify a bug should be used, taking into account the resources available and the type of system to be implemented.\nI am going to focus on the developer\u0026rsquo;s point of view: what can a developer do (related to software testing) to improve the quality of the code? The first word that comes to mind is TDD. Of course, as already said, this isn\u0026rsquo;t the only way to implement software testing, and it is far from me saying that it is enough, but surely it is a good starting point. I strongly believe that TDD/BDD are very supportive techniques that could help writing more robust and stable software. The problem is mine: I haven\u0026rsquo;t ever applied it from scratch so far, so I decided to address this gap from now on.\nAlthough I have been programming for about 5 years, I think I have very little practical experience with software testing. Please don\u0026rsquo;t misjudge me for that. In the past I wrote some tests, but usually while working in an already set-up environment, writing small tests for the functionality I was adding to the system. Here I am talking about establishing a test-oriented development process.\nI won\u0026rsquo;t consider any language-specific framework, but I am going to define only some of the main keywords. I am going to dive deep into the details of a specific framework (hurrah, practice!) in a dedicated article, but not here.\nKeywords # Unit test # Unit test refers to a single unit of code. What actually is a unit? According to the paradigm used, a unit of code can be a function or a class in the case of object-oriented programming. The important thing is that unit testing should not be performed by considering the success case only; on the contrary, the unit test should take into account, in particular, the corner cases, in a defensive programming manner. Why? Because, as a developer, you will run through the normal workflow dozens, hundreds, or maybe thousands of times.\nIntegration test # On the other hand, integration tests should refer to the interaction of multiple units of code, by trying to locate interface issues. The integration tests can be seen in a hierarchical way: once a group of units are tested, it can be considered at the same time as a unit, a single module, to be integrated with other modules, so an integration test can be performed in an upper layer, and so on. Anyway, the importance of integration testing should not be overlooked, since with unit testing alone it is not possible to identify defects at the interface level.\nMock # Mocking during testing is fundamental. It is the process of creating an object that can simulate the behavior of a real object. This is necessary because, in the real world, a unit is often composed of or depends on other units. To make sure the code under test is isolated, and to avoid side effects that can occur in other units, mock objects should be created — objects that simulate what the real object would do, but without the risk of defects occurring externally to the unit being tested.\nTDD # Test-Driven Development is a software development process which involves the creation of the test before actually implementing the functionality. The process involves the following steps:\nVerify whether the current design fits well with the functionality to be added. If necessary, refactor the code to adhere to the new feature. Repeat the tests, to be sure that after refactoring everything still works properly. Write the tests for the new feature (at this point, all the new tests should fail). Implement the functionality to adhere to the specification represented by the tests (at the end of this step, all the tests should pass). If necessary, refactor the code just implemented (clean up if necessary, remove duplications, etc.). Run the tests. The upside of the process is highlighted by steps 1 and 4: step 1 makes it possible to refactor the existing code without being afraid of it and without losing confidence; step 4 is useful for keeping the code clean, improving readability and maintainability; it can also, in some cases, be useful for trying to figure out whether there is a solution that improves performance, and such an operation could be performed in step 4.\nBDD # Behavior-Driven Development can be seen as a specialization of the aforementioned TDD, but it is oriented to the user. It involves the definition of the tests as a description of the desired behavior. It permits a clearer way of communication among different actors, when the process involves not only developers, but also QA Testers, who can define the specification of the tests in a domain-specific language, which developers can then translate (also by means of automated tools) into a specific programming language.\nConclusion # If I\u0026rsquo;m not the only person in the world who hasn\u0026rsquo;t yet established a real TDD strategy in their daily job, I strongly recommend giving it a try. Keep in mind that a very robust system should be tested as much as possible, from every perspective, both functional and non-functional, taking into account also features too often overlooked, like security and performance; so please also consider investigating performance testing and security testing — I will try to describe them in a related post.\n","date":"August 25, 2019","externalUrl":null,"permalink":"/blog/lets-test/","section":"Blog","summary":"Preamble # This article is not intended to cover the whole testing process due to its vastness. Having a glance at its Wikipedia page could give an idea of it. I am going to cover only the tip of the iceberg, but it is important to remember that bugs cannot be entirely eliminated from software — instead, they must be discovered as soon as possible.\n","title":"Let's Test","type":"blog"},{"content":"","date":"August 25, 2019","externalUrl":null,"permalink":"/tags/test/","section":"Tags","summary":"","title":"Test","type":"tags"},{"content":"Here in this post, I am going to explain why I am starting to write something on the internet and what I am going to share. I am well aware that there are many sites today describing almost everything, but these are some of the reasons that drive me at this point:\nto improve my language skills; to improve my online presence; to receive feedback (feedback is the key to growing); to share what I do (and how — I am also going to list some of my biggest mistakes). And what about the content?\nWell, regarding the content, I am not completely sure what I am going to describe, but approximately it could be:\ncoding (small snippets of code that solve very small problems); software engineering (patterns and architectures); security/privacy comparisons (I look for tool comparisons every day — they drive our daily choices); travelling (as soon as I can) who knows what else? Language skills # I started learning English seriously when I began university, and I am self-taught. So I often make some very big mistakes, and in most cases I am quite insecure about my communication skills. One of the most common pieces of advice I have ever heard about languages is: \u0026ldquo;try\u0026rdquo;. So, after completing my master\u0026rsquo;s degree entirely in English and moving abroad to work for six months at an international company, I was thinking about what I could do to keep improving it; I believe that leaving my \u0026ldquo;comfort zone\u0026rdquo; to connect with different people can only be helpful.\nOnline presence # Every now and then, during my daily job or simply while reading other authors, I often find people doing what I would like to do — so why should I only watch other people do it, if I could do it myself? Maybe I am not as good at writing as others are, but that doesn\u0026rsquo;t mean I can\u0026rsquo;t try.\nFeedback # By exposing myself to many different people, I expose what I do to different opinions. Maybe I\u0026rsquo;ll regret what I\u0026rsquo;m saying, but:\nCriticism is welcome.\nAll comments are welcome — criticism helps me grow and avoid mistakes I\u0026rsquo;ve already made. Advice is also very important, from everyone, all ages, all experience levels.\nConclusions # The best way to learn something is to play with it, in short:\n\u0026ldquo;The game is on\u0026rdquo;\n(cit. Sherlock).\n","date":"April 14, 2019","externalUrl":null,"permalink":"/blog/why/","section":"Blog","summary":"Here in this post, I am going to explain why I am starting to write something on the internet and what I am going to share. I am well aware that there are many sites today describing almost everything, but these are some of the reasons that drive me at this point:\nto improve my language skills; to improve my online presence; to receive feedback (feedback is the key to growing); to share what I do (and how — I am also going to list some of my biggest mistakes). And what about the content?\n","title":"Getting Started With My Website :-)","type":"blog"},{"content":"","date":"April 14, 2019","externalUrl":null,"permalink":"/categories/other/","section":"Categories","summary":"","title":"Other","type":"categories"},{"content":"","date":"April 14, 2019","externalUrl":null,"permalink":"/tags/personal/","section":"Tags","summary":"","title":"Personal","type":"tags"},{"content":"Hi, I\u0026rsquo;m Antonio — a Staff / Tech Lead Software Engineer, building software since 2013 and focused on blockchain protocols and Web3 infrastructure since 2021.\nWhat I do # System architecture. I design and own systems end to end — architecture, implementation, and how the pieces fit together — rather than just one layer of a larger system.\nTechnical leadership. Leading a project means owning the hard calls — what to build, what to defer, where the risk actually is — and making sure the team ships with confidence, not just speed.\nSecurity-minded engineering. Security is a habit built into how I design, not a review bolted on afterward — audits and production hardening as part of the process, not an afterthought.\nBlockchain \u0026amp; Web3 # This has been my focus since 2021: protocol architecture, smart contracts, and the off-chain infrastructure that supports them, built and shipped to production with external audits along the way.\nFor the full role-by-role history — companies, titles, and years — see my LinkedIn profile.\nOpen to # Remote roles, based in Italy — blockchain protocol / Web3 infrastructure roles first, and staff or lead engineering roles more broadly where architecture, ownership, and security rigor matter.\nOutside of work # I run, do yoga and boxing, and I\u0026rsquo;m always planning the next trip — Norway, Spain, Portugal, and Greece are next on the list. Non-native English speaker, still working on French and Spanish too — if you spot a mistake, let me know.\n","date":"June 24, 2017","externalUrl":null,"permalink":"/about/","section":"Antomor","summary":"Hi, I’m Antonio — a Staff / Tech Lead Software Engineer, building software since 2013 and focused on blockchain protocols and Web3 infrastructure since 2021.\nWhat I do # System architecture. I design and own systems end to end — architecture, implementation, and how the pieces fit together — rather than just one layer of a larger system.\n","title":"About","type":"page"},{"content":"Get in touch at dev@antomor.com, or find me on GitHub and LinkedIn.\n","externalUrl":null,"permalink":"/contact/","section":"Antomor","summary":"Get in touch at dev@antomor.com, or find me on GitHub and LinkedIn.\n","title":"Contact","type":"page"}]