On-chain veriye "23s ago" etiketi önerdim. Claude bunun bir ürün yalanı olduğunu gösterdi.
smarts.md, akıllı sözleşmeler için bir live docs sitesi. Her sözleşme sayfası, doğrudan zincirden okunan değerleri gösteriyor — Uniswap pool likiditesi, ERC-20 totalSupply, özel sözleşmenin owner alanı. Bu değerler en fazla 60 saniye eski olabiliyor (Solid Cache TTL = 60s).
Ama sayfada "bu ne zaman okundu?" diye hiçbir ipucu yoktu. Mintlify gibi görünüyordu — statik bir dokümandan ayırt edilemezdi.
Veri yenileme stratejisini Claude ile birlikte denetlerken altı boşluk listeledim. Boşluk 4 tek satırdı:
canlı veride tazelik göstergesi yok — bir "as of block X / Y seconds ago" etiketi ekle.
To-do'yu yazıp yemek yemeye gittim. Dönünce o tek satıra bir saat takıldım. Sonunda yayına aldığım yön ilk içgüdümden tamamen farklıydı — ve Claude sayesinde fark ettim ki orijinal plan aslında bir ürün yalanıydı.
İlk düşüncem şuydu:
liquidity: 1,234,567 USDC ← 23s ago
totalSupply: 27.3B USDC ← 12s ago
Basit, doğrudan, kullanıcı anında anlar.
Yemekten sonra tekrar baktım ve Claude'a birkaç soru sormaya başladım:
Bu "23s" tam olarak neyden neyi çıkarıyor?
Açıkça yazdı: now - cache.fetched_at. Yani değeri cache'e yazdığımızdan bu yana geçen süre.
Peki zincirin başına göre gerçekten ne kadar geride kaldık?
Claude düşündü: Multicall'ın tüm view fonksiyonlarını tek seferde okuyup cache'e yazıyor, cache 60s boyunca geçerli. Ama yazma anında okuduğumuz block'un kendisi zaten geçmişte — Ethereum'un blok süresinin alt sınırı 0–12s.
Gerçek nedensel zincir şu:
zincir başı yeni bir block üretir
└→ RPC bizim tarafımızda block N'i okur (head'den 0–12s geride)
└→ Solid Cache'e yazılır
└→ kullanıcının ekranında render (cache 60s'ye kadar)
O zaman "23s ago" etiketindeki 23s hangi segmente karşılık geliyor?
Sadece son segmente: now - fetched_at, cache yazımından şimdiye kadar geçen süre.
Zincir tarafındaki gecikme hiçbir şekilde yansıtılmıyor.
Somut örnek: kullanıcı "fetched 5s ago" görüyor, ama o block kendisi zaten 12s yaşında ve zincir başı 2 block daha üretmiş olabilir — gerçek zincir gecikmesi ≈ 24s, etiket 5s diyor.
İşte o anda anladım: "tazeliği" tek bir sayıya sıkıştırmak fiziksel olarak yanlış.
Claude bunu net yazmama yardım etti — üç bağımsız boyut var:
| Boyut | Ne ölçüyor | Fiziksel taban |
|---|---|---|
| Block tazeliği | zincir başı → okuduğumuz block | Ethereum blok süresi 0–12s |
| Okuma tazeliği | RPC okuma → cache yazma | 0 (senkron) |
| Görüntüleme tazeliği | cache yazma → kullanıcı ekranı | 0–60s (cache TTL) |
Bu üçünü tek bir "freshness: 35s" sayısına toplamak fiziksel olarak yok. chain → cache → screen zinciri senkron değil: cache yazıldığı anda donar, zincir ise blok üretmeye devam eder. Bu bir hassasiyet sorunu değil, kategori hatası.
Daha da önemlisi: hangi segmentin önemli olduğu, alanın kendisine bağlı.
decimals: kurucuda tanımlı, asla değişmez. Tazeliğe hiç gerek yok.owner: yalnızca governance ile değişir, yıllarca yerinde durabilir. Hangi block'ta okuduğunu bilmen gerek (yoksa "beş upgrade öncesini" izliyor olursun), ama "23 saniye önce" gerekmiyor.liquidity / slot0: her block'ta değişebilir. Hem block hem zaman önemli.Block numarası tek nesnel çapa — bu zincirdeki gerçek, cache stratejimizden etkilenmez.
Yayına alınan görünüm:
liquidity: 1,234,567 USDC
↳ as of Block #19,234,567 · 23s ago
Block, anlayan kullanıcı için; "23s ago", anlamayan için. İkisi de yalan söylemiyor: block fiziksel bir gerçek, "23s ago" açıkça cache yazımından bu yana geçen süre, "zincir başına göre gecikme" değil.
Alan özelliğine göre farklılaştırılmış görüntüleme:
# app/services/chain_reader/field_mutability.rb
module ChainReader
module FieldMutability
IMMUTABLE = %w[
name symbol decimals
DOMAIN_SEPARATOR PERMIT_TYPEHASH
factory token0 token1 fee tickSpacing
asset underlying
].freeze
SLOW = %w[
owner
paused deprecated
pauser blacklister masterMinter rescuer
upgradedAddress implementation
maxTotalSupply
].freeze
def self.classify(function_name)
return :immutable if IMMUTABLE.include?(function_name)
return :slow if SLOW.include?(function_name)
:fast
end
end
end
Render kuralları:
| Sınıf | Örnek | Görüntüleme |
|---|---|---|
| immutable | decimals, name, symbol |
tazelik gösterilmez (saf gürültü olur) |
| slow | owner, paused |
yalnızca block, saniye yok |
| fast | slot0, liquidity, totalSupply |
block + saniye |
Bu beyaz liste bilinçli olarak muhafazakâr — listede olmayan her şey fast. Bir değeri yanlışlıkla "canlı" olarak işaretlemek, değişebilen bir değeri sessizce statik göstermekten iyidir.
Multicall3'ün kendisinde getBlockNumber() fonksiyonu var. Her batch'in sonuna ekle — tek RPC, fazladan bir değer, sıfıra yakın gecikme:
# app/services/chain_reader/multicall3_client.rb
BLOCK_NUMBER_FN = {
"name" => "getBlockNumber",
"inputs" => [],
"outputs" => [{ "type" => "uint256" }]
}.freeze
def call(calls)
block_call = Call.new(target: ADDRESS, function: BLOCK_NUMBER_FN)
augmented = calls + [block_call]
# ...aggregate3 tek seferde
Batch.new(block_number: block_number, results: results)
end
ViewCaller cache payload'u da sürüm aldı (CACHE_VERSION = "v2"), {fn_sig => result} yapısından Snapshot(results:, block_number:, fetched_at:) yapısına geçti. Snapshot, eski hash arabirimini korumak için def [](key) = @results[key] ve benzeri delege metotları kullanıyor — hiçbir caller'ın değişmesine gerek kalmadı.
UI helper:
def freshness_tag_for(fn)
return nil unless @live_snapshot&.block_number
mutability = ChainReader::FieldMutability.classify(fn["name"])
return nil if mutability == :immutable
block_text = "Block ##{number_with_delimiter(@live_snapshot.block_number)}"
label =
if mutability == :fast && @live_snapshot.fetched_at
"as of #{block_text} · #{freshness_phrase(@live_snapshot.fetched_at)}"
else
"as of #{block_text}"
end
# ...
end
Toplam PR: 24 dosya, +676 / -55.
Yayına çıktıktan sonra farklılaştırma cümlesini yeniden yazdım:
Smarts, alan bazında, durumun hangi block'ta okunduğunu açıkça söyleyen ilk DeFi dokümantasyonu.
"Mintlify'dan daha taze" demekten daha somut ve daha güvenilir. Gerçek gecikmeyi gizlemek yerine ortaya seriyor — sürekli geri döndüğüm CLAUDE.md ilkeleriyle uyumlu: "AI bir araç, vitrin değil" + "Build in Public".
Claude ile bir yılı aşkın süredir çalıştıktan sonra en büyük çıkarımım kod yazma hızı değil — durup sormaya tenezzül edersem kendi planımı ciddi şekilde çürütecek olması.
"23s ago etiketi ekle" diye önerdiğimde kendim hiçbir şeyin yanlış olduğunu görmüyordum. Kendi PR'ımı kendim incelesem, büyük ihtimalle merge ederdim — fazlasıyla "açıkça doğru" görünüyor.
Ama tonu değiştirip Claude'a "bu 23s tam olarak neyi neyden çıkarıyor ve hangi gecikme segmentine karşılık geliyor?" diye sorduğumda, nedensel zinciri açtı ve sayının fiziksel anlamının yanlış olduğunu gösterdi.
Buradaki kazanım "Claude bana fazladan kod yazdı" değil, "Claude bir ürün yalanının canlıya çıkmasını engelledi".
23s ago gerçekten yayına alınsaydı kimse şikâyet etmezdi — makul görünüyor ve hatayı fark edebilecek kişiler de bunu uyarmaya zahmet etmez. Ama gerçekten konuyu bilen birinin gözünde, DeFi docs siteniz şu deliği taşır: bu site neyi gösterdiğini bilmiyor.
Bir dahaki sefere Claude ile bir ürün kararı verirken bu adıma 5 dakika ayırın:
"Az önce önerdiğim plan için, yanlış olabileceği üç yolu listele. Her biri için somut bir senaryo ver."
Claude düşünmediğin bir tane bulursa o 5 dakika kendini fazlasıyla geri ödedi demektir.