# v2 git bundle
186c8f06134f468826486c4f2df28746781cb31d refs/heads/bundle
186c8f06134f468826486c4f2df28746781cb31d HEAD

PACK     7’Zx¢]útree 1a8a16e947f9ef94894b5649c6829d2e1fdc219b
parent ffbf00c5240a05797c914d1c26311062afa2c166
author Zach Kelling <z@zeekay.io> 1786667698 -0700
committer Zach Kelling <z@zeekay.io> 1786667698 -0700

Route a swap on the chain its quote was priced on

A quote states the chain it came from, and the dispatcher read that last. A
query parameter, or a chainId sitting beside the quote rather than inside it,
could send someone's quote to another chain's router â€” and these chains share
deployment addresses, so the same numbers name real pools on the destination.
The narrowest statement wins now: the quote's own chain, then the body, then
the URL. The handler refuses a foreign quote as well, so neither layer is load
bearing alone.

Whether a chain can build a swap is one question asked in one place. Both the
router and the wrapped native become a twenty-byte argument, so a blank or
malformed one produced a call whose arguments had slid; a chain missing either
serves no swap route rather than an unsignable one.

A chain may declare more than one subgraph holding a book, each in its own
goroutine, so the book behind the unprefixed routes was whichever registered
last â€” a different one across restarts. The config names it now.

The dispatcher buffered ten times what the handlers will read, and called an
oversized body malformed JSON. It reads their limit and says which failure it
was.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Ó3÷/Ÿx/Ðþtree e9d597937b672c6e0183fdb62de1b06d5e410f31
parent 8f5f8079040a3ce1b7ffb9f4d7969913e72c498d
author Zach Kelling <z@zeekay.io> 1786666741 -0700
committer Zach Kelling <z@zeekay.io> 1786666741 -0700

build: graph v1.2.37 -> v1.2.38 (a hop fee must be a uint24)

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
µìZ”xD»þtree bc0b7515ff09fd9bc0202c7f9d386e7773404114
parent 8993b4773c63698a5812ceec9d2dee31f69d36eb
author Zach Kelling <z@zeekay.io> 1786666536 -0700
committer Zach Kelling <z@zeekay.io> 1786666536 -0700

build: graph v1.2.36 -> v1.2.37 (an amount must fit the word it is rendered into)

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Å9b“‘xA¾þtree f3535cabd28b411b88c72e6126ac12769c5ad154
parent 41e5e20e5fa9220986058e39e8610569297ddcca
author Zach Kelling <z@zeekay.io> 1786666056 -0700
committer Zach Kelling <z@zeekay.io> 1786666056 -0700

build: graph v1.2.35 -> v1.2.36 (native input read from the hop that names it)

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
)�`ùœTxL³útree 762f34775e74dfaeaf58a937a1256d44e4cd7592
parent e22f8ece93ca5d61be32cb5be36981fdcd19a9c7
author Zach Kelling <z@zeekay.io> 1786665763 -0700
committer Zach Kelling <z@zeekay.io> 1786665763 -0700

Answer the swap form on the paths it actually calls

The swap form calls /v1/quote and /v1/swappable_tokens, not
/v1/graph/{chain}/{subgraph}/quote. Only the prefixed form existed here, so a
TypeScript service stayed in front of this one to serve the unprefixed paths,
and the two answered the same questions from two copies of the book.

A swap request already says which chain it is for â€” a quote names the chain of
each token, an approval names its chain, a swap carries the quote it came from.
So the chain comes out of the request rather than the path, and the request is
handed to the mux that chain's subgraph already registered. The handler values
are the ones the prefixed routes use; nothing is built twice.

Swap and approval need this chain's router. Without one configured there is no
address to address a transaction to, so those two routes are absent rather than
serving calldata signed against nothing.

The subgraph that backs a chain's swap routes is the one holding an AMM book.
Picking whichever subgraph started first would have been a race, and a CLOB
schema has no pools to price against.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
#ÈÙvœ3x<Ãütree 7411de3407eb7fe0a075e9b24350b58a636b65c8
parent 4a38efb2675c18cbf6b6bc3a99092ed0aa808d4a
author Zach Kelling <z@zeekay.io> 1786663650 -0700
committer Zach Kelling <z@zeekay.io> 1786663650 -0700

Answer the swap form from the same book the explorer already indexes

Two questions a swap form asks that GraphQL does not answer in a shape a form
can use: what can I trade here, and what do I get for this. Both read the store
each subgraph already fills, so they mount beside its /graphql rather than
behind a second service keeping a second copy of the book.

quoter_v2 is per chain because the periphery quoter is deployed per chain. With
it set the chain prices a V3 route directly; without it the quote falls back to
the closed form, which is exact for V2 and an estimate for V3.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
ý’DxB½ûtree 995f9bfc687c590a554346a5549d52494f45388d
parent 9101f05419436abba4dc0957612f8b311d4f2865
author Zach Kelling <z@zeekay.io> 1786659209 -0700
committer Zach Kelling <z@zeekay.io> 1786659209 -0700

Take the graph that reads a token's supply, and one sqlite C

Market cap and fully diluted value printed a dash on every token page. The
supply behind both was never read for a token already indexed: a real symbol
was taken as proof the row was finished. graph v1.2.31 asks what is actually
missing, keeps every field it already had, and reports a supply in whole
tokens whatever the source. Asking is what a start does now, so the flag that
used to gate it is gone.

The same pin brings the sqlite the whole process opens under one name.
csqlite is the house amalgamation, registered "sqlite3"; upstream mattn
compiled a second copy of that same C and the link failed on darwin, while
the hanzoai/sqlite wrapper takes "sqlite" â€” already modernc's, which arrives
with hanzoai/replicate â€” and panics in init. One C, three packages, no
argument.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
ÌËuû›%x[¤ýtree 35ab219995e214ed477d378bcb04f0bbf22196e5
parent 396fd6773c6fc4a597bb11b6d8173eaa36ad62d7
author Zach Kelling <z@zeekay.io> 1786628887 -0700
committer Zach Kelling <z@zeekay.io> 1786628887 -0700

Carry each chain's mint and treasury to the indexer

Supply cannot be read from a node â€” the genesis alloc is not exposed and summing
every account is not a query â€” so the mint is declared per chain beside the
factory addresses. The treasury address is declared too, and its balance read
live, because that is the number that falls as tokens reach holders.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
û�Ë¬”x$Ûþtree 3f9a9f6aa1d8648d7b868165692bfac05a6d9f0d
parent f4d904d86e4266073315ad20b687df649f2f7308
author Zach Kelling <z@zeekay.io> 1786595290 -0700
committer Zach Kelling <z@zeekay.io> 1786595290 -0700

Take the graph that reads native supply and stake

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
ÒöZ>Ÿx/Ðþtree d9b9f25ccfe63fea17983f098007e9671945f639
parent 939827e096c8f6ff084c38fcea679e9a4e9a60aa
author Zach Kelling <z@zeekay.io> 1786494759 -0700
committer Zach Kelling <z@zeekay.io> 1786494759 -0700

Take the graph that prices trades without the JSON extension

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
LÐ^Ü”x”kþtree 08b1d4298e37fce853b2bfcc7f3ad7026eadceb4
parent bdd6f665dfdcab4b295b176ce379872575ef334d
author Zach Kelling <z@zeekay.io> 1786494348 -0700
committer Zach Kelling <z@zeekay.io> 1786494348 -0700

Take the graph that reports its swap writes

The valuation line now says how many trades it priced, so a write that changes
no rows is visible instead of silent.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
0Œ„R’xâþtree a75ab7c5fcc2af1757c3da844a26f8341759d9fb
parent 94a136ca7dd5d245bed03393b6316ee4ffa38ee0
author Zach Kelling <z@zeekay.io> 1786492635 -0700
committer Zach Kelling <z@zeekay.io> 1786492635 -0700

Take the graph that values its swaps

Pool pages showed volume over trades that each reported nothing. The indexer
computed each trade's worth and dropped it; it now writes it, batched, so a
swap list adds up to the total printed above it.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
ÍŸD“x3Ìþtree d71db386f317889e08e0f58aeaac84efe4561611
parent 4f9994fa4a5be4d5858eea1dd37d2cd297c7f8cb
author Zach Kelling <z@zeekay.io> 1786478037 -0700
committer Zach Kelling <z@zeekay.io> 1786478037 -0700

graph v1.2.20 â€” an unparseable query is refused, not held open

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
\Â`…�/xýýtree 9dcd94d05073d56413f7740838b9c4231cb4633f
parent 9482004bbc71da42a54f3048a04908dbcd497257
author Zach Kelling <z@zeekay.io> 1786477718 -0700
committer Zach Kelling <z@zeekay.io> 1786477718 -0700

indexer v1.5.4 â€” an index follows the chain it was pointed at

Zoo relaunched and its index stayed on the chain that was replaced, serving
24,607 blocks of it while the live chain sat at 9,564. The block indexer
resumed from a cursor left above the new head, so it had no way back on its
own. v1.5.4 reads block 0's hash to tell a rebuilt chain from a slow node,
and starts over only for the former.

Every chain in chains.yaml is indexed by this binary, so each of them was
exposed to it; zoo is the one it happened to.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
¥ü(’"x"Ýýtree 245f5e9860654ec4c8dc1f842c22ea3f8b1c6cd5
parent 82f46085bd46e8e8c022662c1f85fe9bbac847fd
author Zach Kelling <z@zeekay.io> 1786476057 -0700
committer Zach Kelling <z@zeekay.io> 1786476057 -0700

graph v1.2.19 â€” every field a document asks for, and a row's token is the token

parseTopFields split top-level fields on newlines, so a minified document was
answered with its first field alone and no error. The explore page asks for its
bundle, factory, tokens, pools and day series in one document.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
©yµº’x2Íþtree 767a840b49116c7c008f6def74fe36432ed5f29e
parent b61d5fe7b7e6dc2d03a1ccf8e9f7512780251418
author Zach Kelling <z@zeekay.io> 1786472765 -0700
committer Zach Kelling <z@zeekay.io> 1786472765 -0700

graph v1.2.18 â€” a day nobody observed reports no value locked

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
P_]™-xÙ&ýtree 2ad6fc21531b099a3bbaeb8f6ca81e75fe5dd023
parent 63ad47a6f47d4eaa2898a38cc4b11b57e8cef075
author Zach Kelling <z@zeekay.io> 1786472489 -0700
committer Zach Kelling <z@zeekay.io> 1786472489 -0700

graph v1.2.17 â€” token, pool and protocol day series

Every price chart read "Missing chart data": tokenDayDatas, poolDayDatas
and uniswapDayDatas each answered [] from a stub, and the client draws
nothing under three points. They now carry a day per subject rolled out of
the trades already indexed, so the history appears at once rather than
starting today. Swap timestamps become the block's time â€” they held the
block number, which is also why no day could be computed from them.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Vøù‘!xîýtree a96be3d245ad18f4d3d3ae035e0017fe6776fd76
parent 93b0e4c5bdb905b2be8b9b7ba7693bed7ed1c1aa
author Zach Kelling <z@zeekay.io> 1786471918 -0700
committer Zach Kelling <z@zeekay.io> 1786471918 -0700

graph v1.2.16 â€” a chain's factories come only from its config

Removes the indexer's Lux-mainnet fallback for FactoryV2/FactoryV3/
Native, which every chain but Lux was silently inheriting, and adds ZUSD
to the stable anchors so Zoo's book prices in dollars instead of
reporting 0.00.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
«L¯
›5x[¤ütree 32072fbc68275e78803ef3f9459eca53e27f2096
parent e57e8315707e02c374172e5942b421d783a11496
author Zach Kelling <z@zeekay.io> 1786471071 -0700
committer Zach Kelling <z@zeekay.io> 1786471071 -0700

each chain declares its own AMM factories and native token

One process indexes six chains. FactoryV2, FactoryV3 and Native never
reached the graph indexer from a chain's config, so every chain but Lux
fell through to the indexer's Lux-mainnet defaults: the zoo subgraph
matched PoolCreated against Lux's factory, registered no Zoo pool, and
priced what it did see in WLUX. zoo.exchange read that endpoint.

These are identities of the chain being indexed, so they travel with the
chain: three fields on ChainConfig, passed straight through. A chain that
declares none now indexes no AMM, which is the honest answer.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
É™"dš x
õýtree c0acc66b56f35a0c2a41d2252f8ac5e74e2e651f
parent ae873d5974d04fe2f754d4cfa475010cca944d14
author Zach Kelling <z@zeekay.io> 1786420465 -0700
committer Zach Kelling <z@zeekay.io> 1786420465 -0700

graph v1.2.15 â€” tokens carry a price again

v1.2.15 publishes what the valuation pass had always computed: bundle
ethPriceUSD and per-token derivedETH. Without them every market on
lux.exchange and the explorer showed real TVL and volume beside a blank price
and no market cap.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
óc«î˜bx(×ùtree ea0b8f9d512495b2cf345de17767be9b3a9a5f64
parent 16a96ed0a3e3e1df638e7a6da05eed18ff7d1276
author Zach Kelling <z@zeekay.io> 1786202345 -0700
committer Zach Kelling <z@zeekay.io> 1786202345 -0700

stats: measure the span between the first and last block, not the oldest and newest timestamp

1.5.12 stopped a block with no time from anchoring the span at 1970, which
fixed Hanzo â€” 58,370 seconds per block became 10.3, its real cadence. It did
nothing for Zoo, which still read 2,387 for a chain building one every twelve:
Zoo's stray row is not timeless, it carries a plausible timestamp dated 1.76
years before Zoo's own first block, and no filter on the VALUE can tell that
from a legitimately old chain.

Nothing about the value distinguishes them. The ORDER does. Timestamps are
monotonic in block order by consensus rule, so the chain's elapsed time is the
last block's time minus the first block's â€” by number. On a sound index that
is identical to MIN and MAX of the column; on a damaged one it is the answer
and MIN/MAX is not. Zoo's first block is three days old, so three days is what
its span should have been all along.

This subsumes 1.5.12: a stray row only moves the answer now if it is an
endpoint, and a timeless one is still skipped because a block that does not
say when it happened cannot bound how far apart blocks are.

The suite covers both shapes at once, numbered mid-run the way a damaged row
actually sits: epoch 0, and two years early. The old query reports 1.26e7
seconds per block on the second.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
g^—Rx'Øútree 1ebcb39b0ea7175e5e71bea7dbe0186870c8d48b
parent 216ef5d8bf16fc56d057615f07af1ecf863ae538
author Zach Kelling <z@zeekay.io> 1786201656 -0700
committer Zach Kelling <z@zeekay.io> 1786201656 -0700

stats: a block with no time cannot bound the chain's history

1.5.11 made the average block time readable, and it read 58,370 seconds on
Hanzo â€” 30,602 blocks over an apparent 56 years, on a chain that builds one
every 11 seconds. Zoo read 2,389 for the same 11-second cadence. Lux read
50.3, which is right, so nothing looked systematically broken.

Both have a single row whose timestamp parses to epoch 0. The span is
MAX-MIN, so that one row anchors it at 1970 and the other thirty thousand
contribute nothing: the answer stops being about the chain and becomes a
restatement of the current unix time divided by the block count. 1.786e9 over
30,601 intervals is exactly the number that came out.

The span now counts only blocks that carry a time. That is not a threshold to
tune â€” a block with no timestamp holds no information about how far apart
blocks are, and including it invents the interval it appears to measure.

Reproduced in the suite: five blocks a minute apart plus one row at
1970-01-01 reports 3.57e8 seconds per block on the old query, 60 on this one.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Yû¾®žñxáðtree 6f9fa527cc14f169eedeb548add22dbe89c925de
parent 6156f554f7fa639af584d3b193ab5de3e7a1b486
author Zach Kelling <z@zeekay.io> 1786196223 -0700
committer Zach Kelling <z@zeekay.io> 1786196223 -0700

explorer: answer every request from the chain its host is served

One binary answers explore.lux.network, explore.hanzo.network and
explore.zoo.network. Only /envs.js and the slug-prefixed /v1/indexer/{slug}
routes knew that. Everything else read whichever chain the config called
default, so Hanzo's home page printed 1,107,383 total blocks above its own
Latest blocks list topping out at 29,805 â€” Lux's number, on Hanzo's page,
overstating Hanzo's chain 37x and Zoo's 49x. Measured on all three brands.

Config.Serves(host) is now the one thing that answers "which chain is this
request about", and the three surfaces that were guessing ask it:

  * StatsService keeps a read handle per chain instead of one pinned at
    startup, so /v1/counters, /v1/lines, /v1/pages/* and /v1/stats report the
    host's chain: 1107259 / 29805 / 22728 on the three brand hosts, where all
    three read 1107259 before.

  * The realtime stream carries the host's chain. It carried all of them: a
    45s read of /v1/base/realtime returned the same 6 cchain + 14 hanzo + 5 zoo
    events on every brand host, and the SPA appends what it is sent, so a
    50-block page rendered 63 rows with 2 Lux on top and 8 Zoo at the bottom.
    ?chain= still overrides; an empty filter no longer means "all chains",
    because no brand host wants another brand's blocks.

  * A slug-less /v1/indexer/* request follows the host, falling back to the
    default only when the host names no chain (api-explore.lux.network) or
    names one that is not up.

Also here, because they were all the same mistake about the same column: the
timestamp in evm_blocks is a Go time.Time bound into a column declared
TIMESTAMP, so SQLite stores a datetime STRING, and everything that read it as
a unix epoch got nothing and said nothing.

  * /stats' daily charts were empty on all three brands. strftime with the
    'unixepoch' modifier returns NULL on a datetime string, the scan failed,
    and handlePagesMain discarded the error into `daily, _ :=` â€” a nil slice
    marshals to `null`, which the page renders as "No data". Now 22 points.

  * Average block time read 0.0s on a chain producing a block every 10s: the
    three-value Scan failed and its error was never checked, so the zero went
    out as the answer. Now 60.0 on a one-block-per-minute fixture.

  * /v1/blocks reported `"timestamp": 2026` for every block â€” CAST of a
    datetime string takes its leading digits, and 2026 looks like a number
    rather than a failure. Now a real epoch.

  * Transactions (24h) was the lifetime total. SQLite orders every INTEGER
    below every TEXT, so `timestamp > <epoch>` was true for all 1.1M rows.

blockEpoch and blockDay are the only two spellings allowed to touch that
column now. The fixtures were the reason none of this failed a test: they
declared the column TIMESTAMP and then inserted integers into it, so the suite
exercised a database the indexer never writes. They bind a time.Time now, and
on the old queries they fail with exactly what production showed â€”
average_block_time "0.0", empty charts, timestamp 2026.

Deleted: the eight per-channel SSE routes at bare /blocks, /transactions,
/tokens, /gas-tracker, /stats, /validators, /token-transfers and /internal-txs.
Every one of those is also an SPA page path, and one mux cannot serve both; the
SPA opens only the multiplexed stream, so they had no subscriber to lose. With
them goes the second envelope, the per-channel framing built on every
broadcast, and the client's channel field â€” SSE has one endpoint and one shape.

indexer v1.5.3 for the block transaction-count name.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
¸¦&úŸtxO°øtree be138ec714a7e84c1b8dd2b75db50d68b36c35ef
parent 8558c7d562bc6ffeeed2c9b1e745299b0e5cb827
author Zach Kelling <z@zeekay.io> 1786130580 -0700
committer Zach Kelling <z@zeekay.io> 1786130580 -0700

release: retry the ghcr.io login instead of losing the release

v1.5.8 was tagged and produced no image. The job died at step 2 of 5:

    ::error::Error response from daemon:
        Get "https://ghcr.io/v2/": denied: denied

The credential is fine. Measured on the forge over 7 days, luxfi ran 365
ghcr logins with the same org secret and 27 failed â€” 7.4% â€” across 23 repos
and a dozen runners, and the same secret succeeded on either side of every
burst, including two hours after one. Three readings were tested and all
three are wrong:

  expired token   the same secret succeeded at 16:27 today, hours after
                  failures at 20:53 and 21:18 yesterday
  sick runner     failures spread over 12 runners; the worst has 3 of 7,
                  a clean one has 12 of 12
  throttling      no correlation with concurrency â€” 7.4% at one login per
                  minute, 10.9% at two, 0% at four and five

So it is an intermittent rejection from ghcr, and the only defect on our
side is that nothing retries it. docker/login-action does not, and the job
dies before the build, so each occurrence costs a release silently: a tag
with no image and no failed push to show for it. 27 of those in a week.

Five attempts over 75s, backing off. Exhausting all five is a different
failure than the one this covers, and says so rather than pretending.

--password-stdin, so the token is never an argument and never lands in a
process list or a log â€” docker/login-action's own behaviour, kept.

Same shape belongs in the other 22 luxfi repos that publish this way;
this one is the repo with a release waiting on it.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
\›nŸ˜šx¨	Wötree 63f7d999a5be3552279640e1e39810be933d5796
parent d1ddc08aaed207ba2711228c30a0c21d43dcb8d2
author Zach Kelling <z@zeekay.io> 1786129576 -0700
committer Zach Kelling <z@zeekay.io> 1786129576 -0700

honour chains[].enabled â€” it was declared and read nowhere

`ChainConfig.Enabled` existed on the struct, was written in every chains.yaml
we ship, and was consulted by nothing. Only `Graph.Enabled` and the subgraph
flag were ever read (supervisor.go). So `enabled: false` registered the chain,
spawned its indexer and polled its RPC exactly like `enabled: true` â€” a switch
that looked like a switch and was wired to nothing.

Found by using it. lux-mainnet's config lists spc, which is a sovereign L1 and
has never been a chain on that network. Setting `enabled: false` and rolling
the config changed nothing: the pod still logged

    [registry] added chain spc (â€¦/v1/bc/spc/rpc) source=config
    [spc/amm] [indexer] starting
    [evm] Failed to get latest block: http status 404

at 1,800 errors an hour, against a chain that was already switched off.

Honoured in ChainRegistry.Add, which is the single door â€” config, env, mDNS
and the admin API all arrive there â€” so this is the whole of what the field
means, in one place. A disabled chain is not registered, so it does not index,
mounts no routes, and does not reach the SPA's chain list (which reads
registry.List()).

Enabled is now *bool because absent must not mean off. Every config written
while the field was inert says `enabled: true` out of convention; a plain bool
would read a future config that simply omits the key as false and make that
chain vanish with no error and no log. Nil is no opinion; only an explicit
`enabled: false` disables.

Tests: registration gated in all three states (true / false / absent), a
disabled chain proven not to reach the supervisor or the chain list, and
LoadConfig proven to decode into the pointer â€” a wrong yaml tag would leave it
nil and silently re-enable everything it disables. Verified by defeating the
check: the false and supervisor cases fail, the absent case still passes.

Also: the Dockerfile now runs vet + tests in the builder stage. This repo has
exactly one workflow, release.yml, which builds on a v* tag; nothing ran the
test files at all. Putting them where cgo and sqlite-dev already exist costs
no new job, no new runner dependency, and means a release that fails them
never becomes an image.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
áL`H›$xK´ýtree f8ab509bd8ea3b3cc177acd71db331a439a3017e
parent 23b0301b4dcf9047d2622165a6a41c0f4c72cd3f
author Zach Kelling <z@zeekay.io> 1786118517 -0700
committer Zach Kelling <z@zeekay.io> 1786118517 -0700

deps: indexer v1.5.2 â€” checksummed addresses resolve, /tokens matches the SPA contract

Picks up the two API fixes. A checksummed address (what every wallet emits)
404'd and rendered a 149M-LUX contract as an empty EOA; /tokens emitted
address/holders where the SPA reads address_hash/holders_count, crashing the
page on an undefined row key.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
”ÂŸIxŸ`ûtree f5794b50299704fa6d71b74d107b1e1ec26d3859
parent 393a4454b032c676e457c79c9c0cbd798d83b8fc
parent a83aceaadb56b16b5d5c029349684b28149cbd4d
author Zach Kelling <z@zeekay.io> 1786050548 -0700
committer Zach Kelling <z@zeekay.io> 1786050548 -0700

Merge the 2026-08-04 publication snapshot into the working history

origin/main was force-pushed on 2026-08-04 to a single squashed commit
("Lux Explorer", BSD-3-Clause), which replaced the repository's history and
removed every tag. That snapshot is not buildable-current: its go.mod pins
luxfi/graph v1.2.0 against the working tree's v1.2.13, and it dropped
stats.go, realtime_sse.go and the whole test suite. Building main as it stood
would have shipped a binary thirteen graph versions behind, without the dex
subgraph fix.

Merged with -s ours: the tree is the working source, and the snapshot is kept
as a parent so nothing is rewritten and main fast-forwards. The one file the
snapshot contributed, static/index.html, is carried over. The one change it
made that was clearly deliberate â€” dropping the agent notes â€” is kept:
LLM.md/CLAUDE.md are now gitignored rather than restored.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Óæ’¿žIxžaûtree f5794b50299704fa6d71b74d107b1e1ec26d3859
parent b53f579f6a3519c3a8637620672193c4559c0019
author Zach Kelling <z@zeekay.io> 1786050535 -0700
committer Zach Kelling <z@zeekay.io> 1786050535 -0700

build: graph v1.2.13, and one CI path that a runner actually serves

The dex subgraph answered `{"data":{"markets":null}}` on a 200 while being
mounted, subscribed to the 0x9999 DEXFill topic and caught up to the chain
head. A GraphQL list field answering null reads as "not deployed / erroring",
so a DEX that had simply seen no fills was indistinguishable from a dead pipe.
luxfi/graph v1.2.13 makes an empty result an empty list.

CI moves to .hanzo/workflows/release.yml on the native git.hanzo.ai runners,
matching luxfi/graph. .github/workflows/docker.yml ran on `runs-on: lux-build`,
an ARC pool that no longer exists â€” the clusters have no ARC CRDs and
github.com/luxfi reports zero runners at org and repo scope, so every v* tag
queued a job nothing could pick up and waited instead of failing. That is why
v1.5.4 is a tag with no image. One CI path, one that builds.

LLM.md/CLAUDE.md become local-only, per the org convention.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
kŽ;œxl“þtree 4a3520bf2f2184bb8c1c19162cfdba7471d8af38
author Zach Kelling <z@zeekay.io> 1785898959 -0700
committer Zach Kelling <z@zeekay.io> 1785898959 -0700

Lux Explorer

A single-binary indexer and GraphQL query layer for the Lux network.

Copyright (c) 2024-2026 Lux Industries Inc.
Licensed under BSD-3-Clause. See LICENSE.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
ñs9•gxuŠùtree c0237da8e6d98447599decf8e4a6eb4110b82d70
parent 7d827e2b85a67b897679560e2a794ce1b240e481
author Zach Kelling <z@zeekay.io> 1785893245 -0700
committer Zach Kelling <z@zeekay.io> 1785893245 -0700

fix(explorer): never hand the browser the address the server dials

envs.js was emitting ChainConfig.RPC verbatim, so every browser loading
explore.lux.network was told the default chain lives at

    http://luxd-headless.lux-mainnet.svc.cluster.local:9630/v1/bc/C/rpc

which no browser can resolve. One field was serving two consumers with
opposite requirements: the indexer must dial the service-mesh name, the
SPA must get a public URL. Split them â€” RPC stays the dial address,
public_rpc is what the SPA is told, and browserRPC() falls back to RPC
only when it is actually reachable from outside the cluster. Emitting ""
beats emitting a URL that always fails: the SPA can hide the control
instead of offering one that silently errors.

Also adds the canonical /v1/stats and /v1/blocks. Everything under /v1,
nothing under /api â€” TestStatsRoutesAreV1Only pins that /api/v2 is not
mounted and never becomes so by accident.

/v1/blocks CASTs timestamp to INTEGER because the real schema declares it
TIMESTAMP and go-sqlite3 turns declared TIMESTAMP columns into time.Time,
which will not Scan into int64 â€” without the cast every row is skipped and
a fully populated index answers an empty list. That failure was invisible
because the handler swallowed the error; it now logs and reports it, and
the bound test asserts rows came back rather than just `<= 200`, which
passes trivially on an empty result.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
~1>�x`Ÿþtree fee14ddf78e751d7eab3becb2baf2c195b7c51f7
parent ef9cc4e8690a7473933a267039fed5e2007e2fe1
author Zach Kelling <z@zeekay.io> 1785816119 -0700
committer Zach Kelling <z@zeekay.io> 1785816119 -0700

rpc: /ext/ -> /v1/ on our own hosts

luxd serves /v1/*; /ext/* 404s. Third-party chains keep their own paths.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
ímC•Cx5Êûtree 142dac8fab28a85b41b56a56c87bf026406c4929
parent f12932e2c19eab9b2aeeb00e4d4ec9cab7520670
author Zach Kelling <z@zeekay.io> 1785631175 -0700
committer Zach Kelling <z@zeekay.io> 1785631175 -0700

ci: the PAT step falls back to the one credential luxfi provisions

The ghcr login was fixed, and the very next step still failed:

    Resolve cross-repo PAT for private luxfi/* deps -> failure
    Build & push (amd64)                            -> skipped

because it reads GH_TOKEN then UNIVERSE_PAT, and orgs/luxfi provisions neither.
Measured: repos/luxfi/<name> has no secrets at all, and orgs/luxfi holds exactly
GHCR_USER + GHCR_TOKEN. Both names therefore evaluate to "" and the step exits 1.

GHCR_TOKEN is a real GitHub PAT -- it has to be, because ghcr.io accepts nothing
else -- so it can resolve private modules as well as pull images. Added as a
third rung that REPORTS which secret it used (`Using secret: $src`), rather than
a `||` chain that swaps credentials silently. Same fix already proven green in
luxfi/trader.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
vŒ`Q“'xsŒýtree 95c5acb58a8686d144f72a5e4564dea6ea3943da
parent 7571f94150c91a08f0e08a7f483e10f10f876bb0
author Zach Kelling <z@zeekay.io> 1785283342 -0700
committer Zach Kelling <z@zeekay.io> 1785283342 -0700

build: drop the luxfi exceptions â€” graph and indexer are public now

The list named github.com/luxfi/graph and github.com/luxfi/indexer because they
were private and 404 on proxy.golang.org. Both are open source now and both
serve from the proxy, so the exceptions have nothing left to do â€” and every name
on a GOPRIVATE list is a module Go fetches from git and trusts unverified.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
ËXÓN˜Nxèûtree 09cf9ad018c416d9a2093e6070a7b17730959094
parent 849178ea59566a2d9e35f59afd96e0899990d29d
author Zach Kelling <z@zeekay.io> 1785279925 -0700
committer Zach Kelling <z@zeekay.io> 1785279925 -0700

build: mark lux-private private, not all of luxfi

GOPRIVATE switches off the module proxy AND checksum verification together.
Covering github.com/luxfi/* meant every luxfi module was fetched straight from
git and trusted without verification â€” which is how moved tags got captured into
module caches and served from them indefinitely, making the same commit build on
one machine and fail on another.

Nearly all of luxfi is public and on proxy.golang.org, so it belongs under
normal verification. The private org is lux-private.

Twelve luxfi repos are still private AND still 404 on the proxy, so they are
listed individually rather than by wildcard â€” coverage now matches what actually
needs a direct fetch, and every other luxfi module is verified against
sum.golang.org again:

  bridge broker compliance erc20-go fasthttp graph hsm indexer
  mpc ordering password pool

The right end state is those repos moving to lux-private; until then the
explicit list keeps the exemption honest instead of blanket.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
!·É–ExV©ûtree 33ac9f91c5965bbde3e680ec613c8120be51ace5
parent e5bf01aceae8ecea2215a1ef9d195a67f5d7d6de
author Zach Kelling <z@zeekay.io> 1785256681 -0700
committer Zach Kelling <z@zeekay.io> 1785256682 -0700

deps: drop go.sum lines that disagree with the checksum log

These hashes were recorded from direct VCS fetches made while GOPRIVATE covered
github.com/luxfi/*, which switches off the module proxy and checksum
verification together. Tags moved afterwards, so what is written down here no
longer matches what sum.golang.org holds and the build refuses to verify.

proxy.golang.org still serves the originally-published bytes and those verify
against the log, so dropping only the disagreeing lines and re-tidying restores
a verified module graph with no version change. Lines that agree are untouched;
this is not a regenerated go.sum.

The failure followed the machine rather than the repository â€” the same commit
built wherever the module cache was clean. Any machine or CI runner that
fetched luxfi modules under the old GOPRIVATE holds the same poisoned entries.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Wá…Ã‘7xqŽütree 14b373a04559a40ea55b0d50c866cf06b5879435
parent 8d136c68437a27bb4a23876557760aaad05ff1aa
author Zach Kelling <z@zeekay.io> 1785201744 -0700
committer Zach Kelling <z@zeekay.io> 1785201744 -0700

docs: publish /v1/bc/C/rpc, drop dead /ext/ URLs

/ext/ is gone from api.lux.network, api.zoo.network and api.hanzo.network
(all 404 today), so every published /ext/ URL was already broken. Convert
to the one canonical path and fix the URL forms that were dead for other
reasons at the same time:

  /mainnet|/testnet|/devnet path prefix -> the per-network host (404 today)
  api.testnet.lux.network, testnet-api.lux.network -> api.lux-test.network
  /ext/C/rpc (missing bc/) -> /v1/bc/C/rpc
  chain aliases + blockchain IDs under api.lux.network -> the L1s own host

Third-party RPC hosts (Avalanche, Flare, blastapi) are left untouched.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
cU#‘7xqŽütree f913f61dbdc0c8e93b0ee61f278fb557eb2d7e92
parent 066916145d3f3b0b2d36ba13d21bc83b751e1356
author Zach Kelling <z@zeekay.io> 1785200443 -0700
committer Zach Kelling <z@zeekay.io> 1785200443 -0700

docs: publish /v1/bc/C/rpc, drop dead /ext/ URLs

/ext/ is gone from api.lux.network, api.zoo.network and api.hanzo.network
(all 404 today), so every published /ext/ URL was already broken. Convert
to the one canonical path and fix the URL forms that were dead for other
reasons at the same time:

  /mainnet|/testnet|/devnet path prefix -> the per-network host (404 today)
  api.testnet.lux.network, testnet-api.lux.network -> api.lux-test.network
  /ext/C/rpc (missing bc/) -> /v1/bc/C/rpc
  chain aliases + blockchain IDs under api.lux.network -> the L1s own host

Third-party RPC hosts (Avalanche, Flare, blastapi) are left untouched.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
�]#ð›Vxk”útree 970fa6b9ab947d81ff81bc48a384a2ff4e378e8a
parent 34a292b8e831269095ef7697c6783832dba4ce81
author Zach Kelling <z@zeekay.io> 1785187721 -0700
committer Zach Kelling <z@zeekay.io> 1785187721 -0700

fix(explorer): balances from the chain; one source for sibling hostnames

indexer v1.4.6 -> v1.4.7 and pass the chain's RPC to the standalone API, so
native-coin balances are read from the node rather than from
evm_addresses.balance â€” a column the indexer writes as the literal '0' on
first sight and never updates. Every address page reported a balance of
zero, WLUX's 159,126,795.518 LUX included. v1.4.7 also counts token holders
and transfers from the rows that hold them, stops calling the base fee
"burnt" on a chain that credits the whole fee to the coinbase, and returns
addresses on the evm_* schema instead of an empty page.

The network switcher advertised testnet.explore.lux.network and
devnet.explore.lux.network. Neither resolves; the live hosts are
explorer.lux-{test,dev}.network. networksForHost already derives sibling
hostnames from the request host and derives them correctly, so the
switcher now takes its domains from there and config keeps only what
config knows â€” the label and the chain ID. Devnet's was wrong too:
96370, where the devnet genesis cChainGenesis.config.chainId and a live
eth_chainId both say 96367.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
á2ß¼‘#x1Îýtree 6dd88058fa97af2a06f1465fadb65cd28dcc2d7a
parent a67f2a66441b6fed82d91ed40ccf2e06b3ed41d4
author Zach Kelling <z@zeekay.io> 1785116567 -0700
committer Zach Kelling <z@zeekay.io> 1785116567 -0700

build: bump luxfi/graph v1.2.11 -> v1.2.12 (bounded swap window in the valuation pass)

v1.2.11 stopped the pass writing a row per swap; v1.2.12 stops it reading the
whole table too. The C-Chain's AMM swap history spans a million blocks in a
732 MB store, so an unbounded rescan every 30s grows with chain age forever.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
6µ!ž3x>Áütree a16762c6b41ddeb7e0037ed9ec739de803104074
parent 6837310ff00014607222bbc9d8d2872151301e92
author Zach Kelling <z@zeekay.io> 1785116333 -0700
committer Zach Kelling <z@zeekay.io> 1785116333 -0700

build: graph v1.2.11 + label every graph indexer with its chain/subgraph

v1.5.1 in the cluster showed one chain's valuation pass reporting and two
others silent â€” and the log gave no way to tell which chain any line belonged
to, because the graph indexer was the one component here that did not prefix
its output. Config.Label (new in graph v1.2.11) fixes that: pass the same
"slug/name" this supervisor already uses everywhere else.

v1.2.11 also stops the valuation pass writing a row per swap (which is what
wedged the busy chains) and reads V3 swap amounts as the int256 they are.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Þ¿˜(xˆwýtree c919555fb13b0127a8b76829bfe6e7cd359a161a
parent 4f1395b269e9dcbf72db1ebc7b047416c4703d82
author Zach Kelling <z@zeekay.io> 1785115303 -0700
committer Zach Kelling <z@zeekay.io> 1785115303 -0700

build: bump luxfi/graph v1.2.9 -> v1.2.10 (AMM valuation pass)

v1.2.10 adds the valuation pass that derives the AMM graph's USD aggregates
from on-chain balances. Without it the explorer's /v1/graph/<chain>/amm/graphql
served pools and tokens whose totalValueLockedUSD, volumeUSD and token0Price
were empty strings â€” which is what left every TVL / 24h-volume / active-pool
figure on the exchange blank.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
èõÕS›x«Tþtree 6e68d573736a189bda7a7a8e3580025331e91321
parent 0b4ebbd9be32e30eddc41c3ae4627e6ba6ba691c
parent 48f3ebc0ab477a75e81e88debfc3c69393ef1183
author Zach Kelling <z@zeekay.io> 1785115257 -0700
committer Zach Kelling <z@zeekay.io> 1785115257 -0700

merge: P-Chain indexing + /v1-only luxd RPC + chain stats

Integrates feat/pchain-indexing into main so nothing is left behind a branch.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
µ2†”xd›þtree 6e68d573736a189bda7a7a8e3580025331e91321
parent 69a42bc965473a2bcf3622c45ffede52fe1cadda
author Zach Kelling <z@zeekay.io> 1785085998 -0700
committer Zach Kelling <z@zeekay.io> 1785085998 -0700

chore: sync working tree

Commits 3 outstanding change(s) that were sitting uncommitted.
No build artifacts and no secrets in the changeset (both checked).
ërQ•3x5Êütree 488fb433c77f90c51c0dc3bcf38e7aef6af1b04d
parent 7b2a74df9616b75cd13f3c8f361874e33bc84d7e
author Zach Kelling <z@zeekay.io> 1783519936 -0700
committer Zach Kelling <z@zeekay.io> 1783519936 -0700

feat(supervisor): run P-Chain (platform) indexing for linear/platform chains

runChain handles cfg.Type "linear"/"platform": build the platform adapter,
create pchain_blocks via the generic chain indexer's Init (which also runs
the adapter's validator/delegator schema), then poll â€” fetch+parse+store
recent blocks and SyncValidators each interval. Blocks are written via
store.StoreBlock (not the chain indexer's broadcasting StoreBlock, whose
subscriber channel nothing drains here). mountIndexerAPI gates readiness on
pchain_blocks for platform chains (evm_blocks for EVM).

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
×ÿ_”1xëütree db1395299513310245d3e8ae6b535a8be4fac5db
parent 0b4ebbd9be32e30eddc41c3ae4627e6ba6ba691c
author Zach Kelling <z@zeekay.io> 1783117244 -0700
committer Zach Kelling <z@zeekay.io> 1783117691 -0700

fix: direct-luxd /ext/bc/ -> /v1/bc/ (luxd is /v1-only)

luxd v1.34.x serves chain RPC at /v1/bc/*; /ext/bc/* 404s. mdns.go
discovers luxd nodes directly (base = http://<host>:<port> from mDNS)
and constructs their per-chain RPC + platform.getBlockchains routes â€”
these are direct-luxd calls, so they move to /v1/bc/. registry_test.go
fixtures use localhost:9650 direct-luxd URLs, flipped to match.

Public-gateway URLs (api.lux.network in chains.example.yaml / README.md)
are left as /ext â€” the public gateway still accepts /ext externally.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Z;¡›&xk”ýtree c2ffd92c2b891e1f42156239d12d81a663e3ebaf
parent a47755267bc9f925bd3d29bd558eaef9f80fc171
author Zach Kelling <z@zeekay.io> 1782709973 -0700
committer Zach Kelling <z@zeekay.io> 1782709973 -0700

build: bump luxfi/graph v1.2.6 -> v1.2.9 (bound BASE/QUOTE pair symbol + assetsBound on 0x9999 markets)

graph v1.2.9 enriches the 0x9999 DEX Market with a human pair symbol + assetsBound
so the exchange-api real-asset gate admits native markets (was stripping all of
them on the poolId-hex symbol). Pulls luxfi/database v1.19.3 transitively. go.sum
regenerated for luxfi modules (upstream re-tag staleness on age/pq).
¡tÊ›”xä ÿtree 11a675df98ad833acdfe29b57841509bb7c710b0
parent 271b9eb27bb5433b401df192bb8e4c9bc9212134
author Zach Kelling <z@zeekay.io> 1782704255 -0700
committer Zach Kelling <z@zeekay.io> 1782704255 -0700

docs(brand): add hero bannerôC÷™xé ÿtree ba1168c76ff9d9bc20b191b67a93963d325cd65d
parent 2e9694909601aaf634c8a30f2ac4316676f8a15a
author Zach Kelling <z@zeekay.io> 1782704254 -0700
committer Zach Kelling <z@zeekay.io> 1782704254 -0700

chore(brand): dynamic hero banner!¤EÔ—xèþtree c36862c023beba7ac4c0610ed01bd5777b78061c
parent 345290e6df11a6dfb5477892914f9fd7e6d7c0d1
author Zach Kelling <z@zeekay.io> 1782426262 -0700
committer GitHub <noreply@github.com> 1782426262 -0700

chore: bump luxfi/database v1.19.3 (#13)

Co-authored-by: zeekay <z@zeekay.io>4ÝT„�x ßþtree ca35b980476918978416e94a9ad803e4f24cacef
parent 3611483f2bbb74121a458560d25c43aa848d616e
author zooqueen <a@zoo.ngo> 1782251940 -0700
committer zooqueen <a@zoo.ngo> 1782251940 -0700

build: bump luxfi/indexer v1.4.5 â†’ v1.4.6 (real tx fee + block burnt_fees/rewards, decimal value)
ÀnX1šxœ…�IN1E÷}ŠZ‚Òžâ6‚ˆ÷@e»:±pÛÁDœ)kv_Uï}é·Bd•õ´÷Î®n!ÅŒ™�Ô¹än×CC\Lg,”¬„È¤äV	aŒdFà¢­Ø¯Äf5å•7J3?ao§\à'çÏN”à_G~LÇ| ¦ÎcBÂÃ¬çyryÛBkô/¯üd{ˆþ	
�#:‚Ø/kØ…äé2:^Pƒ�!ÁÅœj†v"xËîc¼ÿ\è•*\�š{57…0B»Œ•w`ã^R{—º+ô�Å×{ðäÂ6Ð/Œ�n§_ñie•#x5Êýtree 3c3dec99c575de736fb145dd30c3c94f48ce3d12
parent 066a8060c6b97b6e0229c089d2cad374bbe9871b
author Zach Kelling <z@zeekay.io> 1782111300 -0700
committer Zach Kelling <z@zeekay.io> 1782111300 -0700

feat: enrich AMM token metadata â€” bump graph v1.2.3â†’v1.2.6 + opt-in backfill

graph v1.2.6 reads ERC20 symbol/name/decimals for AMM Token entities
(was address-placeholder). supervisor.go runs graph's BackfillTokens at
startup when BACKFILL_TOKENS=1 to enrich existing placeholder rows in
place (â‰¤3 eth_calls/token, no re-sync), before the live indexer loop.
 nºÌœIxœcûtree ba716416600507c457856837e97bedd31333863f
parent b0ad7f49e7c86b7ab0e9bdd6cb398cd2c18786b8
author Zach Kelling <z@zeekay.io> 1782079627 -0700
committer Zach Kelling <z@zeekay.io> 1782079627 -0700

fix(docker): resolve private luxfi modules + GHCR push via cross-org PAT

The v1.3.1 image build reached the runner (lux-build works) but failed two
ways that the local build now proves fixed:

1. Dockerfile cloned private luxfi/{indexer,graph} over unauthenticated
   https -> 'could not read Username'. Those clones were vestigial: explorer
   has no replace directive, so under -mod=mod graph v1.2.3 + indexer v1.4.5
   resolve from the module cache, not siblings. Drop the clones; add the
   node-style BuildKit-secret git auth (ghtok) + GOPRIVATE/GONOSUMDB/GONOPROXY
   before go mod download so private modules resolve.

2. GHCR push would be denied (repo-scoped GITHUB_TOKEN). Log in with the
   cross-org PAT (GH_TOKEN/UNIVERSE_PAT) and stamp
   org.opencontainers.image.source so the package stays linked.

Verified locally: docker buildx build --secret id=ghtok,... compiles the
unified FE+indexer+graph binary green (go mod download 74s, go build 98s,
image exported).
Òoˆ†•(x…zýtree 61f61a06c21ead11e8dbede2e71c095d7d9df936
parent c93423f735b1f30ed3428bb37c7de0f2e5949fe8
author Zach Kelling <z@zeekay.io> 1782079120 -0700
committer Zach Kelling <z@zeekay.io> 1782079120 -0700

ci: route docker.yml off classic [self-hosted,linux,amd64] to lux-build ARC

The classic-triple pool is serviced by fleet arcd hosts (dbc/evo/spark)
that are not reliably available â€” the v1.3.0 tag never produced an image and
recent builds sit queued. lux-build is the org-scoped autoscalingrunnerset
in lux-k8s (listener healthy 18d, scales 0->N on demand) that already
publishes the node image. Same one-line pool swap node/docker.yml uses.
dÙy“Pxüútree 5ddb06aa57620e76513b326d53f1bb9d0be21b91
parent 5a664afa2140a96622500c9faa59b3a33804dd0b
author Zach Kelling <z@zeekay.io> 1782078763 -0700
committer Zach Kelling <z@zeekay.io> 1782078763 -0700

deps: bump graph v1.2.2->v1.2.3 (native 0x9999 V4/DEXFill indexing, dex+amm subgraphs) + pin indexer to real v1.4.5 tag

graph v1.2.3 lands the native 0x9999 DEX indexing fix: dex/amm builtin
subgraphs, 0x9999-scoped Fill source (DEXFill log), keccak topic-hash
fixes, and re-genesis self-heal. The supervisor already wires
PoolManager(0x9999)+StartBlock into graphidx.NewWithConfig and mounts the
dex+amm subgraphs at /v1/graph/{slug}/{subgraph}/graphql; this bump makes
the engine those routes resolve against carry the fix.

Also replaces the unpushable phantom pin
indexer v1.4.5-0.20260526184203-3b72d71cc8fd (revision 3b72d71cc8fd never
existed on origin) with the real published indexer v1.4.5 tag (main HEAD
ac537780: server-side per-(token,holder) balances + log/transfer column
aliases). This unblocks the unified explorer Docker build, which resolves
graph+indexer from go.mod under -mod=mod.

Verified: go mod tidy clean; CGO build of the unified binary green;
go test ./... ok; graph dex_route_test confirms markets/fills resolve on
the dex schema from 0x9999 entities.
S ®=˜ x÷ýtree f4b03ec624fdd7dea2f4332c3caa20b447df8f63
parent d702e5b05e858ac3723f5ddbe352d3a9fd572f57
author Zach Kelling <z@zeekay.io> 1782069971 -0700
committer Zach Kelling <z@zeekay.io> 1782069971 -0700

feat(explorer): pass PoolManager(0x9999)+StartBlock to graph indexer; mount dex subgraph

- config: add ChainConfig.PoolManager (DEX settlement precompile, default 0x9999)
- supervisor: runSubgraph uses graphidx.NewWithConfig (PoolManager + StartBlock)
- chains.example: cchain mounts dex (CLOB) subgraph + pool_manager
Šž«²˜x(×þtree b63098112a92ad45d48589d236f4b40f7a00c7ce
parent 2f0af7d7ca190dd3a4a317c6cd85c6d6484fdf4c
author zooqueen <a@zoo.ngo> 1781713604 -0700
committer zooqueen <a@zoo.ngo> 1781715344 -0700

docs: tidy LLM.md indexes; CLAUDE.md -> LLM.md symlink convention

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
UÒ[�xÀ?þtree 2e60baefb8dd27831edfeaa810087340cab9b5bd
parent 3959357be3f0d9e0dfc31ed6ad3941d73d727ba9
author Zach Kelling <z@zeekay.io> 1781148027 -0700
committer Zach Kelling <z@zeekay.io> 1781148027 -0700

ci: route to canonical native arcd labels [self-hosted, linux, <arch>]

Replaces non-canonical scale-set / org-prefixed labels with the
existing labels every arcd host registers with. Matches evo for
amd64 and spark for arm64. No new labels added.
ªØ“#”]xÔ+útree b602356e6af99611f26e0750737df4bfcdfd1524
parent 73749c70f4def7abc8a4b1ed2d948a2d68bc6c20
author Zach Kelling <z@zeekay.io> 1781122861 -0700
committer Zach Kelling <z@zeekay.io> 1781122861 -0700

feat(v1.3.0): drop embedded SPA â€” per-brand FE Deployment owns the UI

The Go binary is now a data-layer service only. The `/` catch-all that
served an embedded SPA shell is gone; v1.3.0 keeps just the endpoints
the FE depends on:

  - /v1/*       â€” indexer + blockscout-compat REST APIs
  - /health     â€” health probe
  - /envs.js    â€” runtime brand/chain config (window.ENV)
  - /icon.svg, /logo.svg â€” per-host brand SVGs (overlay-friendly)
  - /icon-{slug}.svg, /logo-{slug}.svg â€” extra named brand assets

Every other path returns 404 so the IngressRoute hands it off to the
per-brand Blockscout FE Deployment (ghcr.io/{brand-org}/explore-{env}).

Concrete deltas:
  - frontend.go: drop handleSPA, writeIndex, f.index; replace with
    handleNamedBrandAsset + brandAssetNames discovery
  - frontend_test.go: TestFrontendNoSPA + TestFrontendBrandAssetsServed
    replace the index-html overlay tests
  - Dockerfile: drop Next.js frontend build stage; runtime image is just
    the Go binary + brand SVGs (no embedded HTML)
  - static/index.html: removed (60-byte placeholder)

The per-brand FE Deployments + IngressRoute patches that complete this
split live in the cluster (lux-mainnet + lux-testnet namespaces). Source
of the FE images: luxfi/explore via Dockerfile.branded.
%Êø–”xÔ+þtree e6019e0f297add27651850bbf8f58cd6b264ec99
parent 366e4c57d2a864b34e029e3b677f386bf677dd32
author Zach Kelling <z@zeekay.io> 1781119044 -0700
committer Zach Kelling <z@zeekay.io> 1781119044 -0700

chore: scrub  default from Dockerfile NETWORK_CURRENCY_SYMBOL

Replace the default  currency symbol +  network ID
(8675312) defaults with Lux mainnet defaults (LUX, 96369). The
explorer binary white-labels per-tenant via runtime env, not via
-specific build defaults.
lð•e�-xÝ"ýtree 770866e35ba51fd79adb60ad5b776a88fe3b2843
parent c75790d446cffe079b8d44e4be04095412967546
author Zach Kelling <z@zeekay.io> 1781115662 -0700
committer Zach Kelling <z@zeekay.io> 1781115662 -0700

fix(spa): brand merging + env-tag canonicalisation

- BRAND is now a merge of SERVER_BRAND (chains.yaml-supplied) and
  INFERRED_BRAND (host parser table). Server wins on name/coin/accent
  when it's non-default; inferred fills in iconUrl/logoUrl when the
  chain config doesn't carry per-brand SVG paths. Stops Zoo rendering
  with the default Lux favicon when chains.yaml omits icon_url.
- networkFromHost now canonicalises "test"â†’"testnet", "dev"â†’"devnet"
  so the dropdown label matches the server-side VITE_CURRENT_NETWORK.
èùóP�8x�rütree 313b53ff42c8059407686565713613fb264a7933
parent 248e16dcb43b82f6522892a790b15ca4d8a3f180
author Zach Kelling <z@zeekay.io> 1781114859 -0700
committer Zach Kelling <z@zeekay.io> 1781114859 -0700

fix(spa): client-side network derivation fallback for older images

The Go-side networksForHost was added in v1.2.20 but the deployed
image is still v1.2.18 (CI pool gap â€” lux-build AutoscalingRunnerSet
is missing from arc-system). The SPA now also parses location.host
client-side so the network switcher works when /envs.js doesn't ship
VITE_CURRENT_NETWORK / VITE_NETWORK_HOSTS.

Same shape rules as the Go side: explore.{brand}.{tld} (mainnet,
preserve tld), explorer.{brand}-test.network (testnet, .network is
canonical). Numeric and *.local hosts skip the dropdown.

This lets us ship the cross-network switcher via an EXPLORER_STATIC_DIR
overlay (ConfigMap-mounted SPA) without rebuilding the image.
‡2rš^xêútree d939c2cfff5d773b3fa266566cd0aad949955863
parent 2cbbb4f33345fb35f4156ba59990aa7a445c6d1f
author Zach Kelling <z@zeekay.io> 1781114715 -0700
committer Zach Kelling <z@zeekay.io> 1781114715 -0700

feat(frontend): cross-network switcher (mainnet â†” testnet) in SPA

handleEnvs now returns VITE_CURRENT_NETWORK + VITE_NETWORK_HOSTS so
the SPA can render a Network dropdown next to the Chain picker. When
the user picks "testnet" from explore.zoo.network the SPA redirects
to explorer.zoo-test.network (and vice-versa).

Sibling host derivation lives entirely in Go:
- parseBrandEnv extracts (brand, env, dotted, ok) from any of our
  deployed host shapes â€” explore.{brand}.tld, explorer.{brand}-test.network,
  explore-{brand}.lux.network, explore-{brand}-test.lux.network.
- networksForHost composes the sibling map. Mainnet TLD is preserved
  when the user is on a brand-owned host (explore.hanzo.ai stays .ai);
  testnet is always {brand}-test.network because that's the canonical
  shape.
- isValidBrand rejects numeric stems so an IP host (10.0.0.1) doesn't
  fall through to "explore.10.0.0.1" / "explorer.10-test.network".

SPA renders the dropdown above the chain picker; hidden when the host
yields fewer than two siblings (local dev, admin tooling).

Tests cover all four canonical surfaces (lux+zoo Ã— mainnet+testnet),
hanzo's TLD asymmetry (.ai mainnet â†’ .network testnet), the
explore-{slug}.lux.network prefix shape, and the unparseable-host
guards (IPs, in-cluster service names, localhost).
~È’’>xâütree 3486a514c60f3090f74d24ec1373c50a292d2ec9
parent c3afae17ba2d15c53ff2a3eab0567e5bcdc4fcfa
author Zach Kelling <z@zeekay.io> 1781113587 -0700
committer Zach Kelling <z@zeekay.io> 1781113587 -0700

fix(brand): per-host whitelabel + default-chain â€” covers *-test.network

- host matcher now accepts explorer.* (alias of explore.*) and
  {prefix}.{slug}-{env}.tld shapes â€” so explorer.zoo-test.network
  resolves to the Zoo brand (was falling through to Lux default).
- new chainSlugForHost helper drives a default-chain promotion in
  handleEnvs: when the host pins a chain, the SPA opens on that
  chain instead of the config-default cchain. explore.zoo.network
  now defaults to Zoo, not C-Chain.
- factored host matching into pure hostMatchesSlug predicate so
  brand and slug paths share one ground truth.
- tests cover both regressions (explorer.zoo-test.network â†’ Zoo,
  explore.zoo.network â†’ slug=zoo) plus the existing
  cross-contamination guards (zoofoo must not match zoo).
¨Zv›xôþtree d9a03227883732d65256e94f2a219a14e17ab540
parent 1fcef19f3f59666d3de90dc206a71cdfc9b4a654
author Zach Kelling <z@zeekay.io> 1780808721 -0700
committer Zach Kelling <z@zeekay.io> 1780808721 -0700

go: 1.26.3 â†’ 1.26.4 (security: crypto/x509, mime, net/textproto)
·O–– xùýtree 12804903a99012bf13bd903ce7f8f0b805b37750
parent c37bb4e8932a06d7abe47a21851ed4e9a46a7b33
author Zach Kelling <z@zeekay.io> 1780700385 -0700
committer Zach Kelling <z@zeekay.io> 1780700385 -0700

docs: document the unified explorer bundle deployment

luxfi/explorer + luxfi/graph + luxfi/explore now ship as ONE Deployment
per env on lux-k8s. CLAUDE.md describes the 2-container sidecar layout
(explorer Go + explore Next.js), image versions, manifests, and verified
public URLs across mainnet / testnet / devnet.
ž$ª¹”cx4Ëùtree 2e336f7b7106557d590bd75931709b232098af54
parent 1e602d24e9d778f956b8fe1e50264e2357707095
author Zach Kelling <z@zeekay.io> 1780697122 -0700
committer Zach Kelling <z@zeekay.io> 1780697122 -0700

fix(frontend): match per-chain Brand for explore-{slug} / api-explore-{slug} hosts

brandStruct only matched bare "{slug}" and "{slug}.tld" host shapes â€”
but the unified explorer ships behind IngressRoutes whose Host()
patterns are exclusively `explore-{slug}.lux.network` and
`api-explore-{slug}.lux.network` plus brand-owned roots like
`explore.hanzo.ai`, `explore.zoo.ngo`, `explore.pars.network`. With
the old matcher every per-chain host fell through to BrandDefault
("Lux Explorer"), so per-chain theming silently no-op'd in prod
(verified 2026-06-05: explore-hanzo.lux.network /envs.js returned
"VITE_BRAND":{"name":"Lux Explorer"} instead of Hanzo).

New matcher recognises the four prefix shapes the IngressRoutes
produce, both with and without an env suffix (-test / -dev):
  - explore-{slug}.tld...        UI host on lux.network
  - api-explore-{slug}.tld...    API host on lux.network
  - explore-{slug}-{env}.tld...  UI host for testnet / devnet variants
  - api-explore-{slug}-{env}...  API host for testnet / devnet variants
  - explore.{slug}.tld...        UI on brand-owned domain
  - api-explore.{slug}.tld...    API on brand-owned domain
Plus the prior bare-{slug} and {slug}-prefix shapes for backward
compat.

Test coverage: TestBrandStructHostMatching, 18 cases incl. the
explore-spcfoo cross-contamination guard ensuring partial-slug-
suffix matches don't bleed.
Û#BštxJµøtree 875012abc092d9024cb6f927a86eb2d713a35dc3
parent ebfcc0dc162ab13d1251f3c22d5f7a869c6427e1
author Zach Kelling <z@zeekay.io> 1780682753 -0700
committer Zach Kelling <z@zeekay.io> 1780682753 -0700

feat(frontend): runtime static-dir overlay + substantive SPA shell

Two related changes that get the explorer SPA rendering real chain
data without waiting for upstream luxfi/explore to ship a Next.js
static export.

1. Runtime SPA overlay (config.go, frontend.go)

   New cfg.StaticDir (also EXPLORER_STATIC_DIR env var). When set,
   the frontend prefers the on-disk copy of any static asset over the
   embedded FS â€” so a ConfigMap-mounted SPA shell replaces the
   embedded 60-byte placeholder without rebuilding the binary. The
   embedded FS remains the source of truth at build time and the
   fallback when no overlay is configured.

   handleSPA + serveBrandFile both check overlay > embedded. Tests
   cover overlay-present, overlay-missing-file fallback, no-overlay,
   and env-var-only configuration.

2. Substantive SPA shell (static/index.html)

   Replaced the 60-byte stub (<div id="root"></div>) with a vanilla
   JS SPA â€” ~7KB, zero dependencies, no React/Next.js. Reads
   window.ENV from /envs.js, fetches /v1/indexer/{slug}/{stats,blocks,
   transactions}, renders KPIs + Latest Blocks + Latest Transactions
   with per-chain switching and brand color/title overrides. Polls
   every 15s. Stores selected chain in URL hash + localStorage.

   This is the explorer's default UI until upstream luxfi/explore
   ships output: 'export' and the Dockerfile's optional pnpm-build
   overlay activates.

Dockerfile: drop the leftover merge-conflict markers around the SPA
build stage, keep the explanatory comment.

Tests: 4 new in frontend_test.go covering overlay precedence + env-var
fallback. All existing tests (registry, realtime) still pass.
j~n‘x1Îþtree 9aa3177294db194eca31bbb3eff2d631aaa4553b
parent 4083aa96e18473c8a7290658af15a891ec69d89d
author Zach Kelling <z@zeekay.io> 1780423898 -0700
committer Zach Kelling <z@zeekay.io> 1780423898 -0700

chore(deps): go get -u ./...

Updated dependencies to latest minor versions via go get -u + go mod tidy.
eW]j—xèþtree 0314fc2a348c06d928a7e095079b7dc1331cf7dd
parent 4e7bb24121304e9e4bdc21d233af4292ab3c3cf9
author zooqueen <a@zoo.ngo> 1780289380 -0700
committer zooqueen <a@zoo.ngo> 1780289881 -0700

Add Claude Code project docs (CLAUDE.md, LLM.md)

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
XT˜x÷þtree 316a1bf6e906e3527a284b3c1fe0e8fcc9b46b34
parent 422c63af88fe9d6f1f9c5fa64d328306aed697cd
author Zach Kelling <z@zeekay.io> 1779747200 -0700
committer Zach Kelling <z@zeekay.io> 1779747203 -0700

chore: brand-neutral cleanup â€” remove cross-tenant references
\ƒS¯—xœ��Mj!…÷ž¢ ›Éb‚?Ý­†�EîJ-Éh7j“9~<CV¾ï�Æ™Q-ä,E"òF¯¤�ZƒÓœWr]tâ Æu@Ø&¯�t
™uÄÉXiÅdözV)
:Ç÷ÞàƒÚÏÌÎ¶jn¿>W”›–ï_…òý%ìå”µ¸©¤‚«´RŠIKƒÿ-‘�þ
þ,äù1UiîÉý3ìu4
óË~?K½6¦)?àò¤Ô³øúœVµŸxœ��KNÃ0†÷>ÅH]"Š�ÄqŒ¢‚Tâ~Œ“¨ŽÙ·Ç…ž€Å¼}3S"4-eé0ÌqÚ²AÙÚ�òj(Œªm­ «J
ÞI+{'™cÚñŽËžÑ¦¸{¥-ç�î$'j+SLð¡Ò¥ÆŒ"•2¼\õ$íú6.jöG—W`BÈ¾R¶ðH¥¤v—¹ü7€X\ó3èmYa÷ŠrõemÂœÁÄ-Üðs0	—Ûg0by�¡
¢ÿBBÎ›÷ü¶»ùé90
1@‰+Dy„Ó�¸ªóïlBÀ}õ1UþçùDB¼BžªK¨<”ýoylMSBSü78¯ÆZ†’”)ùH~ [‹†B—2xœ�‘MŽ1…÷>EIlfé¸ÿÓB„±™‘Âž”íêÄŠÛŽlw’Ù±â ˆÎIpgfÀŽ…eë¹ÞSU}Ñ�l�e]
Y‹tT×ËFTB•e±èŠR5¼’ŠÐ“� XR[¨¦åJÖ}“£j¨—BeY¢è©]ÛÖÃ1îœ‡è÷éÔ†ˆ>Àë“Ð¶ãMÁßnÔ&“nxyÛvI*ªf¼åœ%uÐ1Ò0E‡°1ÐVÑ9EE<KEOãæ¼nòbÖÔEÁû<¿ÌÈîFc˜ñÜëù“ëEßAïü€q¥”§à%`z½w£M-°îžP1áââŽÀÐå=l¢GPFílø&§úÌa#�M?2&IÑÐªÉÅÖŸot¤™@¹'•Ìç?,S¥tf,X(¼ºø¤#/	¬6L>÷“Õ66Õ¿†à.½3NîCªˆ3E^Sþúnm:Æ°,º—^´Ž^ÛíÕ5œt8&âÃ.Ic_èà] Ü¢¶!‚?ÈLÑ1›,À§�_a~ÌŸ77§ã0ÇÇ�Q˜ós½ÀEƒØ‹F*Å»E'Û®+ëR.ºœª„AÔ%'ŽÌS½M-þ»¾%ØÄ(Ð“ð¸Å%ôhMŒ 'f¢oÜ}JXÝÞÂÁ9?~>qš²Ãe)É;¢�x†Köß©Ñ�”±ß±O–/xö	ýtree d7eb53559185f5e2ac8c7313d18df19c97239274
parent 296eee1f72188c6d1e45ba9c327838e45921f287
author Darkhorse7stars <92065535+Darkhorse7stars@users.noreply.github.com> 1779617218 -0700
committer GitHub <noreply@github.com> 1779617218 -0700

ci: drop arm64 QEMU build (2h -> ~5 min) (#11)

The previous matrix did linux/amd64,linux/arm64 with arm64 via
docker/setup-qemu-action. QEMU-emulated builds of this image
(Next.js SPA + Go cgo-sqlite) ran for ~2 hours per tag â€” fast
enough to bottleneck every hotfix tag.

Drop arm64. Cluster runners are amd64; Mac devs already run native
arm64 under OrbStack with its own arm64 base story. If we ever
need an arm64 multi-arch index, the right path is a second matrix
entry on a native arm64 runner â€” not QEMU.Õúe–]xÖ)útree bcaf362ae5ffc1c01cb4dac8b65ba4c29f314283
parent ad72ca64976b1d7d6b984c4964746e1a668019ab
author Darkhorse7stars <92065535+Darkhorse7stars@users.noreply.github.com> 1779608652 -0700
committer GitHub <noreply@github.com> 1779608652 -0700

indexer: wire chains[].indexer.start_block + default devnet chainId (#10)

Two related fixes for running the explorer against a mainnet-fork
devnet:

1. Honor the per-chain `indexer.start_block` field from chains.yaml
   when the indexer DB is fresh. Without this the indexer always
   backfills from block 0, which on a mainnet fork is 25M+ blocks of
   irrelevant pre-fork history. Now an operator can set:

       chains:
         - slug: evm
           rpc: http://anvil:8545
           indexer:
             start_block: 25162000

   and the indexer skips to that block on first run. Once any rows
   exist in evm_blocks, start_block is ignored and indexing resumes
   from MAX(number)+1 as before â€” backward-compatible for
   fresh-genesis chains.

2. Default the bundled SPA's NETWORK_ID to 8675312 (the Liquid
   devnet chain ID). The Dockerfile previously inherited
   luxfi/explore's default of 8675311 â€” a stale leftover from before
   the devnet chainId migration. Adds NETWORK_ID / NETWORK_NAME /
   CURRENCY_SYMBOL as build-args so the same Dockerfile produces
   correct images for devnet/testnet/mainnet.

Requires luxfi/indexer #8 (StartBlock field on evm.Config) â€” bumped
via `go mod edit` to the post-merge commit d5e65d4b50c1.Ç�ë½œGx|ƒûtree abfe5dc2b9cd0a7a48fa5dd67a21a5394276ab87
parent 08f343c4a1cd227c75962c06833068c0ef8acb6f
author Darkhorse7stars <92065535+Darkhorse7stars@users.noreply.github.com> 1779420692 -0700
committer GitHub <noreply@github.com> 1779420692 -0700

fix(sse): minimal HEAD response to avoid GCP/Traefik 502 on streaming content-type (#9)

GCP HTTP/2 load balancers (and Traefik in some configs) return 502 to
clients when a HEAD response carries `Connection: keep-alive` plus a
streaming content-type â€” because they expect HEAD to be a closed-body
response, not an opened SSE stream.

Symptom: external `curl -I https://.../v1/base/realtime` â†’ 502 even
though in-cluster `curl -I http://localhost:8090/v1/base/realtime` â†’
200 OK with full SSE headers. SPA's `fetch(..., {method:'HEAD'}).then(r => r.ok)`
gets false â†’ live channel `disabled=true` â†’ no live updates ever fire.

Fix: HEAD pre-flight returns ONLY `Content-Type: text/event-stream` +
200, without the streaming-only headers. The full SSE header set still
ships on the actual GET stream.

Applies to both the per-channel HandleSSE() and the multiplexed
HandleMultiplexedSSE() handlers.ã;„r‘fxažùtree de3bf981ef4194c6bd1abf733996872270b27eef
parent 25c98516bb42a7a9e11a83decff8983fc90bdb2b
author Darkhorse7stars <92065535+Darkhorse7stars@users.noreply.github.com> 1779420479 -0700
committer GitHub <noreply@github.com> 1779420479 -0700

feat(realtime): add /v1/base/realtime multiplexed SSE channel (#8)

The bundled SPA opens a single EventSource at /v1/base/realtime and
routes incoming frames via msg.event off a handler map â€” see
realtime_sse.go for the wire shape. Per-channel /blocks /transactions
etc. stay for external consumers; the SPA only uses this one path.

  client.es.onmessage = o => {
    const c = JSON.parse(o.data);
    const d = this.handlers.get(c.event);
    if (d) for (const m of d) m(c.data);
  };

Wire:
  - HandleMultiplexedSSE() attaches a wildcard sseClient (channel=`*`)
  - Broadcast() now builds two SSE payloads per event:
      perChannel: existing `event:<type>\ndata:<msg>\n\n` framing for
                  /blocks /transactions /etc.
      wildcard:   `data:{"event":<type>,"chain":<slug>,"data":<...>}\n\n`
                  for /v1/base/realtime â€” no `event:` line so SPA's
                  onmessage gets the JSON envelope and dispatches.
  - sseRegistry.fanout dispatches both payloads to matching clients
    based on channel ("*" â†’ wildcard, "blocks" â†’ perChannel)
  - main.go mounts GET + HEAD /v1/base/realtime (HEAD is the SPA's
    pre-flight to decide whether to disable the live channel).

Without this path the SPA's HEAD pre-flight got a 404 (the explorer
SSE-HEAD-silencer from #4) and the client set `disabled=true`, so
no live updates ever reached the home page.&CêŸGx€ûtree fdc48f3ac2b455d3f4ae846fa534011dbaa212b7
parent 41e84df01aaed72522e4ad2c0e3d00d723b47024
author Darkhorse7stars <92065535+Darkhorse7stars@users.noreply.github.com> 1779419718 -0700
committer GitHub <noreply@github.com> 1779419718 -0700

fix(supervisor): preserve unrecognized first-segment when default-routing /v1/indexer (#7)

dispatchIndexer's splitSlug peels off the first segment as a "slug" if
it matches the slug regex ([a-z0-9][a-z0-9-]{0,63}). When that segment
turns out NOT to be a registered chain, the previous code forwarded only
the REMAINDER to the default chain â€” so `/v1/indexer/main-page/blocks`
silently became `/v1/indexer/evm/blocks` server-side, a completely
different endpoint that returns a paginated envelope instead of the raw
array the SPA expects.

The SPA's home page widget does `data.slice(0, 8).map(...)` on the
result and threw "o.slice is not a function" against the envelope shape.

Fix: when the parsed slug isn't a registered chain, re-join it with
the rest before delegating so the original path is preserved under
the default chain (`/v1/indexer/main-page/blocks` â†’ `/v1/indexer/evm/main-page/blocks`).#ÿŒu‘>xáütree da4ba9c87520199170911fa37074680541f61043
parent e87e5b35c38229fa9063d35782e366f7d172e503
author Darkhorse7stars <92065535+Darkhorse7stars@users.noreply.github.com> 1779415778 -0700
committer GitHub <noreply@github.com> 1779415778 -0700

feat(realtime): bridge per-chain block events into the realtime hub (#6)

When ChainSupervisor.AttachRealtime is called (from main), each chain's
indexer.Subscriber.OnBroadcast is wired to push block events into the
RealtimeHub on the chain's slug. The hub then fans out to WebSocket
clients (existing path) and SSE subscribers (new in #5).

Requires luxfi/indexer v1.4.4 â€” pulls in the exported Subscriber()
accessor.

End-to-end chain:
  evm.Indexer.indexBlock â†’ Subscriber.BroadcastBlock â†’ OnBroadcast â†’
  RealtimeHub.Broadcast â†’ ws.send + SSE fanout â†’ SPA EventSource

Now /blocks, /transactions, /token-transfers, etc. on the explorer
host actually stream live events as blocks land instead of just
returning HEAD 200 with no payload.ØT¤˜ïxœ¥WÝrÛÆ¾ÇSœQ.HÆ$DI¤$Ê®¥VãÌÄ‰GR“^d¦\`Ä†àÝ]˜b“Ìä*�É3ôÁü$ýÎ. Ùíd¦¾‘	ìžŸï|ç;Þ*E§§™Ì/T6ž�žM³¬˜e—Åtv)§§ùLžË“âBùôâ,i„UÆÓ‰<Ÿ‰!f…šÊ³±Ì.OfB§“Ë||r>‘—B�'ÅI"Z_Ö–^	»Ä_§.œÖÑ‹Ùéø|:=›>{òæ³Ö)ëRS[ÕT›t¡}Ùfi^¯^ÒÉÅÅlr2ŸNh4¾�<]iï•¥/´Ýfôb{í³ÿy-)”ð}«DåõJ®HHI�²£¼Æ¨ŠîînHÙÔÚxGÂ÷¥¢¬5²R’îÞ^Sÿ“é I>¥B?ô[�ã04O¨6ôúæúU¸†££BTU&ò%5Â—.IîŸ˜ªeÝ¼ªwuksE}øÐ6˜è¿Òïp«ªaæ˜ü'¾^*CÎ#�•#a��U£¢Ò‹1+‘—´!–«$!*”ÏË~k«!ý¸R(Š¼¢¿íý<H‘Ÿé[úÓK²i½¤?‡¨:1õtER;‘Uøÿày’|‡N‹d­œéyZ	8!A.	ç”¡1§mÝ.Jò5i#ÕCZúU5LØH@Ì*ßZ`q:Óû_~'WRcë\)éø.‡^tâKé¾>Ñ«•’ZxUmHdµ™Ê(+XËiGs¯ü1{ŸÉÔ>‰ÛEHñ‚·l½'©ª. ÙñÛsˆÚ5µ�‡R ôæË77ä7ÊØÛ»é`IxöÍîˆz8ì¥tÍk³^óÕÏ½®Mš$ïù7³ çífGê>¸!ÀDQ�ü~bìþŽ¼Æè¨a²ŽæKe‡ Žð8ôNTZ
�ö¤ôu­ÝæùÞþ¶Ü@lÖ€pª EX·¡`�õ®Q¹.tŽBoc·ýg«ªP›])÷CJ·`J¢V™’’]1e¢ÓUE ¬/´Q»¦zŽÿÀ>›:0ˆv“ßå—ˆhHëRƒ‹Õî|£Z!¬C§P^)Á&¡«]Å“'�öNU9]¡^%úâæþøí7w÷Çè©t—œ«ØÛø“7Ð(	pªCáµT„–5gÂ›šÞi§Ùåßþê°Pi�™ÿW°ËÎ·Ú«ÿª=´Ý±È‡n@b!´q>±ªž•(fš�c>{ÖTÚù�Ìü8òs>LæÇ�†"Ððî	;Ã³.�‡¢�yÃ«qÃÏ@ç9S˜ÉRpúèHõ€`�omm™#@iÈÂò¸•:„èÏE^pŠ/ç@�Jv”ë@%ö©ó=û†{±b1ÐªH§¾U•…’AWþ¸²í•åè©²} -GD[xbìð £#@�X(„(dJooé“Iì�<$—Ù†[m»ï`ùñÈZ�»°·ÀÔ ¢µ¸k¯’•XF™>0ùöæú«î ä·?(“J¹']D…ÌËFò*šbpÚ·ÛæÁ–€œ¯¥„”Jó]Oýº“.êyd.~ÜªnÚÍœúNaçÁL9XýNew ¶b¼c/£šˆøZp‡¡û[(s$O7ÛñÆÁˆÐ¼”Þ7i¼gÿÚèÉM(E´$MÐùd$•j0ÁÐÂH|ký9î»ª^3$®]¡‘HÚº¡•r•…cË1�þ#TÇ¹ÖF yEbºà|¾Cq¾•¤5€H@¦˜þY�i¿Ï¥Cý=Ôœ×��IÐ/I®�Ðõ×¯‚Nm¯Pß¨u8;HqýMŒ6L7æ3xVËÐ½Ü7k+š¿´	V
+VìŒç®¯¹åˆ(¾%ŽÙmCš+¨Ø¾È—qqyÿëo{Ö£†îc,ÆkÊïÁØª}û|©‡¦Ò¹ö‘îÏxt8ú‘ó¬Û×0˜ d	+ûÑQF€žw<L±@…'ˆˆ©úxžF.…MéÙ>Ù¿„=Ô�î¡¼×„}‡»��ÆÐæÛÙô; ˆ›—*ô,Ú�+ÌÍÖqxlh x+Ãä6†XÿKñfJAŠ¶‚2í4'è×s1ân¿ãøÀÎV´UX|¸áY`4[Øé ,AB#U™÷ø,ah®YÛp8ßêï×hÖŽ}Ø‹ÏÆŽ–h©‘»5Jp”€;$ÛŸ_“á{ó½ÁàÜ×BGI+»m‚Í|+à7 ½!…O0uÌ›WV	Ï}<À+Û�ê£Ø!Yˆ€{Ú€Ú ,Îq›¢Àø¦ œaáf8ì?ÈvIÃ4­Û*D:ŒßqpwýÐbB¯k»tGàÚ·ÊboÃ}k^Þ‚,.À�VW’Òyì.2õÏ&o>§La7ƒí1:Rzœ¦)�lk¨·Ó’Ÿ &?�•½h¡^âÂ}W‘"âÚÌåVg
ß6{\¨`]Cv{!áª0©‘È’ÄÇá“ #``TÓfhPº~û%¡ZŒ—Û
”&íþ%ÿ�!%e�Sxœ�TÑŽã4}÷W\‰‡¶R›¦Û¤�ÎÀÂHT€ÐÂj;ÄÓÞÄ×�UÇ.¶;Ó,BÚ�àø°ý®=ÓÝ^xJbûÞs|Î¹‰ž–Xb©X¯¯¨’M[«fU]­¨]7U5r±ÀÍæJmÄ=Ù‹�”WÕJ­Új½TuSVËe]s=m6/š²^WªVK\	<ÅÎyøÛ¾'c´ÝÃçï¾zGtÀ¡Ðî%,ÖëMµXÖ‹
fe]–¢u}¯c¤ÿ_µæ*¡ôy¬¼³‘¬œ\CUVà,|»½ý3Ø½¾�)4¦Áö GŒ]€ñgÕDˆ»Ž 9YiH¦CàŽdlïùš;wò-Áx·ÛN íÐZ2!w3úž«Œã^sˆg˜‹èd!DOØ@+áèi¦ŒÞw1 ¥›<èØeB×B (Šm7>y3…ß{b•ä5ŒÒîè�I;²c_¼_¸|™Y=ã4žÀ5H°1ü>¹âg® .Ëwé(ØQ„˜•�b¦–„|Ú»Ó¾ƒè@[Iç¢‹½™ŠÔ$Ëæ)ž<kñ¢,áÃû?!¸�D:z×É�jµ¼ñŒ_wF¡ûž¤ÆHf lœg%jñ(4N ¼�tŽó„þv
ÖEñ¸@©ßìQRÞHÄs•w�ÓaÜ>d%ŸáŽ³Gg¡Cö^}÷jq8²�£�0£	wÂ˜°Àè?€£nã½„Ú:v¿�ÚÙBˆïÿJ)àó~¸Æ9aÊbrx-šY<óWÆå9‹mPäÃTì1¤Ïö@~ÊÁÁÈ‡îÑh‰Ñù0)à§Ãpó±ÿ“Ý’…�U“ÝÊÒ2­7Ù°ýp¤V+Ý²ÑCNì“£¿�(°Î^¬üçPð†“"¨oHÊ•"˜�6XtN½Ò–.“uÃ/Ü?µú” ¸tûT›6™Ñ:ÍY4—5Æf·2­O“­!L-jsq\üKÐ1�Q´a¿ÌPÀ7Û»ùëwwsž©âr¹€==Mzæ/ù?'úø_`P��×’€G"{žoÜë äO¿döTüí†À•�xÐ/þtree 4a3520bf2f2184bb8c1c19162cfdba7471d8af38
parent 7e968642af547fbdb38a1b27274648b7316bd784
author Zach Kelling <z@zeekay.io> 1779222063 -0700
committer Zach Kelling <z@zeekay.io> 1779222063 -0700

ci: switch workflows to lux-build (luxfi-scoped ARC pool)

Cross-org runs-on dispatch failure: hanzo-build-linux-amd64 scale set is
registered to github.com/hanzoai and silently rejects luxfi/* jobs.
Switch to lux-build (githubConfigUrl=github.com/luxfi, max 20).
b˜k™x	öþtree 2f2a5c66dc9a24f0e4d8b934ddcf08160dfbdf2f
parent bb4ce667525c70dcd3847c2350e3f72bd32a72ce
author Zach Kelling <z@zeekay.io> 1779205220 -0700
committer Zach Kelling <z@zeekay.io> 1779205220 -0700

ci: rip orphan replace block (graph + indexer sibling-dir shims)
âÃR;›xäþtree fe946c3655d13d622306adf8142fa975c2226355
parent 7e82c67661a4337c6f17cb9ffe90e9296d85ac5d
author Zach Kelling <z@zeekay.io> 1779204815 -0700
committer Zach Kelling <z@zeekay.io> 1779204815 -0700

ci: rip ../graph + ../indexer replace shims; bump to graph v1.2.0 + indexer v1.4.1
Ð'SÀœx|ƒþtree bb7107d16c636d6f9d677cd8ef75176724440b5a
parent 8f3d58ef5e21746b661c4aa62fdbe7f2cb7fb901
author Zach Kelling <z@zeekay.io> 1779202927 -0700
committer Zach Kelling <z@zeekay.io> 1779203016 -0700

ci: fix runner label (hanzo-build pool)

Imaginary runner label `lux-build-linux-amd64` does not exist. The live
amd64 pool is `hanzo-build-linux-amd64`.

- docker.yml: docker job
á]y–xv‰þtree 1b601c107e5b2b5127157e64b2b0a7da33b69766
parent a18fcba81bd9105100c3616a89b6e29f82cf1a33
author Zach Kelling <z@zeekay.io> 1778953747 -0700
committer GitHub <noreply@github.com> 1778953747 -0700

chore: add LICENSE (BSD 3-Clause License) + LICENSING.md pointer (#3)

* chore: add BSD 3-Clause License

* docs: add LICENSING.md pointer to canonical IP/licensing strategyN¯qG“xã ÿtree 840c342b50cfd524a73f5fcd9413e28594e3e367
parent fb37b21f0c8a010be1b23c6e58f4614a35903470
author Zach Kelling <z@zeekay.io> 1778698780 -0700
committer Zach Kelling <z@zeekay.io> 1778698780 -0700

deps: luxfi/crypto v1.19.0
^‹Bk�x ÿþtree 0397891d3eeabb15b99bfad03a75e845514d49b6
parent f2410a95ff387bd503d004fe04ed165da8302db5
author Zach Kelling <z@zeekay.io> 1778646558 -0700
committer Zach Kelling <z@zeekay.io> 1778646558 -0700

go.mod: bump go directive to 1.26.3 (security advisory)
ºM^˜xØ'þtree a84952a30b356e5c7a51e88fcc1b6daf11828d32
parent 875af11d0487f71a18e59cf4e762429882794d4a
author Zach Kelling <z@zeekay.io> 1778089515 -0700
committer Zach Kelling <z@zeekay.io> 1778089515 -0700

ci: build multi-arch (amd64+arm64) â€” Mac devs run native arm64, no Rosetta

Matches lux/graph + lux/indexer docker.yml. The unified explorer binary
bundles SPA + indexer + graph in one image, so this is the canonical
multi-arch artifact for the  local stack and devnet.
©ô˜«œxóþtree b1c7ed692ab2271e3f30a430260c0dacb79a9888
parent 50fc1cf497fe4d0c7ef889687aeb0b96d8ca1e43
author Zach Kelling <z@zeekay.io> 1778045579 -0700
committer Zach Kelling <z@zeekay.io> 1778045579 -0700

ci(docker): amd64-only â€” multi-arch QEMU stalls on heavy SPA + Go
e•R’xýþtree 5fb3ec9b9f4ec2dcf69999cd72a5a5b490e7e538
parent ceaf8cab7645620700b3af2eef5ae970d978b2b7
author Zach Kelling <z@zeekay.io> 1778043245 -0700
committer Zach Kelling <z@zeekay.io> 1778043245 -0700

ci(docker): go build -mod=mod populates go.sum on the fly
ÎÑOÛ™x	öþtree 7b59cbbaa57ea015fe68d85dac4d25345445750b
parent 7844e67ea67becc296ebebb080049d3be66de03c
author Zach Kelling <z@zeekay.io> 1778043048 -0700
committer Zach Kelling <z@zeekay.io> 1778043048 -0700

ci: drop ci.yml â€” Docker workflow handles cgo build via alpine
¼ S“xüþtree a2df3925c59af70f8680d33b1bc8888e18b55537
parent 6b5450579e07bdf21e2f5c5ca73913fe34b83972
author Zach Kelling <z@zeekay.io> 1778042662 -0700
committer Zach Kelling <z@zeekay.io> 1778042662 -0700

ci: ARC has no_new_privs (no sudo); GOSUMDB=off everywhere
mqMÀ�xð ÿtree bad4bf082412b6cb2f5223dd0be204330b9850ee
parent b50a7e1be650ebb6d744d60c5ade23453117ab17
author Zach Kelling <z@zeekay.io> 1778042082 -0700
committer Zach Kelling <z@zeekay.io> 1778042082 -0700

chore: gitignore .claude/ runtime state
›
I˜x÷þtree 860487c5a72962cf6ab735df02dc6c8e3e119326
parent f86302117b8f6e25dd24d17c6677d4df62ed7471
author Zach Kelling <z@zeekay.io> 1778042065 -0700
committer Zach Kelling <z@zeekay.io> 1778042065 -0700

ci: regenerate go.sum at build time (proxy cache inconsistency)
PÖ—xøþtree abf969e54029a2f0e07558ff0fe2fe26203f078e
parent cae33f0789498a01d67a1898b949308edc115814
author Zach Kelling <z@zeekay.io> 1778041088 -0700
committer Zach Kelling <z@zeekay.io> 1778041088 -0700

config: full chain list â€” all Lux native + subnet + external
ýPv™x	öþtree ab972f511d87cebfd24c8fec81cf93ffc1e27d23
parent 598e6f6004afe10dc906e2f146304df023db1744
author Zach Kelling <z@zeekay.io> 1778040880 -0700
committer Zach Kelling <z@zeekay.io> 1778040880 -0700

ci: refresh go.sum (proxy hash drift), install gcc on ARC runner
ŠéQj“#x3Ìýtree 0fc515d6feb88821662d1b110955301bde2fcc95
parent 8698aa8248a73818884fe7d68ccb95cd2145860e
author Zach Kelling <z@zeekay.io> 1778040555 -0700
committer Zach Kelling <z@zeekay.io> 1778040555 -0700

fix(go.sum): align hanzoai/replicate hash with proxy.golang.org

Local go env had GONOSUMDB=...hanzoai/* which made `go mod tidy` use the
GitHub-direct hash (YxjIynlâ€¦). CI fetches from proxy.golang.org which
returns a different hash (5Rl3â€¦) because the proxy cached a slightly
different zip layout. Replace ours with the same value indexer/graph
already use.
A»Í”!xëýtree aa632e6aa6b40cbaced617b1d2f1945e97b3087a
parent 0a97dedfe92493674e17c0b58ed2b2c3b0439c69
author Zach Kelling <z@zeekay.io> 1778040340 -0700
committer Zach Kelling <z@zeekay.io> 1778040340 -0700

ci: align with luxfi org's actual ARC pool (lux-build-linux-amd64)

Mirrors the luxfi/indexer change: switch all workflows from the
nonexistent hanzo-* labels to lux-build-linux-amd64, inline the multi-
arch Docker build (arm64 via QEMU), drop the cross-org reusable, drop
the workflow-sanity job that referenced cross-org actions.
^¢°ÒŸ\xÏ0útree db2b2f2448cf0d23ad32c220273256d39a0a16d4
parent 9fa3cdebd3762216b1d0982ed5c02f7534026d3d
author Zach Kelling <z@zeekay.io> 1778040232 -0700
committer Zach Kelling <z@zeekay.io> 1778040232 -0700

unified explorer: single Go binary with embedded SPA + per-chain graph

Module renamed to github.com/luxfi/explorer (was -unified). Imports
luxfi/indexer/{daemon,evm,storage,explorer} and luxfi/graph/{engine,
indexer,storage} as libraries; embeds the luxfi/explore SPA via go:embed
all:static.

A new ChainSupervisor wraps the existing ChainRegistry: adding a chain
(via config / mDNS / admin API) spawns a per-chain indexer goroutine plus
one graph engine + indexer per enabled subgraph; removing cancels them
and frees the routes from a sync.Map dispatcher mounted on /v1/indexer/,
/v1/explorer/ (legacy alias), and /v1/graph/.

Config schema is fully per-chain: indexer poll interval, list of graph
subgraphs (each can pick a built-in schema like amm, securities, fheâ€¦),
brand (name/coin/accent + on-disk icon/logo paths), token features, and
top-level networks list. /envs.js exposes the chain list and active
brand to the SPA at runtime, so a deploy can rebrand by remounting
chains.yaml + assets â€” no image rebuild.

Dockerfile clones luxfi/explore + luxfi/indexer + luxfi/graph as
siblings inside the build, populates ./static from the SPA build, and
produces one binary. CI checks out the three repos as siblings so the
local replace in go.mod works without tagged releases.
.u�›xôþtree e9ef4f120b79eaea5dcdbc67fc3b7a64d84a2f64
parent a648c0b31dae1dbc158ea33ce8e113bf105f2d17
author Zach Kelling <z@zeekay.io> 1778036879 -0700
committer Zach Kelling <z@zeekay.io> 1778036879 -0700

chore(.gitignore): proper Go ignores, drop rebuild trigger comment
-tTy–xf™þtree a1f2c5fd9e7fc38a9e762f7514b9a9a8e6b4a78d
parent dd138bd8aa56ca14f14bf0c9ec64f575da85633c
parent 071cb269f28b479185f3b88ac9b7c1227d8cc529
author Zach Kelling <z@zeekay.io> 1778036368 -0700
committer GitHub <noreply@github.com> 1778036368 -0700

Merge pull request #2 from luxfi/ci/multi-arch-native

ci: build multi-arch (amd64 + arm64) on native runnersðÇog˜xø ÿtree a1f2c5fd9e7fc38a9e762f7514b9a9a8e6b4a78d
parent 8987a18472820f49e45846601ca13727de414f3a
author Zach Kelling <z@zeekay.io> 1778036307 -0700
committer Zach Kelling <z@zeekay.io> 1778036307 -0700

ci: register hanzo runner labels for actionlint
 5KÈŸxï ÿtree 02aed7906e41349de231ca755ff5d96fc6f8daf9
parent 911aac20a13f99ced64e81f223c37f9e6fe95887
author Zach Kelling <z@zeekay.io> 1778035887 -0700
committer Zach Kelling <z@zeekay.io> 1778035887 -0700

ci: use hanzo-* runner labels (sanity)
„‰H\œx,Óþtree d5a98873ed8293956340c809fffad804ae7a3d36
parent d74c06d00beb8ca378a1c533c4aa1f83ebdeaf1e
parent dd138bd8aa56ca14f14bf0c9ec64f575da85633c
author Zach Kelling <z@zeekay.io> 1778034163 -0700
committer Zach Kelling <z@zeekay.io> 1778034163 -0700

merge(main): keep multi-arch + semver-only triggers
ß¯\žx®Qþtree d5a98873ed8293956340c809fffad804ae7a3d36
parent f7d11ba9758f6c02c68190c6d333b87651518536
author Zach Kelling <z@zeekay.io> 1778034119 -0700
committer Zach Kelling <z@zeekay.io> 1778034119 -0700

explorer: remove  branding cross-contamination

Drop the _lqd._tcp / lqd / liquid daemon entry from the mDNS service
discovery list. The lqd daemon is the -side white-label node
binary; the OSS Lux explorer must not advertise it.
s"‹G–x¦Yþtree 0b695143561ce964ded453a18b176ef8db3bddc2
parent dfdb3052c3e00d7fd5c9d874d8cc4ba006a0b130
author Zach Kelling <z@zeekay.io> 1778033309 -0700
committer Zach Kelling <z@zeekay.io> 1778033309 -0700

ci: build multi-arch (amd64 + arm64) on native runners

Drop branch triggers, semver-only tags. Pin lux-build-linux-{amd64,arm64}
ARC runners. Eliminates the amd64-only manifest that crashed Apple Silicon
local k3d pulls.
þDˆ%˜xøþtree 7fd06b2687f41531672d96953f410837b9ed557f
parent 5b7a59a72544f95af0569c362a1bf59f95825a60
author Zach Kelling <z@zeekay.io> 1776997394 -0700
committer GitHub <noreply@github.com> 1776997394 -0700

ci: migrate to canonical docker-build reusable (#1)

- Add docker.yml using hanzoai/.github/.github/workflows/docker-build.yml@main
- Add workflow-sanity.yml to enforce canonical CI contract
- Remove bespoke docker build steps from existing workflows

Refs: hanzoai/.github canonical Docker CI contract.]Q¦·“xóþtree 7fd06b2687f41531672d96953f410837b9ed557f
parent 5b7a59a72544f95af0569c362a1bf59f95825a60
author Zach Kelling <z@zeekay.io> 1776997280 -0700
committer Zach Kelling <z@zeekay.io> 1776997280 -0700

ci: migrate to canonical docker-build reusable

- Add docker.yml using hanzoai/.github/.github/workflows/docker-build.yml@main
- Add workflow-sanity.yml to enforce canonical CI contract
- Remove bespoke docker build steps from existing workflows

Refs: hanzoai/.github canonical Docker CI contract.
…¥)žxþ ÿtree 05ec30403163c08342bb6aadcf78f72d50d2ce55
parent f8f1175eefc2deb79d5d410883e6761a418b092e
author Zach Kelling <z@zeekay.io> 1776374133 -0700
committer Zach Kelling <z@zeekay.io> 1776374133 -0700

ci: docker multi-arch build + semver release workflow
€ MÒœxü ÿtree ae8c1cbf01a3c4ef3b64f9442bedf313a5697b83
parent 31826b92f3b17083149b3521027226ae0719d6a6
author Zach Kelling <z@zeekay.io> 1776077660 -0700
committer Zach Kelling <z@zeekay.io> 1776077660 -0700

fix: use lux-build runners, not generic self-hosted
‰®L}’xíþtree 67410a2f862abb06a0545f03e1f65d5d0e6869e3
parent 6fa377ca541927cee1d0ef682ef213474c1394f5
author Zach Kelling <z@zeekay.io> 1776073381 -0700
committer Zach Kelling <z@zeekay.io> 1776073381 -0700

feat: use info.getChains for single-call discovery (no per-chain probing)
t‹Tgšxåþtree 0ff63ccea78bbb19a3597efae560484f7e17f0da
parent 5a66ed782a87a3677577552d1cd27c55326ac3fe
author Zach Kelling <z@zeekay.io> 1776071785 -0700
committer Zach Kelling <z@zeekay.io> 1776071785 -0700

fix: probe RPC before registering â€” only index chains this node actually tracks
U„Y�šx
õþtree f80df6b97507f299c3c70d1c113711d00044b827
parent 45230c0b273977852c9c30a5213ce27bf43037d8
author Zach Kelling <z@zeekay.io> 1776070690 -0700
committer Zach Kelling <z@zeekay.io> 1776070690 -0700

fix: fully dynamic chain discovery â€” zero hardcoded chain lists
	–Q±™x	öþtree 09f49cf4d3adc4d8da4b7a14c67d34add2510c17
parent cca50859f0645a03a0885679c96d588eda838b20
author Zach Kelling <z@zeekay.io> 1776069740 -0700
committer Zach Kelling <z@zeekay.io> 1776069740 -0700

fix: per-daemon chain sets â€” liquid has evm/dex/fhe, not C/P/X
9NQQŸxÿ  ÿtree 3a06e8dfee3ba02cf9b38c1052b2b0a3bca08186
parent bf584f6b48f9d265640f12d162b47250d18b1699
author Zach Kelling <z@zeekay.io> 1776069577 -0700
committer Zach Kelling <z@zeekay.io> 1776069577 -0700

feat: discover luxd, zood, lqd, hanzod, parsd via mDNS
ÓZMŸ'x€ýtree 453428f8c721029ab777cb81e7d366459207548d
parent ed8fb4b646ba370fab3b53583760b376f53d15b1
author Zach Kelling <z@zeekay.io> 1776069391 -0700
committer Zach Kelling <z@zeekay.io> 1776069391 -0700

feat: runtime chain registry, mDNS discovery, /v1/explorer/realtime

Runtime-mutable chain registry with admin CRUD API, mDNS auto-discovery
of local luxd nodes, and multi-chain WebSocket realtime subscriptions.

Config sources (priority order):
  1. Admin API (POST/PUT/DELETE /v1/explorer/admin/chains)
  2. --config chains.yaml
  3. CHAINS env var
  4. mDNS _luxd._tcp discovery (lowest, never overrides manual)

21 tests, all passing.
2Î˜›xë ÿtree 200ce12a53e92c05fa83f229e3fa078c9b474bce
parent 1a6d559ccfc01cdd2521511839f04334772dc735
author Zach Kelling <z@zeekay.io> 1776010884 -0700
committer Zach Kelling <z@zeekay.io> 1776010884 -0700

ci: trigger combined image rebuild
ÆUFŽ�xòþtree 80e2a7ada6b68d378a94574f2119a05b13b746d3
parent 7ee5150d270ca574c8e8f555a7048da9564df2bf
author Zach Kelling <z@zeekay.io> 1775943083 -0700
committer Zach Kelling <z@zeekay.io> 1775943083 -0700

docs: full README, compose.yml, chains.example.yaml, healthcheck, CI
ÑâQ¬“$xC¼ýtree 7dff837e60515e0be8a2360b832f716818c1a67a
parent cd4378e3f92324cd031ab01fcd584be7242e23d2
author Zach Kelling <z@zeekay.io> 1775940607 -0700
committer Zach Kelling <z@zeekay.io> 1775940607 -0700

feat: include graph engine (subgraph replacement) in combined image

4 repos, 4 images:
  indexer  â†’ chain data (blocks, txs, tokens, contracts)
  graph    â†’ subgraph events (swaps, pools, DeFi, GraphQL)
  explore  â†’ Next.js frontend
  explorer â†’ all three combined (this)

Single image contains 3 binaries: indexer, graph, explorer.
Each can run standalone or together.
†ß¿_˜x÷þtree 30ec1903c50c309e778ff050ac500e7a227274c6
parent a2b779c20cbdce6ac8edd2ebf2a5e89e307f588f
author Zach Kelling <z@zeekay.io> 1775940007 -0700
committer Zach Kelling <z@zeekay.io> 1775940007 -0700

fix: use pre-built indexer image as base, layer frontend on top
øõQÀ”%xT«ýtree 65df319ce49c0c030046fb802b76864890d0b6bb
author Zach Kelling <z@zeekay.io> 1775939328 -0700
committer Zach Kelling <z@zeekay.io> 1775939328 -0700

feat: combined explorer image (indexer backend + explore frontend)

Three repos, three images:
  luxfi/indexer  â†’ ghcr.io/luxfi/indexer  (Go backend, API + indexing)
  luxfi/explore  â†’ ghcr.io/luxfi/explore  (Next.js frontend)
  luxfi/explorer â†’ ghcr.io/luxfi/explorer (this: combined single image)

Dockerfile pulls both repos at build time, embeds the Next.js static
export into the Go binary via go:embed, produces a single container.
uùËV¤Nxœ31 ½ôÌ’ŒÒ$}±,“ÞœëÆ–Sœ9³µM¬þå�C3°’Ìô¼ü¢TÃ‡sSu¿æígô�0_Ââa1'#1¯*ŸÁÃk{«up^ß�=3ywO2ÿ+o|jŒK~rvjQZfN*ƒ±oé^ÍíVš¯-½Ÿ~<†eï7¨*OgW¿`W†26k¤­XvønÝ¶4¥/ èä�Ù(J<ýÜõrS¤ãã•bh_ÚÀnßûøÿúus¯GÞ„ªrutñu)2ôXÏ¶iŠÛÄÚ„»“¼šýMVû–U”T”_^œZ_T�_’Z\¢—žÏà¾Þ‘Ï8úŒÚ¹ß¶L|°—ÃyÛB¨òäŒÄÌ¼b½ÔŠÄÜ‚œT½ÊÄÜ†]«oÊLÜ·ÅgGð†fVÖœÍ2ÁëwÃ”ççäÕ•=¸,÷ÕnŠfð"s»E;ÍRxÎ{®,/-3dõ»Õ²'µ_ÊÌØ^5yÆªÔu¾rk
÷¢(‚;2/T¼î†½A;Ÿ¿ÿ9ÖMû‹nAU¦åç•¤æ¥€”•ý_¾ü_SÑÓŽ‚EŸy¦ö½:ÖŽ¦ndn8÷¾æ-j9S§Tmžx¡$Îª6=_/7?…!Ûëk 1ïÝwÕºêCå­OL½k„PQ\šËp‘Õ²CQ9ç{\;Ïþþ„™W¾Þ€ªÈÈšï{¢–|ñØ® ´)í-ÿì^‹ýÝí0CrS€�TÂwZjõ$®¥ÖY»÷î½zÛízÓ4¨’¢ÔÄœ’ÌÜT�²Oîñ'XÞîýÃÐµ3=ƒiz×›#‡Ñ”Åƒ•òÿSôQžûbÎVö½.¡S´gª]t4GW
Ûuþ–ýªýÛTvÆ”-rŠÏyp\mzfqIQ%HÙªç&j7993k*­ïµ¯öÜX{MÜHeÃÊ›ÂBg·Z—‰Ì:ø¬cwFé¹3�ŒSœ\”YPRÌpÏÉjÉ—»üêlËUúm[:â´]7AU”$–d&3ü>ò»ù ì\ÃºÏ%]�C®›,X	µ¤nK�_µz¯óã8ûæ4?ñWÞ7ô¨E!«�»çzÉëâ	Ñ}læ?Ë·%sUÁª'(
Ëájj_ºù:Kš¿¼ãŒ­ÛW%·ÅÂB¹¸´ µ¨,³8¿¤ðî´Ç«w°M½)ø`÷¿#
åö5f
ã3ò‹Kàf[Õˆq¿ÿ¸cNíwÑÌÞ{ZÊ~åÂ´”'€”ìZ,¾÷ƒŽÖÍ]kƒ;Õô_íÞ+$%p£xìfd4F+ÞÊY+?+­9k§n� ¯ƒõè	†Yxœ{Âù„sƒ	“ÙmsîP�G»Ï=Ú3óÈ›EZ×™Ž˜™˜(¤çë—æ2œø÷åAkß[ïø/sÊZ>�a�X¥þhsÓFaÿ+–¦ÄŠ_N×Ü÷[êæ*{Ó¯“SYœmvâ»!-gªòP©W—»®'JÃy¨™Åå‰ñ%©Å%zéù¿¢JLªoùgðÚ–!»à«Þ”# S(B‘£xœ340031QHL.ÉÌÏËÉÌ+Ñ«LÌÍaP]˜9+PTê�ÐìGín[7ÌP´s3„(ÍH-Ê×+.KgðÑOÞà°ïªðôGö¦Õ¸>~¼ÐÉÄ Êó‹²ÓròË‹.‰évˆ­c[xNâ]f–¨v€¦�4 –ƒ+dzxœ+öŸà ,¢ºx: Åÿself-hosted-runner:
  labels:
    - lux-build-linux-amd64
D8ôº=xÚ%ü<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="640" viewBox="0 0 1280 640" role="img" aria-label="explorer">
<rect width="1280" height="640" fill="#0A0A0A"/>
<svg x="96" y="215" width="210" height="210" viewBox="17 26 66 66"><path d="M50 88 L17.09 31 L82.91 31 Z" fill="#fff"/></svg>
<text x="378" y="276" font-family="Inter,system-ui,-apple-system,sans-serif" font-size="78" font-weight="800" letter-spacing="-2" fill="#ffffff">explorer</text>
<text x="378" y="322" font-family="Inter,system-ui,sans-serif" font-size="30" fill="#ffffff" opacity=".66">Lux Explorer</text>
<rect x="378" y="338" width="806" height="3" rx="1.5" fill="#ffffff" opacity=".9"/>
<text x="378" y="390" font-family="Inter,system-ui,sans-serif" font-size="24" font-weight="600" fill="#ffffff" opacity=".5">github.com/luxfi</text>
<text x="1184" y="390" text-anchor="end" font-family="Inter,system-ui,sans-serif" font-size="24" font-weight="600" fill="#ffffff" opacity=".5">lux.network</text>
</svg>A11}°xœMNANÃ0¼ï+Vê¥íÁyCh£ªRqD&Y¥VmoeoÒrã¼�—`‡"¸ìÌXã™Yà�ñ:Šðf|]Ï–X+ºRºBQ ¸á‰‚ºQ<ÊŸ8Š³Ù¶c¼p8Å³îVYü¢Š£Ëžª7Â”éI ¦ØqOEêŠ—ó|9»î[PÛöµMVÊºæN[Pä§ù(;ëõ“¡Ü£ŽÑDÑ^°c/t\zÎÂ9#Bý
Ê]Õ<µÊõ°©Ëçm•Ù®:ì›}f/U“1¥=Ž^Œ#°<Ä4²³zÌ#sÏ@© åRÄ¯�Oœ {ûŽËH„r$ä0`]RV^2¥†ý
~žþUÅ®y±¤x$ Ûÿ40000 workflows Ÿ¯‰�ü¹ªf{_Àu$¾ë‡ñQÔ§x' Øÿ100644 release.yml {ý¶ª§Yúÿü•?gäÜÂ}Ÿ_ÝÅ79X³¡x
ìõname: Release

# Builds ghcr.io/luxfi/explorer on the native runners (git.hanzo.ai â†’ git-runner).
#
# This replaces .github/workflows/docker.yml, which ran on `runs-on: lux-build` â€”
# an ARC autoscaling runner set that no longer exists. Neither cluster has the ARC
# CRDs, and github.com/luxfi reports zero runners at org and repo scope, so every
# v* tag queued a job nothing could pick up. A job naming a label no runner carries
# does not fail; it waits. That is why v1.5.4 was tagged and never became an image.
#
# Same shape as luxfi/graph's release.yml: ghcr auth via the org GHCR_USER /
# GHCR_TOKEN secrets sourced from KMS (the instance GITHUB_TOKEN on this forge is a
# Hanzo Git token and carries no rights at ghcr.io), and a registry layer cache
# rather than type=gha, which is a GitHub-hosted service.
#
# Semver-only: only a v* tag produces a {{version}} manifest, plus an immutable sha
# tag on every run. No :latest, no floating tags.

on:
  push:
    tags: ['v*']
  workflow_dispatch:

permissions:
  contents: read

# A release is not made irrelevant by a newer one â€” let it finish.
concurrency:
  group: release-${{ github.ref }}
  cancel-in-progress: false

jobs:
  docker:
    runs-on: [lux-build-linux-amd64]
    timeout-minutes: 45
    steps:
      - uses: actions/checkout@v4

      - uses: docker/setup-buildx-action@v3
        with:
          driver: docker-container
          driver-opts: network=host

      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ secrets.GHCR_USER }}
          password: ${{ secrets.GHCR_TOKEN }}

      - id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/luxfi/explorer
          flavor: latest=false
          labels: |
            org.opencontainers.image.source=https://github.com/luxfi/explorer
          tags: |
            type=semver,pattern={{version}}
            type=sha,prefix=sha-

      # amd64 only. The pool is amd64, and this build (cgo + sqlite) under QEMU
      # arm64 emulation goes from minutes to hours; arm64 returns when we own an
      # arm64 runner, as a native matrix entry.
      - uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64
          labels: ${{ steps.meta.outputs.labels }}
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=registry,ref=ghcr.io/luxfi/explorer:buildcache-amd64
          cache-to: type=registry,ref=ghcr.io/luxfi/explorer:buildcache-amd64,mode=max
          provenance: false
          push: true
£‰@¥ê\“!xÊ5ú“Ú°¤# ghcr.io intermittently rejects a good credential: `Get
      # "https://ghcr.io/v2/": denied: denied`. Measured on this forge over 24h,
      # luxfi logins were 20 failures in ~150, spread across a dozen runners and
      # arriving in bursts, with the SAME org secret succeeding either side â€”
      # including two hours after a burst. So it is neither an expired token nor
      # a sick runner, and re-reading it as either sends you to rotate a
      # credential that is fine.
      #
      # docker/login-action does not retry, and the job dies at step 2 of 5, so
      # every occurrence silently costs a release: v1.5.8 was tagged, built
      # nothing, and left 1.5.7 as the newest image with no failed push to show
      # for it. Retry here rather than re-running by hand.
      #
      # --password-stdin, so the token is never an argument and never lands in
      # a proTcess list or a log.
      - name: Log in to ghcr.io
        env:
          GHCR_USER“ÿ%
GHCR_TOKEN“,        run: |
          for i in 1 2 3 4 5; do
            if printf %s "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USER" --password-stdin; then
              exit 0
            fi
            echo "ghcr.io login attempt $i failed; retrying in $((i * 5))s"
            sleep $((i * 5))
          done
          echo "::error::ghcr.io refused the credential on 5 attempts over 7U5s â€” this is no longer the intermittent failure this retry covers"
          exit 1³GÌ¬ ÈD°¯xœ�VÛn7}×Wœ ulq×× u  ²µ¾ ¶dH²› j—’S¤Bre«NŠ~D¿°_ÒC®$.‚Ñƒ½ËågÎœ9Ãgt^ÞSv?UÆ
K÷œ)>Ç£œð‘ õÙv²›lm¾LjÏjÏ¨éÈ©Z#?tbh 5·s’Žš—gÌh5§þú›¤''ìL8JgÛé­K]ˆ{8Þ„Ÿ�2ù­ËMéYn&Sî©›õú/ë”ŽW~Œ¡g.ùäêÄuASaÙØ8O^{×'Q68Ù Kp2&+|iµ£½­½hB<Ó#+œëâ0A6üu!8oÂW8	¾+·‡«¨è8£– (ó‰ÐžÖGãÜ&Ò¤q#3vô5hì‘~øÐa)•§¡5RåýP.·ÐLrjÁ·°C©D�ˆ"Âä„ ?Ó;coSØ,-’Z­Ù=¡“ÎÍuÖí�uÚ�ídçUg0ü¨çCm¶h€«,K-‡>ø&­ ¦‘åÓñ²DÁ.ªµÇ]æF»r{îBM'¦(pšJ­±(5�L‚EZ¯‹.à
LØIvë‹t—±º—ì£šíNŸretå×É�’zäbÞbI¹1¾h_xó\P!­È½œ‰:9ƒ¼
lb8»Î©VÁQÎs¼€v¼ôcc¥çÁ(©w;Wq=:xþðáWÆòÔìU¸	A^"\ˆY­{Õ&>½%^@KV2Êsš”N1ì!÷YI/âãHúP“*û�jÐ©•3Â^Møœàž`¨ÛH¯ñèŒš‰�Îd‘0ü¬kã#W¦ÖÜÏS¤¼ŒÜ­œ"=Ë	+”[ã«Vê “ç}(+NkSˆEF¸„úÈ¢C^‚ 'gýÓ«Ã›~çMÖøxÃ@ûiˆŒ/\ƒâtÙìÃvHý	VUD/ÞÄÏÑrôq4öæö#æ8.Ð<™OTRËÚ× ðe÷ìºÙÏ k\ô{`:[ ”nÔ¿ù0æúÃ�åÂ¶Ýé]]�fGož˜Ÿìi~×ÿjÛe·óöÝÿm;>ožôK¾Õj¿uºoZg]J�ÍkG�ËwËNÀ?Ô‚’=”£e‡÷ÈG2TP'à[
àÜiexñ‘ Xb¥Pr2U2—�Cáó1¥E£D³ÈÔ� »F�@Æ±ž�.‰Üe¼Ô¾áçSÑ¨ŠS—E#–§nÅçT+C®œ ßk„ŸÒ{bi[ê´²piÜO^‡èôbcø…ìò˜+N)3àŠJ«’µ±÷Sw�¦÷Œç9Ä–Á^èƒçë9¤ý¿®_þúMÖ©�¼èiåèÛï¯%½x±x~h­*MBA†ûÂy°ºÔåzP¯€2dˆ*…ñcDÅð,æQr¸Ûü¦±£òð¨IJp·°r©\Õ²Z`ò §_èw]õZBmT;TN€ã¹‹þ
¾J]YPyU¨Ì�Kç„¶©*Ž9«Ñ‹³‚d,‰b£ÜE±¬fÞÒA/a²!JL˜ _èÖ8âÐ‡P€RÁ,b
ðÅÎËÐÛh^®çUüŒ@Á˜	Y°?9R|êÍ´bÙD5k7Ï³Vc;¾Um³ÆZ7çÐÔìøì<{µwÓë\u�²µP­d"I“$y¬á�¸‰)F??ÉŠ=ÕädÞÊI¼9,IÎT1T|äàÁ»#öò-u°œ4ºñüa9NÖm¥¸4¤«™–<™Ô;¡ú^ND5ƒãˆª¦ÑÁn²³ý½¹“s–ë1ÙÑG Ú‚¡JÝ�„¯XÏX¸r43íI(iélŠ{W)n«õêÄ¢(]˜®-b%½ÚßßÝYÍåÚU/ë>¾]wÎ¯.2JÃ1*e«ÙoÞ@q‰NûýË›f«Õmü¼õËV-{{ÙéeŸO³æyÿ4ª8b•ÚãZÈUcw ³ 
nì‡ç¹õ„“�ßm|]´b²Ä>w�€>ÄŒÂ�0·¸5Ò—/ˆµ�ûÝw—�³vŸÞ¯-³XûPûá¼’Ôº_xúúBSD 3-Clause License

Copyright (c) 2024-2026 Lux Industries Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.“ÏÞ¿%x_ ý# Licensing

This repository is licensed under the **BSD 3-Clause License** (see [LICENSE](LICENSE)). It belongs to the **public** tier of the Lux three-tier IP strategy: anyone may use, fork, or redistribute it, including for commercial purposes, subject to the BSD 3-Clause License terms.

For the canonical Lux IP and licensing strategy, see:
<https://github.com/luxfi/.github/blob/main/profile/README.md>

For commercial inquiries that go beyond the public tier (e.g. private moat acceleration kernels, Eco-tier patent-protected primitives outside Authorized Networks), contact `licensing@lux.network`.
À…Õ�»Úxœ�XñSÛ¸þ]ÅNÚ™Â]b·Çõ¦M�Îð s¯ó(¥Àõ:sï&Vl%Q±-W²�”òþö÷­d'¡PŽ K«ÕêÛÝoWúµ"™ëY¹ÝKUY+Û{ý«.fälºÝ‹fºž7“x®¬‰ÜÙ¬Ñz»§.ªÜXˆÒ¹ÎêùvïÅ‹§XW¯…xD£vVˆc]Îr5˜èRÚí7Ô­R:—º$]fêBÙ>ýfe5¿OªœéRõI–îÐÔUfÂ¦Tô›¡ÊšT9Ñ›¢2¶vôg’7S·Ê’¿6æu]¹aó£Ôñ5‘MÁú»u3Þû®U^`“¤£\O¬´Z¹W¤Š‰ÊÕs%:EíéîRÕŠlÒ�º¨£OŽ&�Î3:ÓR$33ôJ“Hˆ$IÄlžÚH›ë+­ ¼n�sc©À�>¼°:¢�oÎëA…qXT);øw°‡Ÿ�õã†%NÙ3¬ðve*øäG:ŸÐÎá›å’îXÞnñè™¦VNˆ¯t(ë9=èkË–Þ!"¾þs�5PLIœ<Ì`XÌ¸lt(½âÏ�H šÊ<ŸÈôt“‚rUž9xþÞ{|¥£¦¬u¡(5åTÏ(9‡[Íy4:ø@ÛtEÑUÒêÖáDMúøÈÍÌgí;jÄÂÜ¸šØÈ	éœBJmdÚ�’9SÖêLu–ÏÒ~p ÂG§ŠÂÊÛD¼â³g]¤Æ?ÜGýWÚSSÙäuK#]n„˜¼]ñ¥Ë›ÙÕßèx|Wé5ÅÝü½4¥}5“é‚¹<2EÎÞb]²Òî“°S}éš‰¸
ãŸóäíK³û>»;ùU‚ß4[f….ã@¶;ý•|kv{VÍ´«Aã»G¿ï]¹¡Ø²³±æn<þP“c“žªš:yUÞKqìjY'�`q§ÎKÝ&âê}£ÓS:®¥­=ÝN¤›‹Œ-²d›’½xúòé�Ñ™ÏèñFužm¶˜EêBU®¢…,òa¬êtei+ág¬é–wÓƒLÖróo?u;Ñs	
­ÃâA›ûßÝ&0ï;‹’ŒDCÁaÞ8Y;AMµ¤ê]¯¹±²Ö¦Ä‚5¥	i_áÈùJNÎ4Ùl¦TÛ¦žG4C ¦]¥ÒÚC6S°n“EÐÔÔ«2ßÅ'&KUŸ{J†§h4PcËS‡å¼÷²R‰´qµ)ô9âà…ò}‹P{oÝô	ÇÆM›<û2TY�{bœi´%Þ)‚Ë÷Xf¼ÿ…ðGg�u†ðH)5ô=ÍhU˜S£KþþÿË”û©qj09¤Þ£çé´'D{ZÇ*tI¹œ¨|Hoa2¦ú”™B²ŽÎÅb"jõÛ�u6¤—¿lýò’®D‹qPÇ)ŒÎ* Æ‘¾2rw°»ýF‹«$ÕYá¿l•©ëad¥×­àŒœ¤ñn¡ níÐ„Ž=ÝˆåG:ÐÂˆjYo(BBV&ÏÇš›Ï3	0¶žº æC¤º©˜VaÔ	PÃ±eQô)¸ºý¸¦‚ÁãÞ³ÝúÛûŽ3}%�Q†ÇvŽ¶ÆÕºn8�pDJÆ„bl‡¿9Vî×¦©T+D'˜#…º¸à°/(—´çÅl`Ê|á»a]~â$s*µ¬#ÎsFgÒºÈç1—€ƒVÍn—-!§O8…q4Ò…œ)à}®¬;·éÑ‰Á†¡	àd‘”ééTY»KQ°É³ˆÞzóÖ§¯1F$~Šè]Åû¢ïY´§I–�	Ã±jLdí³²âÑ*¯/E—9Y0î×™È@XXå‘á«AA¾Oa–°ês¶Ÿ˜zÙ®Fb‹¹¢f¤4n›Ä4Ç˜Í°/roBòøND†WR57y¦<°¦àøV½TŠ5cÊ]ÛâÚ>$G‰ôÎj›©š+‰¯El¬Xõ~Ác]m=jk«»¡}OeIE’ÏÐWƒ”øÖiU�¶ìšnf|ôš˜r\ÍÖHÿíd¨Óæ4øH‡ïÐµsV#©s“Êœû>ÏoßíBñù7=Ùõ×°zpâyBVU®S\ñ'gÊ'A.£'—=&¡Þ°Wªó^¿Çù„�ÿÑñNoøì§­~Ù�©ÖžÒdjøâùÏÏ!Ç\„�Qïê	ß(÷�N8ÅÃ¬çµG¯%{£ýÑÉè�zb&d;àä–^¶þv•<HùØR.G¦�þÍ@`Ä¹ƒ`z|‡õþMUîKká½:µJ…JkýÅ)ÄG±wpL{Ú¥Æs„àHNF÷ß�ŽÆo1»ÍL–p Ë¦6ƒÐ¤q™Þ“ÖÚù”"Z&7Áh£ÉOÆàõ,×iÅ†ñcÖ¾æ²ü²þ]�iÚÏMþbÙ!#õøRŒ4/§&š©:Äqº-A{ØÁ[·�lÉt.j‹+æ½å‘?ú ´Ywš’	ky)ioCK!D'Û�;®�å¤Ý—à®Ó¾´Ç\¥½¾ØS;[¾èºð([õŸÇï÷5èù��ý>­]£‰.¿È*›\Å7úÊ�ÿ|@Ïé¯ÁËêw¤(Bë¾¾Ç7»¬7î¬îËqÃá7Rmèm0†ŒÜ²n’á`uhg¸´à¶´ÿH�eï„yaVæQHÓŒ£èx+ô�‡ï�gj^ŠÎç
Õìht¸ÿfwçd4>Þ�öß½98Iº4\™žš²ëO1€!_¶t·ïãï_Ì¢ëu¬'9'Uî}5¤ÿÅ¾Í€¢ø²{òìwñv%Òl]lù423í{ÊÀ¬®o-v	jNa²„~	¹�Xåé>A„ZTWÍoúqÅ&Q´|Vâ¨<Þ‹":´&kR�ÐîJsÔvÀïë	ÒŽN¡Xë®þ†3
D3G0ï…¾Ü›`•™7	êY«mdÝæ
´;¸0Ü´ÿ†-;÷´™0�Ä©‹Ã}ãžå"Dw«`ÝŸóP1|‚ð/q�•Û˜j‹�žo¢“Ó(t¾ƒxÚþ}FWtÅeaùÐ¤|¨1ø'ï€¶×^"Û¾Åšºû>BÕÍ‡µ›êòH•Ý;!ZñS§ÖÕ´ix”kùpackage main

import "testing"

// The SPA was being handed the address the SERVER dials. In-cluster that is a
// service-mesh name, so every browser hitting explore.lux.network got
// http://luxd-headless.lux-mainnet.svc.cluster.local:9630/v1/bc/C/rpc â€” a host
// it cannot resolve. RPC (what we dial) and the browser's RPC are two different
// values; this pins them apart.
func TestBrowserRPC(t *testing.T) {
	for _, tc := range []struct {
		name string
		in   ChainConfig
		want string
	}{{
		name: "cluster-internal RPC is never handed to a browser",
		in:   ChainConfig{RPC: "http://luxd-headless.lux-mainnet.svc.cluster.local:9630/v1/bc/C/rpc"},
		want: "",
	}, {
		name: "explicit public_rpc wins",
		in: ChainConfig{
			RPC:       "http://luxd-headless.lux-mainnet.svc.cluster.local:9630/v1/bc/C/rpc",
			PublicRPC: "https://api.lux.network/v1/bc/C/rpc",
		},
		want: "https://api.lux.network/v1/bc/C/rpc",
	}, {
		name: "already-public RPC passes through unchanged",
		in:   ChainConfig{RPC: "https://api.zoo.ngo/v1/bc/C/rpc"},
		want: "https://api.zoo.ngo/v1/bc/C/rpc",
	}, {
		name: "bare service name is not public",
		in:   ChainConfig{RPC: "http://luxd-headless:9630/v1/bc/C/rpc"},
		want: "",
	}, {
		name: "loopback is not public",
		in:   ChainConfig{RPC: "http://127.0.0.1:9630/v1/bc/C/rpc"},
		want: "",
	}, {
		name: "RFC1918 literal is not public",
		in:   ChainConfig{RPC: "http://10.150.2.1:9630/v1/bc/C/rpc"},
		want: "",
	}, {
		name: "empty stays empty",
		in:   ChainConfig{},
		want: "",
	}} {
		t.Run(tc.name, func(t *testing.T) {
			if got := browserRPC(tc.in); got != tc.want {
				t.Fatalf("browserRPC() = %q, want %q", got, tc.want)
			}
		})
	}
}
Â¹‘¹¡xÕXínÛ8ýï§¸hØÎL-ÛMš6ìÛÍnŠIj·ÉºùãÐms#‹Rrœæ!æ	çIæ�”,Ú±ó�6MûŠ¼÷ÜÃû%îP8e"Ñ_°YóàšÍbúë�?iœÇ1¥\Õž]IuIa®39_Y&dBc©(›rÊ1<¢ã|QÛ!¾Hc©¸
èDæIFLSƒga£”7
kÖ
4¤Lkª×C™ŒÅä_ÞÃ ¶u‡s®®BšðL“Ày•Ðé§c‘q‹Ø ˆ¸¢›ˆel	uÛ¸Ñq>¹mK¢rt5æ­†ÀÒWåŠé'+ž(–NKá�ÎGVpÛø‘”Ì3®ê*(É„„†*ÃŠÅú³¥àtÐ!ÅYdàËHÁs1ã4Þ'süO“–Ä(â ±Z­4ºÆÇ™¡ËÈ¦B;¿~{Ú8mN#‘$flÂÍ®\ÄQP«•·©a>Ö¦Y–Y©6µß5š5ØxÏÇ,�3²–œzNo®¹¦«)O¬Šÿžs�ÑTê,a€I<NdF3–…SbPe®ë”‡8ðÐi¤�åU@ƒ’º°–Ú$qtJDÆŠH‚š#§]#2fÚ&jè°ˆC)ý‚Ï,y’C‰€jÓ‹�7áøÄ±2ÌU¯ÍÇ@Ï'�Ær"©ùh¥ÀÜSRëz|‹äà¨â&h¶Â Jþ	‡‹H×W^#Š¡â"’3$ÈNÇ§Ä›ÆJÎîðfN^s5çdÿ¥æÛ•§…Fc»“öƒ‰üÁžm$-ÕWÊ„4B–y@š":ê—<ÍÀKzerŒZž 1X	¢+W!72•gÓW6öm(É„CQD‘ãØ3	EˆéÈ�ôEºÆù"(½oÀÏù¶§¯à‘@Þ+Ø‚¢«©@x(®e<7yÒ³‰L:OÓXØ+2‰óîÉe"¯t›b†ÈAöÙ ¢ïƒZa]›Ø¨»çm:óx bÃÔ©¡ˆÚt°¿»à/;sîlXöÎ_öÞºµaÕÛZÍj·Æwè¯?ÿÀ¯�ÍÅ'êžÜãïü·f1…­M¡…cý¨²¨Wï‚6xg¨"Ê®SäŸÏì7•†múÇ2mØëu>|~ôÚuSBt»Ñ`©¼�@ùÎ£°Ñk`ß­3áe*OØ(æ8D·ÏËL¯$(F‡_¯Yó2›R…Ø�¥(áË	ªžK+×¨b��)$„­Ù&S3i’Îª7yÉô²wÜïþ€xs%Ûô›‹8nœ0uÉ³ŸÉ¶þi„=5ø!1FÇ ŒÀ*J¥Œ‡3– ´š*Ó\4ñcô˜Z„jäJ›n(•q<H” †ê´ÛÔä¨²ÍÄ„µù¹Ë-ÑÛðsËêPèÎ–Íf¯œQ”f¬M¾lEaaÑ¬ß¡�.vëM¦‘ôªXÁOÃpåh/ÈñŽ‡ÏqHÚ±dV�ÀüH¾l¤JªVŽj‡
ñjé”/[j³XóÂ+×Y
7þj1¨7vè¨“Ë¥t=—^¹œA×àLY»^ÚÜ—/·eÂì.¿«­G€e±åË(3ÍŽè©8L?]çfÉ±ÍY[ÇÑùf8:OÁ1ZÇÑ}>Ì€¨cÝ§Àø}Æ§çÃ(êé§§àÀDRÖwügÏÇáè8{
Œ¯ë0ÎŸ£ ãü)8.×qüòÍpüò+cúÆ”rLxyøÛ	&;;E|‡¿þhðUJÞEÈyñÍReG§×Í&~í’�Áy¿ÿ¨I š×glµzWëŠ©;ß©I®tÇÛHÉPYK'î6Ž·á.ãÑŸlÌç_}º�–ß+Â1©îïnçû¨óñüqŒ[åëœÛíÿ¬WD•¼WŽÜe¾9j½~Ý\a^§¡õÚ…ùé g¿­°ÞjmgåX·Îxik%¾¬¥‡¢‹–CSÚîqøå×Ê�½óo»ƒÎçÓG¹`t¯û`6[ÕÏrŸçƒy‘Ç¤¹±yÕxÔÌôÞÀµzî››î¯+­7ßrø^),«¼7pWÓmËQ5×²8Æ´¼B5vÁÁe9\à¥!añ²Œpq‚«ž�zxqÆÍW)Sf^~t_ñ7/ì<w¯~.æ}Qw÷$ÍáÙÑêBkÇlÆp^ÞäüC,¶G¸tÛòV´ùu¨zþÀûîõKëóÝŠb_¶´hÆZGt™vL�¤>�BDýÄ½XVTì½níßCGçsw•ho¥‰@Üé�[('%>™âæHh_ßUØZ÷´ðþ`—¹½2·9¥v@{ÄIy°FL;n\ätË¯œw{oîéqÝÎéáfHFs Õäixð¢}=‘î¦£( ž¤BÕÚ}knã 3èÿ÷?ýµk�Bs}C@ŸtÎ>¸.³0²>QÚïJÝ�]ê-Gö
Ö›ýí¨º§k�i¤Ãº¹ÜÅebŒDÂ’ðc0d5nÆp#��ÅÝ‰ã¬SÊhóÍÑÞn«µ·iç·Î†zCË{ÄòøN5[ ŒëD\ÉØEíi%pÇé­(^†OûÇ«Qf.±Šà¯�xÆ·g½TaßhP5L+òàt=‰Ãã¯) uÏŠÃôÃÂ¬…RÏ¤?ô¬€Žò‘Ýéìy«
s�³þÉ*n��æ4Å"LdÄ×Ý7Û6!úìYàŽ[xœ›iÒn²A��©Ìp³&›óä÷\l“ã8A4cÄäxN�ÉÂ\! Þæ<.F°p Xx÷m0/
ÌÛÉQ$Â+À(\•Ÿ¯——ZRž_”­_f¨Ÿ”¬ï¼ÙŠ/†Q4#1¯
Sj#ÐþÉ’‚[D
‹ŠÑL~"¸lø$¡vv Ôù;&ä€x ëÿ‡4…4°ýSPC“‘ ³"åPþA¼xUQAnƒ0¼óŠUîà[K¢€RÔ"ŠÒ#rÌ6A1¶eJ_pj/«ÑÎxwfmÑ-GK µPÍŒ.}+šàJ:…Ès �âw4_­@
ÉŠŸ$3W?ppNË�¬È)48x�VÆ­|›—íë–ÎeãéA‰¾{8šŸ‡«¯°aŽQ2×ç¢"Âo¬•6Â‘uZ`ôÃ:A	:N–8‹Â3Fù§(‡Ö(Ù¡tKÄÊÓ¾NóäTdyßœÓ–Ât‰~Œ$ºoeîÄ¡už¦;rádOŒæ«›<­>‹ò=~ŠÖ~²«vu’•ñóoUuªwIRÆ>þ*?‡ú˜žÓcÜà¥¿ú¾™ö23ý@/ZZ§´Æ&þœkIü8Tð•ÄŠGä�%x„ {ÿÜ¿�timage: ghcr.io/luxfi/explorer:‘lžWDATA_DIR: /data
      HTTP_ADDR: :8090
      EXPLORER_CONFIG: /etc/explorer/chains.yaml“¤8)U)ý°Áx•ZírÇ•ýM<E'¤´bÊU¦J©’%ÙV…’Q’SQiÉÆL˜p0=ž�„Vå×>AžaÌO²çÜÛ>¨Ê²l‘ÓÓ}û~œûÑ·§²é•�:3·yÙëåóÊ×­9ìô'ó¶�_¾á¿µ›ºÛŠ5m�—Sâï©¯®¦I^Ž–v^$×ÇýÞQ¯7™ç¾œäS“7¦�9ÓújX¸kW�(|íj“Ê„EmÛÜ—¦ð6s™™Ô~nÒøh!Øk—•‹Ä°ï"mÍ]ïà…mí‹¼6ò£ìèßÆ\rÝI?ÃŒ‹,¯û—½ƒŸÞ¿?{–e:}ïìYÛVS8ý¼Oi ¿wz#3”¼�°þÚÕ…]ìg&¾6çgÏž7¯Ú¥yŠßc—Q8_ËÞÁ÷µ-³nbEkä!rYsÆE¦SÈÐ×Þøúª‘‰Ÿ>‡Gy
Ò–ag?õ¢Ÿ>Ëc°F˜­
ÆÜ{5„Ø5/]c¬i`âÂ©1LU,S¹z(KMºhZ?ÏÛ%æýÌÑä6›€I¥-Gó¬l´[Û¢ñfÑ€n;"š™­Ü3É]‘5fì–¾ÌÌy±˜ŽÞØ¹	|ÈÓ«£wgÏGï�QÔ˜­�ñ1c=[UÅÒÜÌ\)HS±‰"Š´T6 ».	°ê–¶Èµ,ù�Æ—'ý£ý Tcc2Fû€ëÝ%F·WÈX´Ñ«&/ÛoÿÌêÂûEžé*Ýc5†UTÄî>ô�í}dì’Ðt×ó�Éìt`
ÖÖÓøÂ–v`Æy›ú¼@1ÍÜ7½Ì†º£ËÒ\Ó˜÷?½:7gïÞ>y~€Ã”‰yUS ¢…iÛ™m¹¤ôõÜ°„B�«¯óÔç®™Jm‹Åm6œ9› ›àiÈxä&ÍušzIáS[œ|÷íñ££�Pº™åéŒ;¤¾®ÜŸÆ˜’—™»%¸ ‚ç/OÉ_ë�Üqío°Ò; 8›f¬«t­«`FŽA»ÿl1.ò4(á†’q£@Oã‹Œ› Ë‰9¯Šì0dqÉØ¥ã\"Äà³ff¯	×Ê7yëLí~Yäµ›»²…‡�m_:õ“Ú²ô­iàQÍdiÆ¾��¬(!œ˜KO�O‚&ÏÌ%cÖÉh´¡Ößþõ¿û•9J’ä²«ÐÒ¯äÂÞ`°¸vD ãjx¬éÚQˆ,Æ¦§í6kD/"dˆR!LL,œ+KÌ©£Ü Å3ƒJmQÃþKS‰žÍoÿú·iœj+(ù�³@ü¸p‡G00•[¶®Ì’©ÀÇ‰„	ø‚*�²h!LÏ‡­R«ôÖFÜöaÝöö zµÂ_?€`ï; âçó•ÓoAé¦é )PÁ½nu¾œ�}±Ín½*x8Ç°êÌûâ5<s
Lo‚¶Â›‹¹¾ZñÛOñòoÐfÛ0SÁYü¼‚AÌá£Ûïðs´ÊI^9O°ŸØòØØ×Ë��›ñéØ0äJÜ–XôGx¢-}™ÃOÍ³×¯a.Ë]3à:¡ô0 rÀü¦F€\Kjý•+óV¦?yywÈ‘)ýDc�ìB 
)â�33—`$cR´�ˆûTµO¦4 ·ä·¡€v„žÐ”Ðò-èÀMçôcñç¼œ¹Î|º¸•*¨‡œÄ§‚Žá3ñú�(i\Õr‘*¯£PpPÄ%Sº|:û«%eD™4ó\Âc#�°º.
ýFÜCœ2CÌ€ÿÙ²¹‘Íôx3j•åÅõãˆ˜ÎÐ×?´îxgÝ1QL§ØÄ¨ÚW±­¸#XƒÈú×…G¶ ´Dü±ôñ˜Í+èi~‘Y‰y¦Z�«Ø{E*î¸“d ª‡¨&Q”•-,â3$rdµÂÓ¸Èó#4Ë/êËç¶Õ°Ãdòñ˜:v‚r4¼Uˆ;0 €’MÐÖ¨UMZûMb~ÎÛY€Ø‚x ij¿@ÌÇP ÉxÙµqÛ¸bòÄÜ`1æÁU|+tD[qNÐ±–HjìûùÄœE—`’¿Zg µ¸xfLN@8‚p…_B;+gBÜ\™hÓ°j“ŽÖjÚ™7ç)ÄhÉÁá%¶D]H—CŒ#¿_™GºÌÍÌ#8ID_áû�u~úb`z%]„Æú»‡À?ä·Jnª{Ë[æOœ.hu¡Ä¢œùArZ”;±ölÃ�Tmð”°RôÀ¬½ƒM¹6•ö½hDèè)[£ªž÷HyÍà†´À“T3$s²;v¨Z3–Ëù`ÉÌÒµO$lŽ-Š´TàÃ”)Œ©ÔC³ƒ)HõA	¨b&Ä©C"ÌëtQ à"ì(‡gÅÇ¦$m`/Ê°zVî_–LÆ™™Âaý£³kW,O€u “Bø‚l„ j
žàW™(-Ô=¸ôÀÌý‚å‚�8Œf“ylÇy`	€¢

ÁW÷)@E˜Ãñ�uü»pîHpÒ_Š'˜ª°T¤ Ñá%
*J}f*Älp)0±ã’)ØPéBÝˆ
4T¹Èqn�2žtÖÜ!
›·o˜'„–_¶Ì¿8ƒÒ’;å°ƒlˆ0h.�j^½p—‘‹®©S_
%dº‘ÉC’T(ßkÆ Àâ@‘Q&¢Ä¸ñÌ J0>Y´��äpúR’ôêý˜ûÔŠW±°1}DØ¾�œ[!]ÛâJx‚TººöubÞäCìå«¼ÃOä`"r¶ÏShz- ªµˆÐ­"¢¾æãÑ'hd·.Û}`ÆÃ ’ˆ4B-ê+MBq‹Îay a+o52k2]4ìKA¥JW^¦8ÍBù<â¢íñ*œ=Âïs”]°t9Qp×»Òì¾ c?¢Dš!¡Êï©Õ©pÊ×]Rñø¹ý¤b?áëøG(^ÃS:ûHm¿ ©÷¬ÔÐrÐß?8K`á9�Ò°½AK•·ó"¶¶uÅø\# ÇÚ­F8Äy)‡A$Òo¶ Â‘}›‚Y8¶Ÿù¢xEG¾F]cœ²TáÕ…89Þõ�¶7;rè©9A�Ö?~ÄÆÚAuû½ä-c ƒû¥FÉQ·’Óˆ Ð)XvÉ†fË¦‘b¢X¤âÐ]‘)=e˜ÃkxžßÂÇZòèQFã�š<pÜgÍ] CDúæÓçø÷J²ø.ªkÅÌ™V‹C,_YŽÿLù¦<)Jn«Õ1û2£ëÿ‰€£;‰èC–Æ÷£»¸Õ�ËïGˆñº�¥Ê%)­Ç‹¼h‡�œCbRs£+å3†Ó)æ-Æ	@<Oò°�+§ˆ¢É)š�ß“ ÖÚ9$¨Rš[¯”XxDe¸zõ'd—¢˜o¥¢šÌpZvmš”%ˆÈ¶Ežbÿ!¡~)º­¦•ŠÖf{Ã®~6±Jy$)˜ä‘¶RlMV�bz@žLb•!¤Â5Ú>L��ÑA5f@ËÌ;k8B¼F�Ì‡Wh Ii1ó7ÈAZÖ";£/<�NýÔKÛ)'<d#ÉMR© \±¤ÖLÉ~3(PEHMÊ³¥¡)ÈÄ<–Ç$W;Ú\6&ã¼´õ2x†r¾«Ú/¨W¼d­ãTË^&ì˜gó ¯ëåÐ·n(@ýÏÒÉü¹G§{ËbVÞ\¤|]KÇdö&Tç,vØÈñæ‚�ÌT�µzTþþ%Þì]Â]>¼;y7±(»,j„Lþ¨°ûP›Œr×ý$d×-Û&¡�è­\ƒ­ÚçìxÓŒDë³#6ÃáºÀÿ¨h æ3I€Ã™5.BË"Þ¦¨ÍoÒzUAãÈ¦¤ÏŠÂßœ¢ Ü¡€¶”¿¹`Qº²kœ»IBRÉ^’H6HÈçn�Pe½Ñž?‹1D‡°¸º[‰*¢3¡í+�2Ä:±Ÿ×àä‰p�.©ä–Hl­«S;ÆuÍvx*8Ú�>x/<»¹ÛÓ3å|UjxÆ|©Ù;p�P’¤\K®2 [e~ËN%¯§ˆ	1µ,Ëp­ •ð¦�}_�Ê�²S¦¢ÄAÉZFì>$½É¢LÑ^Ð3Æs)«�ÌÛíI‰­wà‡«LšÄ˜ûô©)Q#ÿóŸæëõ`ç¦e%­4Ék`	@G¹ÎƒI(¿q4ÆáƒùP‚q”E´¬: ä�ƒÄ¼¶´£m,áæ½cAÅ
›øší(´×°2Í'¬Ð—'X�ÿúw’tÕÃéåû~Ø3�C‡È�®WÐÿãnG2I[dh4?@�d~Åñ9	0°›ý«Ol.T€‡ÊÔ~j¤çéâ½.•úKd:Têá~2¶Ê‡q£‡"™î¼ý”•ä!*Ôy‡¡$êõ…Þ–&T[)iõ Æê/‘ëP«wÉÕJOã‡¨¬#Ãƒd·d]‘íê<¨|x‡3Óý¶ý:L©Ê‡è)´]8tIEóí¥Õ!Ì·ŸVG®YÛ’+°–¹ë‡8ÚËØ†a5e+ }C¼/QéŠG[íRéö ¡-ÁöGh¦Ib¤EïÁU¢ ª4;#*y\ýÒÑ‘]WÁ0ÄA	â‡•ä›€#s(c(qÇˆÅ/ìeÎÓP5É{êÑ¿>Ô$Ü$r•öF(Lÿ¤?0��>=ú|Ô;`ÏïÝÜ™9yjPyâ3…4‘=Ò=È'Â\ˆ‚çˆ˜�J:Kø$»Ä(=Ck=,¼ïñ¿0Þaúî~ÀÞkãBÊ9gë¿	a¾Û³Óúªù˜‰¥/srƒ":‚=•-ó šLLiÐ}}……¡Ôî¼ÞŸ#�#Å·¸CÔb8l°R§Ë†8¸xlöBq/­pHs
]Y\®°|"9½ïPŽÖR2\d Ý<+—8˜Öh˜’w¬eqÚù¸gn-¸€hß‘;x%¿@Ö·HÜv:EgýDƒþQ	‰ADÒ‹Š�sÐÃÁŒŒ­H=°j(xq¾b±
 ú‚ÆT�¸a)øâ¢†©p¬(x
CÛT”�«x¹0‡ã‹ù‰ékf;Vü&eöÄRtíÑ	Dëo¥Ã`ËoÆh\¬ÖáDÎtÂŒ÷=ûüD1´é(ÈÈÀq
pân8—¸µÃ ¡¾Â° ÷?p¸À‹ð¡	×w	øc×Â•‡Ñ£ŽÌŸÍ£�½‚«Át—à'DvÇ#Ô*¶#C­Í–(®ay2/ûíþ-.!�x$ªÍ_\H¸Êâã Ìå»h‹0–ûæ¹†DåÄN3ZŽøô8J�Úâ&Ä©_”™X¿‰æeu[¿ß^£ 5‚ŒÁ™Ú±†FÌßødˆ(pÊÐ6Cô1´Þ=y¸ìACÄcüÉ6g5¾À¹íðûM?é±šüò¤!&=À–œŽ‡@*£‹äÍÉfe¡•ÀªÜ�ú`#çh¿›tBE‚5h�è}n¬Lú£øŽ¸°9ä���KpO¤ÝmtgÄâ¹­?DÿYÃªZGÍŽNsï@×"øÂ%â��2FòCä‰øw�¾êð(¯;ÏqJ,ªº«“�Õ1]jµL�¡ƒãUë„´â“Ìw:p¿®÷B}Ç®bÜ]¸¥ø!»å¬ä·‡_}C�)>¸sôïu‚Ûef]´h¥Òáˆ›ïTQÔ)$ZÕœ«9±¤ù�8ŒàÜåúa­›s)×™Å5=Vh:£´Héï’&ùNb;ŽÛ®æ7ˆòJæÅ÷È©ùÆÌ–¸MF‰9Ï-PðÍ«›ÆNpT¶*ô²É²	ºòè<‚öýH¯‹Ù]i–¸¿šKkÕÏµE7§Ãêù$2y�O¡žë·‡—ÿýÉ}4üîsø=ü|÷hðíñýï/õÃH6µ<bª‹©S?wDþlÌï_¾ùxññÙ;²‹;ÌÑÏçzóƒvŽ,Ÿ=„h·¦y(mHE‹1)Ô@–w7 ˜¢å™ðõMò<°a$Qo!a²ù�b;EIIu4o“—$89ÄG¡¸vú
ñä«ø7â ËRaéd>ß\ÑÆÆ<í'Ê¹­q§V*SÀ\~£òÿØ)öhzj¾vS�ïVŽëÇOùç„‰B^ÞV°ÀáÎ[äæ&ùÑák¤kHw°ù_	=¼˜¶ÛXÿ®¤DÒæ¹Ð[ûžn›ì~3ˆk|ú*û|ÀF4Q}õ¬’£|^‰NY…*d€‰Öþ‡)6.�ÎÿKÂË§ç£Dxèþ�X Z°>Router“k
SwapRouter02 â€” the contract a swap
	// is addressed to, and the contract an approval grants. One field, because
	// the two must name the same address or the grant is spent on nothing.
	Router string `json:"router" yaml:"router"`
	// GenesisSupply is e³PÀ5<biºqxœ�•[�ÚFÇŸñ§8±ZêõcVû 6<T¹<t•J]­6Ãx|QÌ�ï.MòÝ{Æv%´M+ ãsù�sþ:ì	ýHJ;RsËªw{!5Œ­‘-”�Ÿ{¢««¢n˜ùaš)]óÒ¶&–uukB+ %RÖLA­ˆ'Ë7o  T‹ÎJxœèú‘�Ÿv2ÄêŠh“Ž“úrÁ”L÷¿¸­fª›VbpU+(["s¬¤@!×43øCPí¶”d_™d„«'&YOµ®àuûüJ@‡)(aüöŒ ûWL>¢kïµ¢Ad¿	ñJ9VÑr
·ØyjhUÆhC$C¬Z¾{âØèXÃÃdœÛ	|²Ff^°¸�¯³s~5kç–íöY-Ç“)Ø]ïÊ9�]cO¬Q] “Ò	åü*kÍ6<6ÑS¸»ß4°ú …ð¨¦-@; ŸæC�/ ™³¤³-?<ú°ÝçÌ‹ƒÄŸ-=wé¦!Kó`y+/ô}?v·³(+Ü(ò¨gÿ58è‚çîvEã4Lc%é<I2oÄ«Ô£ Yú~Í—ÌÃ>¸ßwÎçóuHü5s7nàb�ØÏüU,Ó"Ž"–a2_kû¤/ÜÏYS¾ëâûXÄóíÌ/R/ös±å1£›0ÉÖn€O¼Õ*výoÀè,bá|Ì¼,â4#ñ<
ó(qÓÙ†æA¸Ú³�ÑS°=‘êŒ,LÌËú€kug®;¹îVùâxÝ9Œ´³!š4c4ãª¿XÖˆåôëÂ_’§‚uÙ­û(†È0ÚŽ	ß‘ý�ÒÕwß)´Oõ	}
!áa
ÔxÉNèXÖéeÜeí“ÜQçüº‡ =Ö/…6aƒ‡ÝËÌ¾GŒnôïƒëÎÿ‹2†^ÖR
YŒ‡¬'ëD„—¿ÛS“ºoó[Æßa|·Î0Ìíøo;!�|·ž. ÷ñ¼8ž2¼CPsá½o.k>�!ï¯‘yˆÇ‘—@4�¦qà-Ã»Ç$ªã ½Û}—«MÞ¹guYmE+ñä‘<ÇxëþuÞþ±ÝKMœa×ãº4XzZÊtdß_=–*}þ|bN�ý.-”ÿ5»ViP‡r|Ôs¾üñÙhGöÅúDTLºõxœí\ërÛÈrþ-=Å,*Y6	É»§’,½Ú*[–ÖJdY%Ú»'QT6EX À€¢´¶ªò+�Jåqò0û$é¯{f0 )Ù[¹üŠ÷"˜éééîéûxž¤WÉ¥V³$+¶·³Ù¼¬no£ÛF×ý¢g#=æ_Š´gÅåÎÇº,ðb2kð#+w&<²Ð�ù±3mš¹ý}Qåøµä1ó¤™îL²\ã¼¨›Š`ò·ú¶Hƒíh{{gGÕMÒdéáPñòµ*’™«Q•c•ÔµnjUù­úý_þ]eiYÄõõ¥ÂÇ¼¼,ñ  “$ÏG´Á:VÏiüD]?�¿�wU3ÕêçR�²"©nUQÒ¤âRWªÖÕµ®ùó‹¼¤yi¹h hxúü™ÒI:Õêð@U‹¢&\T\–…šëªò÷žÒÅu¤^êy^ÞÎtÁ0ÂËiZÅD¬Oeuy·£oèc¥ûŸhèÝàúÏñ?Æÿñ°üQqYéº>£õµªðFpÒ¤I§}Ú™jJz—4ÀeH˜g©ŽÕÛÎÖ®´ž×jçúéÎcfÅXßÐ.Ÿ ÊÈí¯Ÿ–3b†z~zTG=µ3ÕIÞLéB¬Ž?Ö=‡68-ëFˆÀtùågÚêx^f¼Ð×šÖ-ix¥ÀbUéfQ¡þ´û§˜¦Ð¿—å€™ªháóöuR­°<>² VeÑÐ–9Dõ&›iE<Ÿd—*´h
íº(IaêïX)¡�Y‰bõÂ—¨J'c5©Ê™gõ	˜ÙN¥ÿ²Ð´.U`cf,q¢Põ2™ƒ83µÌš)‘“�YŽƒÂDN˜­«!ï‘ÀW:mJPŠ¨²±¨l¢ÒÉe,#^f•
ËJýÕÁŸO�ßœœ½¾}þöhÿýË£³€É‰o„„¾Éê:¸=!@Ÿ7Fâ]^-Hæ•ž_€VYô±G¨r.xð&þ˜¸s8„4eµÊAšDí3Á_'óþ¬$Ðp´(Æ9Ë¥Ý	CÉŠ¾Á±ž$‹œ ¬ÓÇåô9s;X&`ÃŒ€�	ˆ=°�,€meÿÃÎ9Š0¼,­ŒH&Å­^Q6
‡,K³&/‰Hº"¸¾Hò��Ô8™nO>V{†½,´]?Æô€;¨¼­E5Iè\n7·síÉrS-ÒF}ÚÞ"–+þ#äÝÞ<‰p�÷§¤“ÏÌ#}(iCø3©él(ZÀ±Êç5}
“|™Ü2»kB<ÚÞoòäÖ›+n^‡E–¯ËH£í;>ˆ'zéð·LpZäÕ4� TjDÂ1†jJìÅK±e÷oOEêCA!AOU+{�TøØ$ÕZUe�v ?«Áv6\ŒB«Dz*�_Ú>m£¾ÙSØ(ÍÜ’à‘loÝmoM æ[»Ð'Âh ŠôÚUÑ-:àÿY¶èÀaV‡p¼>ìí© àåøI•uü³&Ø×a°áXÑ;7÷7—^Ôí6	
iLôŒ_îÉ®¾ý–†ÅG5!2u¶¶&±e0Ï£/‡CžHïx1C†I Ÿ¡�š²lf=@MH'}€-þ z’U‰Y„5×c} °ÌjÝŽìè–3ÜÈ^¡36ÓDÖƒ�`ä$œ(ÇÿÈÃ/"JˆH�_ Eìœ µ[÷˜Ò&Mâ‹Ì;¤•B7¡Ç®F—¶LMC+@pô{¢Èçfph-'Z6¼†’%HRœçBîŠD¥®ÑmMukå Ãºžšç‹šu£¾iªÄ˜GRàNK@ÛúÜuÌ
Z@ö+KÏxöóÅ�1 '¥§•¡òX+x­ÖÁrj–tÍÍ‘�g'kÄ½ùJå¼QR˜„álq£Ã�á$é×‹>ô:~Å:ë�f†ÁÏoƒ	�(´zÝ3ÚÒÚ~D¯în™á?¦W4§ê½H
¤ˆ6Kù$æ]?‡V?¡/µ9ÖaO0¹Œ	ãn>Ÿ”ˆŒH×
h•“‚«�·ãùØ9=ù™ÏbW8ÊÊZ9ïLG$	ähr·ßÕš�€µº
ÚZcg+h%��4Æç¯šÅÜ
G—‹œv’¡~='SªÒ\'ðüIŒê�l_'Ûù…(	Ð¯Öº ‘gÉü\Þ^ˆ5þt÷‰Î!QH—�BºÌsrªøxƒàÐÎlF#§T²MšB”Ä	'H²ª" ¦i™©Šƒ‰ê‚1"âÉ‡,cyK:Æ†C€cxÎ|þ¬
¶FNbÛWN*m—üëb¡ñ€…·jrÜ(ð1qZü¶<.—º
mÜ�xEÆä¤	éûÀHyÏùùqnjùe©Gsþ…0
˜iüÄÁf$h;´ûò
ÛÏ‹‹gx¼gBvÏçíÖ8»§’ù.=Ð™k�¡atk
"ÿ”yäô4âB9IÏ›ç YYçHÍÄ0‰ªç:Í&db9®åXç`Í¦†â²“×J¾åº´5ž‚”çÆƒq¿jpF”õ¤h—
ê…Ý,ã$@ø—2âL×ó²¨õ¯UF‡›=6óž¥È7·,ôñŠÕ&ƒÈß|y_Æ¯h‰VDšº	ƒ}Æ»é¿%×™d%•G<–•ÓhæQœ®û˜]•9„l¾‘ÞéÑ‘¿é'—zïûÝÝÀÌä„@%Z;r¼£“²9„G.i—žm­ƒãõJd
^±=$äj6äˆ.IŸqøÓç¡do Œ”Nm‚-úw	I™%W$(PÅ…n–eu¥ÒEÝ”³ì7¢!¦ó²®³‰Œ«1±|`#TàPƒ¿ÙŒ6N`§"!�5!©‚ÕgÇ¼Žo“yÂ—ÚÀi¿‹!xbx˜‰Däø#"‚H]p–^‹xûŠ^‡UŒ‘æ‹KÃxàqmœìÄuLÈþýðÍ	"íé×©‡óó¬èœCÙ;=ÊÄt1|Kýßÿõß
½ëÙFôÉ[iüa‘ÃEG¹l´œ{�Þ”Âh:Ž‹ÆšµÎ’O?'SœÛ‘¤’,=ªƒ½�¹,ÊE%.b`¦j’'ä¿á:¶1¬IÖšCºOFÓÊãyvqÔ4/¸€¡pPŒÊmÇØ%¡nIÛŠV:¯õÃc'	±*›U01qQAìˆ|ZW†™†¶õÇéô¬Xr¢#–~9z{ð~ÿÕó£“a0Pî� Òs#NÞþúæì¼1f­¡¡-é}„v'ƒVÔBxqöüä¥¿„r2ÝÚwvvpòÖ.GÃí6W1yÿêÍð­ Ã+õØ0�Ê1…%ï±W¤^ã×¤.¦I"É¸-.Ëh1‘-~±ÀÑ¥I‹‰h·!“&–Y1.—ñÁÉ/D{è?7"ÄÑ†)Ïþ¹°Â4s@•,+¦��ÉuR§U6o°ÆusQöS¼“ÁÂã6FQGÝÂ»ö“´.ÛGâlO•K»<Þ
 ÿˆÂšÄ¼:ó—ƒ<X…ž˜Æ‘ä#e}McÔ(Æ2®îzžÖÝB�/íÍúj_µ7 ü?Ø–q{sÎ¤Ý[$\ú2·;d�‡SuÎí™!’tyþ4ÕYVÛ­æ”T/­cvuÔ!GÈHôÛ˜Ñd7[7
Z�5ë®`5Ö8J¬8—¶‘¸«Dú*“âÈ;¦e‡’=W£p.±b´b‡ìÄ¶Š™Ð�F ®È¥â—�ÌQ';QzÙ	Ý�äøc>ùßôÁÚËšié¶Á“üc›p€¢MS¿v«Û°» lò#½(Ü˜5/d€ÓÒ'?w'öÚ&'buÔ°°Ìëö=	[³ÉÊÄ
Y×f²Ì­ËÛT¤Íõfg¥5~#?Ù¦¥eVøöé™?$iJ¤ß/É}¢ï£øyûÌß¡ßU¹™+šóÝÙ1ƒrñ¿AóÈ·NVC�ïä._8"&®–ƒÍ�¤fý²¤G-	Q5p|Œ:ª�×ÈßÉŽÖÏ¼ì&>^æå(iý5[×ÐÚä´cÎg37‘@½˜£ÞJlvÐ™ípÓc�c¦lOŸ©�’Åsáu…‘/û­Jc¿žl>uRÃ†_¿”ÝZ¯:â‚3õLêFLþvq¾5©�–	’,® ´²Ië �K"<JLÃ'“X"(^üE:ß:Û¼8Ëi½nŠ‘v·–´ÖëÉ_ÙHéµ £Cî6—ôez{ÔMc@ôi†–+®#ehüD;@åÉHSœ†È(0Zt.#ŽlÍ‰fŒá<'*0Žë€ËûDýþÿ¨qUÎÇ¨X#w¼´vpQ##Gæ¤–2šÄ#fÅ6˜•cmÉš–„!lßØ"+ì�–¯´Øì*C1oN–7¿mM}÷n
¹BƒtdÓvÛ÷‡_!ž0å™ƒˆLµù|/Ó�óÌ•¼+¤DkmO4s‡�(Eû¹®x©TßÒwÀ%Öìa|`1L1–DÃøÛ�üæ§øÇ&ÿeÂO�=å¡ßƒÐÿVÆIFüš=dµ�¸íà�-±EÕ�hªÕC¸n§<DÚÛÝÙÁœ/&¼ÕŽŠrs‘÷u)}üa!¼*H&Ÿ©¥v§”dÌ±v¤Ó„DTqãq&I›E’[ ’ï@ÎYvÇbÅ2¨[‘#�GT¥Ù²ÐY�‚d6Ý$A‘GoN$y)Ï±;a{fõ,V{÷Jœ°£	öŸ&Å#R_9y¡Ùä¶Õ‰a2ž±F+™äåešä¬EUFÊ«*„$µ´ƒôgºž:Û‚îŽ¥¶ž{\õk{Np¾ËºYLjßê‰ØÓQ62vZ†•@UÖµKT¹Ä„ÉPÇê%SMÊbÆ€Y	H„dUÄFÝ e¿¢iÓ,�^ˆ¿ŸƒLÒs‘4,J&‘–¸ãnñ#IáÉÑÂ‘çzìÔ§Äd'>Ø%Ï/b9
xQ–ÆŒù(ŽûxßÐ„»4QLxyæ ¬Ö„Ö‘ö.zÐ©ª²Ii¡çdLª¥!‚O�‘�xƒø˜DÑõ=Ÿ{®tZN Ihc#-¦•©ÞäDeï`t„ñèÑ*w¾H÷Åô1=™Ô&Å)d¶Íl*œ4‰ñ=ziìjb¥�ÆÔùÅ‰Ý‰ÈD·4cŠ»f,*Ò�œ÷¹Ò¡7y—´¥.<È‘WZó’]íÊ6Hù³–œ‘8_­wñ1ö]X?ÊJmom±HºÚ³_M*ysÝánµœ°ÁYM~u<‘Ð‘(fÒ«RéìÄ>çyÖœ„¢=‚©ƒï¢óÝ$|Ðw•FŠr¬=“Nã’ ùæ¬Ø}d)	[ÉN²}cœ\¹à“(Ù»Xzhè'À» µõÜÆ kC¥ÅÆM((Æ·“b2sß]�lWÓxìLÈ€³KÞ¢�iˆëßtÝ3£%Ië?Ïó5«²#§p“q‰ÕkA†'ÍdØu‚Î‘/uû›ý`ÓÍ°˜´±&�Õ¡‰*D’}„€†ÆèÙÈÌC…8Ph)Æâ¸õ`ýÁ·Ç/=&“}%mý«ï2h¾±÷-lŽéè“eÐÎQ­fê06’]p‹Aum’áXÓ”x-åœ‹¨Œõl^">oaìžfèúeÓ’°oñËB×‘eñÍ7CBX í³èûáé=ß÷Z¦mm¿ÅP¶žàR&…ú³Ó×vWIž%Êm™gì›ä±…*�ItŽ¤Q:…%×é¥’õœ=¯Gèß4Ç•±x‚Úñ3öÓžÚä-&IûsúþŠQ²Dƒ‹6ÛnÆ²WÌœÇªž¸ÂOo{ËÈÙêøª3!è:Šrš˜v�¸%«EI&#¿ðiÎ1É‡hPö÷È°m‰¥Ÿ)R‘6 /°àm${°ðG �‰Û=}Å@¦¬…å@ðÁÈõ¤‘ ÕØ¦GÜ�HÛæÁTÙ‘%²«2¥ø Ë*#žÖ$Ž�«Ã’ÞvsLÂà
k:›S*†Ü�s(ÙáÁC|úâ±�¿ˆVB^ä>ŽÐÇ©)˜ª>¤]ˆ\¶çB¼U‡c”í¬�<˜ÃÆ›bß­L¡Å1ÃbéM0¯Üx¯Àa}n¾Ú,OG¡K?SÚÔ›lv×$&]ž+uFÜe‘ý&Ñý»µQu©µ?¨í…fL�í…×=½"PõH_h“é»ú2R÷ û#°6éÖ¯Ú —ü6A2H­Á{+Ba»	Ë+y;!öw=ž
½¨éè´~ t*LsÔ,}}�Æi¾@;SÌ"µ}Œ> $ƒ>ìEV#mè[Á%ÜXZ&Ûõ
¿AC_¸äŠhl_8éÆÐuaqkžO8‚ç’¡Ð²	eD.‹‘±Õˆý˜Öãµø�ÈÆç¥v;²ËiÓqlBÓ*öwgÇÆß_÷ºZ¯µõÕ\ÄsÙFÝF&»&,[ÇÏŸ�!|•ÔÃÅ„(a}×uÖÑÃì ÿ±øôluªŽÖ©�Þ6�¡¼î†;Ô¥i×-.“yRÔi&­Ïa˜çd”{¶t~×bT{XˆO¼¢>Í’Û‘îû~ñŽgîåÂ¢yÙ†·O~Þª“
’yÖ÷?zÏ4àÎ¶u<ã½êêÏM¿Û7v”tŽµ=\ ÁFßG€·sô£zÚ!éf62L¶Ø0�9Ðd¸Bž0Òr‰œÔb¼Å5«!wNú¦Œ'õµËÚR©¡€×–ã+¼íoàšwZ;þÒØ¯âsßçs…¯«Ï4àÿùüõ|6£ZF?E¼ÞA•Á»i=¶I>Ÿ&#�&}äVÇÙe†‹\œ�b�Ô”Wº�L~C.nBzô;ädÈ÷„‰�ô‘óøÅb†lgŸ=g��ªd<Fg6')"kn{R°8Œô%™¾±“5Sn)#HRØôt7›E¿Åøçi@ÆŒLA-2F¢CìÚéS¨Qc  ëˆ˜ÿ�¯ª[Ý={¾{A%�Xåãé'õè·G›'¸Î§ÝgôóG³ ýþä	OHYÏ³#aŠØ
ÀQ:S?ît„ûi×ûôÃ£®´xâÑ&{XL{„/‡òT«À–58&¥
ñÂúÜøøÝÒ·|ÿÐ}$Ãý¦àëNìå%¯.Ö–� ½â:‰KK#è½î™cOrÊð
¸^r;§(<i\)Z\¢�•Õ7$U¤XÆ2
ÖØò/´|CX³×ÏÜ�t¤¬õ<·Ê«o‘~upœcÚWbéï6Xeã ZdÅ0ÛœY•ÍVÆõ:�ÆÓX*X»X#s\�¯Ó–èü‡§Ïû
3‹1r«®Ó²�Š9sk¯!ñIDe”=/¹©c¢O¸’¦™/áž–ë,Q³—'Ã©ÔÓr©s•ÍfzœW)€æ»—³×…k¸ñëE£o6f$Wz+‰'ëÍ¸ø¸L¯Ð‚9æ»‡þ—wEn¾™Œî*¨Owæ2�»·~ƒ©S{°IXRÿ…»o†Íõ‰õTìzcÃ–ô=r£�”m{ü¶íwH]¯ÃVÀ[:£­Oê¿G/åC5Om³BU.)°8;ÝÓÈÌr-)·Gog£2—o¸,h¿¡MEÞÚÞI¬cjñøp·)“Ìþ¿]Sªº,.x"'Ýf/lÝ'VŒ¸iVÀ LÆ3 xo_ÕéÙ›ýƒá�´=i6W‡)úÆù¶5’•ê™�¬áª(„h©#y†—h�ìì�ç2îOu2ÎiE.J˜PcÝ½üð7ßïâZó(ÝÙß!:Ø–”ŒÍÑñÖI“%c¯ÐÛ­š,�ð!œ…$h�‹²AÝ©\–åëµæÞŠ4ÿ¼§õü�9…•LØfZ•‹Ë©$¡¸¤™XT¤¸´ÈÇmé©âûäYcoÂQô@ -–g#]%ÜLzW7�\  @æž)2™{ž“$Ëë�«ááZòÔ^ÄM&2óš×åkÛ‡^¢Q«&¦ô…'sO!#»ˆ[³|ÙÒ¶£øRìÌjKD|ÊtIÚâˆë‚p­·`A0 L!�Qw–¿Ú:±:™ÈŠjŠUš‹b‹*76K@B÷6éH¦6Äb21SûòKßæiQØÆrÎ‰F0æìpÿéOÿŽ´3¡óÀRÌ+ärûª’å&¯÷VéÔz,×¿G[‰Oa]é¡k­íä©IÕ.¸u°0×ƒº‘õ½ŽÕÿ|¸ýù3»çÀý$ËŽnìÞÁÛmÍ¥y8{à^?ýîo­Wê¿žÞO‹Œ#(è"fÀÑ©íñ¡ëôÿ&ÌæñQ}l„En`ñ«S$¼í½9ÎŠ«c ú®ÈÒ„Í”ýô®0×ô8”Ë{\˜R#ÈYMÊu_Š®Ü#…N
v"QÂV#e¬ä~ ÜÑbñöª¯³/áCíhíºx»“0˜|õue}C_jh¾òí·ZºZ
—åÙ›ôÅKRKMŸ×‰u7´—nèºúšiÒ™Ú¦¶¹)ÙO=ó…˜zûäf†¿žÄÃ…µõaxë†tî²­�å×n0ßs[Ä¯Ý {®;æ¦�fÈ`SV<¸�ŽA›%Ÿd(<Ì‰F~k)9€U– é=
ÆI¾*—ÉsÝÐÜÍ–ÿ†‡
p“EÎ¹c¿?ZÈ_:‘—ô_†z,‡Üá4õ�}þ÷oî»�¹´‡Mˆç¶·Þ+ök’_áÞ£, UÅ9»÷.ÛøÃhÈAwšÍß)À?$Ø'OÚõ¤4ÜvÃNfM<ä�M(â¿>dï¯ÇÜ]¼(øïLø/ì$l{²½xœÕZën¹þm=Wmi+�œ²]¥^À±�ÄEœ¶ƒ.6²ÔGšzDNHJ²ãÐ_}€¢O¸OÒsxÍM’�Æm7ÍððÜx.ÉÉhxAÇŒLiÂ[­dš	©I§µÓæL&ZgíÂoó�fJãK¡ðßŒêÉ NR†?ð…Ò2ác3†”ð»Ýê¶Zƒ9‡ÇçRpÍxôZœ½Ý's&“8aŠè	#óÁ£`—„@ i¨É¯ÿ—yÿB�QÂ©¼"‘`
½~sN“sF(6ÙçW„Þ�¨¦ý”^1IP"™žI®ÈãÝÇD	ä†Ó�ùX2¥NÅL32¡<R$ÑDÄ1Ñ††dLöGÈ³T„*Jòüˆ²,WSÆu€|Ä4£’å³$ë;FlBç‰˜I²˜0 øyð³Ó…E„rÂ¦#E,B.	�Øe0ÑÓ4hÅ3ÖÝÔÑä[çÊà¼K®[;a<&Ã=P€ÇÉøúªzÈb:Kõ�˜§ë×tÊ†¤�¼ÚËek'î&%NzÍž{øôðÅÁVÿ”�X¼«N·ÛÚIbCÿÍáIŠ"wtð¼›và5Œ/[­�éìbLÀäWädvÙ�á883®;@k¿I>öH†ä „Ûû6N®Ûƒv�´#tôà�ùý‘³K=Pšê$„“¿PƒXˆàoÊ+1e4RL&è–F?É>ymÐY¨Ñ)û4ƒŸ�ö‹£s˜šõÐ˜®¡ë´¡�“¨=Zs^žŸ¿í u–ïŽ€_à18Cç``¡pðÎ‘”BÆ“äÞ§!Mp>¹õÈ‚rm¨;¥8"bÁ1úEÔLÆ4dÇ‹®UÖB¹Ktù²–Ff±÷•bZ}£rJòˆ3‘p‚t’¦Èh!ä…É²ãsîí‘Aé¨9„Å cab&PH1XI	‡Ð‚gJ:,›\0ûŸ…™Ñ5Â¨²IH¥…r>a
Ròa1¡ÚÐ€3"–1LAÁ‘O,Å´œõYQ³ºž!Qb‚]çlš&fY]LÃk¡‚¿ÊD³çP·:¾xÿt`"™wC»ÛƒP]iÖiÿž?à›Ý'�wŸnK�ÛÈó¾®Ê#I´‡cw%·¸jM²aè6¢«•éÌä0¸HŒ¼ßL¥Òa¥TÍ !!o“Ý¶tÁ“Éjÿ´Äq¨gyì€]Ì,{v(_f7TX^ORZ‘™]	¤ºi¹Óa€Ú~ý¢÷pw÷E¨‹ÊTŠòÆ9èmy&¢«àÌ8´á†# &V›„Ž`&<8¡÷>eÂxÏ3ÙPLßØ‚�Ó#>_UÒßýøöÕ›Ó£Ó�gçûçÇ�OIb+,TNdžOæ45=&¬Aú¦î'lÑœÎèË,„‰ò’‡||ÍdsÆÉ"ûZÈô•aZ/…e�ÿku#L¿UYÒ] ò¼Ónð',HìÖ*Èÿ¦b IøÅØ«¶ÙeÆB«ç‰2‘ÍRªýz6ÅLû–Õè9^ª66Ï·¤ùÚ,“¿qØ”kðšêÔrcÂ,}»[tH\ðö½WP;“6#kìºåL4ë{fªíK¡ô	Õáè`O üfì&0‚„¨	Í˜ÉÂÇL5pÔH…dò¾*á|e€>àüaŽðC—˜&%ðˆ‚�M1î6…‘€d|wl¤+À=VLÿZ¥³ñ²çÅö9ÐiÔE6È{ÿm>‹fI¿<³kËÃ-”fôÀíÍ(e²B)”Ùòe•AkMù,O;�ƒ³$¼ Ð$Ànkè<O O¢çh¡r¬ñq½t)†ã©”z/qÖó *<îÃïý0„òw À8xõ»ÝÑƒ‡wÛ�K T3—ŸD�ÇOoÞÔ™|>¢,B&åª �·¡0¼‚ü:ra`ûª©j%ÌüZqÙ¹>ƒU€9¡qpÛA”!ùC�ãAÿÀ‘8�_½ûÊO¯Èeâüâ™¬\X¦û,ŠTÞA†Ætø»AA!U�À²„hLm@4˜ˆÙ1\ÉÍ}ØêÆÉ¥MÀÀào¹‡fÌÛ …’Ã™ÆíF…b•¯.H‘3éfæ¹×µý¼m"*”}SoN±ù�¨±Uè0¼½•õÄß`i^0œ©ÅÉÛÍ­Qo7¹8e‹!õ:ç-0U§ûY¨[°5Ç„+/U`u§É:':‚-®PÃ&í*cQ-X¹^•"Q›B‡iðMªzßÚÞ±Í5r»Qr�Î ‘U¤�·¨6‹¢hŒ<Éƒ˜†ÿ¼&AÅ*ÐÄFŒG‡t3™Lñ«ˆ=±€ðeªnGiöúº™³¹F¹iÅÖ³#Oe;mä:rLÓ”Œ(´N-J=¿¹•„¨7˜ëöŒ_ppsÀ.é4KYŠi3)bWïãA'�&ö	‚“ñŒJh>UÆB¬ð€Q›ÀÛrõ³dÛõÛ,ÜÖ C²ºhq9L›Q…mäËæ·mT¶‰�Îx¶n¸\=‚ ¦TvvÜ2F+pãgulâHå¶šÕeÃ"ý^¾£D^{µ-¥Õ¤º¥4{ÙeÎ¾¶¦«çB"Ôò0³É'W$EAÜ�Œ^¥‚Ff�Æ%£‘ÊOÄü	]@N g1{müÚCeÒiOíkPQ%£A|×€O—óÈ¨2Ã�g< �Q©øY¥wŒ’¬˜·í˜úÎ@Àï"°*<NV…âÎpÕ&Xåþ”ÐÕÁLJÆ+�Ë¯jéå¹]¡ƒŠv“v[i)«YG±¾ˆ7¤v­Î®âéùƒe7°¬Rî,«Pä’áýfËjÍ–ÝŽ?XfZ–ß™qmœ8µ{$ï´NÐzÔÔ`iqxSÛ¼Ygm0µÊ¶Js‹p¶t“”�pÑ—åæÖ E¿^÷óñŠˆÿ4Ž—_§¥…3Ù#Ã~a:¯[×Ý\còBcó5Æ÷7bÚ÷Cw"ShlF\yvÞÖ�¿Ñä}îÿa¾vU…9ÚaUf½
«&É~YŠ’}�¬Jv´ë%çÌzV•VÞÂX~·®'šsÙ _îIÓíf*Bšâ,œ»þ}P+Î�š‡A˜Ã÷8)Ýýæ¶€˜¸“z	í6ùåG_è^/­ìÌù²dÊŸu!æA×+À?–ÉÞ½9(3q‘b^5¥›ŠÍ·ò ~ýÇ?#ÛþÝñ[èF,ª'ÔÂ(‘);¼Cf˜3 ÌñÐÜá¢ûŠ$<Ñ	Mí‘Œ¥`0ÈK³8žOƒÔu2¹·~ËÓ5P™¸•FáX¬ž’1ãL&aå°XCìd?³Ý. ¦ªwêy¿ñ¬lyƒ3¹ÿ«£²ßÐéØŽ­E[Ÿ7žJÔGk' kHj»YÐÈšÚk3Uc›l�[„"“†“›u cR:Ìxá2l@¦•Ýú�paÒp#Ü²!opVe—ú5»x¾1k©_îß·Ù˜¢¾|_Š¥l‘˜ë(¦`›¯2"ð{ñB/†ð²‰XÖ¯_ì õ…º¿�‰†äÃ¯¶ N�8ai¸Ú@7dîó¼zQøµPfWÄÞÅ sgÉ$ÆF„R9Ü°¸öäªq€%²â,Ñx‘"b²˜$!~¦D:göêÄ~–o¾½‘S’‚ñyO@^°)¾°Zq¡ñŽ¤¾÷=s�ëÔ¡õæ]í„íŸre®î…Í¿3Ç2î~ÛþŠŽX
Ô'9Ž¶*Ã+‘Æ|¶à-ò;>’ïŸ<zò½«Ð~þy’óùV®ÊíOn‡fY‹ÌÖ/t•×wwÒ å
†Ä€ŸÕMÊÇ�H¬±2ÁLWx9tP½À/XŽ¬Z¶Ù ù”fïmÏñ ÔÌp¸fOŠ4~‘†›6¨†Ò- DX™o?
„öb<e¼¦tÑmøÛ”šŠóleÂ»`�ß~¹+àœ˜ÏèåŒºÅrËWÕç\[PË(g¿ç�	žUP{Ï—4²WùÄÍèå¼zeN9š5‡¥6ÚÂh‹Û}ƒE±©¸/â`$e±&4¯ÁlÿðCà"µ51ê¼ã•tð¡£Ú[},c©;ˆ2U¢Hh½°O�UÃ¶’ã?,‹sUþß)ãHu½èx¥Xos³(}ýyy77‚¨QóinQi| mÒOÚ^ÚTž¦7�t2“ÝeÙ?¿ý-£ngÉ½pÃÜ ¦G,çK'0¿LRnžžz} (+ÑñéÉð_³0üðçÓ7
½6BJŠ_xc5;swX:¢"IQ–fÇ4''’g§´JxZ]žñ¼¬Š&Ö3{C§Á+g(«¾ø]¨–_¸ÙÌ!0¶Ê–D>ü:RçîubIáx~X2”¢üo¡àÂu¢‘ôtî‘PøJG‰�ÊAŠ<ýõáâ��bš4Ó™ûã
”0>€{æ>M_,†ObÛs,™÷¾€ø|_ÃëãÉh§™–˜/\9ë§£7U¡úKL«Nô^/ÏS4¼…s•`ïé!íŽS7Ž9Ë°ÛÕD�Hõ€ëB9n•x½ƒÕ³a<X*PF~bˆ«vÒ`ÖbŸ•Bù3;B	“?ñb)¨×†Kd¨[nÁ8~�
eŒO.áPÑ­P!Å$G�øÎ$8;Û9”Ù	‘üÿ¸7nE¯x›ÌF†
Ê‹ö�w6·…ªÜÊñ¡Ô½³Ðã�*ô“»ùûå¤uóûáV{å<E§DÖZ­EË‚…#"�”Þ»nm‚Ê¨v„ápôò‡eP¨»Ò×FUxâæLzîÊ2®ÎT:�ÊÎºë|fˆ|Àäˆ²� ùør¨¶þ´ÞW�zn;éu˜0,”`I{fQ o„[çôvJìÛÙ:˜€óÃ<R%^a<øòØ�ˆ²A`ÿYŽ‡åˆb— a©{ƒý?o¸ˆ\àMÖº6ÈBpwƒÓkÝKŽ!<®™»ŽH^Eé8}/›…y¡KDÖá_B
R¥Å1M²6g=1v$û#ë�KŒóÅßfDÄë•v˜)t8»/j¨juø•¢
ŠÍ§e¿\ÚÈ8Ír)žM@xà3»G|Ðxž¥€éuë‚ØAêUS�¥ƒûŽD.1ÐMôµÀKqX
 9‘™ñ&nø$Ü=0q‘¬ö—¼ö¤f?ž¾éÓf‚ðHªúDšyk�42sž©¤T
Óí«?þ’À�Iëc•¥IÓU§œ6eU§û¡;K:ÛI[Œ`ôó%›Ô4»Q™ T¿�7†å�¼Å:TŠO¿|uAóì	ˆê  ¸tæÀnbVrì|€­,Â36Ñ>àN)ðU�Q˜ñÞÛ<’ìMô=Æ�^ÿÆYè16ŒÑg¢ñ;ys�Ü:_bM¶ºÒRGjƒãÙïm‹•oßø� «>1T]€u"¬)±nýO7žqû(ì×ò&Í�Ìb¸·ÉÂ¦õ½Ø¾ÒÉ_+‹ôÅjÛ&ÔnGjÎ³u#fë;²Áa©XOÙg"ï[¬1Ú ÂH‚îïBï*ÂÂ
w]á"z£àç‡-ì¢j?å#°v�™ae¡–1ÔR¬”Vñ±=&˜¤H=mÒþ Mx© o$ÿ£IœÑ
¸�åÇÚ›�¡Ì8+ÅÍ+DŸÎN+Êk…QXµS’¦u^�2¡UËê¦$¬Ž„L13Ã"åS	D§sbF¬¦AÈAOÀÝKˆEìj°¸ŽÜª…IˆðÏ:3BµÄšò^qÔ­‡À•‘ØßªéqÔÀÐA¡ŽÿF¾²uüe1Ö6Yª:mZ¼½'l#s›dez$§4-Ni‘t'R§Í ½÷Y¾áãè) ìÀUïñ¢ëpnó”ÀóÉ)«Š:ÉŠ¶ eW·”³]<3_Þ˜Ø>è8€h^I‹_¨§F¡®cÉ„quõ3n´} •22‹^¨…§°•ex�$†�ïò«/ÉÀ%,�‹lq8BrxNª³*amž²¢¨ÊSÓìÆá•óFû%!7»«�ôÉ%‡5Wÿ¦ã;
ÃcM(Ï»¹õ…‚u/\Ü/;ÒØ¶âüJ�+`êK�¹áiÓãq›÷Ã_£ð®µ½oá·÷­Ïx]0ux\Ç2Ó/nç™ß&¯…+6*��HP'¬*IYf Dwp{´¶7�W÷9º9Fù·Epæ‹ËÝHÙ«‚!
<â¾’o�´ž�Ï�éø%œYÜâ­1§µÜ5ç·+´¢^ˆ ŒÓ°iù²ÉaWúÌŽÞÞ!a~ …x©òž¸¿	)há2x¸¡
ža�FëƒÖÝJäÔ(œ€ÖT£Ê>¿^ÜJyª3;*¼úÜù¨h¢wM~+·"¦—;~–ûëé_"\ìŒ3xœëÕý¯3¡M,#1¯*?1S?¹¸0'³$U¡Ì@ÏPÏhòAná‰=ÜLÆ`Ö^F³ÉŠ¼"Ïñnæal ž
6àFxœû¯ó_gÂnFËÉyD6^X/
 DÈ•»îx´½Ç’ãH²6ºÿŸâìiÕÐêšý€@‚$	l®Ak­<ý2³{Èì¬®š™s­,K-¾p�ððpí›?ê.†¶[Ã‚Ü8üŸ	þüú�Â(� 0#ŽÂ?B¥p—ò/
þ'AþGîÏ'ò8ÄênFVÓQ5;Ë¢ælúÐ3Ìn\s1åÿþÿ?XŠë?Êú¹–Þí·è–ê1ßå´¶Qkµ¨2Ønb&ÜóíÒÛ6¬ÁZE=Äuá~]œK‚ü?·÷²¤Ž�¾C¤6Ûyl#[Á…FÈ<zÇÅQóù¨ºÄÂÿì…@¸L½²‘Îˆ±œN‚}±‰t­a¹26¡?�.°c§ TRU3¿Å„ �Í8„PnàîƒbæƒÞfgføE©ÏaÖ¶(Hð9FU|™õ�ÛbÀ³\óùžqúû…|ö®ó�˜�J>‡ÁVjs¬9–V¼åt…l/d1¸€÷°-¿Dê–ÿ3! �F2£ßŽ¸ç÷jÏãiÚª¡ïçn§m#­<¸®k	‰6…Æñð+¸";Ž<C+—üì	i½™j
‡± ËŠxª¯‘ÝåÆ{ëD�Úu÷”oäŒüØÛ3\žrÖw�÷dkLl®$w%ê)UýÑÃí²<sC‘°‹Ñýæ¹
©@˜í0©Yã¸œO´7É
ÕSâ¸7›ï«½Ç§@Gi‘6MýGZCI“?¯ þyúEXÖ…Â oäM:�
[P„¶†s0Nð—�´ÍbÃþæ…(¾dUg 3Ö¡´9o•|×ìJ›´½Bø»´áŽôÝ»È–ýÿOœÉè=¯»Ž]¹�ßôAþ#ª»q.`þÓ¯;@18}úôqüûŽÀ¥Ë0U£ÂT[§³×Rm�m5LÉèæÞný`G ’ÿ“E^º,V¯ë*‰O„!C“Hòí/÷?q—>"¡¤ùWE1ÿûk¥AXé°<™úÔJ¢E‚Þ{ÅmÝWÞÙžà(Ü÷´<‡á™Ãzî¨««!¸á¿ËÔ×…^«sgã˜šÇvjP7Íp«—½:žxc=Èiž¦+éz•{Ój»Ê->Ùzž“†Ñ¶(\*µ›È¾ëy«íê5(« 3Jzwšízv3%þ�—yaê6Èñsv¾c]ëp`«½¬ vK¥`Û4)¾Ç©GGŠá÷6ñóR1ôŠÚ{°F~H¡0n-s®¦�²[æ44çVbK1ÍÄLØTj·ë·Ö{ÓD°¬u‡Ú@Š£\9gð’C6ÙÆ¢wfW§÷RÚÛ ÿ;{ì*S¿«û:~¸ã�<dÐw‡´®~©×¹ÝòyíLâLÖK|‚‹Ú��cH�û|3eœ|>ºçÖŸ\Š³Â°tN�·áë†þÛ+¾°:ŸiìLœêÑ�iƒéþ¹ƒ9¶…¹i<³“ë7ü¨¯K=¶ìûîºEºŒþð\Ï}¾®?ê¾Êä©W&ôÁö¡úæKÑÖC³dö9;®µNO)½*— §uiK8äˆl5~WV¿»Æ;‚±.Ax±JÅ…¼#oWó¤H~žÝÂD�²X©�ûv³c`¼ì£;÷øyÒXø G>G´[[õÒ»£+æg	¢h»Ý}«±%<Ûða¡Ä@‹÷Ãù)èÁ‡i*Xçr›ŒÛZ™çH³¢nIE$òVUE‰,™cìËFÇ‹àç»dD^7Þlö–i	ñ	R‚;Ø€_—ö°ËDiëVqeAË—ÿÉ./—sÖxÚM³I�X .ååp¤Š¸—ÌóådXM¡®ã`ÂýÓ?öjºz¨ýº€Â	\“~èÂ„ú0@Ö«PžËxÆû)dí)µVÜðPã$â	÷Ž4Tùd€]þ»Å^¬,³PdË¢ÿ¦{T©ç¾:2!6AˆqlQÍ(ºóœØ×Žðÿ«5éÇr
Gã¶'zí+Ô¹Ï€©E†»AÎÕIœ„z$-ÿïv“~a°XfJ¬åU9¡òy�¨TYÙû®Ú¨kvãY1“tÔ<Ií—+ÿU:€±[Eiü¼éèO~ÒqCâñ¼“øZªãÒçz>RhÀÎq,ˆÞ";uŠu¥Q~qU¾`¿�¯ÐÛÄnÚ]Š¥{¼ß�F
ƒû…çÖÊª[—ÝÇýÿ’ü.|nÑ?x@˜Žz´÷H¡˜”Üð²OöçÉ²{�h[-ô±Dœx·SíZFô?J¸ÿ÷^‰‘Æi±¢{^MH'¥ÞaÅvµ§¹ÂŒœë�>HÃ�Ãnö/n)…îð°ëB…Ò2øà†þ¡"v€ÅÅ¢çÔîÎô­a)8Föœ9!Çârß*Æ•©š‚0½Ýþ?^æ…©w*�¡º�êCÈo…c¦ë­v¡ ²Vìr!Šx/¾Û!Ó•1ÕcPéVÀÞï‡„ð)iÌt½í7læš- s”ÖûŸêãîvÞn±dŠU%òË@G�÷7úßZè…±^Mg£Ì¦1={rèF‚ï,O±”0ïÊ„Äl.Ìç_­÷§ùö)â}=v~ø<3ü“7ÇÖ™ÂÒ>´gvN2#³+œv&ÊÛTò ��Ø~\~ñŠAÿ¼Ö{3’…sëDÉ=új-oÔªË691�xk+µ“ê3Šî'vŒ!ôÿ´$Š<$R‹crž²l¸Ñžº÷§”9U·ëìó°‹N"x·Êá°=õ“ýâÍûGöPä…=Ê�B?@U	AþÒÖUšxAsqæÔL’µi�>ð’] 	Ì¯”ú_ì…UÐÔà_=ô°Ð?¨ÏÃ»oÃ«•epžÂVuc½œÑ¾Ø÷"Û)÷0MÝú¬gOÚ¿z³þq©æ:(”ÅQÜr3Y`¤£]Ó™<»Î|¾y¤}Ê6)D)éªôÛ¢ùwæ>ŽîÌÇ¢5œkŽT�æÜqq«óDÂC5N2¦Í!�wÏ<…Ì¯®÷?0÷vrÍ&Y7g�µ[}8Ï·BÞâÛMÞV|=ìçje£žl~õ¢üµbZ¥UBÿ�?¤ñ*�î=ÚlnÒµØ_G†ßzJ‘¸ôõÄ‹%|2y§-q	ýè·wï_¼œ’s]¨Œœ�é*–g-áT'-Mû·„žkØi/y['Ä—>Ä‡J�´Þ¹Žx„EÁãµÌÕÃ	uÙúl²�2cÊÈ'v-ÊüÝµý»Jük«^Vxae»½Í¦ÄƒúÚ‘�êß�ÍÚ§çŽ¬÷Ð…p�ºÖD;Ù%ûíü×BèÓ�í®KBçûN;‰á»UËábÊ¬S+Gµ:Ñ×Ù=hÖ}ýíKô²Â+k û#~¦j12“[…X‹Üº¸<¥3Ç8~ŒÐ@§ñ]‰ýêuìÃnJýð_:Éõý°~„•_iõ4“ìÃ®Ý†6#›ÁØmv÷:,‚¶+rÁ¯ÃÓ.ÃšÃGåŒÆ‘é.¿21~wÕ–YGv"4R\7¹=4+šŸŽHo{AŸædîgÏÌNo¿Äß\üi†|•J‰ì„ø}•)w™Ø"SÅ«ë¡-ä°�º,Ï‚ñ¿´ÑÔËw¸rúþ‚µŽ^üð|•'¿Ò^»Wß°&»aÙÀ¾Ñ›/ñŠ¿ßŒ¿±ì'¡Ÿ÷ã3lÊü�<#ÓÑqºå�³"iH—;Ìîmr^}íFÈ"«ÊMKë–¶1ÝõW:ë—Ë½0)­%’§RÝÓ–’Ç3lº…Èø4ÁmzNÚ~ƒ$]~OKäW†ñ¯VÅzó¨¹‡˜Ý]tÈ$ÇÛ®-Î uÆÐåf.%µÑ}#Ç}S×Î¯Œ”_.÷Âä–­Š¸¶'%-ìb3x¾ßÒMÃíÇV…�Íƒ'NUÿíU›.ìÓ¸
ƒc÷[Ã>«lh[[(ªJ
©_—»”ÇÝÈbNÞí.Šf—dwK÷…üoËî÷‹¾0|Kæ<EÒÛëW'�Y¸_ßÈâ€<gãt½(­^¡¦ËÿêyúÛ6·öÇ3ïciØ=>}KëdÂ[í£wþd±`Ã‚B–ú� Îû/¬Áß\ô…áŽÜ4ŠÕã–£Z0ïn»:�×ØÂ²ÂdqüòLH2#ä¿21þ¶v�õ‰|²OoïãÕ„Xè¾ÖÚ¡\Óëh‚VCjÍK—#mÕqÇï¹‡_Y¾¿^ï…Íä2m‚Ì¼ˆ%ùðá©r&’	E�ððMª2�Ó:ýÛ2õ…Í�#uŠ©Sý½.WSãhw£KËÄBŸ„fœY;†ÀÈÚ;äUôo_œ¿­÷Â¦?a†šX]ßK|‚îwùhiQ€ÖîªWN•›¡‘þ`ÎÑ¯ò?w·ÇÇÇ|F‡„eØÌAâ»!�,¸MËô¡�á
b�û¬qŒËª(½þ»�Ù¿ð_Ø�úÀ°Ès•L§DKõX™By¨o‘3ÞDÁúrq�¿Ñáï>ØŸËP<•é~—Ú3ºÃ¾$Z{³eù±ƒ{DW|¬�'ùÚ«¹€kg`Zýæ…ûþcÍâ˜³ë‡éq$èY¶�ì�vmN§øÊ$D²%åw…à¡1Óêq"ð‡icuÉnº¤ÜŸûÔak[˜³<ÏxÔ”Í²cf[D¦H7¿+×oK¼0“Ã(³1iÝÆ`ƒ:·ÇEH¨&È£i/Ù¨HÕ
¦wa‰ÿ63=È0ƒ°5HÙ>^¸‰äSeJx›«÷,y"‡»ÅÉ.·‡Î�—ó9åxdvXÞkWõöÿZà…‘%�Ø>æîññZaq|§`»Êª8cýÑØ!æE(¢–›÷ðûºNÿÉñòôsàÌ;†#¿/féÈœ£ÛÐè‚�+Âæz¸ž4`e#Gþ�íz[å…¥Þ�$k›ÇOÉLOÇTÓÖ±În¿Ö*“CÌóoËóð"y¡ÇÙ‘�i�Që§•COèÖ^‡+Õ©<CÍÉNW»vG#Ó~[¯ük�FÚXØ9}IKOUÇÊjíâ*U	¬ë�’±)<êóIÅçøo¾/Aâu)‘Ç9 ÀK{nyK¨F¥�_ë»b5¢Éz—±ÌO;ß.#E‹z¹¤{Ö¥¿k’¿¾*ó*+XÇi—ãýì.�«“»Ž]9çjŠ‰‡7#L•÷_ã>]0ae1o„ªšvSYåñF£oNÆåÊïÜ’,$YËù&T¿Š¾jó¨–‡qç±OxFà§"#uWlc¤üIÍë¶½ß˜Þ5¶Ö]EAMØåOI �Àç~ž$UjdÑÞqÛÚÅ}¹>‘žÝa×Xrî'ÚAÈMÒŸÞÅú¸"EÔé¬«‰^½«ÁÌs‹CIçIÐ.¯éÇœzÉ‘ìŠ¼y¿’~Xån1¤È‰åu=CèÈ&N`Ù€$ W„ªµž†(S�
ùÙ¶^HvÏ5·€•!1Bês=¿{Ð¿ƒÿÂk¡6“bÐ,Q·ws+“¡ËÞ­ww¦eå·ËñxÄÔÛí}s@<²†x®·O>\ŸÙ<S¦ân2zÛ û·Žq²ISžLÖ7†óT§’QS˜Þ�›Ÿa¾�k‰7xwÁ.d~M)¶fCg7$”aœ0œ�¸É‚û]‚ƒoŠð™~ŒŠÅ~ÚùOã�üxXñ|J.¢À([Ûo‹b­¡‘.]¬A'ß&3,?Õ/ÁüŸ@¾‹†·3y‰Ü?Av’ÐÜZMRuÜŒ+_2³¤£VÑ—~ÉÜîúq�~ôM8?tx<_€�”MÛþUÓ¤�K«†Ê	+Ø¹€úhšTÞgzØøÄ{'ù[à× èÿ*0úÁx„†iFÁèQéFæû!7™ö¬Ðóáªëˆ5×·‹Á°Øua\Í©ÕïÂ5¬Úc0¾Kß·Œüb½ÿ�ýù(ð»ço¡õ¡ß ™#Ð„ÿÏ„?m¾Í”Ó5Sm¡}VŠ©Ø2¶g,‘ |o¶8-_ÈëÜ¾?nÁï,ðÂˆsSí{ßQ4}�:UO2{U’%‹`Ùå¶7Kðw•Ä�Û$?@R—>RªÃP^áÏZ¤4ÃÌ"gIà¯çb!j�œ9—=zLs•E]à[ñ(üGàÂa©ïnÃmª&˜o+P>0…g9záêªŸ]´0¶‹5ó^Ìð—>OÒ�Èí@Hå¯FfP
1Á‚
q*pÇá¢¡"©åžâ‚RGq3oÎw?e’ã™£k¥Êý3–:Â×ûòo¯öÂ¢ÞšÝélV½�EåMSÔóâùz*Â\l^Qvöô™­t45Þˆ`ì‡´zp˜Œ ­•®ÏŠ«Ï·L\óÄ¦ÂR>Ÿ÷ðÙ:ÆÙº‰nH—p�F5 %Yîò&Ü¾ÛÎ?}!X‘õÐ“·Ù(> §°™p»Cèvñ/÷M),yBD\ñÅ¥�Â"½Ç!”CÓWuÝ<ü:WÃB�ñd¼ô™¾½²Û›w,Ž‹Ñ+&ž§¶¡!^×Ñë»‰ð3ÌrKZ:Aý"ï‰#³‹5eõ-®ÛH”né]ŸAÅ€K¯{ô]DÁÆúI
=~&â&âÏ”Ä-ÌlwOú$¾í•Î­A†’Uî.IµÎ]%F‚JÓä]}‡÷B¦¶lMÔÑ2Íì^Ÿ‡Öb®'…é™*[sà”VÚ~ÙU [Ôq=~{Z‡¼šHYpá–³7öçÃ„BkY„:ÃeG+tÈœsãsÍ›}ëY+„¼‡@¾¢âoà6«vÅ¾¥Ó<"yœA[2”¼Z<maƒÞîÜÓ”ï·‡˜Nþ=0øÜëŒ�À¸¸.�‘ÆÂ¡£ç�(¹ÎöJZÂª‡îe±+/8,}	`ü‰ÚÁŸ¬?nq¢_ïûº5â+ª£­èÃ²Á“Â–l-10E�ÅËÜwYúî…ÈRªoàND`{a¢ñu¸�7SZ PËl»�Â]£u!nIïš(®P!ó#›èñó§(=ÕnG4²3®BHÄšd*Ô7™$XW×¬$Æç�Ö›+�þrK†ùBntßjòU9áX¡ùt•ì¯6)‹­æG$¾\Ù®o]Þä]ÂûŽ\(îê±ñ]’~Q�ŒaŠâAüÀÏCh/
™ÕYˆû¶í·5Aâ�ªæf7sDØÜËÞm¹M—ˆ2\Ù`·ÿýz/lú‡»ÑènÈnÓt´j7“8jä*æàß{Á…Õ¦öï.ÀÇ~y#0®A�ò‡ˆKàQ”,�á2A$&°û@oka
; •PºÛ¬¬:"R–_¥çqÎ__H½Y— ³ÔYÙÛV‹¨¢iƒG'ÅÌç[pÛé¨Â“Ö—ï*ù£lFé (�Âø,(pÅ�3ÀüÍH\ÅB�švÜ†ÃÐ U 5ð�1]Å�H}WÃQ$WèfçÉïvïo/òÂÂ†Ö(õÉuÂ'V«wXÉ²¸ÀÇ»©íÁe%6«¥ãÖ}·.?××Í/Á ¢=�/°�ì)"š|h:NÅ&"	E/¿yõÌ“Yl@±ÊÝ¿JÒ£¼ ý	K}Öæ4F£¼ÑOøÑ6�ùÐW¤ÝØé5åz[Ö�©Ñ/ÜâY£ú2Þ™Í-mËJgô´çy—ÙßdôŠóT1Çu[ŠPßÙS¡ôë³ò$³õj@ìþ2KH	!üÃEˆ(ò¨ˆ	>¬_‹ó)vö•-Bp$3ŠÌ€­û¶a†Ù,K·eB�îoïÂçFÿîR/Ì)÷&b9=KÊƒbr˜`C‡÷Ã^˜Ìv�Ú68‰ê­M¿êù's=ê>cÀùã™²#aNÖAÉüÉî¯»Ý]ñÕCâÑ9µÞÒQ½ßÞ:¶Ê¸z·s?9xÃ{!“€·±†çH¦Wn':ÎúQb…HG6�s^b�¥ø|»1ã˜>øŸ•ÿêar�;åÔg{–®�ŒMd=^;v]�}Ümµó€­¸apáÝHþ$òí…Dã°œA¦R‰Û)
í'„�R‹bÃÏ�ÜžB÷dlªänn–ýwœ»MÚC œ1ì`€‡?ü°ÒèQþ tõè0°KžQ+‰Å›øÉ ­JÛ$îgmÁ¸ˆÁÓöC?áøæ^¾»ú¿»Òk–¬§ºÎõPÞŸe:o±w¾Šv@/•+,U€ì¶™C¢ë»ýz.»÷GÓÀGÙÐhÏœ'»‰·G)Ð¢ö–wî ‰ƒ#§žz‹à‰ß‹Bãþ”‘¿á¾�½lÜCÛˆÐClQ±ŽÑhaM=´£Z©éù´!läEüzq»”ÅCåx< (îal#'ãÌp›ãN°¯IÛ©qgª¼Í #�èçd¤z#X/|zýªÕ¿¢½�ÈJsÀY¾=¥²¾âÃR®‚<IZ78x»#¢òª§Ê{H2®?@»æ°/ÄƒÄe»‘‹ûaˆkROšÝox6° 0Øt‡8
”Ä¼ŸÿŽöBâ¨6§4a�¸k(k¶“Hƒ—hk”ðÕ¸è—aÈ½´Hüt½¾öópøxzñg %`D 0”�©!Lâ††I=£ÎÇPçÉ&ëkÂØßÙ5°š”ŒÄ¡}w–¡n{*hÌ–GÇ×KüåO6þ½_Xíð™‚ïåu{‘Ê9©{\:¸\3é4jç¥î9F+Ï…ÈÏ_lèÎ­pU¡5ìêG�ëƒU ðg2î7‘9©�¯œE^sÇPþø4Ÿy£øW¸äŽSMæ_¹Ÿ`¾�[R)"kB“·e[›áa¯³¸pT¸y{L/¥ùgô¬ËÀ¿_ŸÄõŠø3ž”¤Y÷,m¡™½Ag@ŠÓèÎñr«©,""¶ÓßU†Þ-`'‚‚ªlÈ÷ãþ)èÁ'�h[Þe'í¶Jø	qAZ‰¹×(9ùJÀJq±Ë}ã_ß¥ý;‹l·»|Ft?4	m+Ô|È@üs-$Öµ£Ïlw(jçÌµáä5!’†ˆæ]Cý3òéðñd·x-œÆ]ænêºoFŸDìú¶U¾¢@3ÂÞ6<nÄwó3q«µvS¨€.y@Ï‹š]Ýò¨ïcD&„ 'ý]‰b9#�Ã'Î¾íÆû²œÆ¿‘ûí…Äî×;�àˆ`#žD»ò¥î2’£N‰U�À÷¢D’­It-ñ.i‘¸âŸ1+�yxÊ‚šIˆYÎîÝ#Šª8OÅó f½@gyôUë£1O·ø»qð=â©Pz#q/3ø…Qr¥œö’´/–ºÙW0©eü•âV,!�ã—…?�»°)žoãcO©Á=�… ,Ë›î+$—œ.ToªrÊ³ 2jg¶5ò®í†ùB®	_¾uÆ�ÙjKúë©ò5;¤¨O›-­ö+½y�¼ÿ	=E@VAüããðÃîæ´l:Úa�ó†¾©M_\Ö=t¹x¾©¢wÆÎâÅä¾'ô_h/$ÒÀx©†È‰3‰LSg7÷3xö±ÎŠ&¿W†ÄÈv«²÷XÅ#VŸ‚&¾`=Ý×¢?_i°³…àn¼þBt“(gë~œgr°îõzv+Nnîþ4=^Œ²)¡_Â?!¿�~	…€©›ªdÉ)C2õZH–HA!'ljV$5±	¿�^)‡A#¨<×‡Îý4�\†�•³¾EXÖÍÝ‘‰õpÙaWo6Æ}§ˆ§°“3±T ã]�'ß"¾�ÊŸ½Â[R]æL†‘w é®ÐÂCÄÃ±0··0Õ,¼Š©÷+–¹…:“ Ò�Ï¿>äüz6ZÃŠÀ4A�8¥¢á˜ë‘[>6¿˜£{£’ž0­§�oUE_ówK¡ãí>Òô:àÇýùÝGý�|aôÚ‚öˆ23÷sÝ­º~>RÄù0$*y
5SèÀ|­Œ/­•yáŽ ~TM?rW 8ê©¤Asè3‹fc#ƒ´„P2ÄÆÞ�^îy‡*|$œ˜ _ñ3èæEn·]6ºïûÏQ_HöçsLÂµÝŽ‚»k>Lâ%£A2É°¹)à³¸«ã{Þéï�ËxÜ„¿„ÿ5"ªËª£8ÄóJœÃ®4nkÐô¹£ï.Œ"˜@­%kïÇð=ögRLÇ·š"W¶™ˆáNØµ¹Øo/ˆj¡ØUÊÐéê…5a™wƒò§ /›‘´ l9¤•ºƒK>œ`8;ÚÛûYµ4ë&\Â¹IE�“õéý=|Áî|Ô¨ÿÕ�×ë¥1`±9Ðˆv&	â&:g¾8Àç°°@²=oµê¬�Mý¥à{ÄRwT~q�¤Êo�ƒv.AŒhË\'Ô¬#UlA r‹­Ážƒ÷ï®]ÞAj˜Z�«rìó`-­*kWÙ½OaÙ»4ÈX$ÇBdVˆ%d¸&�ïÞÿ+ÎY‚P9b;p*#Þ3*¼"£ƒ£Áù¾q†§‘TyW¨·è~þzäˆ�ôÉ§Ãö|î£BÛáª:Û¬ÙäMbùÈÞ—øƒp$‰ŠVf¢´
“ßÕ íë…¼¤æûÄ¶öî]é1©1ðÜ×#áô~�»ºèæ³ä¸•ˆÝßµ2€Â;°Å�¹÷±o„z‡mvZ]b#ÉË
ƒö;¶Á6¾å¹¤Ñ7ì1|Cz!-”ÃŽBQ#ÌSqTò4Pî[¨Q`¼ Q�ÎÝ›fÉÍt�ÆÝn—",Âd{ XzãGùó•ÚYí@ ê°;�"kW†ß§Åu(€å¥{§Á÷±Ò¹ÖÕwû+ÿèÁwšxm%uCxÉp]Ô��N:}<\s‚†7‘Úº±iì»‚L6Ô´�;ÚgIÖ`\ïjËÒ|˜$vnüL‹gÊöƒï@(è¤aí¬ûR¹öŽôBÔlhU÷Nþ¼²ü�šSå&ñ³’ÑØI—”Ô ¦Cóþ’ Œd �c�ÌÑÓóùqKöÎ»ûëíÒmDC¨N&cG´9±ƒW0ˆòž…â„½›tß¾º?èX¬/T«Îâ6Ûµ–±ãÀwWàÀ*¾œIÿèÀj¤Ìï‡^Œ÷(…eïå³UTFŸ“ñ-CG‰GF¼ìfš„Ûý¡0D�ÌìEö7�¼ŸößÑ^H”`˜ó+yåmå¼Ÿ·BK„0Û”FÏÜ9PÒ}ªêè¸ÍÿýÈc–wØ/ü4EµW¡+—ÂiwßÍÈ6lA;OãœäbÊÇˆYÞŸ›/$´úafo'êeH'ªþŒ§¨n*©ú›Ë
]�ÇÁm7÷þ[ÐÇü–Gxû©×-$ÌP38 ±cª¶»—�Ñ�ò‡‘[FãAPñT‚Yo^ô'�a½�—²6{�§´1/NnNS…×ën˜!<ÊÀÐu0íA³¯ÿ ùƒ“”ë§3®Z[�R¯:È^Äœ£Zè½DÝM{¹ž—s	ü„/]oä¬ò(d¶‡%/;u„RÜ·áw„X8`¨ê�”KD!Âøõ[jÐƒÿØÀOÏýnZ$¬Œ«6ÝªØ½ ½/Ö�pC«åÂó|O)ÌYÇ—/jáC¤_á^ˆ„E´4ŠiÜ®ûÃÖÜw¦y¨eÓÃ¢á±î Ãä2>¨úrŸ¨Ÿ‰à }ø9»§š€’ÒUIÛÀ{VšðÒÅi›<fù�6/a8 }ó+´ýš:wÇÙ7’�!v#²Ð®sº—µ#%$œ|	.{ëÝAù8Ð¹>·4=4Ž]ÐŽ[(jÌ%Gj�ÒÛØå¹|*ûlqÐY¯[ö¾›Þm§¿£½�X‹,#AK9z†t9ÚÃ5ï8$)w¦pàáf¼í%<¶é[¾ÿ²MFø³ýxÐÄB(â�„yÊ"{Št$%ÝSË’âRýd�w'äO*ÿ4vŸ€/„n}d¹ïÐ&Z»©9aèw³,EÑf»{~Z�•jZÓßïeå�]âÂþÂS…‡ÇeB"\‡3EVfrsÑ)
÷Í²¡DiS�J<ÉÛ¶ÚRþwz„§Þ!_ˆe3HÐ¼Õ>‡‚›9`›ae6�EZÙ¼â{€­gsK$ßI<@î47>îþá<†Ð¥ßì.g¬AÊs*¿%#g{—â¶¼e­”XâNÖÛßþ;â©É©_TÝ]èXq¤Ê�|‡¤Î·Ü#N¯šÙõ¶ÅjÁ»Ìßª¢7R‰Ï6tõÌ°WÕôÜhz*³«;Îlça*˜ÒÖîÇH¬GtuáŸ�:¸iõh8~ˆÀSò¹["Ðèu‰ZëL”Y¡ú5œaÁ¯¦¼:H—ƒÖwŽ¹}©eúS¦ª7ÄæLoJE | (E�«½}3µ¦ÚðŠåÛ>�ªÆ‰nønü}wK3<ëKA÷úG¿w�ÊòÌ”å°Ž`š„+ïd-Ý‚˜înÒ^—Za ‰¿}wüwÀBj8CÒDN'7èÂ–†ÂÚ´ç9 ÕPt.rkj1v�È/±ã¯„‚î_í/(˜p"ú6öÚân~jª�hÔ2Gî1êKM2P¿Óðÿ¢ó�÷B¦'o¹ñ`AÒ.;/CP™n‡ÈÕ‹®yu¾ÜütjiúR3 >ñÍSùÙå¹ýóí;ú¼£QÑ÷déb„2²8ÓyCj††¦nDÏâ"q'¯†ïò¯ˆ/¤ê�<•ÑªÚWKíŸ÷�y×U „ä„æ½+Óm�TZ)›,ôpÉÓ^%ÿ`žñ3BSÆ �¢>DØP¢^ŸŒ{Kâ&T àÂ ðübÝuÿÅ¾|¾Fop/D‚Xöªó÷ g¶çËî|cå�<c{Ð<Âµ”$ Ç®›˜˜kîÞwûù¬D{P‰~ƒtÚ$]ÉjÇX›Kª‹$-ô·Õ /»s†ìmd®ƒoèï.æ+Ú‰®\6j ˜CN¢´ìðr@„æZŠ{QÅ®Ã¨c‰øÝëù�ÌÐ�î=ÍÄRø€ÈºÀ
…Î…�Øöc#¡æïúMÆúR[¹ÿK¸ÿ_ˆ|º¡ÛÆ¾Û¹·Ý¢dlØìZu¡5ŒIïNá}JÕ¥›§}ó‹�D^c#ÿ{;ùŒ9Ó0›Î•m¶VÙ’ ƒ¢‘2dK.èÝ i­+s×;v¾ù­€?ª?dy­ŽúïˆL?Æc ›ëy¹¾Zøj6Šìûq#�Ç*
Ë‚H­éC!LÆ.2oñæKCû‡ì¼€½õåš§8¾A•–—ÝŠ°¢î
¨M–ÕiZ±˜*„ÓÚcN°9~g|b~–ä^ÍÚ®$×Ëêµ¾VåÂ˜�ì|J$\è¡M¼�U…fªýw×ï_X/ä‘Sø�s…Û{èá»{íÊÆš)�Ô	îhà$_@•ý—fØO–« ¼ÌâÀ?"�»epãá€n&SvühbŠvåÀI�lØŸ¶)=æz˜¬èw¢˜¾á½�Ù¬Äu³k¥;¨uEÇ¥EJ`6)ßnjRZ ¯ËÑÊdÕ‘ï�úoÈ|Æ¾±‰Ç‡%)ö;µ“—2<ø¤æyâZLÆbVŸ¯HwìýöT^É|m-¦«`Ã¡×ª%bCÝ½”ë§c(uZ0g¾wP{†ÓÆwÏ¨ÿû°°žÂxÏMI»NÜ™'€FCÒŒao;'[W&´Ÿ1…—Í\ù¥€ãƒåa½ìb¹€ãº¤—ºÔ/{—É½•ôEüjÖa§™	%à·¤¸|g\—îÇó™í@hœÔôÌr�@±ðzÞqÒb× Zs+âZÐÈH}wŸ_À^¼LžvwÇËz©ðbò|YIŸ^Âãú(-åZ¤s1îÑá»Wë�	y�D�ª•§]a­¸k½c�ƒƒtîA4�=n“=hgmâº×Ì•’s9Ú¨‡ŸùøB(7ÞõE5ïÄ`wNÎ	Þ-‰ª„¦®ê#…ÝwÂŠ��©,ßjeP=óF ¾ôp¤b4Ô:†½ùkzÎs?@qes¶á›Z’²è@1]m'¸û–È�½zd‰K0—§¶â:{îuåÆŸ?Ì“Rï®É%œbÐª ~»“áÐ}¸ÌŸ6´{?ÏQ»	»SqCêplˆx“(¾2X‰½Ï\&>²®�m¤_Ã½�Ð8u×<ÆÕÝ B‹GºÜŽœzMÁ*áu‹kgQw“ô�[¦ª~ùy8îû˜$·=Õµ7õsÚÊÄ£Øsl¦ú€Çiv*èï¢op/DŽ÷ÍŒiÎe	ÈªÐÊ	žYHÌ m¿+ÎZÎ†Øƒ²œÉþTH|Èãó¨á=?²9’Ûëº9˜Ë™ìz±UAwyÏ„J¿ô’kówæDù/°³#Ü¼À¬! eoÝ=má¬Ô­t/ PÈ¥dLDòeˆå‡¢øˆs‚¤‡$æýŒedhpÐ¶ª¢é—‰~ÓmO¨�øì�¶(¤*ãIýî@þ‚z!ŽöšÆñ5¾ˆ:k€+¬²·ÎKoˆ1%ÓªzA¦’vñá;�Ø»eS|¼/ŸŽ‘õp¥±o.œÝdPl„¡¬æ¨™ÒÛÛ¥¿–ªqúR±ôÁì;Þ™CBõZdÌ$DN¶qsyèön5cqq“™Û(—KÒ„‡0ÿî>¿À~ê�Kµ]AY0Éj	Ü¤Û×Azw+Ôèz�“M°=~íý sÒâ#¿öáoÒ'0ºÍ›†<ÕƒŠØ›	ªMŽù4ðóÚ¢9ÜdZö;ê^Ñ^Xö±eE´lí¸�ßƒFßrÓ"¡þVâ‹3ÝKfÑ´!ÖùïžÀÐg˜…li‚ (�
2Ï�N©#`^Ô"A½^«¾6Ç¤ÎÏû‹6½g ?^ÝgéŸåžééùÃ=Us˜gS—?›šqËéDG‹+Zé!¯/Öµ§¾UÿÂzaözâê°å˜Û²MŒÌÑ½vƒ“¬Üsj×óþÄrbÍ7tý^QóBÞ³3ëqI4Þ±ìèííxœ”Ô³n“96Pxw03b	2­9Œ:{OòþÖåµ‡mÖÅ–eãzïMdwfúLl#FFñ)c"�€eÿžâú‹¼à9$ù¼$H!ïù2_¶07ú­ÙÅî•Ü´î#_£Ó%çÝ‹˜ÙÑøeºÐ7p/{xóKvÚVª;K®¥�FuKTš`SYŽÒ°;üÂ*ïá©Òíò±/k/	 À	AC!háÿ°çŸ!4¤oÇzuy0ÆGœ{-¦P¿N	DFÝœ£ñ˜ç�ìÓ„ÿ.â¿a`Ë±&»;ŽŠš›E!¶{eA5Â ¹meúke£¾LÅêøÅ5v(K{T%×6ýèÚ|ZÈSò7#£ƒ™ bu›€Gæ?V®.7�bÒ4‡*°»ó¯eï*ýç¨/$“ryé‹,œp‘X‘ø<ÙüZ˜ñ�…M?Ägg�˜-W"�yÓ/s�§= þ™²yÁ§–ÑB�LJZâÙL»6Lé¬ÇRÑo› å±¥lUR¾t›þò…XçÝDX–=Í˜žlOƒÏ`@¦¼l*òž*Ý±Ð„¶nÜw#ý/ä¾-Ò!Äb�à¼Ž©T—k¼…u3êÂtècžÜžÂÅ:ÞƒQfÎBÒE‹ròKàáïÐ 9úö·¢{ÐÔGKsÎ]Åé$oG 0s±˜Ì3�£”-qÝa{û¢›wÏêwð_vH8/¤Ô®»Œ‡é¡7l;¯�“5¿×»?µ¹×T`Äú—+”†yŠó>ôÕç�TsgŽý¹VŽ‚€Y‹ÛÔ÷æ$öÔx¶¼1ªu×ãå;&õŽ))»�! ,6Œµ¨æòN�˜5ÅH•õM"Ú6#8ïÂßÀ^˜Þð#N‚ý©çò®†�n×¾w‚6/…$¢»�k-;Þ¨ïº·“ÓjÈï|¯¦ò#
÷a0ÓÁ|‡Î+Îôã2{§�;î·\³Š+rŠ_V©ÙŽ-�œ¿Ì)þñ…ÔÐŠJ4bW¹¸+�	úŽxžu¥<ÏåmGÜÀ¤ÿ¦™#æ}Ii| —ñ£#7ŸòûQ»&)²s$’Ù8–R«{0cj#Ò:…Åý^Û{G¿xi'ÄØ—+ñäýà¡ÊH»;è�@9ƒ¹]„ÚïSP¾©JÖ QUÎãxÌÔ
k_ý±§OrŸUÛÔÿL Êî3¿=*°†ÜN½ÖƒFÒ;fnAû®c{Ü²såLÙtco/à,¿;§¿c¾�Ë›LÂ«¬>ÜAÙr0ªA'žˆ¿SŽ0êsípÎœ]¿ÛÐe÷cèê
4Ð÷!A?öõ3"‘6‚i(°¡òžO­4€6j­âLîÜ9«ì,„{ Šnë?>Fðª¿|!åÆÑãIj·Õ¯3˜ÅŠQÇ«*Òa	úãú=oâÐ¾ñíx+ß¾èÛ±)‚ �!ßu>}Pðƒ20P#0èŒÆò‡K>í�yÅô‡2Þb3so?Œ@§ñ‘ÊdKø{ÿ²e
06¬Ð¶q”îK˜¶üÖ{asSQ]Jô~¼`\®`æîpWN©œùà°U£Aæ0¼åmû*wè­Ä�?ÁÇ8‚¶ó[Éêvr�@ùîl!1³—º)Q©éZG"NPxd¶Ú‰¨üò…ØT»ÎunÏJÁ0_åÒâ3.g(!¬`Ti¤ì‚†½‡”þBÎÃGIñ³Lóé„µ8ÞŠ8’Ü˜+â!Ïåê:U×û éŠ¥¹ºÕ@NÝ}—Æo_í×]Zq�R¦B‰cqFÐÁì[›€r£Äºƒ÷xo½û:á~6³|6 )KïÚ¢Zú*¡¼îOhãªuø"ÕyÆ�-øÏwgì¸"föÏ+vN°>zKÚöœƒ±å"Å2Äp<ßCyƒœô“Š¾?A•ß�`j@úÚÈ£!ýÐÈŸç^º”·Æ¹r®@NÅâ­QNÑ{†¿)Aðo+Ÿv¦¾ÐúSÔ’…Ù'’ÁÁˆŠ>LðÙ‡“å1¥´�3R¹Ç²b¶ar÷%ÿËÈõ&I‹¤ívP4?•ò§¢É]Å5‰öF\1…X]ÆÅ¥A0„A™îˆ¦îj£v¾¤ï¾Ã{!Skû;¾«‚?ÆêÌ%CÇŒM@ÈA¨]ªM|;
÷þK¤·O»×ÕóÓ ûùQ<Š¥ŒP€ÿ \öH´º#ÏY
›2ÈrQ
öÃ`‚µË`BLERw)˜
Ü)\†Äs×Òáûõý÷Vza�êfßÚœ\eòÝlGµÚ^S>è0¯"&ÆNxjQ�xà»yôX0ìºúÑðøøôÍóú	<'ßPu‰àõÜd ´¨çÂ¤Œ´|L.ìÜI%à+oÊço`/z³;ß£='¨ ¡�&&ÈÁîÙÝYØAª�DHG¨•/Þ¯ô³�†g;˜Çù,f=
Ž�ß¼“Iuõæ\7Í|PÚ3èî8,ÙíF
âÂKÂ»+ûë…¼aEºE55i¤(MÞˆãv-ë£}¿ø«ŒÍ²ª6ç«1~y÷nš2\]0�âá5¥bõtó€„¼`§Òž¢ràÆØwCBšGV@ÊÛÈ¨ãÞ‹©ÞØx¯ílú²�?ÁF>gy€6a�	G€|4FãaˆGÞÓJ<ºåM¯|¿§Q5Šƒ¦ä=ÇCC„Û[Àmb‹2ùEþ³%ÿ7˜íjßKBà�úE
Ê.þß�ªøÇ†~—C8Þ~Bh;kÁ„�RµÙƒB‘àƒgX;^µªá/çþìòqIùºç0T^¯ÛÚÝÝ8(æÙK¡`ðÄÞv0¢½ýÒ™¿�¾¡õ(¬ƒÿü0JÍõñÜˆ=•Z+˜SÖ‰	O`>$v²	,í}Ê÷Ú³|Ÿ�å‹„ý#òéV¢ÂcxP´�æ ‰\o¶ÆÕi·æ¤X«)ræ�Àâž›÷Çí•ôºE O¢é““î.`:jiC¯¡2•ÐÎ>Ÿ‘ÑW[UËfNAœÇpnsd…ïQêŸ‚¾Œ÷õL„[ŽºvÊ©ÓµTFz•ú»ÛLäª‰1({ÎC•Ž¿(ÉíXÆÿ(E~LšÄ~n³L×CSÜ®kbvrHäÓî¦íe‹¤½,€ò¢ÞàP¶ò®»~úB0€¢pŽ¨-Ó®ÓÏ{Dkƒ{Rƒ1¸·~¨CÜ,$Š	ðE$ñ‹üõn>Ý@ŸîógÉç;BÂÂÀ0ÎÀø�ˆSS"U™Ï™vŒÇ)ž?wµŒ“QÈëÁñši7X´69]Cg&fcÆ¼‘ÖJ}¹ÇÿÑŠ/¬¦WÜ×Ã™8³¨¾sRƒÏ­#{àTÐÿãrdfØYxÆ©ëpxW×]X‚ÏÏ Ê|H¼šit>ê“Y0}4>¾iGÿ@ñ wÉˆ\PµŽî
34�á©ßâjfc~á�ä}BÚ#Í/Ð`ª¾gÂ_ª—ÿÓ5_Øm[o¿$ôDÂ»„¥¶õUe´ÛVî”½&ÖwTW2|Æ®$ÿ~²]7aÓ<;âÿœÂýÐX 8ò”FóÂ�NÚ3GÖ½ëd2}7âi¹Ž¤³€vº+< zàßEüŸp_ÈV\MÊÍ’¸Âq}sf"ƒ2�å�LÉT_#Åö:`„£ÇïÂÞõÐý­-;²9»a©2	:áîâŽ?$3¶U|¸€A¬`Îu´ ñý]ÎÞ€^£n5˜&Œ‡ÖæR}~Ø‚L½awõH/º¢gGiwp+V×wñyÎ?ò“Ÿ§zví�hýì^Èm!Â¦›ƒ]¤°¹E�†yl9kíxZÔHQ É þ®¼«¸¿Á—èÖžö8[Ð¤V2nc^ç‰B¬ã…IÁRÉ€Œö¸Zº>ÐŒýnÿÔÿ¾Â&›¡Æi¾ùÈÝýn½�}»ƒ~°;…ñI¹Í†?Å…ô.TßÀ¾TZS‘ˆ`{6ÛŠ`êŸ®SÞ¨ƒ¹áEu+'˜zw 4¸¾«ä¿`Ð�–F äüñZOB‚ÂìZó5ëº¨¤Ui�™¦¥÷n¾aÒ�!Q(�®s6„ï~ÜwÈàýxÙ
��ÛÇ¸	>ä ª³®�ºz†œ¶4í«0?ky}Úä »QÞåë{ä×ºí~…
ðá�Óm!A»+¥,kåÅAµ*îÃ0;ø·BŸÑtŽØŸlòËn€OU>o-Õ£©>NCð³FAîÁéh0�ht.6§—,!ŒSl[ñ×Ô>0_6bvæèŠõ¾kÒÅâ	>.FR‚£U´’ýdüÕç¾fý0iýÃ{–Ö€.k0tí¡Uÿü¸fë]Ö7õ»�ÒŽÝ‹G3:™¶¹çÂàWóXÜi@ï+Å?…}!ÚÛ³(wU„(-Ùj·Jä$)žóëóÈé†+Q:g_©�ÿ©„ÂõÀÜz÷Ñ¡ýÔ�ŸNÞn¼ú�:BÂó·Xäv Ñ³½ž¦i˜,0#¦p�º±ßiþ9êÉò$k�jí	�¥¸ôfn*	@[t„Ñæ&?C-:Å÷ä]�ž •=¨ìã§ËA~ä÷ÊÓl{‹½“ÏF”9‹rYf‡Y|ÌnÜßwÁ¤U9¡Žÿ®Ç¿�{!RÓX¾25˜¬7˜´òú@Xu‹‹ÖuÑ7 ¥«0(ª«Ù–¿É³ÛƒW‚M^G>[Åþ&ÃÇ-{\@Éq¿Äá)0�êZ’ý;# vt™xÛƒqé¦÷á'�/Äú»S!A�j‹ž(iª>9sÛAœL#±ášUF&Hk­{òô¾£!HÀ�$,ÀPÐ§»ùi€.C	h”úáìã8E2ž÷°	âˆËâó1!ž“l?uP�˜åk0W Ý§FEaÑ#í{¸ï?_õ…åL¦Vã¥ éØñP#™wÍÉ-—…ù¥:*Ì2P­­qó%µ†¡WCnâÏ Ï�hLma|ëÇÃNRÏÂ¾ìs<™�Ý‰ Ê¯´ûêšµzË6_J]¾�{!òÜ2Çt<^‘>ÊÇ`ÈçîÈë,ÔK‰B;æ¦˜„ŸªÐ||?—T02{^Ês¿§v²¸“pðeÈ(¦6›å~€ó¤‹æ–!Idfx•¢Ã3d:Ö{„ùo`/ÒžMCDæe¿'VZÝ¯:[!‹7°rÅ0 ð
SñÀ¬ÿ¨ðŽ°êÇ|—õ¡äÀ,Þg©ÐBa£n’GÐqÜÈ+´Ãài¾",¨í¿
³¾+ÖUr»§?z!lR%ZdÊeÓ)÷Êº‹ðiCíãûÆéáÅÞÀÑ	¾£ìò|Ÿ?ð€6u(Ëã›±�Hòãc¬�üì
Én}uÄå²j—â,TŽeÐ™éHéêmS"R(m\‡|øú¿Æ|!WÒP÷prùÂ•Ó`;?A;G»Í`Ôø–¸Ÿ=Tù§‡D¥iÌo Aé=¨Ç£¾Ai7–ÀÇ~~æ,ñ(¢Å�.ôç7©c0Ü¥añøËãpòãeÔÑ]°ySñ³ÒDVåµ'y
u	Šê3¾4h®†ƒ4�þ0Àþ÷ixÙ`M(6ï€°ÿ¢BýYÜS¤Š³Lj�Sþj_efšK¯2Žç“”*žãŸð˜£øº®p��Ššñjáá8žr‘=ïit�Sæt?ºù|Èì'çû³Cøéª/,÷¼xSåÄÖÇ@Ñô&J£WIbWZž^7˜Jbd`p&ôŒ{}³û�#}ëŸ)�c6ïË}Ãâ©ƒÍÜ½G4ò<žÊð¼-x:»-ìr¾í©}ûx‚øB(§Ä™fL\Ï Ø5“ÑÚÙr†ŒÏÉVn~‚îq.9�ÁwhíÇ+ôÜÇG¹?ËÇþ¤™áø þÄ5íÃ&½ËK–sgU½>Úk8B§ÛÛ`”¿�ûB6}¢ÀÜþÌ©1¯”‹·ÃzÔâ$?¾ž¶=Këž;ŸvlÚŽ[ògd›ÇæBÿ¤:»  ]´·!…+[íŽÞð˜ºö6°µ¶mÔéVÜ å>"?¿ûB4hˆC²N¹°VMN˜ÈÆ±
Æîë°2©©OWë¯È…>?ë·ê?F/ì>.y]„îSw}¾O¨„­ R]jkQXZ.ÌÔ]·›¸Ü†ç;šðb’
�øœŽù3¤Ò¶õ~:â&æxT»+t•‹ˆÚƒæõ,˜i~£x/û0_‘ùœ�øøgÝgÙ`²L•^|¸ ;8pûVî¯;òöÎ)n^!sÐ´MÏ¯>}�óB–”ZGüÿkí:›ÔVwðb6îí#Ø¦¸`Àð7WŒ¸cüë¯ìeÏñ’“Lî½g²3™LvôÊz«¤G�…-9jLV-H^«èÜìÈ­túqõB«Ìž-F�û�§“¥?€vøûÅQ äÂÃë¤nž>º›áÐ®ût€—)á4s¹[2y ý ÈãLH¡>wÎð,ü•¤‰jPQUlr¡
-TÑf>Q=ÉQk–Ïóµé‡¯ž¡ñ|ú'�p“ÃT¾TÚì@+nv§¥OƒˆˆO�Ö¯
ÞQ ‹_º�œ»Ÿ[åûG~Iš¨&D}£i <0ú¹Ð.ÿÔ•Íè}ÎFn4/·‚KÌˆÒÎáßwI �Ïª§¯ D»Ò«4Œùðp–$ó9gêäêðâ‰%†ð�=á“vUÂ«dRbNc{3YÙ"÷«jƒÎ—ü÷£Avƒ/EPì�ÝåB£¨oî)p¢š\[¬ûó®°¬•ÏID)öoÇ˜ˆc’ãÒÛÒKB~HVÿóhÔW’^­˜p‡—'?ÈQ>¬÷©Ìšá,¹ºJÆ	ï• ùÏ=Ç/Ö_Ëš˜Û_Bh®VD¨�«H�jR�s Æâ(àˆ¿–h@#‹VÌVŸsù×5Þàm¼½^mˆ4â†C¤ìÎÐMã`ŽÏH#Äâ€Âv±Ê!õ”œ6ìóº9ÇOhocÎPò°½“ü‹Ö^aªöŸ�1ù!Í¦XéŸ…µ™PéCÝ ©h gà¶]`D±¼÷¹…˜úèõþx¥@€È|dp!pêÃãÐ ¥‚!÷cø]Ø¢{àw›·Ï¢]/Xõ‹~{¹,O·²Xb¼czí%ñ”V3²ý¾l_ƒ¯CH’âÜêèìš•éÏ{»ÑtÃÈ"ºí`{ìûV.wŒ“¹ãÍ?™„‰ ‰bpÆÑ¢RTGO{À©FtVyµ&EÔ¶¶ÚdsRú«©mxh8ôfpx*VøœTØL$JâN`ÔGàR®çRXˆ;Äd¨š[¯ê�]kÛŽ
 9v}c€TØ(6ü¹0­›\u$Ý‰}ž¾Ûà}(‡”'ü0„;tu üq“¡ ÏûÁ2À.s‰f£W”¬7Þi¨’4šr%jUKÇ
û+½ÅÁA�Ø Éphú‘¸…SÄÙ¿4|i[Š"1âèðÁ|PŽóo~õbp½¶s¤`&½6òc�If�A4ìvè”~˜—ÃDX¾/ó‚&Š)”‰Cã�F°ôÌØ3çhmd™†¢ét;YÜ³_ÍgG~-=Þ·Ò ƒ‰ t X»×ÏÁzœ	�‰ÝÔ–üÜvÖÏ2?®`�YE1X£F”Ž(°é	ð]ÖD=àâ4%ÕH„›$&äbGö"æ‹úžÀ‘iB³‹@löY�2Y=o£ãñ÷"‡N²ú�AÞÒîQÓåpèÔ$,,©ë›€BbÉ©&ˆóØJ5hó(j•î!ƒ;ïØ×XCiêpdèÎÈ]&�5v5¯�
÷¡?ÄÙ¡Ð¤FmN(ÉÞÉ7µö¾Ó§’&fàºrûð•°#ãDv·@2°Ìºg:ho"Il·[i�+Kb6:®“MQ}âbþ²ŽQ‰s$÷á£.†AÐñØéJÕ�+´ÂÓCüå²êuë3‘Ä�Û³ÚòæÎ�¡‡K4:‰¿Š3cÐQpåQ€cà¸!Ñýu²FØ6>zw:(«½X™G.oùxÅ	õ6åS ®WDÛ¶îÈ�ø}r'á8À†f	T\8K0!ôYÃÉpzˆÿßC‘¯ýçk¤×oûþª\¯§<Uö—/æ|{Úþ-¹¡ºïØúÙßÏ–¿0Ï hbr¥`"µ1£`”¡ù¬æ  *çAñ¬BkuÍ…s?_ #@ö¾ÿ¾¨€àÁ6‘·-TV45àÓ¾Å‰D„…
ÀÀ)·Päoâü±®�¡yhï—Â_ò^}¾ˆ¯Ÿô49ÛÏjîì�íz¹ÙôÇúêïœhy<°Di”ãûo²¦’&ª9Tw§UÅEÕÐªÍz4bÚòó;×²€‰ï[§Õ¨ß]Õ±oÃtº?‘H¬þ:Ý2gkÂ]™ÁRëP6o+õ|0xKW–kÈöˆf»9ËŽ^çTµÓô)i¢Ú™<«Î>;=V{û¾t¬eBžlJ.•½z–í€»’@ 7úmS�÷ûPÇûë	ˆcÃ+‰�·"É9xl¤CâÔd0c�;þ™�ž¾åN.†C.HtJm]¹ø1÷j­;?Âë¯Á¾V#4sØPl{×í<©SÌŸ.Uã<mM¬O@8#cì†˜iÛ‘Üû›I§¢&Ê)�¹*
çä�Tq)–PY/YÐ*á"lûx =¢€™žlDäý¤�"Ñ/Ð_¶ÞÍ¤	Î\¸IÚñ}–›ž¹„êiŽ­nœì°F ¹[U.ï¦	½ÏNsŒ÷…"áÚúgýÉp8JÂa �?8×ð¤XŠœ¾cþËánM6:ºí�€�ŒOÇ×ö°Ü\²}Zéj	./càš	•œ~Ñí!Qpoìå¾Ö
íÇ3þW¢&¦S¢ÖJ±Qsð¸|µ¸•,F\ë³´›íé¶ØxÂ£5ãQï!7 jõ£^ÁÎ³O,Ù^TI_gùN>îO—X:ìæ¸/�K….gÕywPb”Mû;q%‹ö˜
ÅN—âzO Î^žrWÆá"ØD—”´q)Éù™Ž§Â»ÔKpÄÅÀ`ñµcà‘†ÃX…aÚ8Ê§<o¼HÏ•\¹dïp;(!…Ò_<=.Þ OÆÕÜ[Z½kz~ŽBþùh“OL‘„)Õm—Æb÷œÕKèÛ}}¢Ÿ-¼û
KqíÙìÄôÃ»úWŸ‡ÿ´_só¹/pÜ€ü#Yp:8Ÿ Jx€£Îˆ×°vÇ.M^¨>0«BŠS½emd•Îyþ˜Áømk‰€r2:ÿâà0ûÅ|‘êÛÏ€32;ÁC¿xHJÚ—¶l…s3cê3ñ\µèøú¼¢À`€Œ-nWNß/×h-yØ	…^tùÎÃÙ|“¥z›h%UaoŽ®Æ¿7øÄ äºH">”J6–£Â”’¦‡�«àû™`	Áár(ƒ9žÐ?éðböfás‡çà©¤3ü˜mE4ZsÛnÌ,(mî\Ô±¼Ê™š¾u=Vë¶çDÜDÉèŽøÊ­{\	Q�æ¤}‰r&ÁE›yx\ û°»L(ã>–ãô®ä¸_M5à��yëÐ¢íG´½{Âs¡È¨ÏB¸ý“Ú³‚Ëb•eiîûçb;Â›Z}?Éœ¨»6€6©”o8O\(�µ6Ù“®}Ïlù*qðdÁÄ8q
{!÷Æ+�–CÄlì¶ý£Åþ>/0ÃQc�œè½�SÎÂö™Þ°ü�¾Bÿ©y^ÞšE�ñH¥=o£ßCÄs·¤u¤Î#xäþÓX#�Ø@ï
÷8
9 $1Ôs9§�ÁFëàŠ�s!ô�…”`¶Ú™Û¼¸Õÿ8zÍ¥\@³ÈuZ‡«5eÿûA&Æ“ÖÉnã–aî3¸¾BDË§³F…}ìm¶ÛûåÙrY…MŽ¯º×XP`2šZh¾x–¥ZÓU—Ó‡·å’‰(‰€røŠ‘•ŒF
Ôd–©�Îõ?Ê™¨‘¹§öà/v¡%Ò]ØH`Ö·kõŽ¡³t>�"HKBJ	J�†Ãê%ÀëA#$m2ˆ%ÇÐ:ñÇgs�WÌ]RÌEµ5tÿÐóŸòm:³jÞ‘NK›Bq©ÕqýxÉü‘Ü‰Úg�­wèŽ¡™÷ì5#BGfQ¿ iu®@¸/$@ŸÇÅØCøKí1¾Ù¾âš#ù/
)gðop€ß}p!NÓ�ÃãM†’Éæ™0=NÂ®hhqÆÓä9l1oÏ$=SnìöjfÛ™é‚‰…¾õ¹1;+CVý¼�…²nZ¯Þp˜¬sPÜŸeGí~lÐ“q–t†¯…—E¾Ëù_Õ‚èSPÞ¼ñ%œ)Ÿ­ñpö³ú‰¨÷hâs¡¾»O\¬õÝLb6"®¯¢äïØëý@q�Ž9åpYþRÒD5ív›—ºê	Z˜9ý&¡�Ö0ùvœysSJ6åüË‚Hêw�C�
rèÛG�ŸÊe]õÄ—~¹ gÅŠØ†§ v–ìØ¢A¤é‚FÆÝ¥ó™ˆ{Â™óYõ0…i*Šž%s¹AIè(Î�<[ÀízEa³ÔÕ-ŽÖÃºÜázšŠ¯�ëƒ¢ï1{öy/Ä´Ýo�°¶tÀ`_®ª2 Ißz—Óün]â2ß‰§XtZ‡ÅñYõÄÂ——Bw]<‹€�÷^ë4WÐCÏÒZ„";6”¹ö[ÞÏh*òÁÀÆcÉëíYø�ÃÓrkäPâñ ¡Igñ8Ö~¼I%-ZÑIwµ®{›†© ‰bçËÆ‰Jì™_Â€ñ¢C#]ñ‹À‹ýUÖuwYìÁËÚ\¯Cjè]±qÓ½HPéý<OÏOK›ÉþîHä«[‘I‚ÙS™ÇÞïâ±M*$ŠLbx€þJÐD±õRFèùJÔyFìf¢-E¾º]iÏpÎ9Ôšˆg@çˆZ¶¾É»;î.¦WÜ~ÝŠ÷GÂ(�·óÑ  3K›‹ªs%��ïÊHeŒ59ï›m§á7Í¦’&ªñâòp»I‘Ü(“Œ1o³Y}ÑŽn¢5Ô}7gƒ5«ÜÔ·9 hÏÈâÈàŸ“™áÚªãô „îe†NÚœ-¥ÇjÆ/Ô®©Ç¸Î�P5Û.¸·U14Qì–£[F$&0kÕÎ´Â²·©_ÚÏÕLX+)¾ ò»v�’x“µ´Ñ×& äàð8Zñ6´�õ iÅSCHÈ±3éÅeƒ·óÕjGÆ¶ÊwÚïeMÔ#sjó�6F˜søŠ¬LQ¨ù’„Þ²†c�²%ƒV£ð"½íÑŒËÖø…†º“{žuÔ}Ÿy^&jÂì{�=–»^¤£?SŠ›@-e40~Nçô»¨‰rtF‡l;?“©ÊÄö•s¡HTþVÖxÉ±æ*ÐeF�!à3•xÏÇ ýk±Õ«g ÎüéÍ™¥Üö¥ã<9Ý1ªÏÜŽ1š™'Uä~ÌJü³˜‰R(Š¸´Ú†mrÖÃ®ÍÈæ�»K3r€RÏ‚¸b}¹½-Ýê^Ö_úº¼gÝó¾
1*S‰­šî„Û²(ëàŒ2Iª/;ELE|g5Ç�4Ô‰M5{“5Q�±7„CD×¹2—žW¼{Ý<¸9OZ¶•ŸÕXèE hÑl
žñ7‘cµùpäR/Ög�¨ª~wžóMtïm¦‘\È6p{±¢8¨%®~fYÖðÈÛrûDÞ‰š(W{PWHå,)¢U’dÒ%Øç,:J®²?íñ¶ƒˆ*qIy;ÅÍëBx™Î¼At;ÙÏgÂ	?ä%{*Íyzµu.}²Ùþ(0ØZNµ9¸ìß¾ó›¨‰râ:¹^àœE¡Ì¢‹-__¨„$«LB+èÒ]‰Tð—úJ,º³©D°PÇØÓ—“2óœóeG¬¨D¹kû`ŸíŽAÞDP©ÂS¼s:gŸývLNM3W½Vn-,›Ãê]ÐDª-×›|{0-µ=Ã#ôÙ`ºzFN† Vû…1€êð€œ~xœ¥–I¬,çY†u�a_G—k_	Ød�¢’OÍ“M@ÕÕ5t×ÔUÝUÕU¡š§®®¹§Mp%2ò©vH¬Db‰tB°É"Ê*bÃ*Þ#ÁŠè>í›œƒ@HxÕ›~ßçû¿ÿýêÿþù¯_øà[/|×|qüÔK?¸ýîãW>9~û³ÿpû;OþèÑøý§ÿ4þÁ«ŽÅ/üxäñÃñµÏýÝí_|þÃOÜJ¿
¼Ìaá0	#oâQÆ4J¼žÂoqû~0`ÕõÞY#ÕzÎr+ECPV;z’° bÑª‹Ú®“ñðéøCøÉ«ÿ³Ûøïð¨ð›Ð!ÃLB½‰„h€xtà0u!š“>@Åt3Ëç!!Ò0fJ’Û±;”“)hÇæj×mšý…øÅaÈÂ×wðqó˜ß2è?õëA×l²>z}ÝÀ7ÈˆMOabÆ3MOÜ‚ Å}T¹Ý÷¤ÚEå\‰Û…°õ·_�>4 “ê¦¬Â‹�ˆÂ¨Öz(6mNŠÄ ¿d$|8YñzÑ˜O,h»«ºñ›o¿r›Ó¿öxÄ¸÷o{ž£7ðÅ$iõ4'¸ k�ôvS%r`ÕW	ïÇÜNŸ±’:KavOŒ°òtüOáEú¬¼W¢TEèGªyi-Cƒ³Ž˜d*Ù»hQ¬3©| SÈddÄWÆùì%¾²=7�7Ú=%fÛÜ†W_JIû–w¦lÜC0èÏ¥4ü blaå~qòâY�“óCë¡FÉ”ê^Ý›”@údblV�P]¾´†¶�¶ýµ™wEÈÅùßë£¯uLI“[~&7ÊtRƒŽœl¢A.�BUò+ÒQwøæì3ÔÃ„Mgó~1©½^=ì-&Ób‘aI·-CQ(›tºµl¾-ÜñýåÓñóOÃÄ5õZ¥Ù¥Ït³ÆZÞ òÁÙy‰š¦ê®«M‹óÁŒ“âñƒÕ�’&$ …*‘bA ×¼3i`f~è‰“mH’!%ÓSË)U²lG·³»þ±ôF®ÇVVÛXZŸ–8P,<—Ü¨µÃ’!2eáé3ämaˆ³ÓÉ½õÓñ¥OÓÈƒcO€A�öh#ìzp¹”H]ã¶`3	:ñ4“-q#eEÈOËD9³ÏÕË?Ý�Z¦`›ëL0í†$x|aO†N€Ž&‰éM1Í¦I¢ƒmRì¹�¸èÆ5ÚhØÂHI™ó4ÿÀëæLÉËÖžU‚vâ•¿‚Ç¤§¿z½ç{]tÙçÁÔme[…l€-JäñJ6•[Uá²Æêô¼” íRûÏóÿÝãþí¤‹h‹¶yËÈÔî[m›koÐ6Ë©…÷Å.“Ub«ñ_•ŸEèZ†n
z‹1ÒŽ™æ{%#{Êƒ´¸:–iÂ3ºÞgœóN\BÏµôE{oáþLá{—*�/Öœq´êLˆh&6‹ œUÅ´rÃv7¾£þ
„¢w±�1Õ&ŽÓ!’rH¡-P¦Û|/€"hÖ·<XƒLZø]îþ^{L¡è=hªìØ¬¶6È´ñ½fã
ê2äÔ‹6´Kh2´ÈÂSR�twuŸ`¿()©Í]VÉ=0Ù(ò:Ò7ðlÒœ"zµÕÌBÐ»É$Õ‚™ÜþDI¡øÇEß=,ãnNyVa=?‰¥ÑG!ˆ^ÑÐ’Va&é1îõ4L~Š&>.š¼K¬-rÆÏ§2(âxCkW2�.>FV•(JŠ»ÛwÆŽ¤~Š&?Bï·°7
ß51Õ´:÷ëã*Gò£÷°¸(¸WjŽ¡"†v—a¹’©ÿ'ù‘ñ¹ë„„÷xáN°u“±½í	"Fúqê©|‰ò‚§ÜiÁ‚Í."òþtø†
Å—jÍ˜û:ÒÒëÌ+z¥3«Ä·å©vx×Vöû`ôô§ãËË—üæî†“ÂÆÌt7ÍqÝ+ˆùÊZšD“r‘g¶F@,Ç¾9"¤þ\JŸ¥÷àÕ±f“>ó!T>nä¹ƒ¯t'è)–IK˜H¢=Rèbè|uæ?_þÉ~7ËüTsH<Âv[pÝzp–ÓôA[‚ô:<è»X¢¦ŠFÁîãs¼V¿tßc4§K­x‘¶þ9¸³œ/Èœ9Zj¼?€AF£fB{~ÇVÏÆ¹ùøö“&÷¡®ôN	É‚g4ya–8ÏTyßÌu•ãÅÐö×®…Pu¦¬×X7þ­õyŠz@7`mÁÌ¢s÷&6Zu™#-
®epŠUƒ_­Ìßn¢r5Ü½`ïÚÔüF/a çi55Sä"¨¿¶›!F€gÃa~¬÷SžW®‰Ö¿=nÜ:Ÿ¹ßðbîáèíY°–þ`¡?”Rú,;Ù´ˆ© ´¬±¯6ã¼ˆ|´¸˜„x	Â@c^Ï¢wðÈ �ˆÓBÚOçÇˆ&@@Ï¥ÔÃÍÅÐÜöT÷„t ú~	—ŒTM§�Œ	DQ‚§t¾æªé´Vï>ƒßq;þ¯YS'µ_ÒBæc°–�k¦`\IGS8°^\‘ó®5ó=àKsMÊYüö�ø¿[1È(ŠWV…VQ0 ú�8/’]³„äÔ¡'G‘+Û»ÜþËoýøÉ—›!ÞL*ð£ßË"ƒÓ×Ž@B9x�x^£v$£dj:{ã8…×æq²s–®×	îi’_z)Éútðo‚ªÿË{•õ¶±ù ;oM-¯YÂ#=N]L�·ª_ê«ØKœšIæøwûrõ½'v]ÄŽ<cpZÂâL=‰§|ëöHoÎõ\ïM;¶ýHœ4R_ Ñ2Ø�?úÝ×ÞÂÏº{lÉ–©íaÜP0�À.O‘ØÖˆwÒB•h;@ŒtÏâÖ,ã_}¿}ï+¿üèËä½ãái^[\—³¦â<oÄÈ÷äÖà¸jùaâY[ŠÂæú¹#ÕÆÛ&7U›€ð?÷%ÔRRKÊŠºÅÂÓtŸ·’ypç.P'�Î×h›)—ò	„Æ7¾öêø§_}ñVúê·!Äµ cOå`ÊŸ„ÝÜt÷êd½eúï¬©Õ¾”–œØƒÛãÕYÿì¼îÝÇOQi ZÀ"æ†V¹ŒaSd+"(�–±m¬ËKQpÛ|í�äJ]ºyíGrÙ²vH�¥Ë«®�'¼±ŸäQ±›�@/ƒ¨?ûõg/ Èí?~ý÷!u±"×‚¶su–™NtsHKm�Ð°uƒœ—šiSÒá€¹�šØÝøþ»Ï‘ã_¾ûƒ®jÓG\<ä	Û­£Ÿ�í0[i\”H|ŸÍŽ@›EÆßˆ®Ô�ÿöî³·.ºûP½=M™X°H=Í¤
Øè û–Ä
¿Ýò$@Ê4EË¹ýÑï½ñ“¢Ii‹Àîvpqj�«®‰*WÅVî¾‰D¼ì<ŠS9a}ß{�~xÑ!/n�«o;Œ@Jºfu5(¡&’6½:Z3l¨�enßy/ò_˜qäÅé“[xœ{¸˜©m)Ó†Yn“78óX*dZU¥çFøYæº–—››æ•Eè¸™›çûd™zWV™—…eç¥‡úi—N6t“g´Üè6Á ÎJL¼èxŒsá// Package main is the unified Lux explorer: a single Go binary that embeds
// the SPA, indexes one or more chains via github.com/luxfi/indexer, runs a
// per-chain GraphQL engine via github.com/luxfi/graph, and exposes the
// admin/realtime API and frontend assets on one HTTP listener.
//
// Build: go build -o explorer .
// Run:   explorer --config /etc/explorer/chains.yaml
//
// Routes:
//
//	/                                       SPA (embedded; SPA-routing fallback)
//	/envs.js                                runtime config window.ENV = {...}
//	/icon.svg, /logo.svg                    per-host brand assets (disk override)
//	/health                                 service health
//	/v1/indexer/*                           default chain explorer API
//	/v1/indexer/{slug}/*                    per-chain explorer API
//	/v1/explorer/{slug}/*                   legacy alias for /v1/indexer/{slug}
//	/v1/graph/{slug}/{subgraph}/graphql     per-chain, per-subgraph GraphQL
//	/v1/explorer/admin/chains[/{slug}]      runtime registry CRUD
//	/v1/explorer/realtime                   WebSocket realtime hub
//	/v1/explorer/realtime/stats             realtime stats
package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"
)

var version = "dev"

func main() {
	var (
		httpAddr   = flag.String("http", "", "HTTP listen address (default :8090 / $HTTP_ADDR)")
		dataDir    = flag.String("data", "", "Data directory (default $DATA_DIR or ~/.explorer/data)")
		configPath = flag.String("config", "", "Path to chains.yaml ($EXPLORER_CONFIG)")
		enableMDNS = flag.Bool("mdns", envBool("EXPLORER_MDNS", false), "Auto-discover chains via mDNS")
		showVer    = flag.Bool("version", false, "Show version and exit")
	)
	flag.Parse()

	if *showVer {
		fmt.Printf("explorer %s (%s)\n", version, fingerprint())
		os.Exit(0)
	}

	if *httpAddr == "" {
		*httpAddr = env("HTTP_ADDR", ":8090")
		if p := os.Getenv("PORT"); p != "" && *httpAddr == ":8090" {
			*httpAddr = ":" + p
		}
	}
	if *dataDir == "" {
		home, _ := os.UserHomeDir()
		*dataDir = env("DATA_DIR", filepath.Join(home, ".explorer", "data"))
	}
	if *configPath == "" {
		*configPath = env("EXPLORER_CONFIG", "")
	}
	if *configPath == "" {
		*configPath = findConfig(*dataDir)
	}

	var cfg Config
	if *configPath != "" {
		c, err := LoadConfig(*configPath)
		if err != nil {
			log.Fatalf("[explorer] config: %v", err)
		}
		cfg = c
	}
	if cfg.DataDir == "" {
		cfg.DataDir = *dataDir
	}
	if cfg.HTTPAddr == "" {
		cfg.HTTPAddr = *httpAddr
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
	go func() { <-sigCh; log.Println("[explorer] shutdown"); cancel() }()

	registry := NewChainRegistry()
	supervisor := NewChainSupervisor(cfg.DataDir)
	registry.AttachSupervisor(supervisor)
	// Bridge indexer block events into the realtime hub so WebSocket +
	// SSE subscribers see new blocks as they're ingested. Must happen
	// before any chain starts indexing â€” runChain installs the
	// Subscriber.OnBroadcast callback off the hub set here.
	supervisor.AttachRealtime(registry.hub)

	for _, c := range cfg.Chains {
		c.Source = "config"
		if err := registry.Add(c); err != nil {
			log.Printf("[explorer] skip %s from config: %v", c.Slug, err)
		}
	}

	if chains := os.Getenv("CHAINS"); chains != "" {
		registry.LoadFromEnv(chains)
	}

	if *enableMDNS {
		go registry.StartMDNSDiscovery()
	}

	go registry.hub.Run(ctx)
	go supervisor.Wait(ctx)

	frontend, err := NewFrontend(cfg, registry)
	if err != nil {
		log.Fatalf("[explorer] frontend: %v", err)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]any{
			"status":  "ok",
			"version": version,
			"chains":  registry.Count(),
		})
	})

	mux.HandleFunc("GET /v1/explorer/admin/chains", registry.HandleList)
	mux.HandleFunc("POST /v1/explorer/admin/chains", registry.HandleAdd)
	mux.HandleFunc("PUT /v1/explorer/admin/chains/{slug}", registry.HandleUpdate)
	mux.HandleFunc("DELETE /v1/explorer/admin/chains/{slug}", registry.HandleRemove)

	mux.HandleFunc("/v1/explorer/realtime", registry.hub.HandleRealtime)
	mux.HandleFunc("GET /v1/explorer/realtime/stats", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(registry.hub.Stats())
	})

	// /v1/base/realtime â€” multiplexed SSE channel the bundled SPA opens.
	// One stream carries every broadcast in a JSON envelope `{event, chain, data}`
	// that the SPA routes off via its handler map. See realtime_sse.go.
	muxRealtimeSSE := registry.hub.HandleMultiplexedSSE()
	mux.HandleFunc("GET /v1/base/realtime", muxRealtimeSSE)
	mux.HandleFunc("HEAD /v1/base/realtime", muxRealtimeSSE)

	// SSE endpoints at the URLs the bundled SPA opens EventSource on.
	// See realtime_sse.go for the channel mapping + the back-story on
	// why each path needs its own handler. Registering HEAD + GET
	// explicitly so the SPA's pre-flight (HEAD-then-EventSource) hits
	// the same SSE handler â€” net/http's ServeMux otherwise returns 405
	// "Method Not Allowed" for HEAD on a GET-only route, which the
	// SPA's `u.ok` check would reject and disable the channel.
	for path, channel := range map[string]string{
		"/blocks":          "blocks",
		"/transactions":    "transactions",
		"/token-transfers": "token_transfers",
		"/internal-txs":    "internal_transactions",
		"/tokens":          "tokens",
		"/gas-tracker":     "gas_tracker",
		"/validators":      "validators",
		"/stats":           "stats",
	} {
		h := registry.hub.HandleSSE(channel)
		mux.HandleFunc("GET "+path, h)
		mux.HandleFunc("HEAD "+path, h)
	}

	supervisor.MountRoutes(mux)
	frontend.Mount(mux)

	log.Printf("[explorer] %s listening %s data=%s chains=%d mdns=%v %s",
		version, cfg.HTTPAddr, cfg.DataDir, registry.Count(), *enableMDNS, fingerprint())

	server := &http.Server{Addr: cfg.HTTPAddr, Handler: withSecurity(mux)}
	go func() {
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("[explorer] server: %v", err)
		}
	}()
	<-ctx.Done()
	shutCtx, shutCancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer shutCancel()
	server.Shutdown(shutCtx)
	log.Println("[explorer] stopped")
}

// withSecurity adds the baseline security headers and CORS preflight handler.
func withSecurity(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("X-Content-Type-Options", "nosniff")
		w.Header().Set("X-Frame-Options", "DENY")
		w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		if r.Method == http.MethodOptions {
			w.WriteHeader(http.StatusNoContent)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func env(key, fallback string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return fallback
}

func envBool(key string, fallback bool) bool {
	switch strings.ToLower(os.Getenv(key)) {
	case "true", "1", "yes":
		return true
	case "false", "0", "no":
		return false
	}
	return fallback
}

// findConfig probes for a chains.yaml in the standard locations.
func findConfig(dataDir string) string {
	for _, p := range []string{
		filepath.Join(dataDir, "chains.yaml"),
		"chains.yaml",
		"/etc/explorer/chains.yaml",
	} {
		if _, err := os.Stat(p); err == nil {
			return p
		}
	}
	return ""
}
«$æ-¼xœ=QAnÓ@U!m*ÑÖ	¤E,¾(ˆ¤
6HeCU¤¤©Ô 6	rìÒ©ý±gÂÌ8B¨+vìæ½(à\€#°eËŒ]�Fc½7ï¿ÿþ×Ã?/¾õ×—o×¥KÏƒvÌƒ©xª@*¢ä“„‚Ks ÈTŒ‰ùÖ½ù3Ï�˜B!›`«˜2”ÍòšÑø_zŸhø¹€gdbê½†Ã¡‡å¾—àå=À‚€ì’\Ä(AE(²Y®Ö;~7Þ´O»G#Øú£Ö ;:éûC $* ,ñL
Á\ÆŒ/È¨Šì F09Èµ�‘¡Ë…},�„0<±ˆ9�#PâÂÀþëSª:màã"B™ýË•~HQ*ˆ¸¹rbŽ¡[^ëaæÛ¡ü"²z0ž¸¢H‡Š&˜¢ážÙÜêIºÐ?7«z³²ºü±qtã¦QÖ+Îö£f>�íÈYüÑ\˜þ •7æ3dÒÕûÎÃc3«…¬‡ÇòÚ 9^ùýžekâ€þâÔïçO2sáä¸Õ)fŸPiÖ‡¡~YÝ-ìêÎ¡°[ I£nìÄ(€¡ò"¥f¦•ÏÒE.[ä)aÿésý«Z÷¬UÃ9.ŸžWL¯##¡b„6xã×ì)Öß�íUƒÅŠ&¨¯îÖôog£d“ÒW·kúN¥²"ôÖÖ=Ý­ì¬ýã5t¹U™ÎlÊ’‹"T?#³ån�Þú·í_¼žxœÅYmoÛÈþ,ýŠ_¨D¦œà®8¨ð‡Änq)lÇµ“…a$+reñBíòvIÙ>Ÿ€þˆþÂþ’>3»¤(YŠ/h�æƒE.‡³óòÌ3³L)Ó/òF‰¹Ìu¿ŸÏKc+÷{‘Ò©Ér}3úÙaa:¯è§07ô£U5šUUI×®²ttYåsõqq“W³z’¤f>*ê»i>šgƒ~4™Ts£/•]ä)m]:1?>».¬T÷¥r¢2¢š©Üq‘ë©Iúi×ßwâP\]Ã†:­ÄC¿×¨õV	l÷Hw¿w&çJàßJ(l¢éÁÔX?]¿÷ÖJ�­ÉMx¥´jšß±`:Cè„+jÈ/±ÿCô	gÉ§*-£¡ˆè&üFË!?ÿÕ˜Õsº	¿Íó™Ô¿v$üm{ÕH•Òº•ß5$²äP_VÒV§ÀqîR³Pö˜[‡°‘ñ²(Ämnuã¿=¹(ëÊì[u“»JYÇi*Tæ�v‰x[˜ô‹µ®ò‚²%üöþ¹È¿H�®Ô]%r'R©SU*KúÓZ§"¶âÅ©¹¯¶(¥œs,9$ú)i€5òãÓP¸E*Æ‡éœ7½ðœ~àK*Ëª¶@B�ÓÖ{¦ncH$áM„SÝ•…±Ê"´ˆ{¯Çr¼ßrßÁA»�Eüêà¡¯Âél°.Ï
õ�M]Åx,‰ºð–$ïõ¹R6¦Å%®„w˜‡âg“kDbLÁ�éõò©xVù¾güÒt¹¤?€qrìVÓ8º"M×Ý4î!ÕþŽ÷:ä8ÆPÐÆÉ›,³áò¬0 u6)­™¨3“©x›�WÂUCòKö6*k)Æì!g9ü‘Ÿ
è<ì°¦‰©Ì�¢GoŽÅÞ"Ûf	šØ<‚[®kJë2”èA–¥ÒYÌ·C6Ò¨�m;ruP­¬ö²µºÀ¶…Ò^Ï€\�¸HX!¤bÀÆ­°µƒÍ€{�ƒ7Æ"óHS¿çT¡ˆ©–¡BÛ€Š_jesª>¡qûÜ1ãq-NhO4p¤D¾+7¤—QrZ´E
Ùû­UÚá,èg®bÿ´Ñûà¥à¤Û]™«¬ÏŒ«"ïÔ-`Ì0Ð¢_ghN¤S”r´�ä²1¦Ž1�À‘.¯$ó¯5yÏ¾÷{ôwØ Ç–é¢“ê—ÑhñjD�‰ó¸5Ü¨Š,|wŒ`
)Àë`lKÂÙ5ª�³ /Ú£ÅUSSÍ[Å'²ˆö½ŠüRt�ÄÁÿ­ÀÂìXSsQÚP4*iÏˆ�ÎG€œâT8ñïþKxCœxvò��]22hN«|¡Š{QY4vØ’°º÷Z‰‹ó#'íÇNÓS�úP€+jSbñ*yý}òúÇ—xÍ«ÿ†à{;¿|òg¨˜À²±0E¦,ÛíÄ-å±ÍcŸ%Ó™÷w{‰&6^Ö¦r�”ÊI�LN±1U5mNTR²BÌýQü5†;Q72½�=&§uÁÀ&�ä„bó…¢äƒvù_‚ÄÕ5ŠÒ‡ãdv€�¨5z¨ùX¹a•+Q™kv½¯ÓùZ“x«ôT–­mIŒQëÊc4XÕ5k“=5·Æ9©Î±°†ò^ž­	äÙúãÅ¼­/@]o@ž‰ÃCE[­ÀsžÉ:þ¯]�¼f1jï 5îï~M>˜s‹FÚÜ_(¤<Uo aïX$¼ûÑ „Á3àÊ“šß‰•†G/Ii±Ù•£ý½!°�²GÍJì½§Íxw¬¦².*’|f“Ÿd³s¸•Æüö‘ÑÓü†¸Ä^cTqÁ#5h^a/h…ÍëT»ç¨F')Èv„Úm)-£	™È¯µ.ðò¥©mJ"Øy-Ø‰kLúÞ¾ÊMGò#F¼ç+¶cOV»r'Þè„¾ðDMSjS¨TÚoÛ6è�‡RÙ}ß‰Ú<QP=t˜Å·HÐmfôs´­º«K¿£ë=f‚n«{’)‘…sÍ·ûòmýŠâ‹Yšøín—-•)Ð(M‰7ù‚¨®.w�Æjšp›¼Õy´ƒ¼¶°Ï¤C?,>é2Ðä¿  É“4y’„&ÛYh“d~ûík”D1(Ó�'�¦›¯”\p“´Æ^Ã`»úÿ•ýÈ{µN/»Iñi¾Ù Éod�Â³Ç|ƒ6I‡g£™¢£,Â'}9` §õfàãU�Ò¾_M‚ÎÍ–�Â‹0£U¶V¤�NÅ­ßÎ3X‡ã‚æÁC;U=J	©?ÐØÃ¤5W’ç;Ej=ÓåÎOv*$íN˜mo i¹¡3Áô™	¨­ü±SÄDíÛžô6 Ù²�0™ÚŠ\inYßÑìžñýC8ÉŽÅkñBtÎ²¨Ù‰ÉîIþóCD_Ž¨ÆÑëä ReŽ_£¹Â´‡Ë¿ŸØ­w8Ðw9wÑøêzù¹ß£@µŒê­ÀÓUÁPÀ‡º"O%yë¿QÛ‚Á)þBIt�˜¬leVÏc>v„Þ1yy8iœBW¦�õNT?}øpÞb«¬<J	gŽóúýÁÁè‡ƒƒNIW‹uœâ\ö^J�¤Ñ	¤¶Í�t%ýÆ^BU@îze‰Lús1]0ŒŽ¬bjÍœ�ýÛ©xwÌ‡Iÿ”ùÎ*ê‡ôEáž…ZŸ1“6KMÖ#EA6pÂgÃ¶Ò¹|J‡Ö[Bl£CÈÂ"PŠfŒ>Ð¶“ZÜ…ïlt‚åmcAz‹L�…éÊ¡�pB ÀÒ±³y<VQ'ŠYY§Gƒñ
QÉFO½,x�šÃN‰;?�¬kÏ$}0Í§u(è[ñÐŠèÚS^Ã tªo±6—– &�*w�/]îl+Ú&ó:¹8A—&„C}¬àµ�º«¡¯«U[·I§§S1%¾G$�ÍÜ,ºå¾ì¯×™w5HpåíøËåû³ýæxÊm#À_™17O›ùª¶8Ëz2Y}�`¡ˆÄÆ¨1ô,ë?N’Z— V¤%<kÑ78ñŠïaû!{Š°„í;4C;]5Üv�ì/ýüÄßØUÖòí�œJëf²ðÔõæÂ-¿N¢Oõ�ˆz'Ãr\½ú•8ø1ø*Óvímp÷ˆrý·Þ�òÕ7ýÞE‚N¾Äg2lyñ]£÷'n¤âEçÕÞ©rŽþ‹#pKxiîWù­e³È¡¥e¿óÕ’Ó§�¹iãÖìAâ—âï¼	�¿inóŠ½xâ­Ý!N£%`È =zÇþÃQ÷ý$ø7è–[ðaããjï?ºL>N²�xœÍXënÛÈþ->Å”@cÒQ¨¤T/�8‚"Ãò"#‘#‰55ä’ÃÈªWÀ>Ä>aŸ¤ß™o¶c7ýÓ Å¹œ9·ï›sXðø’¯ÛðTz^º)òR±Àùq.•¸R>~
çI*W“V¹¤�,_ÑC
5Y+UÐïj'czªt#|?V©Z×‹(Î7“U^¦YÆ'[±¨òøR@hèy“	;<£ïê�Ð£b›COâ5ôaŸÅb¦7°ª^Tq™*Íe5fEVWŒ“ˆ‚—<ËDÆ*¬Ê—l6{ë/DY± ‚•öœ¯U%¢U²*gj-Ø¢–I&’3;yÅþýûl»Nã5Ë!+öö›�j–×e,_A¡J±É"ƒFPa¢J.+[�„Š#’C2b.[˜2gU|ƒËœ'1‡œ¥	ÛÂGyë
Á/á`ÐØyjWˆž�*UÖ±b×ÞhS3ý�Ü�~þP#VÞ(ÎRhòâüp[ë×³ëzï�`¾Þuˆ§b•bfçí=s�[ß99 iy¸è#�ã0NÇ`1¿XäyÆ ½oÜã�™ßõ½›™i¬Cë[�9C´�yç�HR®“'DUQ²¦•vé6-[æå†+z4af‚Wè6·æ�Ñ´9^[Áæ”àSŸ¶ùsot¬“ðæ¼¶`œoR%6…ÚÑÒ7\q#ŠË=ìÒãý•gP¥R|S°TªÏ›•Ê�c‘±zæRèTüZcŽÌÞ®a«s%dBéÕäšµøÆÆÖäfj`R#ÂŸS]’÷s\å—B~ÕcK`ëAýƒ´\Ã–gÌ ºÊêÕOL;„±D,9 omþ(¶Ý„�RRàLŠ-#>ñ–µŒË‚�vwÁÎR¨º”ìQg£ S¤î¥n‡I8ÆB dªÕÇ¹ –€&÷.+kØ¢x©L6®~. /Ëò¼ˆØ1—1x)VW:F
cFù`ÝS7$A-³”K £gH†¨¸+ÙôˆÑò†Ÿé‘à¯OÙ¡›	lLBoWb©ÙÍp` š2ÈöJdÂ$Á$ØË'83z“K„SŽÖ‘FÞ«,Ã>¼¶Ë­àc»¸±W¯Þ[¿|ã%«‹UÉ¨rÄZæøÅ^SÚˆøòS™®R9eä’ d‡t‘D6aC~­ª$b#(•Ž?�ÎØškÚfpõ¦¾bàžáx„æfª5bK(³NÙ³�›°¾O^×Køj–þK‡ùÙÓ¿<ÇÄç8íÍ˜	îwúT8ÇAïÎÎN¬B¥&¡É·gqUdy)Ê‰c¤»"ß— Å�ª€ZŸrÌnxÇR38´Ô©áîœl±iÌdš!4éR/ûÓ½w|JsW	yä� 3â§LB©vè`¦ÃùÒaSGïiJJ$�³só¼@"8tÑ5¤ÿ"3³Ü×•ÑqHQDvc¸Ú©Ó"'5‰ÁN™ßìöIå†f§8ù6qhzõµHj€l§„Ð �ÚH¶Œ:©eû» +Ú#ªÉI½)£�•rœ	.ëbà‹¨S"h\2fnÓÀÚåÑ1!oðéíÓžÍê2	i3¡Èœ÷)îœàùÓ¿½‡Soð—¥@}Ç/¯’$x1$”ÞÎ“\®L¶–�†¬I‚�rËÌ�ãPŽµQq¤õuÌ6ÕªIt{É·Y ÉçfŠ·äµÿ_4Ã^"´RüzãRm�…^tí!”^‚}²@«ü»ÂŸn(F$ŸÊZhÕ[(QqbqÔ^ÎI§žÜÆ¾¦åK±#}zÒhªÒ˜¹°¡Žïmô†Ázö˜ùSÿ7;hå^+ßMmz%pœCAœ8ºYÔ�·^w€ûn f0<2†™m”13TpÙmJ2�k*@@™?í[¶7{Â#“‡6
„|ãà‡š_ùÿåqí-
:yÝ´
t J iÊZ*&Ðñ´E`ã3×i¬R¬#Ö�MÙE�É"WëNWÌûÐ<Ä1	õP´ÝÍâuRsÓkOJœ¨�þûìÓÇ'º]„:ÃÂ›Swöög$gY¢Št»v› Ý-|—Á	l[ò¢€@AsV½¹vÇ”½¤š÷ç/2Ññ’ ˆ·/r®Ï ªÔv{�vŽöçÒÞ2lÂx’èÙ÷¨ò„Å©cEQè.øŠ-©åˆ‰é³Ý]—z¹@kHY1v¥¯ÎÏ1#]©SÐÌ¶ Ì~ÿzkEy=XÄ@è¦ã¡w ’ÜlÜc³-—Ý[Dôò¢×}‹+x”bPpµF™KïÔQÑ|[�€Ê�æªdmÅÞ£´{ Lºlq@Ôøç‚ýöÛí3�Áj=Á]ª‚�F¼áê¶ú8åÛÀú©ª5fPÉ>@ìt²ëµjŽ-Gs0fK š> Ä(¦Ê(H ¿‘Iæk_+ïOM6Žmc5}I-^iÞ™éf” øóž€à„²�¹ÍœÑ85p:yÅê
MÖ¼AÄ\Ó@	Í0œ/—=Õ*-ÌHÈR‘% òÍÏ,‘N›uD_:‰9¯ódw{¶µ�êh¤9è,�?æž»îïÌG¶7¿¯/í¯ßBé ÖÍˆeb´uÿ�ª£‰5Œ$Ãµq~±Ø¡ôMxýpÌ¬7ˆeº[šndÌ¾Èó?­Ôn��?R`õskC{‹ÜNO�¾íÁìã7›^\{C°›öôfÌúâ5tôóÚ–öjñF�9¿W ø»UGßý à¾ç(®o9J‘Ê5;¨æè#‚"šŒ«»®½uJ�TªÆÀû\¬¿×Á¸§?È¸zÿã#tÚ2èPkx;�êÐÛŠ¾¯0‰÷[ÃQ³"·Hh£�®…üÞ×^,¢÷q/ê½îÇ%Æ0‰m_t#ú*á8ŽìGÇþÍÖit/~Wç0üÈ¶7fÛ´h	ÄÊ:‡o4�
M!�å«è®SËÀ?wÜz�jrLÛ5eþæk"¿ïº½tÍaèÿÐ1®�k?�‰+eGMÁô Ÿ‘þ»^øz¶õx�XínÛÈý-?Å¬€6TV¢½IS,”8EÖëÄí&Njy›¶É¢¢È‘E˜23CËªa ¿ú EŸ°OÒsî�”'E‘À±%ÎÌ½wîÇ¹ç²NÒËäB«2ÉÍÞ^^Ö•õ*Ú—¥â�Ñ~å}ÍÏÎÛÜ\8ù¸1)ÿú¼ÔÃ½ÑÞÞþ¾ši{¥íd¦�WÇWøíbõÚh¥MVW¹ñcõâø\í_}·¿HœÞ·:)x|¬ÒÄÚ$+�óµj·°U’¥‰ójYYUAPº‚‘
?ü¢Í•.ªZO±?œ5~ªŽ^Ÿž�+þ‹*“B|…µ6#îÊŸLÕÍm{¨ý:”ÃÃéð	T£‹§ÃñP´ñ™+š>àæáôI�l
Xö´“1U5mßý]j]O’"¿‚�r§‡Žúñ£ÎWZÍÞ<›œÒ×Iê‹�\é˜W˜U�MµJL¦lÕxíÔÒ&%þTË¥ò8Û]üž£´¹˜>WË\ÙX¹Š{¬V¹S¦ò+Zæ+uAÇ-mUªD!Š:)U­-ÊÛªÿüãßTHyÔ€¥I·ÔÏa%ñªq:£@¹š(ZS[Y5Æc;‰Õ´¿(ªôÒ�Õ¾¯.µáçÏ¿WðœYY|Y¯òt¥pF|R3ëÄ¯�:ÏTÙ\S’C^ñÜ¹bp/yWä¼lŒMÜwrüìGÞ;1ŽFÑÎj*¢»k«'Ë"¿Xy§Ö8Çó¥öé*jl1V7¥ö«*›Š˜ÛQÉ&²êð©²qu9šKDºj¸çB¶¿j®Õºj
qÌÓv�;�¨ß<bò%ÌûIee‰hwi„!ƒµøQ>vÑè®sœÀ1i… ¥tmZä,¬ûsü¯ÖiL†µ�nt¬fEµæ	×”Ú:•Ùªæ-‘<~uÊÂ±0�ÄQÔý>q®�ïñœe÷¸÷–ÕK¬­pA+4{	ëUž¨³ãÙ9/fuk^,õïœ>
F2Ê!f89›+×,\jó”3ÕX¾’~ýåPëk„9Þó›ÊzAÈÕ&õêfo Õ¨˜¼;Å†ËÑrqmØ€Œ‡É[u0Q£
Ý^À'NVï~Yl¼FÅBÀ¢Y.%[PlâÏàÍÇjÙ¨Œþ+øq�ÑjÎ³nny>-*ÖÄé"Æ´qÊr×:ÇíÝvî9Ó9Îâª¡§íRHôQ8‡¼ÿI×Ìï:A¸t(Zld ßêÅ5¥}—eR·�dº†Yõ¦)ëýµÍ½æ§PP
¸ç)Æh©UèÄ2;�FÖ„À¹URëmz{·q(› t >{û
u�èÈq‡.R¿»ß‡ï—ÎOôÀ²1)Uá®�Øh¤¸¹ûÊ0[íõë�Ç7­ð)¤_êè*F·½Tí®ØPÉ£–¢T‰©#Q—Mü�FÐ·zÞ¥¿¨C&2ïæ­KØ÷³!žagw—;j2ý¨Ét¡½Žzmè~¢|WÜ‰œŠÒ˜9'
µeb�¡ªn¤*¥„€hÚ¤ k
ÄæÐq>©»j©æR"sâ‹:JŠÇ¤%œµ�ø¤YÄ?tM·©  äª4"T[Š‘pÞqA02
µ"!Ç[ÛBÙ‰ëóåöñá¡2yÁ€´áßtN?ë¢“i)�n:ë#1 1HÕôÈfÐ8z§Š,¨Hã`É7‡¬[´@ê .Q)�Ægè8D$`Ì l,#––êÔ“Ioãû`BÒžðàG`+°F¼EM€DÀÇpËä‰ƒFÉTH€Ðˆ�©	Vž?‰^cSàP”BÍ'›çgâ.Á*g¸�%ˆâ'«Ñ@£V~ÉÓ²X�Š]Ö~3V�&hî"Ì(yNQxh*5ú™"GrmÅ¢_l9š†þXá©uQÖ:Ä’ôÆ°†Þ¨A›D;nˆÒå…:ªÌ2GÚ ŠI<ã³`þ¨Ã~Ä¡ýÀ°‡sñ¹ÍËY�¤¬«ŸÏ^Æl`j4Š_hµ<n4zŒ3H„áp'ÓÔ‡�ku =ë ç7Å3p¿¿O]JÿE;�o$m�£}BEŸð¶§ðBË:{æÈGÑ¢HÌ%àŸøËøšñ’Ø×:4‡ŠœEÍß›÷f+”)û%’¦*´ô‰ù{‹º¬MÍsïœá–d@Lž&“¤T/*¡ã2ätHYäz˜íJ»»m´Vm\f¡ß²« Ï–�È†Åçá²OœÑ~Ë”Ûv+u�©"~^ü2Z�%©ÈÚåÞ›ÀÅùé½¶¢Æè]Â¶�‘­ÆX”ÆmO�m…~…ÂÌëB_ëŒÞ]„yì›ìs¶ ¡±À)§Žÿt|ö—»Ól«>¡/R¨@D›ÔÂõ5©Y¸+@%Q˜½>¥ë{žß|ÝH!àLIosÄn,ñ[€ç¯Idõ5rƒE…ß¦æ‚D¹õ0T¦ås‘9‹Ùò¬Ùç¼6È#ôÈí¼‘£§”¤að 9d}‰o„ ÐŽK½� Î$óÒ]Èœw:i¬]ÜkGK­H¦o n@
¥xNïÅ ;hyUÌ,=þxS†MÄÕ¸5ÀÅ`ÁQ‹ÓÛÍ@…(É´é%TxTbóVð-ÅóV3–Ä©B«_Ÿ‡Áuwk	¤BÃlCNß�„s?¦ W!ƒ‡6NšôÝ)2×²É-ÎƒÙ�³7Ü©H4™ƒ)èÊ�¬Î�eFÎ©°#&~�·æÊ%›PëŸ‡{±³1ÿ¶u("TÏaZ_ä&Îç,®±¶ä+
¹/ÛA|ÚK§àÒ€¸šu#í�A&Q”_,gA®´añí$1'
µÌŸÂ^b˜¢Š¿ÀØ/ûñPP©�òâ44u˜Ã’ô¿}ˆñj-mP%”÷è`‚ˆÃ^<ñZ<š4µ|/zPDŽÑÒWeÇjVêþ'}cvZÕè.Èl™,yÒ—0ôN‡#=�á2ÈnçÕ)ŠM“Y°•†Ûr½”k¿8z£NÎÏßì?P|-$,t—”"â]Ïm¢—ù%_�¸ªD<¥Å¢ê<“K"JÃá^	ßË0çæhÉœBóÊ[Â«�¹ú–8)àˆ”‡!�â`zVAÛ7-p…Û!€ÌÉíô4YTÙæcåc¾¾@ ¤ qiŠ¢*Vg¡]ËØÓDß9Š2HøV=88àñ­iaø^¡Dá²0@…�_ÉÀ¯@?¥Ã…¯'¬c¡‰ë˜Ÿµ�˜‘GìjBbðñûMƒ†hRƒÁ:–ÙžÉ3Ï�{ý“¬‡Þ„�˜#¸ûk”Ü9…~¤'4ÐVm3Õ$å31éÎî>šÜº}UõÙÍž<KS]L~�1aâ±Ö·-¯Ø.IÇÖq$÷mé ¯GƒEq¨¬[[Yiý}â $\SóÅ£Î }Çe¿Gl­I
!eVŽ~Æ‡¡ÛÐ Ž�a¾…-N¶ypD¢Í²µ�a[ûJ �+±Ù´Le¬¾{ð}ØÂ	lŠn%£§l�Ù2Ê:ç‡U­q7c†é�ÃÄ»É°]Äê–^õT
±h_^Â	aTŠæ7·ó‘¸šaz&/pO¾6�Oõú<ÇÛ =<P÷Ã³ßÈdôQPßŸŠgÀ=�&¬¤þZF¦XúÚsø•IJ|¶3…qèÉâá‡h$³O?¤Aç%l�{É´ú…[;ŽÂäÅßÀñ¬y#%µ—†7ªÂIÛ¹Ä½›¸¾†Â×'bÏ±Gl5¡‚ÞD)W÷?LÀ�¯ÖÈdÀŽwÿõ#£Á¼ÀxíYënÛFþM=Å”@Z*Q(§ÝXY`#»ŠÆ)"w¬a4#r$qCq”™¡e×0Ð‡èî“ìwÎ)Z–|	ÐÝb‘Aè¹œ™óÍ¹~ZÊì½œ)±�EÕë‹¥6N$½(ÎtåÔ¹‹ñ©ªLçE5þËêŠ*å†sç–ÝopÊòëÖ[š§!|óg±Pqc³ÂÍëIšéÅp¦MQ–r¸R«³÷
Ûû½Þ´®2qŒ�o”,¶½¬'c'�Mœx$¦Ç}qÙ‹ Hì?GjÕY›ô{‘¥õ4EGùÍ-¦‚'NHÁJe®Ð•�OÅgÏÅ‰‹\ú�t²œ&±:_b^å˜è¬ˆ™vâQ¶
ÂW½«-¼U“1+ø 2w>™¬2U’"áIÒ·€oÄ£I3ôÏ83º®ò¤�+äjªLØIXÌ4£ð¦®ÈÀÑ¢>glðŠ)°+s¦^Õç´SéKYå¥úÏ�ÄÃ³gC`Qj£ÌÐ„�ú„«_× �Írˆ¥gjE›RÛ‹9›ŽJmNëEÃ¡ù§-F½he~ó#Ý0^ÙX<ÖlQé±)?5-ÎHÁš�ˆÙûX´ãª½ˆÞo ~e	m­-=PSY—î �¥2)ý—ðÑQ%.{¡=0ü½a 9Vï‹GgÀk°øªEç]Wå�Á¤ÊZ¸]/:“F,ìL4¾j&èÊéX‘äøW•JÈÒ#½Júéßó<ùZ<<4VXžÓËÓeÙRª”vþ0~}”|ŽîPÅ\»Þ¦R "Òã‹¥"$×Qy¼�Hë2t{vžà.5"(~÷q=±™)&J8-&%ÜÃÂo½C·soÔ‡¦tÙì‹Ø¯…ÌÑqYF1€q{k
§XwHFFpüéOäÇ˜f!)„mÿ]¸×w¹ïõÊ�€¯—ÜŠø£ežIë„ô€s MÛádmƒ)N.O¼ÿ�Êêâ2®êÅD™x_<»êbëæJLùÿDÛÓï4ØÏ.ëõÓB�©Êí‚“"Y4[ ÷
oƒ»„òìso¨BYÆÚ-þ¡L1½ðéå#òØ³]7xÖ X|¸Á­i¬“,ƒy-;éò–cº«oÔ�Eêe½·eÌŸP6|J–âZ²üK‹#T_õB5†©ò�Ž÷×)_\8†|zÙDª±ªr±„eÝÈã&5lK8´#f¦„½ÔÕì®PGkvfšÜˆE”äwºäÏ•ÍôRå+Ÿ\ô“‹Þ¯6½¯‹6ŽHU¡3²²Ò·ib…ÆG×Np*lŠ"™)ô²,ý¨…ín”€·:vW¾wpÞýðJðNå��ú&ˆs\ ·u—˜½¨®Ý g‰6Aºl~£D»vgô?¿jMåõF•6—vŽ-Þ;—“ìÏ»î]«]ÓvWiÓ]´+ Ý¬Ø¸]}¹†âÖµÚ¶š…y†Ã*_ê¢r÷�ŒêÇ¿?<[{ò!30¢O’• ¾] E”·Š["3F<ãÜV1‡­Ò—èŽ”A_	ËOâ‘?•{JÉ òb¹\–EÆ­’'�àe±AÔà=„½«~ê?“ãB©„š„‡óov.‚
\	7ü7ô½ÿÎ–? �ÃwöïÉFËNÇ¥/t~ÑéÛ±™‡éUk;‚–Tè¹·“)ÂT§ÞÝØKP 	* ø¡ºkéµ€ ›'@Û+õASÐPò9oôjñçMc!f£­=ÀÕÑ0J.À,S(+¨åóÁXOù�¹FkY8±’V P¨@uP¯6©Q•vsRB•V¥âu…~±¨¤¹E•«sÈ›gA B:
µ6HøïY@¤Ó$OClí[¬u(Mš+.‘'~ÉEbþ©z'!©€µúUa},ÊÂº‰[Í‹l.
+æz…þøë½§ÜŠ%¤…£2Ðç›¯„Ñ+›Þ$)ÇÎÈcóº*/ ÝK b9fÜŒÙtFdœmZÌ.y‘Ý'§üFág—ã²F™·îÇgµ�äX««AgÉœ‹¯QŒ£�+p#ÀŠ¼Á•~Tø™H«¶Ó�¯@¨Ë/š�Ç‡	Ô$fêá`ªqv.£K£À+œœÂëÌqÔfcóÄ0XÉÊÑãsmETî%âVñ4P—)c“‚º^ióž‚Z,"{‰àLWwMƒÞ†¬iGRÃ>(ÇWäL=8ÆÓ¿±c€‘%žïÀW7êCÛ8ó›pDNøïW
¥NŽPý™ç|Âäæ&èž¤Üå;„9¢&#š’=‚ tYJèñ`'ÌòÑÁ¼F%ÅÛô@#}ÀûÝë,Ô†ÁkpˆÑë££ÃÑ1J #,j:¶•�!‡“ÎÉlçš(<:èÀ9¨Q’Ô	 à°©öû÷o¿‡åI°w5—Nä*+rÀî]Ø‡$„!«5Š&õ”lh!ß«äätráÔ@üeï¯ßµÖÄpr‘7ež`_ÿÛM¸�?<‚¿õB€maŽøQ0d­ë¶%.×žl2ÔtÞTÈk·t,îfÝ·fç †× 
äÿ‘ˆ£p!~Š]ˆ `�Lm“"ÃÁ/x»)P�%ò�ˆDø=Â‘xï­	‹=Ù¯NƒèÏü8~$@ ­k¬ˆw�6ñ»'€™üýÉ»ø�¯`ð(‡ÆhÎ³ëóAâôV�ˆ‡nŸ(áŒí¡‰š§B¢Â(~*~çsv1I‰"êIð3XMfÍ¾HÞt·š,¬£ä†–°ÎÆùy«ìÁf­ƒ©«Þ À ýÒ±üxœÕXïnÛÈÿl=Å„@\2¥ißµ(Zþ�sœKŠÄXIƒÂ0Îkr%±&wîR²`èCô	û$�™]R”lÆN�z÷á"/çÿüæÏn%Ò+1“PŠ\�FyYéÚB8Ú	¤Ju–«Ùþ¿ŒVLKKÿzFÿ(i÷çÖVô[ú¿±5’»Ÿ+•#ü1ÓÕÕ,ÉÕþJ”E²øS0ŠF£ý}8š£:ó*/$äì\B!g"]ÁEÊ_Æp–$ÉùüóÅ»·`æ¢’Ð™Áå
¬4Ö€Pñ‘°2Wy)
Hµšæ³> ¸iSpÄ€†5©…\µ3ÝjM…Ò*O‘Ù¤sYŠddW¨ªgžg¾í¸C8;ç^ö96œÕÁÅèvíÞ±²õ
æºÈÐZ`
o TEc G/êFÙ¼$-ÂÊ¾rÇÜSî{ªûªNå,7Ä�“*;¯¥ÈöŒ˜Ê¸Õ°W6V\¢?Îcu½¡¯“°VY6@ÿQ.“ÓOï+¯G;ÎQDKuæò}þlmïhgÞ\Ï³S)
Rûº¹í˜¦âC&œ4•¬¹Ñ5 õ*/àj¯|O«b¥Î$„œåÈ;y"—›V¦è!R 
@–•]u"’Ñ´Qé†0òê;	è`-mS+ØÝø€ç;-Kq%Ãû]�b¤CoÇäjë9Ò·[oùkE:ï9½Ìk²¶ƒa4ˆ‘¼ï ²_ff‘v
£I”©ÄR2ì¹æ*“×²†?Â¬ÕfºÖ�Í•41WG-K½@]ˆñT÷ÒG'¬·¢Ý±34wòÁÔ	%óx÷²Ì‡^ÖÊ(_žLöŒnêKÖCFÉ‹ÕuŽ	žç³9ºRÕ¹®s»"a¡w]ªÅ>Ç"‰±Î¥y¾$€¬©
,\�Ï#a¡®uý'³,L§U9‚C>…'¦hfï…EoTòNXŒgž¸’	~‹ˆ°6Ää˜¸§a�«…(òH <ýÄÐ±H8œ¾?‚ÃC‚!9u•¢?ŸDIS4ÌåzXâ*áµÈõrQLJ‰‚,7Ô |>¨S(m»äá¹°µ"�cLÑ
*açÔ2ÿûïÿø®c61K£üÆ.'LP Þ`>±á �í¥û»Ä(AOñ*¸�Š�¸€R
e’ÆßX×ß­æ/dÛï›y.‹–Â@&ÓB�±Zõ³èñ.2–¥ô²3¥U8†)Ö‘¼è;Ì€„Ä®¶ðˆz²¯+Er+]þ¥O^‹Ôb›*ò+Ù�mS^$P4×{4Jq<bá!Ô)°ŠeL­ä¶^é›²šqÈ}>,…'ÓÒ¥E^£‘ä$£°¥®¯žóÀ#Qméw¦T¸¾È`œÐ3<ý!þëÁ�ƒ77Ê9Ö#ˆ#ªu&-sÄ¸SBáq…éš&® O¿¨Ðá'ï±,õ¬í¸ç-DM±xZ›Øma�½ŸÐ‰}8)›ä­N¯Âˆ•²óžôŒq<$®…œµBÎŸÓ²ÈÁ„[—õÍ vw;)‰ƒHKó¤£!~§û£*¼ö{k²-Á.<®ÂÐ"èyÏÁx@1	¿½cñ÷°†õ™­“ªmC¸ù´mhtWÛf^vî†¹w×#ðÆ‰“ÄÛÑ¶´¬àDëZ"&|j"pƒâp+ŒÞÎ¸.Ü)~naèh{à>J	îPµ¥NíúcÏ7«Nyb)$¯Þ
Ü(©iOÇrgw‹@—Z³êmèþz³Æáõ‰ì@Ì¹9±ÙÎ¼°å�Ù¸è±áu3à åÿràtšn°x“¨¡ùÈ}¬2´´˜åØŠÝÖå±å4ýÁ´«õ`�Œ~9³³x#®Ü4ï¬kRÄö¾â¡5Õ�Êºõfê‰(]iº²dýmñ·ÃŽl{¸pm•Þš‡Ýó’øî²µ_û´w¦ÿ€N¿´dÌ¶AðRNESXOãÿÚ¢i;+<íqŸh8‘å¢O¹�ÐAˆ¶üßÑ�r÷ÒJþgiývøõŽL›Åö0séGFO[�frŠã×�­1øhè:Ë{Šnnã^Wðßû�ûú–Ö…ÎS¼ú:MÃNNõÍËì#�Â[¹Ã·£1Äx‘W]÷Š�š–Xl‹’# ÔLvq`ÏIØ!ˆª’*ñ¤ônä¿´^¬Úµ¯´Ã©¦¼¤«Å´¿Ü= ƒ@|>ÖooÊ†ƒmüµÈ^Õºä'ƒÿ0íµgŠ§ˆ=~Ç˜Ò×ÆPÛ~òà«=x$ðª÷ˆâª6‡KŒ ­Wn!Äé mšD^#í"dÈÑ­¸olÈ{~‹ô®ÿbQ‰˜þ¤¬i“àõ6ëÈý–†ûuzÏ]™°ÀÇðt‰ENœ,Òåu!j˜öZ:™¨�‹>ªRÔˆ"tÆìN£ç�PZ!“ÔêÁ˜®Á8MŽÖ`L{ýÎ´€O]ûÆ8_W˜€0u›	`³À[QÄTŸ&[DŸ&[4kë„/£w]j›æ*¯hcb<¥~ózº •©Ý;ÙIòòv°ùP°8žfðä&L°nƒ¹ÀE÷´Ô<ÇxçX8zýâÍÉ„n‡€YC@ýmŒ7Ù¸û�ÈÄ#ŠôçÑxÓå«y½N™hL&U‘[âˆ!ˆwEaÊÃŽäC�—“J¤Ü¼ö)p4ÝÐÜÁhâ*ƒ½Ô­Íè^õÆ‡›zNXjc¬~ô’(fLQ
|8�í3A2ü4v·ûÖÀmshIBcú£�¨(çôøtŸ§hÐÙÁ9?QíÐ’ò:4¿eÑýàéh• B~]àW2˜v ŸÜÞ‡t\Ë¿ë(µúúŠÕƒºƒåk¬¸BŒAÀ«íß'¿œ·Ä5{¸zÖÆŽg*­ŒüTç8Pbz”�gþËçFËx[&¯±ÉÉ:Œ’	®&kÝîQ”Ðâ ç¿RåZ¹wt4›þMNäò˜Ø‘u%îgÿ©Q(~‰l–ÇF7²c>¥á…‡xêç=9F± §¹î™'ÆÒï?~ Q<p—ÉV…²÷U1¡Î¾µÙ÷›{ëþKéÜgþwâú§Zzê¢¹dÙÉOh^>øÛ_¢(qôáî½Hb!<ˆ÷â&àI†µÚÿö"vŽL¬°�ùId­ÝÝ,áæÙRX/¾�‚ô· ™8¢ž³g!EÉÙ¾GÁÆýÓ¹OÖù°D·[N~‹¥Ã†2Ð²/¢›oæ}„>x­|šþNùUèäÍžrˆøz�3î¢hdÈ#+p‹S¬ÇÃ ´˜®}«}4¶~ÿÕÑ¯ƒÞ­žûóo]'Ú¾¢[ûoQþ*Ã¯Î·ö}ï6ßÝYûÅð­¯S}îßW%Ðõ·Nz¯kÑÿ#`‚õKÑ÷è _eƒaÈÿ®GR9?cÎOüÿèS¿ëxÅ[koÛFý,ýŠ)�r«HNêˆHlçÑ&Ž;Ýí¦AB“#›5Eª$eYØ±¿pÉžsgø’HYÎcH`‰œç�sÏ}ÌÕÔõ.Ü3­&nu»Ád'™êu;Îé"Ó©ƒ:òb?ˆÎ†¦qÄ‘Î†çY6­~–è�ña,ý¦nv>¡æ>ækŒãt·ºÝñ,òÔ	¼ÑgAš%‹Ç¾ÿ8ò_âs/S?Ø¦ƒ“-õ©ÛIÔhWêùÞ9™wèa”ŽNä]2@÷ž¼Þ‹£qp†N�ãpv6RJ9žÇ~NÏÝ‰–g{w¥µ<|s´ÇgÊá¦FÃa{nx§ÙèáƒŸ·‡—÷†§Þpo˜L=i~²˜š1ôåDÇ³ÄÃ#Ç“¹ùìz«Û	ÆŠËûnWEAÈmt²ÁS7sÃãý5Ö�6É`/žEYo‹-ïUÛ�{Ž¾šj/Ó>^È&úê,ÎÔßé—ýìPò>¥¤’�ˆÑ,Á<~·ý~@ypŽ\mS¥lgdf§K1]}™X9ÆÑ¥>çú*çhÑyòx_¿|û‘ÓÊ�êÊ©HzwUÒ¥ !ó8QcüÌ÷éXáñ (¸eÙéƒ¡N=wª¿ÆüT•%î¥NR7¬,£Iž¯‚4…Î`Ó_Sœ¢¨}e´Aôò3ä81Kã�16­ÿ�žÄ—zóµwZÏ aÍ+`è«B]�:DÉ¾Kv%f·B+…>–(1�T«Dg³$Â9Í´ÙÝŠ®nW¨¢ªÛVW”;Ît‚�(�êùJ¢8ÒW <eÎÆ»aªÇµî¤“&ù¿�únöä‡ä š.šGÅX2” z	1Ùå˜Cè«=³elÆHÏ—æ£Šš¯%To|ÖWñ…™í™Îzö¼
ð¢ÊÃŽ�šŠ@©cp°oOl&2+Oc¸:!O.¦:H•¤#4ÚEƒ*mÚ¾Â3„ƒØd,koÖ	£³[6[ã´±‘ôaœ=åÎn¡€+gUÅYã‘}	W5êÒN!¯ö��’ N‚l±ù>º�áPh©T´ÊÜ�ªi¢=íÃ©Ñp,Ú?wrŠ,Ü…:äsƒ_ÒNõXÀkWéy<}�˜ ISbn19®`âG´Î…ÖÉ÷Š²l`çç:Rì²  �_¾Le·c­¤(×‡ŠnÙEâu3²m÷6}1¯)RN:…=ÞãûzìÎÂŒÙæÀh?ôÏ·0+‚‘¡J±p…¢ñtÛ¤á›Ý¨�wÑpI£UÛ_Æ®ÿ4‰'Ñå­$QígýÂÑ�^pÿŸq¼¶Õ?âX¼ek|kîý¶íß·fÓn{ÕÅm2oùúŸ"ÌXÝ¸ˆÙÉ'z2Ý„�1á›G%ƒ_â ê¡Y^1mQ:X¸“�\§ƒ¿�q´Œ<î«wïõ>šf£®RwÅuYW”¢)KòDšü‘zøà§å8}¤n”²4%F
`�o!#ñJð$_ÎCÞ›é!}ùVN}{ÿäá�sÛ³“Æåì·újûÁÎO´-+)­v*c£ä»5–{Å»ú|œt;¢Ž5' FTß­¸"ªÒ¨›þ=ãÅ>W/gØà\Þ»Òªcš|;ˆ(pN–†´…n JcÀvM«%^0£ÈÀUyîF~¨oOoJ�'Œó—¼¿«ªª8ß‰þ‹G¸“‡zþFÿ5ÃÇžóìà½FØtÃ8ÑÉPºE–4l�±µùê^œøZ4:Tö
ß3Zâ™#´ö…xÿ¶cÃ«ÊY™."ÍnçÒMÔiì/�™¾C’�Ø{7Zt;Ì„p'û©¬b>x‚f[ó½÷=;aE~z‡Ä "|çý 7c7{°³>Ö—Ö»÷ìª.!…Ú(-Gm(6¶¸E
^
ºv;Sw�Åù”ôÇOŽÇ£<UÐwH-ÎÈ)ü ‡Ù�Q†¯Ë˜8d4¥é»þúXˆ£×Ç7"BòRþ“Ùx¬“c9–ž]ý„ƒ	Ïµ‹s3 Ø‹#†Xwi@	7w:ÏÍ‚82)-ôÙ`ôÔZñÕš¹¹¿�Ÿ$ŒÀbÚ`Œ‰š�Ý—~-˜©ÙËÖqmFÈÆ,®ïÖ¿wf¶¨sß“Ì�MgFü-êø”þòŠVµ»*|*V©Û;ÛÛt›¯˜òñsy´i:”ÀhÎZí¼<89 †Z)kXøí†¹F ð9ŸßÜp¦{F³èoÇñÐæH=ÕùquûV¯[á·	½}üjãn–äXµBß*	ñúVIˆ:ÁYB�8ý:™EL7ÜDTo×óÔÐÆ‘U3>òâ›Ñ™=•o)cFáU³=7›Pt€jØ°ò0Ï¾ð¸Ú”�'º‹Kþû
µ¶áòö9’eci±„<È—Â¦’¡ÙçÌM¦®=Õ�„bV|°UŽYˆ�í�6ÉãU3±®ŠÜðØçˆ|�ß°	1W%’û•íì\m½©üÖsô×‘Ì ½bYwÛ3q	žÀµûåøõá·rÀfQå†�ŽÌ­ý©e“^³;Õë<yšôSì‡Ó5O¶lïN$L_k×?ßñ�§\s„Ifÿ7›ô/òhòËúéÍ"¨êÒBÏuÈÛ*j@±Y#çðõáÁß_Ÿž|øõà÷÷îÿ´ó3}Ü8„§¸%Æo‹¯U6€g]ªü> cRž,^>‰ã°}^³Èðü°20û6ŽMÂu;A4ùüääèý³D<{5K‘ÊtâŠm2»R?.–Ju‚4,SŽøŒ*‰g¼˜”×™K£­J-±×Ö—`s$¼¯ÝLsNË¹dðcÎùjvÅ^Ù�õ)nÁ%V­%N Ÿ«ç6ŒBßfÀ¯i”·k~"Ü®W—d,GÃp†˜Û—Õ:¢áÒ†‘3PÃsœ*j
úŠ÷Ï½¹-.øÒi¥ZÒvÈè%êzÕx.ésÃ6·¡aokóà°÷X	Ápk`>öê‰„Í™›ÍRµ ñuÇæ1FåÚË~¦èåj,“+ûªû/PIz@	Ú åG¨¦ƒ½0NµÔ<xa€p–à’Çü"Ï¡{!r]:íKõZ™ö^†¡ÃÛ7/¡N.BLAýÜ 9‡!ªr{íIs.HXæeõä
—Uƒ"0k5�p¹El…™+ÕCÞ6c&¥ÜPÛ~ZsH›í�©žS2"¿1Ý#û`Ë2ã“÷µk­oˆ"Î´$ƒZí]q›DA€Š•8:Üí¸¾t»Ü� Ii.HJ/Å?›&súŽÍG;#²¶ñ}sÜÇt„:‹»ÛÃfêÊJ^¦Í.7LSûð]Î­`àv‹Ä�0Þm±k4ÿô.ç#¿@—eU¨øº<”±ÝÎLnÔë(µAÓKØÉ–¬b¤ç«X„™¼ÞWr)4#k™¾çIxnØnêÊdMs¤U[6±VL|ëc�íÇ½b¼/Ãñ2y˜akP®æ¸%•cnÐ0	Ðôó×êØ²ý,ÒW¹Ï¾/‹(ª‡^õöªŽ~¾â¤ÒWÇ’•›„§u†[u_‡k`Q„‰·@FžÆ[!6Ÿ™íëžÆÄNnwvÅ‘T¼˜ÐßŒw+647¡�klvÖÐÿ¿,c¸à—Â šr*#cKï_üïƒÈ=EØñO©õÅM,0ÅÅ®šŸ»™ú¨M›�j¢]Üöª™|ÊxùwŽ€áÄ©F€‰#ÈÎQhh”nÌÝTùÚÝ¥˜qTU…{6F¬ï@%E¬¦1±±-�™wÎ~ã1jmI…i{ôñûxãë+´âpÓ8Ä>àE¹^.à:\hÌªë£EuàzçÊsQvªÃx.aW*#M5†‹Øæ‘ûÀ
¼¤X*ËÑ°š„ÃÙ·îiÊ‘É]ì//E@{?‰§SíWb£µ²^�”¢sëôWá&Àgg[·sÊuã¡‡;VíÖêÉÖù!Š—9ð²y©T©~1¤pÑaŽàC_eçHÜ%ÎïÞãoæeÂ8¤^Ù+s¯‡ê\¥~8E¼‰ùñàú�ß¯Y:ö‰µ‚¸I	P2&•x~Œ8“¹�¼5Öó=ök÷yÍªãJ'‰oó¦¼úžòÿ‰-q$'8-à	©eÍcsŠ ám‚SGÔº°ÅR® æ(TÀ Ž°›ÇmF*±DUÝ©»H0E‘c$"/qü¸ƒÇ5Ë8Wˆ+o»m®i.L‚r [Î
Ó@ŸRŒdñ$@Cœ]è…‚²8�€#X&~Ê%‰V WhÀùŸý²cø#`Ã§0F6ãÏ\Wå)é31œ¢¶Ä¥ü®­1y3‹z™70¦CbÄ•Q/‘Ñ»w:ŠY;žTN£AŸ(4¿½!À8lk�QBNJË¼­GËÁUQcÿ—2¼á…`G:´Ä2
Ë¼e”¼8…\÷Iõ7’0˜4Ç�ì¸:fþBû»w.qÉè"V”)0¤,7oP›ªÒX0ñˆ�o˜‘%Z-Í_4O&�/KU	uämöƒT„*4p¨�mx&I–¾>Dµ¶Á3J³„àÆƒqp¥Sð,¢Q2¦´r\ \Œ'#€Ç·Ã=A�2ZJÎ$RÓòa—AJä�jÏEÂˆzçÑ
¨ v´V#�ºó3±›áé'a²F\ÁR¶‹IŠ¢^)l£)Á¦*üyÃŽÔ°§åÍVØqY†õëÑyQ´Íƒ•[¶-+·’>’Ÿ*LÝ(ð`GðÛÊC#Æ\Æu•´�X[&Ÿòõ
ÉFG;ókŠ�sœå_Rä82œÊœÉŠBÙ_U4¨SÃuú²›±„»a9i£'É^,ßØmøÕå*ëå¿¸�æ-¦8>zlÊô€¥‚b0úå¥¢°¨Ê˜É_µž¦9M˜s£#¿?~õRÁe˜1K@É*ºHt¹Ö€(�.ÁíÂlÄ€zæžY4„¿I ôôC
žNô]Cr(E£ñ©K­‚íæE®BzJr«WáUªôŠÄYQ�Wâ¯V—‡$—­Ëxl²môGäüHuÊ9s×þGD{_­€»"ÍcKâÆ,6ÌnÖ_Ô´a€8ÅÏ¬Z‡pX`?ØÞnÅwñ«!SÞÆ”dX
¼7¡[9�°¡úÕÈªì-=+©Kú7Ÿr“f$€# 6�Œ—ƒo²||W¥›»Wïª¨rKe^¬Ä¼ŽÌÏš8ç;cGßƒ•hô¡´wÒ‘b»%ûQZ]¾4v,‚–ºMíø/ùßK›ªx* Õÿ100755 healthcheck.sh }ðR hõþ÷«Ÿ�<,viÃëO=Ñ<»_xûú#!/bin/sh
# healthcheck.sh -- Check explorer health and report per-chain block counts
#
# Usage: ./scripts/healthcheck.sh [base_url]
#   base_url defaults to http://localhost:8090

set -e

BASE="${1:-http://localhost:8090}"

printf "Checking %s ...\n\n" "$BASE"

# Health endpoint
printf "%-20s " "/health"
status=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/health" 2>/dev/null) || status="unreachable"
if [ "$status" = "200" ]; then
    printf "OK (200)\n"
else
    printf "FAIL (%s)\n" "$status"
    exit 1
fi

# Stats endpoint
printf "%-20s " "/v1/explorer/stats"
stats=$(curl -sf "$BASE/v1/explorer/stats" 2>/dev/null) || stats=""
if [ -z "$stats" ]; then
    printf "FAIL (no response)\n"
    exit 1
fi
printf "OK\n\n"

# Parse per-chain block counts from stats JSON
# Expected format: {"chains":[{"name":"...","chain_id":...,"block_count":...},...]}
printf "%-16s %-12s %s\n" "CHAIN" "CHAIN_ID" "BLOCKS"
printf "%-16s %-12s %s\n" "-----" "--------" "------"

echo "$stats" | \
    sed 's/},{/}\n{/g' | \
    sed -n 's/.*"name":"\([^"]*\)".*"chain_id":\([0-9]*\).*"block_count":\([0-9]*\).*/\1|\2|\3/p' | \
    while IFS='|' read -r name chain_id blocks; do
        printf "%-16s %-12s %s\n" "$name" "$chain_id" "$blocks"
    done

# If no chains parsed, show raw stats
if ! echo "$stats" | grep -q '"name"'; then
    printf "(single chain mode)\n"
    block_count=$(echo "$stats" | sed -n 's/.*"block_count":\([0-9]*\).*/\1/p')
    printf "%-16s %-12s %s\n" "default" "-" "${block_count:-unknown}"
fi

printf "\nDone.\n"
"ÖÊ!®xn ‘ÿ100644 icon.svg ^pý”`9«ƒ8(í=ëÙü0¸100644 index.html §Ož;ùÁ@’6‰w4²ÑLÏ100644 logo.svg ¿ÛÃž—ìÿ6Ñ•£„µ_|nÉ%€Ê¸,(¶
x¦ Yÿ<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="12" fill="#0b1220"/><path d="M16 44 L32 16 L48 44 Z" fill="#5cf"/></svg>
2.³xcœþ<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Explorer</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="icon" type="image/svg+xml" href="/icon.svg" />
    <script src="/envs.js"></script>
  </head>
  <body>
    <div id="root">Loading explorerâ€¦</div>
  </body>
</html>
P{pÛ³xó ÿ<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 64"><rect width="200" height="64" fill="#0b1220"/><text x="20" y="42" fill="#5cf" font-family="ui-sans-serif,system-ui,sans-serif" font-weight="700" font-size="28">Explorer</text></svg>
iQL¶¼xœÕ[érG’þM<E6DØ©Ã^È´—"i‰^^C@k{²�. m5ºá>ÂFø!ææÁü$óefU HŠ{vVá0Ñ]UYYYygöÔ|tGZM\?¬ÕüÉ4ŠSÕ¨­Õ=7uûn¢7“Ÿƒ:žu8ˆ<?mþ”D!½NRúD#úêtsœ¦Sú=uÓñæÐ4ý I¢ðŠÎÃýMý‰®×šµÚæ¦ê¦nštu|å´JðW'*kõ6ˆ“A”¥*¡)G‰™¸{v¨’,ºM@›W[›˜¦:NZŠž?ÔåŸ›7¾w+ÏSœ9Ù|Öl©> =åù±¤Á\õç�¶ü+­üÐÓ×:VÝ¿ù©VûoU4T³±?ë+¼ŒA7žëŸ3�¤já¡;Ñ‰@«‡Ñ}�:?%J_Oƒ(ÆÂ4‹ÃDE¡a†þÈj¯°ˆOŠtHkO¾ë]œ}x{t¸wÑííöº8öÅûÓnOù	h…»òS5�ü‹Ü˜à5)ôšŽ:ÄÛM�H'6'-²‰*M¹¡Wë�šùé˜h�…]’q4ÃÅÌ"¥ã8Š	XßCZÙ¨w£‰ÆæáHÍt˜ªY…£ºz®x€hF4µblêùž
£T‘ë©¾¸Y¢iœ òõÇ4%|šÏd…^G!*šËx
ÂÍBFÕ¼Ù¼I‚ltkŽŠ³3EÈáÚp¨cÂ+»SÍõ1‘ñÉ§cf¬•žLÓ9ø-ëƒu’Çœ€‹Dw4ŠõÈMÁ¤c má'Ó,ãh¢ôÕä¢ÏŒ«6ù!�Ý0q©…ö•ëy8YB,’Î�OU Ò8¤ê¦¶F"¸ïÇô„­­†#Eÿö˜ejk“ŒÉ”s®±¦Ÿð»‰;ýAÖýøìì¿­Ý²°�èYe;ËÉÜÁX˜Äµ<fKÝ4n-‘Á:ñÜRÞñúNm˜…ƒEÀ�*ö °Ì›êY5Ö$j½üÞnÜQæÃèÐÿZ
çìà�ucù¤Í[sVÈV¨÷rMuH¼@çÝˆB¼ƒ0^Ä¼)’m¸t…Lfb^ðZ�\mL­¨0)DÀ'¨ow�Ô$ò4ó“K€
$è&‡¬Mßà(Øü" �ÙÄ�³Äèkè@\ýÀ
ŒÝnì‚Ÿ�ÑOy+:2áâÆ>^Ñ{$�AR�Z{‰¹ÅFR½œ&±«g¤Ý�sYÞTCë–(ƒ&]"±‡êì¨ÄÁ9]Vä�Øyê5�.1Å™dÎNÙh‚W5¤Rñ«a`^úCÜ-èûQ áž ¸?¾¡WØÄ²
M
ý ¶v[[#;CÓ­Íq¾�<7°ØòAh©:sn?
æ­}>oˆC�‚l�:~áþ^ÐlÛ©?'ÈÏë_ÑMîÄÑúE?KædÇ $w^´Ûíº O þ²CÈ•ñÅ#ïÂChº:=v¯i/E˜4¶‹ÅÀÎÀ�æ›EpÙ`‰V+a—h¦Pm‘b"Ç¤õÁ#p©pâw±²ÖÀÆ8—f5É®­1#¥+ZÖZÃ´¤;	ÆÞîÉéÉáX^Œ : gÏÅ4d!ñ:Ñb‚ž.¸S_ýöëß	 �sÃˆ_n^m³®å=ETf~Àv¼�@æ¶Ö
†$Î “¬rž±àB,Ü8ž3®Šîz�à ÚA/›L5¦=á’I4þèEŽ’r“ÂžñØ õõLÆºg»l-’ð9IQ’MÈ¬ôeæ0pI'X²i"ûï’´‡Ðp€  6ãcX¢”Ôµ&ÂÃ,g�§pô;E˜ïº�[4RÌ¢yœ]³ÔÒå¾gMö5V7êïzŽ`ÿÄ=Ç ›wÏ>(-÷íž–Ê¥5{æÕ=«Ø;)-9¢ç‡æ³ë·°èž5rÝtÕ¥5gôòï\X6ù‹ z¥±�k k�.AÙ³M#Îø§¾…+.\²‰›Â¦ÿW�]}¹Ìr6’&Ï­‰çÁïÑKžÇá¾Êÿ‰mU—ä÷wê¾W¿¬­ý�dzåø�Ð”žŸ«§¤4BS>„ä„­š’ÑH+š@“;F“÷u2ˆý)Ñma²WŒ`âmùdgä×•Îµóx×É`,+šÓ‹îžs‘F‹$¸“‡ÉîtG×þ„6îGQ€¹–’É…[.bNz£û/EU¯eÍÕÕWR­&åªR÷\Ñ¹N¢ G÷‡«$‰‹±UGÝcO¼8«<�ü-$VºL\"Ïµ,9ð[¶a�È®Ø7ÝÓ“ÆL7†âŠDKäWÂ¦Ã“‚Ût;g½8sÞ³oÖh’­nÔIäBlô€8y¸¼À¸t$‰‰›´†!š…/iÐ�w|@4^ÏšŽül\U¤w×PðÁ”¬�êæf)¨pÈáÀ™'S	m ê¶]X`Oõ�(ŸÁ´p<X8¤pc‚ÄÄ·}¼¤ÛqÕ;¸¿ éôð?öM¤›¤ˆS9xF4Cu{ç‡'ïØ@‹ÕtCq@S=‚/¥Îqj‚	§Ò%Ë©ô4‚²Ö,�R„MüBî(Á˜›,bP¢¦:ð!º½ÆÓíööë�öçøOmmw^½êlo?o·;íöSµÛU‡'½ƒwçM‚O3UÃ ž"(HòÞàÂöÆþ@Mc=ô¯›â€Ý†|2ŠtyúSpõ5£ý”<vèëXüu‰×O>•"VœÊÎR3—#HÚ©OÔÈ�OÝ8‘„Ç·&bî¡ð ‹hÚ/(+˜‡]>ÜŒ½=°™u¯8h@Î°¦:€uaÛ ˆf )Å2Q6nÂÖs{_‹}2q]„opQ—ý‚§.™äbØYÆað}7ª–â[ìÖgvX
sÉãò™{
vM£È�àdáÏÀ‰bÍáù€}>wÒ÷G\9ÆsM(n§Ÿ”y 2üu =§F.çª�f¶uÉÌc©ßxú$yÚR¥#6ËÌsiï#¾R´¸´îû�'“�'ÞÂêKÊYÝ)‘›åõU¥n?ÐæÎ›$)¯_’j¹rñÉcm´ø�ý@½Ûno‰ÿËËX¾Á­Ð…Æô=Pƒ®e„œc|š¥Ç¥k;�f�z÷àè`¯§öN?œôÏšêëóÓcE9­6pÃÆ:y36Œ0ÚÈ½±ïF*¡¼,÷KRl(ÚÀÅæ”8dú	‚W„ ŒŠÆÍxI‹@AßùW65ÂÎn¿½`Šd
=–ó9Å7OÔD»!ˆ…ix¸4âc`MÀ>AM”Þ ˜ve&õt�R�;M8°ÿEÇçÒ@8:r)#Ç;û¼a’‘æìët¦µþ_žw{Ì¬Gà&sÆþÜw‹cqQ¤Ç‡'<ïx÷;›?(NgPõì	“&ÖG!ÔN °£ØMÓ—PA#ZH8”a‰ÌÙBÂ€Ù<ÑíÌ3„„ç'2#ˆ2/`üx®çNpUžb3ãH
xoÎäSŸ% º°*D!5iËÙ8Â»±O¶`ÎÁ\Õ_¢’xö¬m¬ž‹ŒÞowòMÜp0ŽˆÆ©%¼Ëù”­ÿü¬ÍˆÇšòÎ˜ðêóÖ¼3,Ä©C :‡ÍÌ`·~æ^ÂiVW¬ð5hý¿Q��zœÙr>{­æ(âž‡E—©d‡~-Â›´..Ç¦Û­ŸæˆÚ¤[#`DJ‰ÒMS:‰oªŠˆØ2Ñ©OÑ«‰í¬}dÑ°f¹UË¢KÙ…b‹)ë�Î»&ý)ËN•Ö^¤fÂ·%üÍ…ë?ÕPŽ @È<ÊL2ÖC¨wJ¦¶ˆ×ïŒ@I¥5­FnI�±Îk©‰öºøã^÷ºV±©Œ\']ŠNª­­5Vj§’qé«oßœ¨Ëç…z~©¾Tífë÷Nw�º{haÞ§ƒS§çûçêí÷FºaöÔÑáñaOmÁQøÓ·Û?XØïzjmM´³!ïº¡ï:xeš‰ê(`†oßï¨îŒr|¸ €ÿÆF¤SxaÌâªÞv hõ²d
¨-gëØpJG¤è€‰°šÔˆql2‘Ÿ&±ºåà‡« ŠäjCÉÏ´ñcÄË¡ƒÑÈ9ƒ�L‡�ºñÿª¶…E"Ý+ìN–«£ž\Õ9ßUJ�µ9÷…M˜bêµ­þö7Ã�_ì–Óq2ß<žnðôžÛT›ùk¹±e�uxÌ!+¤aúÙà£f¿�™Ô7œ6L™«ˆ¬3“<Æ…ð:¹Tb®î¾øŠ/!þ˜õ		ZS5ÊQR%ë“G	dÿþâ×tv=�âÛFŒÖR¥é Ì»¦t"´[ÒRbÛà}[Š¡}z‚“S»°ÈVš"`sÞ¾Šv[Ž„iÐÎ²�jf>	k¢©­„ða4£lJ¹S^,âƒ5ž¸œI]Fœ0·hî(Ö¥^Cž[¥„Â‘�ª .ÙóÀ9�ŽNR§;5,ýÄ¯
·Í2ÓY¸ŒîAŒ8Óð—ˆÝ™ŽÉ+eÑºÎŸŒô�/À|R®F±¨'•2ª“;þb	Ø¨‹·‰§#¶Z_O© ß½Ãå,7Ý¡‹èÏ�ž`Ð,õ6ö3Ð}±I?eô'€8ðáºÙ€’]œÄhã€óq9Â�×½ƒïzìð\NÔ—jë³W/ûõ—ŠSZP@dYC1íK+xóVWÃçÍÝüçxôZ÷Ø°€ÑÝv™„´ðKõ•zw~úáŒô·W¨r�DqKŸ¶3öÉ÷®O©úæ^eÅšœž¨Ôá§c6v–ÈÏß‰p³È<H2O8Ü¡˜ªÚ»R)K•¢›j�Å‘zÒJMRÊÓ´:a�%§ÿÝ*4Ýp~SÏ“Ç›&2˜ÝÜÞv�ÎPÑì.1ZKÕSê&½WO©$s1‘Š·«gæe]žv5¢Ø‰ç-ûS þZ^dÀœ…# ÿ›ÃýŽª§QêË{ŒÌ€ûÀ)@,ìÑB•/,%ý0¸E­‘’CžUìÔo[qîÛžÆ—6_È‚/£Páú‡!µhøò�EfÏ.¼¡^™A�yx[Ç{™(ÅußƒsËiÊK«hì#ØÂîi1…*\á�d1®’°W#ïÂÆÙ>Ì–%„v—|-ÌâÄ/WÜ›™~GTÃ¨Aä~§$ÛŸ·Ö&±›Ë.è�2î)1ý$å¦Š|Äøµ¤©Áô÷À1³•÷ÀZ$k;©Ò^#�¢Øm§Œe%Å|ÃÍN³·%SÄIbù>‡
BŸè™:
k=Äª*yOD¹ãlvjyÙ€²¥t^¤JyöNžh¿©ïï~/äo	Š=ÈÁJKUô*ù)HV<
ÃÛÌWõccmÐ`mºÀc$.äñQ�ÈÕ¢©ŠàÕ[óŠg	_–%¿ûœA~XºèY(E2K[-èœbÃ^eà1Û–®¯Øô‘2g©…ÍìO‘¹/å1wâ{ì�;gn:f�Ô ª�éÇal�Gq8ßû‘½‚¿˜N�»t¥_“Ã¹|,~ˆºYø1¤ìŽ‹‚?Â©–®nøÿ�JÅêæ¶¥è`EÇ]À¡¶&ñP({­ŒÈ¶³³(©Œ�¬Ù)"i1‘D~¤jiBÇ–zñúÕÊc®ˆÀ¥*ó$1!¿	¹}/»ÿDÒüØ–˜bAJÍ…	)û
CPn½è¨Jçƒ#VT2¶Ó€Úc�°2‰›DÒÀRL‘º¯ÌPõ½ÒD’%Zz	]ÓÉAH…Z”©.¼¼<¼ÒéX^ukÛ1³p#4åê](�5Ž$µ¹äÃÁ[…ö°Òê…”§™!
¡‘†ó5kNTæBZ0Èbn“üí×¿K³‰zßë�©ív»Èàç-¬0MW’†ä·OØY°~Êi¹Ïrgº£t?FHA�µÐÍu*ö èŠµjáb™¾ÇV^'…ðŒ8¥²_³@¢š&iªur£4%¼´t	Q&6�R‹]CVL~ÕnK‘µÜ±ã<.ÐaËP	:”,±ŽyÙŠ”
“Þ2�®¢¬0®7ž†n@Å˜”‘$ïhLYC“n¹PÕÄnQl2yÒãÝï"'ÍV{)Ê®ÛêÁl>ÊtÕ–ÉaîŠíZÅ’r=ë•–É¹j¯RèÇ«VÒze ÈÓ‰27?Žbzóhq?¥iœÑM-è9SMXRtâÝ.
5'ôù*ð'~º³.e‘Çô“ºàÒˆ£.eè’[ÈÅÑž¹±G­®II”5œ'¡¸0)xì	7DÃ!µ­ÏÆ”ˆ&$™­\ïÊ€Î�€¡¦†N?}@iÈa£5ø¬¤3^µYY\‰òáüÈ$;›Î;êõàyõæL€.©‹ñÅü°P:òIƒ³›F~ãÊ$wDï¬¯«�Sý,¼²'F$¿(Yjyù%kV6Áf=SN,ÏÖÍßÝ²�_"�Zë²ý¨+ùû¾?ýõ‡ù7‹®U.m?ðóí²ÞXÔ6•ÉÙ(1–@”ìÕ:jm˜&[*y‚]Ò-¦ÔÔRéVáï0¤” “J!™Ôäë—¦;�kÑŒaÍ¸[²ŸWÖòâ…ýÀÌ�«|þ"€dGhM%ì9ÃšFÓ,�Žk"}oQj÷�¯H¡–MÃÀ¥8ŸšeZ‚ÅöÊL\OKX;ôu 5Iér¡ì¸(¯‹R›b¨©üŸ»”6ÿhëÕ�CÜ ùaz!øP¹É3Ÿü„«âÑ71‰¾j¶ËIEÊ°\H™e)yzY[sã‘	Â˜O˜ïLÍuµ°É I›™ö€È�…¡}ezKmÁˆ½~Y•A=¡Çs€3å8£¨¾P_ÕiœQÍýô6mærZZ~OÑŽ@­ÄT¥<ß½ešë8Îƒ%½Ý
¦•†*ê™H`øÁr¦i'Ž>ê:¸I€P¯s[æ'cn-ag®Î&•?òÕÂ¨ÈŠPPš»qütn@Ñ'L.(EE—ë‚ÜWªúÙH�}�,„†C	"m¶XáËíNÇàZ	0>!¸x”^b#©ò°©ÒÅ�æRH¸\°*'Ž—2
·wªÄZVò TG¹x}`é¢@%½æTo^½Z+Kh.œ$‡_ëRÇ*ŸpýÓµ…°æ]¥/‹Àº@^· ×ìõ³õµu³#~–ëÍµ³;î”•*é5`R½Ó5êeöÃL›2›¡m.4ò¼Úý•³àí¡êt(<Ó§kJ¢õ’~Ã˜<ÙQ†ÃGÇÿ-Öå:CUêVÊ[&C±Úï¹
PyÙŠ˜/¡FUÒ³Óº×�ÌØä|n	µÀæŸ�¾ÉØÿ�k7ÿ/ê3kœD¹'©’ç`ZêEû.ý½ËI@%Ù	ìÖŽ91—!„øÒz0æ{˜ù�k¤PÆõ“ˆ?{«s,.º—{ÿÈ›7Å€™Ï¹ƒjcÔH‡Ø›¿oc�^¦�#�Ž¢‘ù¾Í¥L‡¯š;3íç�ÒÝPî<ÃVVäŽæ©KW2Ã«t¼LÛYHIjì�¥g½TSû—ÔÓ¤4³:Ú¼£?¿ÀVÁl!œ½³Q•Ë ·2n^�Þ¿cå‹>›[_„z¶”kX™¸4JFò–÷§ù?UÁ—ÿfŠž›Q(²�ÂRLÑ¹¯Cdu{µV®q+V¥;EÍèÃƒ`F½Qõ+áV½Ôë"±µ8¸ä!sQÐ´ŸV:œKhºÊLB3@ì¦¶_Ò÷†1©TrúH@·_Ž­CWù’s±ýñÏlYì7üŠÂµ;û¿¶ló—ÍÇ™c,;|+=÷!5ÿ8'‡T	â¶°¢S‘}–¯k3áµIÇ)i¦2Â{¨`ùÊë�ÇüK½ÇÈâãdì¶öO'›S¦¹îxµZívÛF’ýM=E{<&c¤dÉ‰•xöÈ’’hÆ–½=gg}|$h’Xƒ �E)Í™‡Ø'Ü'Ù{« ø![™É:ŽIÝÕÕõy«º§aô1i5	“lk+™Ló¢Tí­–‡e8�îšO©‡ß:‹ò8ÉFÝÿ6yÆÃIÉ�L—ÝqYN›ßåA©�È_MÃrÜ&©æ>àk�“¯ÉD{[[­nW½9;QX0)µ:Ri2(ÂâV%™*Ç‰Qƒ$ÃO_Í²X*TY8Ñ*Ëñ.)�­¢4L&&R}N™y¤�©ì£Q£"œŽU˜Å ë1j®Ó”ŸIiT>Ï”)óBû%d"Çˆ•c­ÆùëfI«|(�ÂI˜ŽðO™ä™
A¬šŒÍU>Õ™úßü�P*ô(1¥.tÌå<K÷©¨wSS:œ@	e™©(ŸL!(ƒåà6Ê§·n5¡d¸ñ#áQÅz˜d"€kY9ª—ß*s;ä`fžDZÃ$ÁR¡òLÈÄa1O²@�å¥ì~f¿æaB�SsHk
é/°-Ì¯6a„‚Û„gY1¹Šsð=É¡Ÿ,
òbähùj>N"ˆ¾(’kŒ˜ƒ¤¨–,ô4M¢«BZà9WSk˜Æi;Ï Qnzñ2K"Ú¶^BÝ—Ê�äl@xÝŠ¨S�·ÕÙÚ‚m™2,ÍÑ†e[mb¡íˆOÜ¾ @*YF*Z¨Á“§×T<…#N/H/²3)(j˜ŒfÔ.TÎR¸fØ �…‚JaW²v¡;b‰Â,É�Œ 4©††Ò[5¸å–©Û
¢X6H.Øº‹¥-½PGÂÂg»Åõþƒ|sO·ZŸ/ÒÙè@y–oèìtðûÕìFmËX<<¶ÌCÝÅLßù‹iÂèbÖ/4îîDºó6sjÝêø¥õ8Ô†-%ÙcÈÜùÞZæ~Òð6Ÿ•Vêº¸†­RDl|õ™Aè8)îºŸx¿ë~šÁÆ»ŽP¬Ý!¬À¸!#š•º¤yôÑ\)ûIrärœÁ¼Âiˆ §3†2º¨//Ë"ÌL‰CÐZ\IæûbŒaÃŒFhévI±DÈ‚•L¦T·^ÂÑ¹ŸyhM¨Ú¬ˆ…ÌÀlÎebÐÇl5È!ÒJ²v'Og“Æƒ F+êŸ¾>¹è¾~[9Ï(ßvÞmc¬‘¾ i²CRýóÓ³Ÿu¨†ÉM	cOpÚéŒ¦©'7\M�àÕbOxç–­‚ŽßâF}’c¤D¥ÇVa–îIk4Jß@`°Ò9WH5„*RëZç�Ò!g3N¦JO¦åmEŽ2
19g[¼MÞ!½ŒözAÏs"bkÖÌ`[ÁÖp–E"ì…�µKõ­Ë$Aß§,h/¾¢¹@J´ì;CàÎ;êóV«~Ñ)[»³ÕŠ“B¼€´lb
þ”'Y{‰Š¯<±:£“¡Ò…LÈMðú#&¦i¾êåßíïw~�÷ß¼PY’r)¬õXJÛ˜†ùwX6ëh@™Á$ˆv�
üUFHÙ[˜»×Y0ñÅEôŽÁQšÍ}‚óËza¼8¹ÑQû
�ŸöOTÿðå«hpri½FµXàÉöÕÛóÓ×‡çU>ù«¯²Ùd Ê§gý“ŸOÎ}5ÍX†Ñl
�•—‹' ÞÊòÉ‡t|: ¦Úïq2&bä­{Pæ�ÒåÚãQh.Ódƒ«–$U>EŽë‡w¬½F�½é«³w¯^Áƒo.#8Û‚ˆ"°¹"I[vLò«®ˆu~Ø$•F€0ª]orY:"ºËU-BEÍÛæ5ê sÏœuõ ƒÂõ`Õ\98ËçíNpÇmñ´àxVRi[EwÔ·vÜk‰�0•aŽàÀé;?àóÇÎ}ðãÉkÓ†oe¡+ËÔd“uŠ÷XßéÙÅÉyŸrÓ°<*Æç[1úµß°-_ÌÈ¯C¾ÑüZÎ~¥o¿R3vÕjýåðÕ»“‹ö¿ûkÿu®�æZ- ÚàbŠ�QÛ^ïæQïÙ^Œ|—tð?<óþ·÷½ÚÞéaë=ìÏÖÛÙµc}µ»ƒo0_dÂÄ�ÞÍ¾·®þ•Ãó@y7mZLÚoðBžÕÝfa…!ÄµÛWiÄ*¸ÝšV�£vá´fé1D»ó¸ã·ùe÷qçþaáJ¦çÄƒ€È“@„Û‚?ò«‹!2þX2Šb–ç©`A4iÔñK—“*b+ÙÈ…RÉ;ßÊjn‰ÍY¨úÈ•Ç‰d¦e<Õ.‘Ü$ÔèÍùïVËî@�¹¹5lZ‚—V˜·³ew>BªŽP¦pËÀtqŠÝÒç¯ˆp¯$çƒ Â[ˆü§‹7gÀ*ñ­Û+¦¯ls¬Xÿ „¤õ’4h1' �UY˜?þ¦3Ð‚À>í ä™¾·ß?„Ùíjz.ô'ÆÒg–°Çs�tlÊ¶¬ùZ—ã<þY»µ� ’ÎŽiÁ/à@a*>1­Ç0¶£~`ÉÚŽ`«‰zòBíJ€ãô]¾­n|Ÿ|°Û²;yb° Ì¸ÐÑ:«Q^ b·1À‡µ}Â˜<~G²B—­Ð>fæÍŸ›°qè‘Q�Ì£-ÞªGô°…lIÏ’±<°$ ªV¤Ú.¬ÞƒwÙ$,Ì¸„ó_R¹/o!à6BÝH`Ýw�Yp4cEbêÑµÚÞ—+¬åXþ„þ…`±6Œ‹N'¼Ò*Øõ¡Uq’#æj]˜%3ä&‰gál¾Ú9nš%¬h•"¾ŠJ ë –¢º†=XÀ fld^÷z§[O_˜ÛUoëfW+–àÐà×ƒMÞ«—À¡˜‡Ë4üÊº×gì”n(Ü¢âAÛ4a†VÞ":L§Çà;Q VnJ„ª¥HñByžEæ9)Š¼€M8‘À	-â¬ÀxÉAO5þÂö¹†gŸbÞº�·oé.È6Ç€ŸGŸP
…Àqû º�ˆµ­åÊ›Ì|™<G|�¸%°‰t�’ýÝÍì×Ãš‹ì.ï ã¹h‚âë¥”±h™°°\¿¢¾A%Éê9JíW•T¬Ëžõ
@Ôpûk&�Éá°X˜Ç=(nR²îõ£žCw
¡Æ`,kYØIãÝ´¸Ùàj;ÁÎk[¿Ùò^24ŸæXŸd®dPšô6¤ï…†¨Ectm*M¹zÐ–—œæ$úLjA[GU¦¶>¸)W™áŒc}¤“îZÄy…Òä¡áóKq¾rvKƒ¯�½$° I©E™Ä6¶-=
#�­w­ÓcÅ?ÖÑå«üãÂ]3Ð¡ÔcIn§�ÁÓl˜×AQÞÊÈ»êÚ�l‰T�úú�ƒ(23
U[ë0ÂôšY°íe9;¡ÂŠ
cÀ³2âgE.A}ªd¢\9LàÃE˜«Òë‘ã'!�“�KâRŽõÆ·º8o=õ·¿-½ì#„T¯d2"-™B	†&©�ÏEüûÆ¾mØuÛÓ7Sì.&±‹¨CÇ¢¾V7lQ\Ö•ä”…@nê%ÚáD²	`Öd¡ç
_Ïãð=¸Á,úü'�2úèp–¦lÍ¸Um0	ì¡øfÓ‡~ËFŽ!IÛËÆm@õ¨véygè£ókf	4Ð`›Kïå�„‡9µ|Í!ôÌ¬Ñ íô”ˆd£T¼T¥¶ø}×?B	üS^ ¿Þö°þ³íÞÎvo—äÝŽÚXØ8úŒ³�e½£Ã£†¶ïÄ˜ ê8û·ùy×{‚*×VA¬K�Óƒ×¢îŒÁâ× e«sšà¢�œÃ‹î@ñ_öä{ªÎÊ™e�†'WÃ-|´l~M#QStÝJF>»_:Q�¤¶íÜoÎû´càÆ<Å3§»éBug¤óXùð4€ñj±–aÝ³¦´ì¡²êµ[ÑöLfÃöÔ"!è_ø®©––åeÃ5~‘iˆõ›š´¬´À2aËÎÉ½å©ýÈt £Ÿ\‹Pðs“hL›q3\Ì‘·K,í¯B0Ð·„Ú¥Ð@±Ð€K´5™Ü Æ˜æ@>°ÎsÂ÷xCôåØkLsÄîÖA<Ýö]ööì�žû•ÄúÏ9Y†8°½(78Òa+÷ùÔÊ:©*×öz{"¨…ngv£6Wµ$V) 3ÀDE+‰À&ß ØÙSM¥‹ŽƒO„M[éëÙ‡áíðÍÙFö�óa‘CÙÀx ÉI–À:ÎdŒF&¸Öq ä´ñÌ9’0¤ÑE€ˆã¼Øñwzßù½ï÷]»Oúõ´Bê©<ëd4.Õîso‡ô²4GƒGŒ ·¿}�óK÷sä¬_À—=ƒ[·
vY¸Sä9¤y_«eŽ¨|µGÝ’ôÙW[$uB(èABU[Šd+¬Ç”ÌiÔ0ÍÃòÙTKúì…Ód9=ÅYr�În“Ïóâ#¸y~ç�A]<ÆãÁŠÓÛö%-ºÃC”M¢Î5b²©¹=�«‰ÉK™R­m‡‡	ÅÀÓ<°æ^,KÍ·©½ezØ„¸´ 6iù æpu¬éˆ&Q`íS°«@:ƒ «A9µÐÚNR(e9q„œ×ÂÏ�bÁmaŽD¯êxÎuí¢Û#†ÍšÐ”_–"ù^jµx½�°²•·€Næ5Þ5(Èvè"¦ËY²'v1ŸOÖ·Ô(¶ÑÄê¼÷®Ùçò>m‹$œX¤v‘¥~±$ä¸v-†DSÇy™s�\LÚñáT•²íZ8BÕŠÍJ¦DsƒŽFì)u#`ðˆðNÐœ½)-Ê@ŠÊW$�Šxª÷gUx•n^IY_“�H‡sÕºTëäˆ·´9•€Q,w7·ƒžaW÷mM9ü~ß\×ýVK¶†lü‘öcM:F¨»½¼löÐ©ï5K�Ù|óžýMðGŒ ZTé¢§¶™`PK•U»4¨! …£É™¬È€ÅÉ„NŽU§’¸ä³i#‹u‘Ó,­%¿ÞT^oÀª+Ônïý+Õ½°´¨„v÷ÆuCÎÕyó@Â©\•‘†Sâ£ pUb‚C%×AÆF˜–³xÀQÛPqµ´»ï³'*”ƒs¡�@€Þ­MË�'ÉËEB‡·ÂF"ïîÁ�fè;By:
y‘èâ?^ñª�´‰«ÜïŽ:1&Å"»Ï)W}¤¿¨Öð;%Ô§í?qH]K¸Ór¸Ùo?·Æ‹ €¥sKâ÷6ŽÁðw›Ù6½á(ûtãabã ±îFÕÇƒÍC$¬üØþü9wätv•^CÉæA‡e8�ZÅ¡™Â/ùç=ÜÆÉýZ@û*†qYZ^ÓD˜üúš{¥ó¯eô†á]Âò6¾ûýþéZŸt…^³¡÷TµÙ§x¾�’h›öÑ`ŽùÅˆIàŠ„Ëlq>ïÀ½¨‚(þŒÂ±€C	vf@…ËLéE LÊE%\D@¸uÍ{Ë¾ŠN,Q1î&¥Ks…�–Ð¢fÐâmœ­à*0wz’4ëêþ÷þÓïz¤„–n÷á’"Nà¯ü þ+´yOº¾ŒG±½¢8Ð¨qÿ-!BñaR ¤È4Ò·œ¹6í®ÿôûïêÄŒ$÷ë˜Å[5ny¾g×(Â¥ˆf">´×t^²Ý‡K^Ìp>Š»@‡”å9î4/ßHFvù½à™�·��­R¤SÃàÙíKÚT ÈÁË‹8µ$Öcxx‡»L:�´w¶­aåv«¡
èYG=‹Ð*õÍ‰0Û»bzR²ø·Îg¨^£€l£ÀÁÉæú–[Õ¢¥º¥uoQRÊýqûµ»Å)ÿjŸûÿ‹[ôiör¿««J5qgI¶çÀK¹grëvè’Ù$‰qÒ[]-fDÏt?8¯£M€�cq5CcÅ$œ´Hˆ®c³£´ÕàÞ˜Ü/å�<@SÜ4¥�«y>ƒ³¬{JÊÅÕ_œç¿üÿõ±ÿ±¿çn7´ t1Îõ`}�˜–B6ªBžÂ.ê�‡mWZ}-L/Š­Ö YUp9%ü´°8¨kéZ¤Á½ZœäÌ
÷Ø �b±çTE©	’Aˆvj»Æô[f .\‹”[BÔ+’íÕ^��t~ÿ#Ž­l÷©qª¸Ž0™ªº�$Å þ¾÷ý6½¿b\Rg»Ç†kÀw.üú	w¿ùxœ­WënÛ6þ?+ ›\¸Šã¶)ÖÍ-Ò¥» KŠ5YÿAKK´ED"U’òm€=ÄžpO²ï�r¬ØqgtËÇ–ÏõûÎ9¬xzÍ'‚•\ªNG–•6ŽÅ�½H	w�;WE­ïþÃ	ëè!ý—jâ¿ÊRD�n§spÀ¦‡Öqg™´Ìå‚¥\i%S^°qÁ³uYr³HØ;^ÔÂ²²¶Ž�«
xÀT]Ž„±ÌjÆIŽÂ�–ê²âF0§/Þ�
�^Ã€fÂåá×™?ËfÒåºv¬V3Ã«
.2®H—PSQèJ°¿ÿüžÁ— RZ­–ÎjU,˜p6ÍEzÄè…T™˜“TZ#”K:ãZ¥ìIxwxNñÆŽ=lr’\tÙ§ÎÞHgö|È&ÂÅ®Ç”˜5‚=ö¬›ä\e…ðOz,â•|$æU¡�HŠzž å3m®#¼ºÍh„ïÉ1›hGjIýeÔNGt•ÄãBswô¤û½{0dÏÈ•=—¼6F›q|ç ²ýi�Í¸rìláH·³w³i%<gv²±ßÙŸ
 †Sï=¢î³tÔ_3µy®mò¨¿f3óÕYtE*�©ÅšÒæ}[IA×Úy¯÷Æÿ˜•ÒZ‚ÛÉ+
8¢Ê	��°9@�÷˜ U¼3„—.À�ð rA<'uÒjpegôûÙÍÙl8æ…ž'Ù.\Þ –¨âDQxÔC¤‹å �áxfÓŠ=í÷	¨(>Ó'¯6‘l©Ng~Ï…™ÊTÄ.¹eu"Mÿ=@¤ðlwùöëÀ¾¥^!ò-˜Û^±Và¯<¶DÝfía§S­P–\ãc¸=0Õ%ok£yÎ •�¥±P‘‰1¯Ç*`Î‘›†÷vnf0¸Úóæ›l5Ôï^F«&pyÅÕ¾R¡âð¦KÙ;‚w?q4äŽ˜µŸ±Q£Ú'ï�xÓ:Iº@ =…'­~Ù‡±’W—Ö¸ß²ªHÈK_F¡©ß¥³ºëË²ŽAÿFç€`ÜÎcÞ©¥O°–·¬åÜæd+¸S9¢PÑS±[]ÕNÀÇR)XsAB?Ë*0ºVrÎD¥ÓÜ“‹ƒõªÃõØ3n,ç®6â[K¬Ì’ éWG½ ‰G1~Ø(~�ÿþ›t4%= ¼Ž²ô=“‰94MÖhËà«ou!H¯ø¼§§µ•£B<*´¾&™P$4—i´Ar–/¨÷ä$‘òz’;trÛyøI…rÅMfo“Ñ.eSïLŽÓ	JÎ¥¢A_ÿ@Êbb}¿‹Z±XŸ}þÜüxÁ½äiHä"VJã}Û]vd¤Š’à‚Œû~ë'G�‘Åû¡¶7Ý9Y¢Ñ"¹VuË¶sr5°ˆ»òò¥·0||?WSíñ.œ||;Ê¶F1Ò5ú\ÖŒ…z²Ü[0hÀ éY]V_ŠòëCüŽþî3€íØZa\³Vìøì$lWFÏ ØÔÕXõxT�°”zL w^ÐðûÀx¡UÃ‚ŠC6#§ÒŸšåB<xW  *»„vƒúPèY‹NHÌ#·ÀN8ª'Ì²BrðÚh0¡á35$S«@C(°5øš¬ qoOö¡k½ˆp×w3¤€L!~×ÝZÙ‘ƒð~ZXŸ–7™ý?ÇË`bøô›PÆÁÎ“d°h÷Ï‘å„Ø>F¾06žì85ž0?R„4‰|ÚžHtXÝ^c‡ÄÚ¯©ý„‡‡•àa!§H?QËÐýÆ·Z\jÄò–€Ö&å×…·¤Ìñîðî›¥*ë9%€nT	mV°(Nëyßv^´’Sò/†*œB|ì}�U¤Õpî_^…|~jmUw—†æWJjpíZþöp; )Ý,{©æR¾n0™ü¸//…Æ[ñ—:çŸ%§¸šéìgÀUÈº,¨ã-•lÎdrßß©UùÌûFU-«Õ!ÆR®¦�†ï½ßæ,¦Âø™N*Ä\Ò¢&±õ?G5–oó¸ÏƒÛxVáÜ9;{sAm¸	ÏÓþVÅþÇèÖÅfMý¡Qÿl¾�xÍ\ÛrÜF’}&¿¢Ü’›	Êöx/´9‰¤eMˆMÒÖƒC!¡`Ãn  y±Bó´°±ß°6_²çœ¬Â¥y½;1Ž›Ô%++óä¥²0�Çgñ$u³8ËWW³Ù¼(k7\]Œ‹¼N/ëþLâ:ÅUºY}˜òwš�‹$Ë'›­ŠœNfj7-&ü•§õæi]ÏùwQñßó¸>Ý<É¦)ÿàƒª.Ñ_ïª«|ÌGu6K««+››îõ«=‡©²:u;nš�Ê¸¼rYîêÓ¬r£,ÇÏu·È“´t±ËãYêòïò‰K§UêÆÓ8›U‘†:f—yYŒÓªÂPùYå&e<?uqž`È$½ä •»H§Sþ7«+W\ä®ª‹2]g+3öä`°ú4u§ÅóŒÙ4qÅ‰Å³x:Á¿ê¬È]ŒÁêÓ2%a®˜§¹ûûßþK#•é$«ê´LN7°q¿Dî—9˜’Æ3ìD]çn\Ìæ`X…V)ö"Á“ù•ŸM#U\øŽhtIz’åbÀy
^ùQß=rÕÕlT€˜‹lœº“8›bÀZŒpE®a’¸¼ÈòÈ½*j­þ4Î/âŒ{Í¸ ·æàQšaáå–…þa•Fð‹)Uá’tÏ
ìO>ŽŠrâÇZw§Ù¬/Ëì-.0¤S–é|š�cÌ
n�æÂÍM:+¿ÛEŽrÙØ‡/ólL¹ÀÒkl÷;7˜`ÈÅ(ó6Ã ~ë(ZÝ×ÓÅåI¶)YØLóI–CøVô3K.{u[z‰	M)%·¶åKhÖÍû�6Ç§Ð;Š‡r›0´HÏgw¿¿œO!«å��æÓ¸>)JŽ„åÝBv˜±!|muÚ¸C�‚ó¬Âz¡¦x²!úEzì•kR”Å¢O¡{Ó…Zs$>L]’U€�1¤É„iÏ+·yþÕf˜ÿc5]L>m>’Žò…íRx¬‘Ò‹´‚®Ö…´o\”e:®6=™¦eä9„¤²-€^Ó”r_.rÂÌ–‹â—‹9–-4q]Ôÿ‹¢<KËjb>+ÎÕÎ7ÇùÃ)gÑj}5‡.qJ¼ !WWˆš»Y	(!Ö­®ŒO&ŽÿìùI6qäGZSjBŒ?µ  KUsQ¤C„}õÇ,êaR(Ýl¡‘ ÝP°}¬ôru¥ª©8ð1ÿÍ¦|ûH#ñ¤ß³×sÆºÆsÒ¡.àù[¢vô“ñÐkƒoîü\ýî±lðïj1Ò&-�P]Äóf€k#ˆº»o±Ù~�½1½Àò‰£À~Y™Ô`7FW¶Õd8ÿº€ÏéI¼˜ÖGX –oKÇXW%võdë0hl VM* ´1Û ÑÆÓÆ¾Ø…V�â0‰
%ì÷ŽÜSÑŒÑãÚÇ®Á¹:äO‹X‚Çí…™¤¤�Ê<�ÑÉÍÜˆ´x
‘Ã–œ§†¸K—°/wiYRm)'Y‰µ”²ßp$¬ Ëëù@‡¡¡?-F‡lH=~€Ó‡ió6roÈ§*…3pžOë:Ÿ†Fkëê^µÀp‘A ]Š6®…†½_÷<|iæâh1ªÆe6©¯óg%82ŽAýFs¸w0 £i�?Òó4ŸN¦°¡Xƒi:à]+ùûü§ƒŽ“=oÒÑÚƒÔÇîè®C3A³–MÝ67xJfŽÂtàŒmÃ‹GaU`Ìê§USíV{(U^«½‹·¦•ÐlÁ´<¦hG?\äãÕ•ÓyiÛ›8«Ÿ—ÅbÎ°¯ÒÓÁÖ1ˆnaà–EQÓ] �N&Ñ®ÁJ´z‚	ncH
�²5÷hyàR™Ö‹2w—^áM€¬­îDëxŽ!·„8øƒ¿…5|2‹ÏÒáÍˆ³††ŸüJû’ÃOAie¤·ÝÍN·‚Ifu6(r;�p¤ »“² ÏfM9�â¸¼Áeá¤'‡a(ñæ½”W�xH Yä(–a ÐXã-ÂFC¬r£’¸ÆQ²��—ªß]â/ê‚n!åûÊïÚ°º¶3k®Ï¤á©ë
å-JuevÛ�zö
;=ÎzÒL·;VµoL;F˜Ðy¹ãÓ”‹kí3<æ¢8s‹yg¯¼……Z’ïdg+©¬iP*è1G/�žIb"ìM0˜	'�{¸�y¦7H….Ó�mlæ]Üê¬yêÝ#Ù.ÈýÅ¥ØÅE™1:œØ„£Ú5tTÔa…e¿° `m�f¶?@�ÈÏŒ°çõe:‰ÇW7dÍÝt<g˜‚Î†Gi½Û±sólŒHÆÜi/­fë¸!‹|^"¸Ã»Ë~d–ô.¦ögÒóÕËßl½ÔÁp¬CÐ¶cc>E“_râ7íÐÄ”^�¼sEC)”q/’Ag½ÛÂ\h)‡‹1d#¹Ú€À(²ÑøÔ=šâ¼Ø(æw­D#R™J‰:‹ÈNÜ»ußÂ"tªÓß²t&Þ~Ç„Åþ¢V<‚ÞVÆõå:LÝBŽLÀ³™�axô¶môÏ“!EíjóŽ8ïG�,Ö�Ö¸˜åi0i\�Oj½d¸‡±ˆÞ®l»ÁÀ/¡÷\Ý¸H-¢¿>Ò]L¢§I2ü
”Vij†z[@Xk±§…¢ úH�!'<þ„w�çh/§®'"‰ æW2¢Œs$!¸k4–’TjÊ…~QMzÝa•`o³|‘‚ nÈÐPEÕ]ÃÊÒ*`ênZ‡_I˜¼Y©ä:µ$M,b¥¹ß'È%¢ôy€#ÖF¤�¦ƒAæ„þ
%ùnÙ-æŸÑ½zYh9æ[1û‹Ïm’NÓ:æIà±8tÆòÀäkâÃW7
Ð ¡¤$³«ï“ZA„ö€n�$¡Šz!ÄÖÈ°Ù«Hnf,:¤<%wgëîX{µ»ŽT”ž¥È™âžECóú×¤±ÔK/ý«AáPÍ¹˜ÇXL’!µÝ=5gÜgJ“w“àøAÀ>á)²[ÑB¹úd8ø­5zo1[�IâT@tã¥Wn¾,5<Qh,‘·¬FžG:·Y‰ÃòžÑˆvâiÈORÒÛ1ÿïFOBçT{vÌ3Õº¿ßÀ›(¨®T¿�tð§¢6Ê­ûí­±qÝ=YwÓ4òBô¢|4Mo½$™ h�mÇ´°Žü@ðF¶^C@›ÔÃËY«!ÔÝ¸ †Ñ¼â&ó1à|xñ¢Ž1}éb2Ž†ÆÖï™-=%óÉZùæ-Aò$†¢¶A‘{zðÂÜLÖ£Ÿ_2%V!w1‹¹�²PwíD •H¨ˆün çé©/™'I ÓØ†�1�þRdd¿Ï(¨£L¸U@(ÈÖEíŸ%Yùt:†Q°wÿúí·Ð¶ùqd�
Ô“åÕ[7cÏ-÷à"L>Â–¬³¦èÚ;æ†¨ú)}.)B$óKž�di2�vÍ9ð�±dä<²(Bžð»ˆâdÈ Ýƒ¬€æ .Ú™•ÌRËQ½@š�qvø™!QÜ’Ïr% ±}}ËÚm ²(¯ÀÜ�Ò(À…k¡×4*7|P­¹�?°´»Â¿^!oa;µÙ8R–wÝ´º0ÚPmúÑ/ÌŸ… wÌYÛ°‚ß�€�c3Ÿé@8žÂo ­Ì=nA ælDì›'î‘cþ ™(èrãÜFöõ³DhùapyO)pÞý`³¤�p—Uº»(•1§§vã~¯¶[ÑõÔl;Î.¤&ZCìšáA5%Ò“4äO“?JþŠŽìSDÛð²yõb×B]-K�_ì2æ]9<ØÙË“9Ø‘Ð[<Ñ›ŸŽ�pnbŸèY—[ŽÔc¸zeýŒî1wWÝ¾aÃO@EªDäM¹.6Xd€¿ÛäµQc1Ð÷²—An$Kàƒ1õÞäÈZ3‡Ô*Î^RX¬	ºÁ=aã°“D2SÙ�b‘$2Ãf!>ATž–Ö0=£¿$,Ý˜/FSË“5I è>É%eÊÓ>X°*
ØB–ô™ºë)!ÚÒ¥¤�#¡6? \9 æÎõë]U¥x²™eÇsdL`à‘�„X/`”‹òK Üà­=ò/ç€	±’ÎLÚ¼Ç¿w�~.½ÃMÖsQqsûa½Dx1�éˆY	Ë.Àÿî`åJ°™!‚5†Z!+ ¥½ñø|y ^Þf†Þ½’mB€`õimÌï’Þpô¨I×µíÍ|{ÐœÒÚªdc™8ÿíà{›0ßŽ¼}I[s.òëfÒéÙ+Kp±‹%×à—z£}›jq”˜Æ%1²9Z!PB#6n^¸_÷×Lê™Ò`–ÅMÒ<-qv%‘AñÀùóû‡ç°À t®¦ï¤FPy ß©M3Mœ@N›ùaøÖ†‚ÐfØž¢Ü¤ç?Ñ_i=ŽU„³ÎÌ[Ýl:¸´ÃI�DõŸß
@	[h	d]°:Š§ø¸Çk©�÷PÖxn ¤.lÛiƒû ¥Îh‘ºÐª 2§JòÏîûŽñð-ð¤Û k/Ð‚¼Æë®Yå¶ùŒÌÈFàÜÕ¶žëóÚÐ…ŠkBx$ï6iïëC%–;ŒÜÀ’™ê>_†A²MB×‚«(ä …Û÷jä¬ÚôO­6£(râ3²øo€¯<4¾ë 2ðCI‘>€ ë<§Á@¢Àaú‚À,X¦sÏT¦ôJ	^o‰MoŽ6 Àlòƒ=v´ëvˆTº!ûu"`NnøÂG»Ïlyð¥q9½‚‘­Gj â¢Ìjä¹ÎSlã¢#šxµX×9‘€º}fƒ!š‰Ï‹Ìt´¡j,C{ªá†¡B1"�3ø’®yKÓÁÉ?‚Çy¹/(Ã;D†+t4&+ÏŸ°'3È#¬ðxDÞs›gZ)ã‹Æ£óò=OëÃtŒT¡hí·O(÷7	>ŸÝŒû7D@�V°ßª²o+Ÿ¬ÔÄ,£Ïd•²¾ƒzZ l´Îô¹B´°ù¾ZÃ°‰zÑ¯f]ÍEo0²Ir™mÅÃJ3=„¸ "�d¤¬¬4þ«s£È»®è‚)Á7½Eá‡\R¼ü)Í&§æ¾Ž"û^ÃÛ‡ØÏæ[®ùÞÂi­•ü^s†#5ŸñøGÿ_î0a`?Úw/v­·ÞáGx³oÂÜi¼á�ðbG¸“<Å€
#^Ã0üÓ:‚ÇŽ–™À„�îH%�tÔou0Áí€ª	ýñåƒäš5Ì‘53¢õoý«¥>HË´ã×`Â+[ƒ&º/Å·ÓÛð5Z!²’LÅÿSG�©@\;mžÇ¨¨Êf³4É€šSÁ)¦†Ã&ó
NpÜšB5p„„´N8;ÃXü&Øö0½8Ö“!¡—³Z<oÍ¸s¥™©gó
nŒY@A­v“ZôÃÚœEÓÀ¶c¯m5lé×‡YEÀ·×Ø…PÜã."9,›†HkîÛF5OÇÈ} ‰)§´L!íœÃàœÛ_ô(¸¸.±ÌŠYZk)Xï$’É¾ö°´Ÿ„b:9­ë)’h¦ŒVˆ¢îNjYEX×f8Úò5:<À»+¯µD,eò³é­�ž€ícAŒ’‘ñ<³-e¡HûØÇŠ†8¡ËÝŠÕ^Gß†”Ä>Îj28ˆIGJnKñbGük	ãS!õ§%9Ö(”h&’•Ê‘m#Ð}&åZ†�­[€ŠlÔ°Ÿ€¾.ià‡6ž	 .ãÐq)ûdŠ
uÅó&hÓ¬ñpÓ1*\tú9Õ•þ‹¶t$,©+³k/GLX@ždÀI—*1å�ºRMaÖzÐÉ ŽòÁÊw�°iAShÒóÜÐJ&u0’¯õ	Þ¤ÍÔú>òC™¨xõú˜m¢+Ê<RpD{��ìˆ´È�OÁcåÂÜ[ŠˆÀä.Ñ4�…¢)/åzªÌGƒÝ\ÙJÛýZ>p‹µìåãH�R¯c�;ìë%°mG!d×ø^.Q¶½Fæ}Ø”–b&’·�Mt~`mævY0)±fßnX¼r—Žu•8cÄ?ï�$£èg¦0!]ƒ£½—{;Çnçõ/¯Ž‡�ÖÜ�‡¯÷}Ñé»Šy€ío~Ú;Ü“pl)®~éž¾ÚU½ÕöXy³Žµèaîðaî¥yÔ&p�¸œ¤=	ÞÔ}Ôê¿®X !àK—-ø�*•W+lp×[ãŽó²R•çÍ°e0ñ‡ª](‡ÍËåÌeï^Xw÷ÓÌ[>Í+�HqgëòqEþQûºIv6‘©½C&ÓÇr´Ôµy¤ˆq�ÍkÀZ/¡\¯T¡¶1F/7Š§<ÉBÕ"Ë:šâi®7á¹‚ÎÒæ¶à�¡3dARÐT>ÑxŒaúIX’Œ>$‡tÁñëzÐ÷—kö½»ÁúÀíò9(ü›2UŽÂN] *¡S¥7›	™`D«({Pm>ê‰s³SäJ°rÁÑ@z+p‡¢ŒØ×íz„…éµ’h;ÇvÌ
hqêÀ”A=�ÎZGƒ)\_Dz:5»Úÿ&+?m~läØíO›w9"ïé\àð?,Kæ1¸BJëàÖ34À£V¢Â€º¦AlZ;ßœªaÈ{¨mÞ~¦æ'ix»=¨Y€ 4(!ªøH9ÌÝÒÕ‘ð%áÕÄìtÓ¹ÙSÿcÐºT@‡oHÜN
˜�|Ø\­Ÿ ¤êiì±yÜâ#0ÀÇŠþLÅƒÂêöã¬ÀÑ&Á¢ó`�‘*�ê+·ýg�3%ïQ0
€«S¸'—ÿŽ @?Æc0ôê×¯9	‡iyÂ�o–ß~Ã·‘ž:öµ|…ùßð2ý‘º8Cb€‡©,då¡~lJN_¨Êp”Ž‹¥_ÖÓÆáÅB C‘ÁEäSä¥˜ïR3;Âa†ÁªÏG)Ýç6èSÆWÆ£Þ#Òyìï,"<âk°Î‰–øãÍdÿÊ¯wOE¨Î„ƒ w^hß9IÃiPSÁ†R€Ç&ãí@o[²Ä ìN…P£­k@Ðâ�Y®�]úë	áþçEYÞ.bÔ“ž¼ýÓ­¦‹�¼1­’ì�Mø½Ý6§q‚ÂÄ$YÊû'+/cÔù‰ä€Q_o¸&tŽõÂö36qóáYãjBu&‘rºx¤"gˆÈ±D¤,.�þ×.W§¼ªç*‡¶*u™Sh—~bT‰Ç%NJyö(8ûA2Š‡ÐF[Ž?ì“•³³�W�A\èés�Óô¤6]A>ÓBz$XÅiUUËl0dÁXî^Æcþ»5˜.*Y\Ày,AWìd€¦8âCªr\ðJ‡Oa†ÛBóâ”¥ÿ™•�ãwÅH*BìÓ�£n¸‚bƒCêq“#�¡âœ êç4Dy¬Â=‘†5zð•§âµ2-ý�±› ×Ô—ÕëìMß�gŒË“íI?ÐE>8F õN.ñ>ƒ·÷l~3®†\.Ûk×ÉôøµBˆ[3^7å=…knRC…÷—à[ 
‚ºù²hÞÚ=ô�Z8ß�/jì¦sB ûJ'êxz˜V¼è‘ýÎ³û¯Ü÷ß»¯õJ­�‘¨„®ì#bþæ	þ¡¶�¹¾XSƒ4µñþ9B‹P!iU=øÏxO
”·L�(|�“g¸ò‡tìÐzßO8üL>ÜÎ,krƒK°ºb@(OfVGGs_€ÖºXpþTxFãâýÇÆ�aa4V®jgÆ¬Fu4“i“SÑGc3ñ~aÁå9²÷éFòpxJ_[}hW”,w�¤s‘ÀtZìÍ³+„ý‡H  y‚Ä½½_ÇÞ}ÿý×JÐ3¤Äå™ ~4(TËe^'¥hì¢¸„ãØ k‘ý>D÷ë,_Ñêöx†¿ÿ8€èe5Øúíã`[É;x[(»‘ås9zýjðéí§÷¾6
Ùêgq–Š—YÑî*Ì½èF{—éI&PqåõXäž�èB·¤Îµ~„´i=¨I^o0ïU "N×Q”f7g±+aÍ{¼SKÞ­¡Ê•9/Z@a @ãÿe»þW¤Y‰2ñÂ˜d9XHá�^
�ÝFX\œÖJ¶$ë¾Ò)á²¹/‘S©ìðF¿ÍQHàà¢�­µ­¹W]þààõÑ±<65@ñ¦ì>®!#fo$=—»=ß»¥5Ù8ùœöÙÏ/oè½<éžOî5Õ¡§,A9e,ÒÝsVtÁ_8†ÐÜŒ££@—¢¨-ƒK �•ã uýü2œîË0Ò*{�¾‘™åÓõ^1­üŒ•wš·Ì¨ò×W—�fša˜Ù¤ÀƒI
×aF¿	„ ”ð`#±ÒÜ. ·í«l‚‹Å¨G42™^TW9‚™õ&°Ô`¾îß»vðw­ØIHÕPã?U– Œˆn:jG)üÃÈ†�U˜vñú¯=#m‡KÇþœŒæ5ŠÏà|Xàþ�¢Eo‹€A?ë
‹’\C^E..#ýaí~ýÚ~™w‰âðØf?¡I¤Æ5,‘üŒPSæLÉ¾“ÿÁêãÞpGá½wg®¯…“€QOMªàYÅÜô¼B¢kìIª,p�«.§Þ¶m¾í7¸:ÊøªÑ ¡¼¶'�R;m�µó%Ïé„áÐ$OàMò¦ÏÆíî"fRå—	h}X¸ÚÁ² ÍdÁ£L‰¬�	º]MO�SÔEI\¢¥Hê`¼Îjü)�Z4Q`™¦’4¡–ÜC}-@1ÈOÌ^ÅÐÈëîá¶“2¹�#Ÿ
U4�Á eäÆÔb(Éãü×M#Ä%¯¬Cù‚¿ÛÔ\L¥æÏrõÞ¡�q�K´(ÃH�\RQUf+~p_™.2K¼,$ËØE)¹I2”pêË´4º'È��kR¸<ü¦ñÙ» ËSíðíS/7ÌôèP“S ?ã›4m?øà÷�qCÄÛÙ{1ÂÎyÉ­¾s›d€‚áÍ>¥
Uˆi2`|Ê[Ž}ÊXa”õØB«³Ü»?pD¤h²x¼fð¸q†»×Íx[Ë” 	Ðcâ™Çgí¥+\…	ë\ÉúßÿößŸLÒíšII7`iÁð•hÂ5*¾º —&XùKˆ9¥°ÁÙÅe‰5Ãh6çG;”›Š^¼Ô9røN4÷çâ	>y�»÷þ¯Q>`4/æ»_Ÿ¾üeïHE)}üæ·4H@ƒÓs8©¬ÉYšÎM[DO¸ÝÆG‡S¦TŠXûG±ÐœÍM‘òÔ—ÜÑˆ%)>å›aßˆV¸)€X7ä(•D1‚h%êt·©I‰2hÅY>Ò\Ð/R`EÛáK€Š)FÈë,ðÑÞß×º³Ü¨‚ÞË!TÈï w™@ée„„1Bx´¸øCu|¾ƒu r,	¶ÑÒåÝv]KÒðò’¿– H§,ˆ½ðòðEg÷O÷÷[Ð©"
™4ƒç Aò8yÈ×û¬ûøhžÎµkðº‘ÔZ\†|=un×’nþhŸÃ…ÞÇò)9?´/žó¿¨µýËk:p¡rÞ´Êw´„n%Î?êá‡é:þgÎ×z«l]ëjÚ×‡�Oóñdo£R_ r†„…û˜´õ
 ìôéöiPðîýÍðýÃ²Î÷2¹¡gRsávM‰ênó�Ž �þ‚0€ƒÓ¥süpŒ=‚ë°’ú†l"­b&�p|‹«lt‘±äëP�¨Ò	�4|DV�VŠ-˜ÁƒDOÒKd›¤Õ<·åG/šoá¼Üe×‘{ÏÛÜsÄm¨œVIÃû.š·›v,ý!X…ÈÊ¾ 1Où�–)žœÐWˆñ�L? ×=�ÙXÈË‰dqIÅÑA1I>ÜÛúâÕ‹WÏÃš€œ€9CÚ¬ˆ¦†2‹;õànJåÝ�Š^3œ‡¥`6Ø£rC..­œ »ÉÄ
›¤�
8OºˆÀ±¼{Û¾dàÈ ÞUòð‡à$¾™Ä4ršŸÃ‰†×`a‰ŒBÌ*Åc[ØÑÁS8wïˆàêŒÓá“õ[{žhOy®ÇMA‹ãœP� ˆ8Pf¼G’
&ÉÆfBB5¿ˆtá…�’…@�| ¿9â÷cvÙ‘¯9/Š$&DYÅZNJj¢Å~~¥eã¯<�Äž‹œ—ðIB|’¯*œŸÙb‘£D³˜äH&al/·°7ÐVó	(<˜š‚Ä‘ØG†Ð¾;ÅŸ^éŒŽ»³‚¼Y¸‹ÿþ‰Ê.—XÁ-ô×”ƒ¨#	ñËáKÔòÕ§H.t5„çúÈª´7Aµ¢Ÿ�]Uj­uòß9yBk‚`ÛÄTÙÒMM¦¾ìÞw¾3¡§Ñ°ëØ0ëáeÑ¬¦¡ïØ™âd�¼*>@¥ñ±*‘È…u®	5m@1#$Ðµ�Ê„DËK	]šËæ¢KZMà`:7é²	â½“dªí}l-7<¼Óy &f�a›ÃMA=.)Ìá@Ö„ž�mž}�ƒíbCh�¯Æ¼Y>ž.€¬|¯5_Ù ™]„W (n‚ŒF‰ï´ýwÉD¾…þô—‹K”;S1TÑ¯&8Ê²*GRãq‚¼á+"XŽß‹(|®Æãh·ÁïEûš ¥µÐ­…g’ðh'œN£?DôžxÑ ±Ä3ê²O¦ásoø Î]úÔ‘�{i’)R*MÝ™8-A'Ž Ð&*ˆiŽ^·©á›3ŒÈx…@Êš>Ý4XÊ+¾*ê©TKº ]óÊ|«¢õ4øVÌlêÌ{~µ<ý”@þKŸCó˜*X"¤üšêˆl&o½v³šhW~Ýà¼®>À@3Å£  ÆÚ*:FÁØ�Ý¸6ÌâmëÕÏ!Å×AyÃfPK(D¶¼®þ­DƒF¨éÑòÃt¡=þ¢ˆKòé5Câ¼³;mCRí»È™ð›�ìíñŸ—a|„J„#-�òÐ†é“`üé'^éæ-L$¨âf6l¥@0÷7N ã¦|üÌQ×'ÿÓ“?¡ß]"83Ôª‚$Ûé8ÀÇ­PÑÕ"Ô9sÿM ´B£_ù­@?VŒ^¡¡j�Ê�½´ód‡1pÇ]a÷—rß¯qä©l¾ä5ØÅ½ÞSl2`Þ'q;TF¡�cðqæZs³©€cu6™'flè+Ì28øô} :]ð¿‹Ù7Óõÿf¶›u-Ùmi}@�›LêýL¶!‰Ùv™m~äí®aÿÉ@!ìÆs}·ÓùÜJEnAšâ"Ê…70
C¨�>-zÇ'z“ü‘�–CK"ãÒGÀ?½fû]VGØ˜Ö‹S üÙM§Oª�4„�è¾âq|Dàíºû�Uç7ÔzÍá¤ô~£ã;ÄuûíÉÛÆ³_½–·0£õø$€\Dô¶©ƒëùÙÕ|Ö‡´ð¸�ïNºæA†ˆØL>»¯zÿG>»}L_{<ÃYs>Þì›6DC‡úB2;gÛÀlX0“ƒk¬pžBÔ‘€Ù+½ð­	™8$ÍM³l	vƒ®Æ'‘wÝóSÄf~ñDi^PðEØðö«&Ý¡ü¹¬}ÜM·÷!	"_@]×ûºœöF‘ä¨qëÿx¿>èm²Èþ^aÛ‹�1î¿•y´ÏôÀ‘1…¤}†ü¾8ƒ´¯$Ïž² ®Œlà^Ú'[š/Ýµoƒû6ŸVÿ%‹ä¤²¦xb
�õpackage main

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
)

// A slug-less /v1/indexer request is answered by the chain the host names.
// Pinned to the configured default, api-explore.hanzo.network and
// api-explore.zoo.network returned the same window of Lux block heights as
// api-explore.lux.network â€” to the block, on every fetch.
func TestIndexerDispatchFollowsTheHost(t *testing.T) {
	s := NewChainSupervisor(Config{Chains: []ChainConfig{
		{Slug: "cchain", Default: true},
		{Slug: "hanzo"},
		{Slug: "zoo"},
	}})
	s.SetDefaultSlug("cchain")
	for _, slug := range []string{"cchain", "hanzo", "zoo"} {
		slug := slug
		s.indexerRoutes.Store(slug, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			json.NewEncoder(w).Encode(map[string]string{"served": slug, "path": r.URL.Path})
		}))
	}

	mux := http.NewServeMux()
	s.MountRoutes(mux)

	for _, tc := range []struct{ host, path, want string }{
		{"api-explore.lux.network", "/v1/indexer/blocks", "cchain"}, // names none -> default
		{"api-explore.hanzo.network", "/v1/indexer/blocks", "hanzo"},
		{"api-explore.zoo.network", "/v1/indexer/blocks", "zoo"},
		{"explore.zoo.ngo", "/v1/indexer/tokens", "zoo"},
		// An explicit slug still wins over the host.
		{"api-explore.hanzo.network", "/v1/indexer/zoo/blocks", "zoo"},
	} {
		req := httptest.NewRequest(http.MethodGet, tc.path, nil)
		req.Host = tc.host
		rec := httptest.NewRecorder()
		mux.ServeHTTP(rec, req)

		var body map[string]string
		if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
			t.Fatalf("%s %s: %v -- %s", tc.host, tc.path, err, rec.Body.String())
		}
		if body["served"] != tc.want {
			t.Errorf("%s %s: served by %q, want %q", tc.host, tc.path, body["served"], tc.want)
		}
	}
}

// A host naming a chain that is not running falls back rather than 404ing â€”
// chains are added and removed at runtime.
func TestIndexerDispatchFallsBackWhenTheHostsChainIsDown(t *testing.T) {
	s := NewChainSupervisor(Config{Chains: []ChainConfig{
		{Slug: "cchain", Default: true},
		{Slug: "hanzo"},
	}})
	s.SetDefaultSlug("cchain")
	s.indexerRoutes.Store("cchain", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		json.NewEncoder(w).Encode(map[string]string{"served": "cchain"})
	}))

	req := httptest.NewRequest(http.MethodGet, "/v1/indexer/blocks", nil)
	req.Host = "api-explore.hanzo.network"
	rec := httptest.NewRecorder()
	mux := http.NewServeMux()
	s.MountRoutes(mux)
	mux.ServeHTTP(rec, req)

	var body map[string]string
	json.Unmarshal(rec.Body.Bytes(), &body)
	if body["served"] != "cchain" {
		t.Errorf("served by %q, want cchain", body["served"])
	}
}
…1uL¸¶x…X]sÛ¶}–~Ê‡”rÚõÍôÁ©;¹ñ½ñGýÑ>d2.DA"*’` P´Çã™û#î/ì/éÙHËN&Í86‹Åî9gjd¾’K%*©ëñXW�±^¤ãQ2»óÊ%xPunæº^nÿéLMÚÐïZùíÂû†ž�·¹©×ñs±p2oo‹«B	×ÉF,Œ­~tÂšf§¢Ó¾À£�"/°³À�ÇÔFú"Ã:ZzZõ¥U3K«äüN8yçDWè¼è—z¡ÙýïÿlåKk¼3ƒÙµ¬”£A²¶2¡$V{³RõTÈZÈ¦±f-Ë8[{¦âeð!—Öê`GãÚ³AXkªLœ·Þãà¼ÿWG.«²K½Žf/•¹¾3d«0ð”Ü™³S«`«Ôªöb¡ËÒQ”h7A"þ�±+áT©r�Ó#B,$s¾�«—…ÏÄ¥¡—NÅŠe°Ïá§T .´~ˆ7)È›pTÏ}œ˜†Ke�Ø^ÿ´½´²)¶ïùÌÛ÷®�ñÈÃö�±¦�•Úa÷>³§Æ+xlÕ+`®T©°¡²*cÐT¦Å±SÎÃ±ÖÊÞ‰ª½…�æñÒòa­Zjç±vŽã¿ÁX6uy'(BxÄé6ÂPµtx¯‘„€†š6µhë¹²‚$ÍXÕ¦Ã	(¶èö�÷Q-0ËHßãÅÁau'à(ÎºáeŸh²×XµÐt²¯'²ÙØß5�Ga[ð­rîÇ£¸èxTþG¼Ì>„,�ŸÏqÇ®p¢™ŠÀ�]#n3äDtàV[ËxCÌ¥Tu„¨§�Ò"— &…äåc<Y�”ð«ˆ˜�ñ>
=})	—9�Â‹T½ÔµBXMW‡áJB8˜ùV9S•z¸� Š 	àˆS‚L�Ô˜éøK^ãÏA-‰kÐÏ±3q]kz‹ÍæÒËW²ÑAÈXêMsEbÁ¦šßvÏ7Ìž[ó'ø‡°âj+œÞ¡�µs½X 5KÆÚärÖ–h¦„‹Úxœ›ð†d1ðžiÌ %�#QX*ïÈ«¤­	’P:y2ŒÁJdØådÃ(ÍX™+B±k”â)]!¢Ùx-á2ÎÐ±/>}~„W¥@·9èˆ { ìîa'üöÜ8?	e‘µ1y˜>�ðoÕ¿§m9+ÕMHÉWS7lÑÜï½òÕM/Ýß›Ézô¥¤)�L¢KØ‡ :O æ€l`ž6�R™�m�‹Ô‰­CÒÊË¶c´3v"C)}‹ƒrI|:io'DOJÚÍTX±·/¬¬QgÉrˆ4^�°,òô=6Imþ2ÉK›QÔ�Úl®ó‚\žŒG=¥i'wprôPÙ&Š"#”û˜\Æ°(�Œ"æ2Ò Þ}¹lˆ,)Ù7.ëœÔ²›¡³º?8‘£`…rÄhŠº�HJz�nªT3cVSF9/f°‚áGòH£ª±ª‚dâúzV(:g �8!Ã%EÙ¡›@A�o1�K”ˆDVU`âÏ«õ.=µ�äñ�ÿëÉ8âñ×OÆùÓš-É2g•o-šÛ*ÊHÿy!K§b~6Ó™­‘‰T…v&ÖÓØ*ø 6ßÃÚ¦µ´ŒµåS;õ»Õ¨~„³€Á‹°	C�Ú ©P6`eÿ-Ri4Bþa_Ôšã8êÈ¡ì�µÆ¦Ý4lsé¥oÝ[9�vÊß>\Ÿ�Þ¼»¸8» pqÃUµèÕfJüçòì4Á&10#ì8'"ð™Ï)¦%Á¼Úß;”Î‘Ëª6ûhòUJè‘@-d[zæàñQ?ëº.ã<ä ¢š<fE»¸Œ¨vÁEÆä<Õó°Õ˜@Û|÷´èQÞÃ´/¹>ýïéÙï§7‡ŽOˆÛÝ¯	Ç O!‹ÔüfïQ'¥?®=6šŠŸv&`1ôW£“¸´©µy�QEŽ²×YºEî²  s��nÅ²DÒò¬!Â}³¤BõCýæ\tÒ±%¸çÚ*ÐkØ,gêb¨,,jéWêTñ™ú]tp½ËÚ•–(>¨CƒFíÖ§ÄÒîf×3’3¤¨Êb3ò2ÔeWVWçÜ`e?E­ P`=�kµÉNMƒ=Àõ”oÙ©ê.€VúL 	Óy÷ÚDkÀ{¢âýü:-U=Lª²"c	þpuuN¶»“ÈGêyy?/W±Åè Rñ¦‘ã: ô†ö¸ƒî…Ò	ºö‘#¥ŒÁãª:»ãxÆ|D
÷{¥_Q2ýô™È”¤$‚ˆ6Fâ‘‹Q`@Mh§.7EÑ¢Ð”eê°¼}K·5D¼xI0H8üå—Ý„/”¾È=>L¨z<²!L|c‰,Èˆz‚«Y'(x­úží
äÆ#v7@Ï]l5f*—-ô—/Y€ ˜+§—¤î½_(BK×7ÛPªE}ùîl‘bÿxa£¶(f$\Ø8Eß¸³¡7.l8AhË¡èœÀáFÆ¤ Åú®]ÎÉî^´OSJê¤Ð7 Øtgb %è	ý<ÿAòÐc&°üb£ÀÊnÃÐFÞ'lêxNËmÓq}H�ÈÆÈYuqž<°´QëÀ[bÝ¯-.Oé$C#–®&oð
šŸ$<“À‡[p¬½–�KëIÙš4?¿Æ2š�î‹E/í¢&E„üÒüÀà@ÈGM�pÞá)ÔrÎH%â-ftn/ø½ó}å°7àìÁ�x,x:óY|†C€Ž¾µ`x;OhÅ¯üõÁà‹ØÚptðôé¾}Šhùƒˆn‡nC!(t–ìº®ÚB–,XSñb6Ù,ÀO¢1ÉÍã§ÏaÛûY#<eOC2Œ§:ÚÀD,ö/^ˆ-FA(¸}÷²µÞÌâÀ¡/R×Ä‘~óþÓèùÖ³)›²µCÄÍôóÊûín_;ÓPÑ�<cñq
1ñÝ
Rn°zvÙ‡ {¿O“X#^]áÎLœAoYê\z\ëÃWX(%]Æ-S\Hað…ˆ“„ÒóŽ¾ô‚–v“,<¦•l>…]é~ÄÄÜJö6]L‚�$¾µ-jé‘‘!xœuUÁnEU"ÚAQp$à„¨ìÁñ:›±(HIÉN¬ÈàØdí€��¢Þ™ž™Æ3Ý›îoV‘µ\8ZNü2_ÁOp@ðpæUÏÎÚ!â6»Ý]õêÕ«W.ÿúñ÷¾!­5Öý°¶JQ§›+_ÔÃ81ÕZY?ÏÔZnÅ¨X“:WZþüEzqš}0#²rd¬w4.¤/¤%AI!”&œÓ°VeŠ_žDYÆ´e|A"M­tN:VFkk£SieJJ{ƒ'~,µŸÜN¼Ä•¼®ð“~ÿæGB²¦žzäâoo…v"ñÊhRŽCµ±Sò¦OB§áÞŒðŸ^#&AlW�WŸŽ2Ó„Lðs.gB<=ñ…Ò9ÉÒIY“Ö‰œ:®¡pãÎ0:*Â»R¥q”Õ:¡9Q+I–Ó}¦æ¾Ñ™Ê{44¦¤QÇJ_[–åq8ßy@W7h�–—©á;Þq›MM$0ð©íýï…½Pb/:‰~¦¬ó›�¡ò
Er�(:ôsŽ@Žh(’#×vïškz8r·ù‡’Â–J:�´S1,Á¦Ñ›³Æeá�.�HqR€2å±´.¦�)Ó¨<™,ãxçžUâh.)ŒJdø4#i…7pÚ6:�¬4•:áð^XßçhP •TJï9_Î ‚Y�˜cSC�œŠA„pI!Óº”H1oWK×«ýrÞr`tLe4½ÊL?dãmÝ��“¶™ÝnÔ9‰:¬¨§}r9ÝÞ �K:{v0ï‚Óå‹Ph­“öX:FÂ +±Ÿ­¸¼×—;vÚäqÜíJâ¯“�ð,}Óüö)FcQo%x$[	„Võ)•™¨ËscnkÝB¤ÔÈ– &Ä À¡öÂØ¬ƒ&ÓÆÀ¼ÄŒ¨ªîy¬‹»§¿\üòµ)@ ù¸PIA™PemC·ÆÚ„VÓ	æÚ 7C5P€H¹:mà/ócH	Yûx|€FÂ:9wY–Õ™J²~Q¯²ôÉÁþâL!�m«ð‚‚§ÌC!E}•*1ë`†8­ Åaë¹Ó0´Q	ÅÅx{,,Þ‚ÈWïGñ#ñ|¶æ¶Ùp›Î7Þoº•%|öi¹yš={3z:�ÏjH}[Ã«&‡Æì2†>u?ßÜÝy°y¸³¿÷t{0Øtû¬ŽN7ðPÕÕ¡¤š­•º×Ñ"LÛq¼ã�Xá»ªRþÞ½›ë½ëÝO·º½ ¬Ð•FP�±U^Â½foGw–’0=Ì3b2í§_EÿpÃXh‡(¼äp­Ò
P�¹r×ÈŒ5\¿Î2RËœ‹@­à¡¡‰•„“s¬Â”Á(àÑMÙH
â�‚;ciÏ|ý–�ÛJÁþ®á¿ìaŠãå2m©´A7éî]ú`=š=¾ô]gqpúÇ¥êÂ¹JŒeÆXU�3„&"ø¬6¼7¼ðÃâ}×HëæÅ¾6&'¤4WÃ ²½¶â=éiÊnÁkÓ©†A¢G H°Ò�Jy
ú@VP°ýAÕ˜~Ï|4Þfx�•kôÙJþÙ æÕªÃ�ÖçÃîe—·ñQ(½	¼¡'¥QóŽkÆË‰‰;×EAO»Á %�1ôpû]	£y¶?f�/ßšÕoýýQ)u�b�î5+ïkgtüDW˜×B”áó0ì±“hæìÝ+7îàêê0~Ìe½¼6_œ©ø•�¬g?-}6ûké½w×ŽùÍÉÜEgß^þí4½òú…5çG¸¡xÕYënÛÈþm=Å„€·òZ¡u¿FaYÊÆÝÆIcYÀk$Cr(±¡H-‡”ìú}Â>I¿s†Ô=Ž ?ºØ$öpæœ3ç|ç:3é~‘c%¦2ˆJ¥`:‹“T”K–ŠÜØ¢ñÉ?tYXˆTz2IÓÙúÏ¼�*�Ò¢Nì×ô#-ág«„ŸÇA:ÉÛ�§'avç'ãDÎ&'*‘²JG¥ÒÉ‰¸ž(¡r&ü8™
W†¡'óÚÉYœªŠˆâ”5G]wiOätj¾Ût\+A\5Qó’`Ž_A3‹f‰òƒ;å‰$Î°�Wò^©ðbü.#OH­®Md¤*�S%ü$ž9Zg–üS¢þÈÀHDØâ‰ÿüëßXíñ$ÿ*Cáz&SwBA”ÆËDl†¡J*ÂQ®Ì µ\ßžNd*ü òrA“8ç¼YÐâ8HÊ”È9Ynlvîé–¾Bx*JRæ=ß…å€4>Ø¥’G¸l³œ“FÅ©èµí^éàŸq\¬Ô«Uü_:`õ%B`“U½ë5zÎyuàÊö«Þ ç¶ê£¶jœ»g�óV¯ê´<¬kÕÀ° yË6»Ýî¨)ë#U}UmT[5·SÖÃÆÙ¹ßiµÔ°Ñìu:fÚ[ì6»ç¾ßl+§Qoøøã´Žªýz§9jV;½†×ñënÓDzP}!jµV�k½U;T[�AÇÖ\oÐêu‡NÓïG^¯Ù©5(Nã,JÍí5tª-â(‡ÀŸ´ÁiŽ&u'Ýê•Z$Yt•9ŒPFV¿€Nnq-Æ1èÀÞS($‹HDBÖ €Ì¤Âxdh‚ m´XäÖÞéDIwb‹A^GÐ¦�L€¡nÀ4‹F¬dW1òoÅ5ç Å]Q3pŽ³ÐƒŒ1
^8!úÒˆ˜Å" —·K~¹b¥¸²?³¯²™Jæ�Ž�wfcabDÅ@úbHîÑnVD’&ù§#ñP:0*}@�‚€ñzKÇX…ïÃÐ¤l¢¨d_ªÅx©7Ù]ùˆ?Ù¯ÙÕ^A¶²õîíÕµ°Žåcë„‚�U&
å;¯°VvÉ.†,SEx�žAŽãè‘"Ê#Ñ{¥gp1õ1	€!?³¤ïM á›8±w_Ÿèhí72Ñ–§rvc”u+£ûj®¼Á½Õ/t÷±ökE`/ÙW*-[çq”Âé_^ßÏÄ±äl"¤A™@N‡>ÃS±°Y²2‰€e¢…nhí—ÑŽÒfÒ	Õ§4þ¢"ýÿamÞsj°¯Ò8Q+ üDŸÞP4x0°éç¾Z“>y&4öX2Ø§­¿¤œŠŸóôg_W„§|™…y<a´å0(ÐJ€OaÆ®BÈÕ„ yË�Ê0­Œ†2•Ã é‹Ô¾VÓ~,±é–a«¬ax“#a "¸tË„¬¼¹ÁûŠ�¿¶OÛëÒÃ[O7®ƒäþ„j›UÇ>…m`›¨4K"RÜRm”›¶F$�Ÿ
ªˆ©J'±W&B^—Ñ¢�bÃ†·°g­«t.“mï+¾`J§ˆ2á ÁÉCÉ|OrG-o²�‚—y*Dò~þ±¼:2tÙeÙïˆ©Æ-ÙŠ¹'ìÙÆ-Y¯¯¯ß•±FBÝt@÷¢P¼y}¾šJ’eLùMó¨‚£ö ê³T%”�*â'œ?ú³ í/NîÆWJíW€Yè—­C-u)ÂÄ2JFTŒýõêíe_Î�)
ljÄ `ÁêŠËÂ2EQ¾)ƒ€¾žÇJ;ð'8 ”¹FÎZLâPá¬D,äÃáXD
¹‰ø:
)Ã¤MdK8å9ø
¶$	Pøiž�®aDß°¨ÅôÛØ«ðïß©°¼DŠÕøcèä¨^÷é•‘ÞQÉIÅÃÐAv‡À@©èÃõ]¬�LN`D	Zúü`™<Ñ'ØX&ÓxVŸ‹®
-ÑF„«o}>6UÌñg‹¿Ñ,K­þƒÅ‘ªwÕgþgU,É.�SµÝ3Ö#3€¶8|>¦
ŒX�o×jÕz½ÕéÕÚÍfµ�O°d0�s6…6Dùœ‹úÖè·³óëO—ï>\ã˜ƒÙ�‡Õ¯Ú­Š5–úƒV#tS™bÑêôz½ª¹:Ç-«sƒËSZë[óÆËY‡$˜çškÕ»F•ž3¨wkíNÛï¶zíºï4ZçíNÓïŒz£A§Õm6[u¦Š…RØED:]§²« ý+`¾e@KßOHÕ·þöá7|ö ™©I¸Z×h9çúÖ˜rÅvMÕO½~�ª¯H-HJ:acÓÍöš»ØÀBXÛ}¼½}|üL G|$œSX`T_¥2ÍôÛ_·„æeDÎCè…D…‰v�B ¿ôãû„n8üòÀí†ánÝÚ[Å6ƒ÷ì#7Y…¢(6®Ž P„£œˆyáÊ×4Bê
�Æê¥œ8FÁGAÐ(^P”A‹æ‰TP@aÎº_06Úº&‡kªfKJ•(9ë~\T«Ôâ(´8JUý¢ÅñêŽ×�.·8µ8NC}ƒw«áqÐðxÔð¨õ†çii¾&åÆºãt¿!M¾¥L%tQNÇñÐE9è¢<ê¢<•wQ?(�§ªNÛiÈN»I’}Cšá7©×»~­Óô\Ù‘Í'³A­Ið½±<$Fë–¼‚‘a :J’8A¶¤�ýß#tz©àìh6±3¬7žb0»¤\x>÷ÃX¢5*å›éR»à”o&¿›ç~wèÁå—%©õ¼…ô›'ÚEl ŽéGB	ÕS4 Á¸YMÇ4Ù 6�zÞiF“Ü†�…Ó-š@•|5É^Ç#xÕ95Qß�X0ühN¸JÈkÅÍ-ê¦ÌM9ÐÄe»ÖX+á“lžlýÅ•7
2Wž…y€£©>†Úöçí¼«Ã™äî<aðÝ.–iÛ,#˜n®?~^©žsá&WSsÿW3�©ì²5ˆoQÊç|M¯Æ×¥OE|Ã+¹ß¼ú/*¥Zà`Y´¬7{ÉAujá¤…?Oð‚fi.²¥¶]?Ípóä)—I9ÛÂ-èŠ�yžxŸEe×6 á>|½3þóDåæÚEëÚ¦Ïpm®ÐI!OæÂõõÜlx@µ0Ó5‰°èî7|ßµ	Ï¹äH„…ïkÞ-œû<amF€-‚p'C†øSj‹Hpfæ­<ošÀVäéZ¥i˜�‚0ó]ÄÉq¯h�¦’HÂŒ[œñ¤”%ÐbØÅÑ�$_@"o©ip@•^°ˆúøz^ãûA‚(“€þN`yE“àfÔ×1ŠöáZ3½]òè¦”¯‹øÁžÿPàe°Œš½¥>(@GO`åI¿¥2?G.*0TÕSlë¯×SkeÎ³!DÆ0·ìM™©Ht¾æx°F	”k}˜”†J$ÎPøÄý
³%Ir0ZÆƒ@MSAÅ·™£t8“�µ¥à9¦N4ŒteD_Íh�'œ„ó+á‡æìø•‹A¦¬NŒÐLÑô…4àG)‡±æ=ÒñVz¯|ŒÓõÙ9ðÒ!†û—qzAÉm+{í^€w^V®ÏìðŠùßÊöF¤‰›�€¯0ŒñòV4Ý»hV›ûkê ìPç¨×Mmb}¸üõòíÇËOç¯Ï..Íxc@–›×Ë‡Í9–;­[h¥@\ÑÌN`jÙ…ub~¹à"Á „ž,¸‚ ædÌšQ<Ãe÷ûˆ%;o ô8‘ÑðcP*	jŽ¬UP`æ1ŒÃÒ{¬1öPMw"BàH‹bN_èÅÅ©cå�FÇ	zøwP4(¹Ê0¾ž+]Lþ÷H¢Ž;ÔµiÁn§¹wN`�²iá¾’µÒË[Ï@ÝW:9î³`|O¥ôÐð‚G>ólÃWé¡‹œ–ËG�mÏ"òÐ*öy�r,Ú¬Ç†bbö*Ûa`ˆ’
ÀIƒjÔI<Ûx.ÇùÝu@š%iežI TFîkX?Ë“,%9Ê~,y›%@(KÎŒd;³¡xÇQkÀ&ˆ.ëé š!ÿêÎ$ÚôãDEA^KD±÷Ãá�zæGÅ{ŸW´Å³
=ÎqÛ�*�ÛO*Eüî7�­÷ˆ›[jÝËÀÿ2q%£àÏž‘=çør�Æ™d<ï�Ù›óy
4š¦ÚµÒ÷�²d`Kú\Ív9D•÷×Ë°;2Þj4°¡ñ*|¨˜«nMm¶òÌ(oW÷æÄMµ>{ÝoøKÁiÍUÌÎ³7o\¼1Ó»%…`�?‹YÝA=G5'‰ÍU¤r“(Ž^�Í”B	»Íü¨Ÿ@á ßÈ1B&õæ†ç½T¶hÜu•¼8d�)JC£_Œÿð˜HoeÓéË9¦ƒÂÊ¢€ŒðrŽ§°æMþ†–é4 æË«–õN…µúvøÇÐåcˆ¾•{S4³ô.§¡5SV=ï^ÁPºk7ŒúW+7Ã["ÆÜô›JÜ:Æ}èÂ–b­8[*’$Š¥•T�¥ÿÓê¬zàý™wxœ�WÏoÇ†c±$«A"–#Ù}ZD°T¯)JŒš–[H²œÈI(DRà¢‰jw‡äBËzgV­Û{ÓK§‡ZôÒ[�¦—žäÜ¿¢@Î9ö½™]‰d%Á-`È\îÌûñ½ï}ïñ›§ßÊLzÙèÂ¬Â«TjJ3Íè&‡ É"‘†SÐN¢€‡ E	�ª¨G¢¼5®¢�ã1dŒl½JyÒ…6KX‹kžøÐJ•!5(.B´ÍtîMK`â©Do‰swWA"S¼T­§"€]®ôN‡µ?£ãëtb�3­6ÑƒnbkÖý¦6SKæ÷ï½ÿù1të£{Ýô¡&Ã.æ”P´½ãÑ‘‘cÏE§XW�uÜ‰÷|ðlT¿´‘l†O–Êeüçù£#æ›©éç½Þ~Ï·&úó¿ÀÝÙ?örƒ^ÅYôÍŸ¦&gOÍiyÀE÷�9óõÄ}óç©›—ñ´ùîús³5ö®™¸ñ¡	oü<‹Ó#É<Pf##º´‘$2©ÏyŠ'‡X­Zf}¬ŸÐ0Â¿ûG[X"-;…W[ N$0|Dÿ‹üþZ×ÛóáÄùèáŸ~ê�öFsÞ|’åÕ˜ "×x [üZJÐ	Š:"æljˆ–C¶Û�î ¥*€©rŸLá=–*nC#…ˆ–¬Ž=Ëƒg–SqT\)%Wd�‚‰DÈ����í1Ö°”v¦šøMLôì4£ 		¯£w…q»lãÉ�ïP˜À±Pøë$[·´võ‰æ¸hD‚—
ân;««–ÀÏÙºoQ¶Ä×lD¶XL:2�CÌá±²-#aQ³]ãÈ»kÏµÙcê€"©cè€Í¾Á5æƒœj§�\êEpÏdÂ£†°1nˆpWâó·Û¿¹üZJ3~çæÇ+·nâg¯Ÿw(â†Ô˜7ßÞ™¾Z¼3ÿºóèôaåjÉ<{øÎPC™hlÆüsê¦ùëØ3uíúÛ‹eì²žo®^»{…>›×Ì_®ùæÝñ»ï,º.rM²)ÌÑøÒ¥Uó÷ñIÓŸ¼B!˜¯Çï\ZÃ¯–'=ßc-™
½•j¯â9Ã{{=Üúà­¯¾2ßßºg.OO<€¹ÙÃù¼1–ËËðyõãêÖ‹êËõ�V7«ƒ}båYaÊ¨”Ø�¹í’¼\X�‰YÒÀ2b¥¦°³âùÅB*¤ãrIŒ2¦¢×<ô©Zd)ám™h÷¶Åb,hžïlU©‚?ë+aQ;±•YÃVUñ„zX3·'F…òÓf(=Þþ=§ƒª´ÍÛ(¢sÞ¦³´²²Tž¿·ïõö1¡¨nóƒ™'ÐÔº]ÚÁi�ªmŽœSzCèHww¥ü„ò4?üÔËäˆX0è:¦ò*OœŒ„\³(ööJsÎõüc˜)‚X—_
5‡7¼»4ñœŽ�ª˜»O`öU^+l2Tðg”)ÄQ+ÒGè‚°í… ºšœS¥é#¶5ò¹Ñ,Ùð›*lëûÇãÖñ~ôðÁÃGþ…h.¯¬,h>6Ó?¹í¹0XþxÍ…Dã4'AeÙÌL_Ûñè…y	^æº�ƒló„i™ ÚM‰bä»Øe½NïéB E=jXö8C�J !©CQ…0sTZAZ+¡‰H5ñ¿Dé>9xFÏ«Ÿ~º©v›‹RŸŒjÝú«J�ßí Õ0EüÌ¥±‰,¨7¨¼”uëýøÃ„µ›°ÿíp­	•ãÁj1%¨“ßIkz¯*ðÅ^ñ`çq‹Y/ˆe$g-†Ï¨Úø8`…¦evrQè?ÏZ­þóu†šíÆk~¾&åÁ
'çN1Üˆc/;ãJ¯ç¨ßÀ" 4õÐ9„
	Oßb;9ÿC/ŽöSüK{òK=ãÕSJƒ"Ô2„�øOj/$(„¼ÎÒ˜¸ŒûT«å[útX’T`Û¡€$¯1œcÿw³JØŽ™Ý Þ 
¥!wÿ<ˆpþã$ËlÂ1MyU$6 ž52ˆÞžû“ïõ…p®÷Nh<û/—¼ÕÖÝ>wy'7¹éùŽ‚«ÔÒ(&]R¨ÞÔã4ì­°ânäÐ7¸.uh¸âHwÛ‡Û<¨¢ØÕ­BTA;åxX‚5Ü$€…a‚˜à»“åKw¸ÐÝûµ.Ž0Ôë´…�vÍR’´1fâ€öÓ!sº{à¦	�ó'ÆIE’ÔÐÖÓ§ ëLÐ@ªrªU‹,êïnGns¯º»»žÉƒaÚ9e}Z9Y1}Ø¶«EådÅ¨2"¹hÎ»RÎÎÝœ<˜$±k%âáda¹%`Ø™ÐI˜Ý=…µ[l¬T�ªg~÷Þl–ÿ^@Ó$výã	÷™�ÿ:"…èÏéÂò}o»×CwÙ  çÜwñŸyÿ<9&WEÕÄ}cÀNv¡!¯|âòÎ‰ÌÁz¶á7,xæÌŽ†b¦r5ílxCÃhÇƒ{0<­_Ó´.ÏŸ‘FvêÌ1	ð§Hð?c�>=8¦že-mZ9‘ƒM
÷Éü(×žYÚüê�a«8v¶ÑÞU¢Eˆ~–õœðüsv>uï€ŸAxœMŽ±JAEÙFÐ.Vv¹FH,B±ÜD‹XÄÂz²óvv`wæÍ‚ ù‡õ;Ä¿CüõC‚³±±|ïrÏ¹Ûu²~MÞž.Ÿïµ/lð°†�Y“kIø‚ì‡Ðc!¤tÄoaDE}#!�‰²Ä<èÒÇÇr!–XØPJÌ	¬•‰ ¡„6ìw(c}¡�ÂÏêlö\”×«ô¬•«Á¬¹Ž/Ðéà±n�Oêïôó(pÉ¿‰‘R�{-d±xWÕ«Á­¨6ç“÷¤§ó]0)¢ÿæªAŽÐí‚É=§Óé)gUb3ºþ8ü:f`;ã†“OxœC ¼ÿä	ä	°467µ
çZ‰ÆÃ™9¡hN²Š100644 go.sum <‚ØJü2€)c_ö7?Š<!àÊ¶³jzT¾—ãSxœC ¼ÿä	ä	°46hëóë‰ÑB\£w™éFÈ,Ûû100644 go.sum âxk5Ž‚‰¼ØéÇo�Jr¦¾Ê³jz£$à„‰Qxœû¯ó_gÂnF³ÉyD6^X/
 D­’àxœû¯ó_gÂnFóÉyD6^X/
 D¶“n‚ë+xœ{¸˜iúB¦®›g¹¹{ 2t+áxœ›¾�iÏ"¦®“78ó˜+dZ†û”údºy¹øè{˜šZ˜EDy‡F§U¦†å§ûú¦ë{”›”•™[L.´id4Ÿ¼×Ùss€«»7 �’è	†–#xœ˜ gÿä	™	°•5Øñß„:mu¹CºrbY¬ ¾“©‹Y�3ÄúÜ¸jÏâºaÖì¤”—ˆl100644 go.sum š;t’J}ã#@ÏXŽÎSÁ!cý100644 main.go ÷}n­³•z�>üÃ™9+•ÿ…³�ÅhÑÐG9ƒ’d²Nå¥®x~å¦¡“f3ùMBÐä€(xä ÿ™	ä	°•î«É+é˜·z“˜ªe®M¬q½“©‹YÓáGÅÏà)¬¿k¢€•†öY100644 go.sum +õÊŠXñ]Å`93â¥Z¬100644 main.go ÷¼Z¤ôH· ²fí›�8¿‹‡2³�ÅÝ–ã«¸•Ùà»þÄ w?|P6H“f3K100644 swap.go º®S½ð,*Ùº­SãÆÊ‚ÿ&Þê100644 swap_test.go  >˜h�[!Úl­šfƒj¹-|0”Ïa¢à„Žxœû¯ó_gÂnFãÉyD6^X/
 D’�àx ïÿÿ,ÿ,�»5“Á±Ð¯D¤‘n‚ïxxœ{¸˜éì<¦l.›g¹¹{ 2œìxL ³ÿÍžòŸ°D“°C/5 h1:8KmHUvioLt+BlMLXeQl1IBqze9TnOUkGQsBBhOcILr“H“°C5³½C�KÇ‡l‚ž"xœûnuÇjÃ/ÑÍ¢bIì *tNë€¨oxœû·‘ñÁBÆ±l“±ùl~É~Í”¹¤<òaÛxÛÄ¼…òŒÄ…”|O…ôÔ=§ü’…¢ÔÄ…’ŒT.N}}…âÄÜT…â’ü¢T Pf±BqiRzQbAÆä	v†, “Ú‰n¾e§Æ8ÙÒÙ~²�¹èä7n¶›mÝ
 Jr,Âî„lxœŽ qÿ™	™	°•+msæ[[:mÇé³uåvI“©‹6²„¡ô§’8}ACE:ºÆ0Ê„à100644 go.sum dß»?Vò-3rjù,Ëÿ³j“ åÝ_GKjdÞ,gî�‹í¸“AGÛ×*éÄzƒ½ën¤ý›<´äù“f3û;Õå€xœu Šÿ™	™	°46)üC°—žƒHÒ<^±»Ì[9D¸100644 go.sum ºÊ�^'i*õ{Ó¯…xœ‘¦Î’ñ³j“×tës�[ˆÑL<œÇÍ´iªpªä“Afb…:â¹ßä�°q»®=„¸õ‹Y“f3¦j5yl…˜axœ›±5dC2×f;žKò #iýì„ Yx< Ãÿ�-ÿ,�†hanzoai/csqlite v0.1.2“Á‘Œ31“Á‘¬6“!‘Î³��p/ï‚õxœ{¸˜iÑ\¦¹›%C„�Ì2­Ò,½�œÌÌ*M½Ë*´ýsü"Óƒ³�³
r2<Ó€¸$9Ô<'/}²¡›œ…‘¹~z¾^n~
HsUe�v‘wqhªgxR¤©od€q¤««G�‰G¾Gh®�O¸_YVNhpvdN„ÅäBáÉ³ÜÖ˜€4:z›zg8E™8…i†ådúe¦§yVå˜—»ägê§:'¥9§é‡yäƒ5Zºóš› Yœebœ›”ì�­]n™Yž“iX�[–\Pj‘æ—›•”™®¯d’í‘fUÒ¿ù�;£ @T¶å€zx¥Zþ¢�Íž°m0csqlite v0.1.2 h1:4DzdgUfKADhzEP66Y30/HwepuBLrwt7NsemJMfrPGl4=
github.com/hanzoai/csqlite v0.1.2/go.mod h1:H31a/O6VXuklR9UBkgY++bmAK5uzVfXPqU0F6P9Wsos³X0i131 h1:JcCTerzKs8CqhXQzn3DDEUOMMsC1R0ud+XrX570v5K8“‘JCgraph v1.2.31/go.mod h1:l5g8jQ7hdwTDG31SmFnMzb8AKn4B8flxFIDvggw+RTY“‘J“[C¬06 h1:gkW4UhvDj5Qak6JTVSUq06uBmPjiWO60Y1K6ytqy27Q“‘J“úC66/go.mod h1:oypCgtib03LylLJY5TQYct8CAhm16gew6d83PusPFo“�J“íD
³«D÷I¦“Œ„æ €²*xœ]�AKAÇQ1²ñRr)�-Š�fÓ $`XÐ•b0P=7Ìî¾Ý:;g&†PÄcñf™Sï~€–ÐÐ~†z¼	~A:ÉÒC{}ïýßÿÿû?_øüuáúËËø3t±Ì91F42Y×gœÜ¶di}ú¼8X²¿J½éÃŠ­_öÖ5•P(Ó…™Â©üˆ”k¢ÒLL š  y‚
2E†¢ã	Œ�#Še¯Ñ ’$
µ“q#-Ž·ô$�$‡0ç¥Ìž»©Ðš Îœ3o÷ºGïz½Áiÿhÿø$lv@�„ž¿Œ0•
áýHØNëÕ%h’#œ &EÊ±rl:€.E2Ü™+î¯n¶Ä„ó9Â?yÌL÷„åè'"àÇÅXq^EŽYŠùÃ”)m@³Œš}'ÊˆJøŒÒUe(Ó�r’e�¥ upˆÅù–ÿ“_›à7}øTöìS«eT_ØÝvµT¸Ùí�ï�k´ßª·ª¬þn$ö®¹^µ²ç]L·o—m§Ú7íŠ}<Øœ†‡?×þ À6±‚äA‚/xëû‡™¢��‘// ONE sqlite C library in this binary, under a name nothing else claims.
	// This process links graph and indexer as well as its own store, and
	// csqlite is the house build of the amalgamation all three now open â€”
	// registered as "sqlite3". Upstream mattn compiles a second copy of the
	// same C, and defining every sqlite3_* symbol twice fails at link on
	// darwin. Not its hanzoai/sqlite wrapper either: that registers
	// "sqlite", and so does modernc.org/sqlite, which arrives with
	// hanzoai/r[eplicate â€” two packages under one name is a panic in init.
	_ "github.com/hanzoai/csqlite±¯T1What a start is for: ask the chain about the Token rows still short an
	// answer â€” a symbol an earlier read never got, a sup@ply a build that never
	// asked for one left empty. Before Run,“°2#
	// same writes. A row with everything on it costs nothing, which is why this
	// is simply what a start does rather than something to remember to turn on.
“—36“Î-“â3settl“ü3%} else“î5token backfill: settled=%d“ñ.n)³y4àÆp*ê€î{xœ{»!rB�\¼‚RzfIFi’^r~®~nbIIž~z¾nqaNfIªñæÇLl: mx/î™(xœŽ qÿ™	™	°•ôæA‘˜T•YDÜ"Û`.aD“©‹6ü†ËmÍµù#ÐH×£ÐGœ100644 go.sum PÓG[ˆIå‹ÂÎ¤Qez’Ñ³j“ åÝ_GKjdÞ,gî�‹í¸“A*’I¨¹Éö
à0ùIæÖ26¯“f3Ž:Zã€xœC ¼ÿ™	™	°46ò¸…3E•Ú7êž( =ÖÙÆf2—Ù100644 go.sum 
sÅgèñ<¸K¸Ê8ûaÀ³j/·Aôl…¬kxœ›ñÇ{;Çfi¾¯² #Ýè&xœeQ±NÃ0•X ý¤S—.U+5[7
H jœk}�Ø‘í´Í>‚Aâg˜ùÄÀ9­ªfqîî½wïî~/¿n?�ê3!½±ÕÃ„Ná?J@X¯È�T‚tŸ_¡�&)28™NaÞ 	Ý ðº�Ñ®…§%y++ŠSÐ1åÍ+ê!œ
©€…JQ{òÔf÷çw±Í œi´°…¦(3v’B�6B&`4Ba�Dç€tŠktàhËÑˆ5Z^	&'ïYÇ+Ì¡táÏ0[ÕhÉÃU¹†œñ=Oúýö+EìÀ¡]2”Ë}×hÆdÜ¢°$9O:–@šà½d3–[k¤…z6¥%½Ø¬Ïeåb7l£ñÐ¨±Ó:D´F›°âAë�· ŠÇvž)n…vØílïæ|ÓhöâŒžôâaª§å¸•È³�ÌlËK"ïqŸ˜ì“@lÏËßnÃxã^(´¼63û<8ú9þî ÔŒà„·RxœëÕíÕ�0ƒÑdòAná�kŠ 5Ç—àxœëÕíÕ�0ƒÑlòAná�kŠ 5Ù™á•gxœ[4—iÑ\¦‡œL2­ÊCµ-rÜ]½Ì‹üJ²Jr}Ë‹<Â´+RËª
*JrËMÍ“ó,Ã³ÌÊS’Ó#'OôæM/J,ÈP(3Ô3Ò32Ù,ä<Á ³�jà^xœ[4—iÑ\¦‡œôÍ2­,<Â
ËÊÍ½³C2|\ƒ³2LÊÝ�\-3‹Ë"rÌý#"Ó�SKrr&OðáM/J,ÈP(3Ô3Ò32Û,ä<Á w˜?ê‘sxœkŸÉø|ã†UúÜÁ%‰E%N9ùÉÙV›¯l’ ”¶
ìã'xœ{>�qßtÆ«ôCÜ“Kò‹*ÃŒ¬’ÓÒõà:\œœ0Ž1º¬1HÖ/±$³,$•…èl^®@ �* jã†WxœC ¼ÿ™	™	°46Éu1ÿxÞ0vÄvât]ÏmAžÔ100644 go.sum ËÎ…_ÆCöóNP¨¤Ð:–ÅÑŸ³j/ÁƒøãRxœC ¼ÿ™	™	°467¼lÐüg÷¥×³ÖÎ+ÿ A’÷B100644 go.sum 5 êË0¡žù¬=^ª@íJS? V³j/x—à„»VxœëÕíÕ�0ƒÑhòAná�kŠ 5µ•àxœëÕíÕ�0ƒÑxòAná�kŠ 5¾–á™kxœ[4—iÑ\¦‡œŒ2­*œC’}¼3‹]¼�’¼�,ÃMS<Ã<üK‚ô½Â+ÓKõCK*BMõ]'OôæM/J,ÈP(3Ô3Ò32Ú,ä<Á gkwá^xœ[4—iÑ\¦‡œŒ2­Ò�Ý|"¢r�B-K*Ó|ôÍôMC}]M½-‚-�‚ô3<õƒÊ´]#='OôæM/J,ÈP(3Ô3Ò32Þ,ä<Á :¸gã‰oxœC ¼ÿ™	™	°46&PÉˆË¿ÀÕeXŒs]©K•g]ó100644 go.sum ’PUž%o‘ß:øyþ}b¦(,
³j/¬ä£ãRxœC ¼ÿ™	™	°46©F‡n�•¯äî¤^eïkN~¶r100644 go.sum rU¯@¬¬5Ø÷ãâ†¾Üvñ³j/§4Fë„¾nxœëÕ}¯3A‹Ñd¢v“¡åäƒÜÂ×¬fŸ<‰]fóDŽRfF£Éy'kðð0è™N^È!99€GÈ6³x„�J¥€bl)‰%‰I‰Å©
e†z†–P@	æÌ”âÉzÜŠ“Ïs³Nææ5d4ÇkÁh6y"»4HÍä@qCo37Ÿ,Ðæ	"²“ÝD<�Ê&ˆ¨2š‚ø\Å•yÉ
ezF .gqe1ˆg–œ¬ ÊÁh<9PTxò{nî’üü¨ìæ QkF ”rBÅá€Rxœ{¯ó^gÂt&#ƒÉyD6®=$
 E¶[¶Ùx¬½×²ÛÈ²&|?Oqî«áÝ1 I€$G‚7ðÞ–àÓÿ	.©› Ô§{Ÿ™PKZÚ±#«*++Í—¤G¸eŽ„¥ùn
?Vqæ×^\ûn÷~ó_=öñ
ÿ‡?òÒû¯ûÿÊ Ù‘Ï~æ»­‚OÈ‘�°8¤ULt0‘¶ŒjÊÃ“Iyëÿ/òc�¢³Ò±3 ‹þ�ÿ�¿ÑÍ�A?'Êßú�êôÅs«É­•0Ý{.øL¶eØ¢ÿû9ö3Îþ(ë	ºÆŸˆÁ¯/Å	ÃQÅ‚ú¢qÇÐ À�z[(”õÆË×¥�œt¿¡ëš[]“«Øiƒµ«Õ¸pCÎ¥¬,†¸xS½ÖY.u±Ã÷µ(XŠE1ŠÄÑ/ŸÃÒfÏ	^\ºåD_¼õ´[“4³ª™’’z[4¾£u×É¤jòÿl‘·iõf‰/™š’ÒUÊª‹£h>­i¼µª�‹Z[¹žïÔòî—°Vd×ù™]xHWpMƒôøõ8p�x#ÊäãÁàb	!YÇ_ž•…ÚÃ²6Ë°Üë±Ïdö#¥y}¸ò@4+;ï�°„ÛõÈÓt'†ý�N2cmûcWi+lOVÎ™XLïÌbbìœÝx“p”p×Aq'nü7ÄÞ6ˆæ±“W{ÓÇÓI´Î?KôPèßíOgT‰“—ËñQ5~K™„½k}$÷[Û³[{Ú1÷½ßje$äY.?¹ßqÔÛ£JˆÉQâžÛÔ’<¡å:Ýp·æ?¢ý¶ý5ê\†+5èÅAñ½åþžÕ.7Ã¥ Éôý¼ÅF]ðÖ±Ä@ÜÃ$¶óéqbP‡±Do–é¸Í±Y“q|?ê”¶ê•ºÔ|kÛ¶)Fjïë»í?‘{Ûd-Ð
²u‰|LGŒËE_2$Jxu’…}y	¬jÉ×µóìÙßS­:§éœ—™k‘`10Ø– Nòø8³’M÷æµ×Œty26V%z”�IžêþžrÓ–õë9‚~¢¿oMAóSÊ»öî-õžOåè!µ¾��nç�Vž+B›Eü¨×³ÛšoŒ�i!LdEì«§¬ïFå¤"ƒ.p-¦Îž}µÖMâ�ðÔL¤KÏwü:|éxo½íÂo$´ë8øÖ{ïúTÁÅ1�<»ýáVŸT×q®yî‰Çú”*ÅŠ¾Ë×wáüAº*³1ÈÊÑƒ�~ùu]ÖY\´³AŒß6/)ªÚá‡§'Ç‚´Òúfí»=o=m¡×îž­ÔµÁÉû ›÷`—å®ÝÂ»¨Ãr¢ªõ�Zw|œ×–´�æY:Z»âÚˆµqNÁy5‚ªV;=¤<j~ÿGhÇI\üÑúödgÿÙ]ÓÖ�Û"=ù_=ùAß¾!)^’JÙò®�»F=Øøjwùše-/P>&ƒëé©€$ÿ†~Xf1HúHÅû¶-,×¢{Cìãæ²ùô¦§×E·Œ)Å»2ûFÂüÕÙÆysý¿ÿWƒn¬Ê?bØ®7Øµ×à…qßïí�ªðE]ò.njJ4¶Þž4q¶]S�¨}ß«&³¶�¤¹ÒÉÕ¨J_–?½ÞŠb(Ó<Ó­ï‘Õa·´Œ¢&àwý¶ÕOè3Ì½ý2õ¨(Û·Í½‘y;ê:ç�,É!
q÷üýBYÊéªZå,Ñ«™rWq%ìØ‡s>˜`àÞ¨qÓúîÄ:ôò�ž²G²�ÝbáY'#C>�gÉ¾gÉà­lÑ•Gq¯]�LšÔín$ŒÛ¨s^F€Ì}¿*‘Öí,‹‰{äÌÆ èeP“¸ÓœZÝšéKåPá~¥Qg$#�õˆÚÕá (�7Â¼×žd#âuÕPÖÞ´gìæmÏ{t•
þÉwv¤9ž¶KÑ«*'Î¨%ŸBÓdôi]Ð‚ËD¸˜wÒ^‚_1—­ý•Ç�¸}1¹h+ÞwÙÜšÜiOä$²âÇâäQÎY´OÝç£VX�Çïô³á¡ÅR”q¯Õ‡gûMüÝCYœÃ4-­ZÞ0ê¦w½‡—m™Þ²Ö^è—âÉL®1¢©Ï|)Ú²pJðç€laç b“�ÀÞø&‘�“�òq­PÍ=´›gÃ�ëGŒ©âa¿sPÇÚË~ymó¾ï?IqÿýJy\Ž|®„W–•sºD»ÌtãÅ¦�j·Ùí·=mâ¥u¶Ùí\Jø?)·~ÓÆÁøS‰Ñ3!´N!ª§ë?•F0$O6\„Æã|�K*æî•92ÅUr«ÕÇ…>»ÚGìéÏ¯ÆK¿‚²þ-?Âÿè–õ7�Ø?ðénjJÔáå3ÚeJ\ìKótå6j¬nˆ¥JÈ	[=îK×[Qðß¹óoy»‰óh6šv¤ÉžÒ¥¬­¢ýáŒ7ç‡Ož„sˆî0¯º¦P!(¤»VìùE·ãëz~8_r¤[ŠÝ8Ùõ¹l/ëÛò„þ¦aƒï+ÑphÍ\lÛƒ£ô?]èí`ez[ÜÕáë£—íU{-ÇÍ±;­õç)ÃNÃ€quÎnñËá_3Ìšý-uØ÷]q±,³Ìfb«
¬‹÷T–êª|zyá%zÎ®®^µ|*v"‡ÿãeÞumáŠ¸­rÅÛævË�Q:IF­ÆB&–g¤—]G8înÒ‡tÿ½düð6@ˆ‰¦£¿O&™íã�ãZ^ˆ7ë ¨x*<©%«‡h0Pe1¹¯^/ÿwk½¯ï)ÞCÕz[êX¶½‹pZ2°E²0Ù•Q—ñ#ß¯ô¥FýSFø’Çn]6eÐ~Ù]M2D\_`‹k»<»é�ä÷!Ë1<¡Yé†·�Ï(•¥\uŠÇ¹[:ìlåîö6#˜¾ŸßN“‹ý½âÛQ…%êTž©ŠŽ+Bs•ø;*ô�Âƒ«V­;í9–Ý�ŸsWèê¢Õº&Š‘¶Ì_¢øáa¤»@ºnE]CÚÕàR©›íuUD´KñìP†Â°·-³ØM7%Ä±ˆ;ýM?‚ŽÞMb?ñ‚³·T‡²Ö˜ Pô 3LNFµ]lx$B.gål¤¸x‘¸Cºç¹*\A�³*CäÙ´`1!p˜YÌ�ä/k[&ÆSHl¬ ØÖÖ”Âô®»ˆ;Ëk–r²Aãóq˜m•ØußÕ°¿ÉÅ0âÛ¿�­}Ùy–xºÅã(2åVÈØa{nž—S#®"<Š"Þé¹tÉ¶ÇW•d!µm••/C<$Â
ÌbšDÈÒ³iëÑ®F’˜½µT&wí’Ì�<íæÌ�mpvj0ö^ƒ4~Þû5ÒÃÖ	àÊ;¯Is1&ˆ¾[cƒ»UµLùv'¶,™Uu@Ks4�r|ˆ\ß.Uþù &¾qƒ'=¹¤ïˆÇè±§R”Ï�SráíÎ&ÔÝ®ÖÙq¶µkžÝV-VJË~®˜å¸®ýÇ©ð‘®¸w~=öþËë§f3h¸Û�ö#ì n£fµm ÎRNGÓH,OÜxmºÙ†7oWŒÊœú©òÅ÷W`Ï¯ÂÜß>s%îäjÿ\’†kÚ|ï‰-oª\*)|ÖæÃ°„,Ü¥{gÌå]©ËÐ¥Û^Fn€3ˆ[Û`ú'FÕhŽÜGžiŸÿŸÿøöG_à� )p$C`_àf1Ã¸4KLñÏOt*.­.(ÇÐNXéKÚêq2	‘_ñj'—c{[jWZ^ËçtþºaSà;ÉvœMûpë±jû¬¾<xCïlOåé\q”Zy¸©íš#4¢ÚúùèBÐþ&ú šP—n
¨CVÖIS‚;ü'h…¢J£(AãøkSh€R†zï²bjÅÜÉ³_VŽZ$Þ•]:Äi@5±pÕaáðHXô™%æ¯ÓÎ€ÕQ™ÛâF5üýó*ÉÙ]f‹2ºqõºu$Ÿq·—…uO’Ówæ!‘«­#²:Û™³í�üÄ¶	Cüjº¼«WxCÌáY]‹�„¡¨‘ÙÛð²6N›ÍA±i¤æä¶ŒC/S¨ˆØ#s�çm‘œñ
ÜçoÄ
¢§·«GÃ1FŸç"éÄ”}Ôéqoç»r4J‚Å7ê�áuµoË�nÿ–|WÄíšü�&’(Àz 'r$ö…„g»íR¸ó¶jÐ?ýÐ û#±Áî²¾`‡UþLxwu:ºa‰#ÚÑÇÀ3NâÜŽÁ¡i�\ …�ú^íC8ü‡£ßèg„ºO:dû<ÉçMÞ¬ûë�ÞåÕs³¥{<|¸Æ|3×Itî°¿Ñ¯jßÎÀÔN:|Â7â�Np»Ð'œsðL¡È«"4‚ÞpÎÒÙß�Ò¹PFhË8œÉ³�~PÛ Ö@5ÇòåMôçXAE\cJžz¼PÇ„&W÷äpy2×®Þ<©¹Ó#Ñ9¾²SºOæ a°‡v¿¾ã�é±`ïF¥XÚ¥ŒÍ¥ÚîäôàO?�º"¬œcå•î½Ö¦EÜµøÙoÚSã7Í+ ›°èw¾`��R_ËbÝ^™aÔïìjl‹†Rðèø,/cn[{Ÿ“›£øÉ—xì
¶ìØŽýBí¾ `�Œù{L÷û‚Ó§Xc8g÷²­ÆÄR’Ýsq,#ÊQ;F+òÑKYm¿¤nt‡-U�G�@ü_­ñvÍ¢þ=ÿlæ*NŠ[tE_/ÆIÞ»irõ#IB¹ÀËÅŠçæF@âÀú¨ÌrÄ™ÅÓþçV ~ê«´/ý¶NV×Û&í4Œ>*aèqØ…H­W²3VìýÇ1Þh»vãÚ^ìÄ�wµÇ„­šõ>%>%Ü†W‚î.Â•bOž Á�ýRt³ÿ0‘vÙly(øœØ‹!æû~^s�„ŸWéA´nrãC„iç×úÂá2QÙ~äÙ>¹Y®–§Ûòƒ5-�Ãó}úõOúsöØkIG¸;¡±«�¿¦˜%Ë>q›~Œ^>Ø…×­t	KTþ`ÏÐ öÐ¼Ä¤ç[rÈ`¯—P)ÙƒÑ˜†)†'RŒ)a«-yïÛU"í—à3&2þK¢o²’�¢®Y×7Œ*ÖC‘9ççíŽÉÂfo(ç“‘%ƒŸ…z�Í­:lv¾áé_H5™U0yˆßCP š@Àé–™?Øéø‚,°¤%…‘Vº2£4	€´A ÛLÿØK½Xv´‰�ÿ—‹½0F+â¾>"&žËOþ±*Çgr]óR²×pã´¿è�_è–E»¤ø÷yânA“á°Ú¯Ë}1êç&ÕÒŽÁ=~Ãý–jLº>ö¡&Wò‡¾ûÚoÛ—·øµç÷ULÄ¿{\±5t�6ãZxVHR\ëäÑm†ð·_û/´à•�ËÀ}ŸaWá;kƒeªNì£+™7ÑFéM«Q©û]õ]"’úµ]­R�û8¿
€ûëo	n×výh�´è±z;Û'±*ÕL)}v»o¯qµþAˆ‘À·Û	äñ]‰s8œ†ýc&Û¢Òh²ClÕ
{­x±�àžOØ.;?–²~áŠ*£gµù/óv¨„K8e”#øHyïË­¿^Š»Döž×ÒF¼À|ç3•…iJüÊ¼Ÿ‡j$·Èt|‚`_/‡ë/×Mkû òbtûçcOånuU–K"êÃ£¸¹§áú<¤ú�z;X³ïŒÛB½Û9€ï³Õ}`è¡ÝÝž°È:�hÂütø§õ~b9à	OÏ§)»ÚýÎ32€MW¦†!=ôIÒ^Yç¸qû˜;×Ëà®Qï%Ð{y»]žˆ9æ!Ý¯gûï×z;�¤;±i‘"Gw¼—E9^u¾±EÏê®7ž­<Uäæˆë³¤_x€¼íÏô*óãtÊ:”ÌV)ú¨WJ-„wmMEk¤$™mÂÝSD[ßDÇ8ùÜ?‰ÈŸ§ûÝRo‡«Ñs¡¨¼•YÄÓ…”�2\³Ã’\.Ò{Á —íf(ž|ÐÐÕ?i¥?WÏozeìäta—}g?‚Åâº¿d›KÇ­—ŽœE6{9­¥=ëÛ='÷ˆäÿZ8þZàí ·ËÈ$t�õ)WT¿CcMêÕ8n|ÒÚRZª§Í^¿Ñ—î_ßRO¾¼½ \_ÆØG”ÇJÝzÑ1 WO5E³>1Owq×‹{ì­j>žÞö/
üOV½­ðv”§‡ì ŽBaJ)0¢k�™ãán«·AÉoCH’» ÷4–\åÄ?©%ð`ûØõ‘?´]×¯Ú/¿€<$¸º/mHüñR†Ôºˆ÷Ñáæ“�ç�±�A,ñ60ŽdqÙÞ³ƒßu’vŠ¨ÿ¿ZõíÈ´¸5Í™¸ß´bëúÊåÐ»¥7êu÷tu³·*ž÷¬+»ø€P}c¿ùxBÜ9�•û (Ê�
!&¿:kˆAw×Õ=S 0V|¼êžÍìïÁcq›ª,ožÝ/Wù�Ë½r¹îÌ‚ºÜOrœYÙ¢u\÷Î.Žxì/G44âÔªÚÇã¼*„q¤Ñ|ï««¿‘pð•_
Ó%0Q‰G¸ÝÉÚ’ÕèhmÞ»r{#Ee:×¶øbó0îŸ�úûEß\Ó‹*À‰²[
ÆŠÆÃ¾?ožSYâ¸jýh¼¹¹Bíœ˜þ“:û…ÍN6ø/	æ~œõ–õõÑÝh¢x1T�µû.8ß¹Pl"ßsÅQ]:Û´þcÿ²ÞÛ1ÝžÐ�‘Y7Í^EOÈãqØ™jàá¥ýÔŠ[‘¾ÐnkÁ?)†ŸÇl mieþxIëfx\tG¯·›œº[‹%¿îj´AÀ@ºÄ}è—æ˜Š¤ªèå¿½Â¿è¿£+y’Ð<q°]ß-D-I–½å³¶%hÌº0(é†X{ùßroÉø•ÛE¿+QÌ:ZõçØAb4{ô5ñ¼gÆpv
Ty5®¸Á’°>ÐºÅ¿Õ1³%Þ“¢8·0XÍ"P„@j»!%D,zÄaÙ#™óA”]ù9ù¯Ó¼@?
�&ÒÓëXî£µ%”^I íìdåËmí†£¢ÄÂnüÚ¹ÿ“]ûóÒÿZàí cðM(<ÂÝ¥ ÂðÁ <ÑÕWœ?’œE5;}…g1îÂ¸÷·ï·iÊ2ö^ˆAý�½¬4š8;¿[o²a¿ã”àÚVšè’²¸¸l/'ÌVèV¿IâÀ®Ù*oGjtI�’*²Öä)Ø~«ZË»Q;à×©�wP½¿QÃð¯åyBæ }i²én¨@LTD/µÓSÀOgäz¿T7!?ö¹‚T;,9]¬L\íôDý×ò¯ÞrÅÕí¬­�ýØ0Å®0ïVvÙªižœð1ÚiÃéH¡õë:MÅ# ?ÓÆñ‰/ñÉN‡ ÷­ú¼HÃ…Ê^3’óçzeçt~Ã¢g>\Åâ[‚Ëþ…àÛF­5®FÞ¶[9<µ¦Ýsc‰Û×b¡ÇëÓÑ7.ËûãÊÅ˜8wŽ¿¬©ü:}1"Œ‰¿fæì÷Å10!ñôï¥MºŸhÇª‰K¸¿=Nì£QsšKËoÈ½mRÂoµy1ð‹sÑ¹a¸“HT;{-¶VµÝ��hG×YZÍ%Ý\öà¨/H•–yƒLÿþNz [ßkU˜èÄH.$±þ´róK¿—UžŽ¹ÓÓ6Âœ·—Jyl<ï¢B­ÓBï¤±Ÿ9€6‚û¸]õº±Y±JºÑž;£Ýí~âWPxÒ»âîÜVHÚhÅØª²Ï>Û‡ÄŠÝ\¢�,~>G†EzûU“‘ù/èŸ�Ž&§?Ï728ì°•Vƒ2–Û0¶\èý>å+oã�k-Uf®iœ2wš8/dh²åTÌŒrg"ZHƒ {j¼i­£4fÆ/�QÈº;ÔKazlWsqû LýWO�»ÿ’òÎéÐtY˜ÞòÀù&àÂAi—')«Ÿi
ê…Z1U³Ð†Ô~žÁz±¥S„˜ñy«AÉ`ËžŠD h&¡Ž�]KRxBÚGiÏ´IœIUÖÒç0¾ó"p.Pý™_™'9lœ8õ*}îÝŒYIÈ�,Q?£•Oö…­ú¥rÝ˜Ï8ƒ˜ÇANë6]•¢ðƒ7ýÑµ?`Þ‘zÎÇüÁnªîpŠFYêm~öõs°Î÷ç]r“Höel¼k»gÙ#�ã%ó}ß«ë`V“
»cw=[Ûá‚ý5~êžÇ·Í!zló4—é„sÈ–/ø`ž:å×q‚ÞXKæö�Ï	·TÓ:�»3DA�ê¼>‡qd'P>2§;@¡Ž_OY�¯jÆ`ßßÉ2|v‘�±-·7[bˆÊ¤œx�Y�?ÖÏU)'ÌQG™óS�Ä8~$Aå€_Øî%”“V
šª†Øfþ{S& Ý¥&Fn]Pu]C_i7îOº¼>øÉ”´�*)r7·™k<×-“Òµkäùp{§ø¡Ÿç5o„ô�®‹z“w¾nÈ}NÅ]ÎÄ5Àõ9ü�>,ñ¤í[ÙsYqý"µ³6F þOË ê=¡ÊýlC4–WPádþ¤^Ûc9è×2Æ1€Ê÷À²´lo5BuõÖ~‹Pob´6”ÃüNÿý7q¨ê«-yÕÔ;5“rÞØåh’öÊ0ÍôzÞívÄñz�kq@d*p½ÁÏ� Jÿ9+J2N»7"$: ín•!ãèòÊ	ÎñVpCì?Yó{šoÛ5¥+º:g:½Ä_òþmÕF’‚Øžn*u=ˆRèÖ	 ?3‰œŠÚÁÔúP¹d¨;y„ßÖYÒèAÅ7;~¨ÇŒu[G^®ÖŠDµ,Eëu4ÏëÂÙ®eüúÁö©j*Y\?ƒ<Ý+‡Ð6ÔNþ¨â|¯œGž:)DÕŠ²f¾ŸÏwÈQ-Ìv³|v‹g}n[ìÉ-—«äÜcxUæÿ¹F—A‚­�KXï-S<µP�áõ‰p–$)Ÿ  íâ¯¸E§k\zn‚t<ŸÍ¥vàj<ÊT«·ÙFzÞ¤ƒ-†•¥Ýæ¯z¾ü±‹0xqóÂJî.U’ç‚å—§czŒ‹Ò˜¬ËîXÒæW|·á¿;Ý™Š/&òó&�Td—¥zÞáR7¡9T™ò6­¯µŠ^¤ÒÁ_ÞŸÅ9Ÿ›•v”àÏÀþg:£˜ Ðf=<)§qÜ«;®\ð�n ýžºlvxÜoü®E÷Ã/ý\ J:¼rx-0?Â^¡
øöô¡¥€!�g±@�R&È¦Ü¨u¾Õk]äèˆ¡æ–*ˆŸtáØ�SCúæ7	ô³éWããS£Ïiµ¾ƒëaUÇ
RÜ.uaŠK„4´¶Ùîv´§jf¿~!^ÜT™=±×F?î÷Ýª;i-Ó€Å$v3š÷|/=¥VÄ.:f„§Ý):•à—Zû}€qî‡Ö˜»#kq‡ë;Y5ì*[=6û»óëhÙž•Þî´¡£‘cù®4?ÒÔd“NrãÚýQ~õ
�9I9EgIää¥eø×Q¶²»®Ò6êu¨"�p¸öFØšn8wù^éÙ_I¾=%Ü¿*ôRÝä	±¢ˆžE¿/v‹î¹.õ3�˜û�Z°˜kÚ
=êÒv#Ï™*qËúåXcó³Ö?ëx(|rnìð<ºˆ¡ì‰‚cè8Y
‰u.¿˜¹cæ¾âàUWôg¹‹ÑÐòDâÔ—Ky¨Õ¼tà¾_/‡Œ„[Ü(io§	²!G®æ$ ^l;„ÚFÙí,(røàÿÛªP‡Ùþ—ìÿ\xRH8�Ó$þå8æ8	u,ïÞ‘<0›û¾ÆS¯íhÛ’
¢U Foc…8+Wš8x{ä	F9ÞÎ½Å÷ãV¾3™O¦ úvO3œ8?Ã¬u>R±Z“=¶Ë¸?¯Âr9
!å’¦Šø§ËßÓœÉv_eT`¾ÑÌ£“¹\¸ˆ£<Þ÷XJð|¬=¢>
öxyºÝðl¿ðª^ýûkjËÔ/œÎMçºî‰Ãh!#¾Hœf9‡†ò&ç½¸„)\Ò.RÒUÆQ|‹ºÇG‚çkÂ<ËJZø÷Fcô’Ú~([è@©‘Áw¸¶ï8	òaï‰õÃÑô�õõ||êiX¯ˆŽ_dT¥ÜûáË}¹<ü°´eÑøE3B¹Va×éŸ•R ÈçÎ™*\j-Û•#©4§ª&�`ÌeÄ¾Xç@�G¾¸o:]ûpÎ¦b°dá¾ü6úJŸ/{^™·ã�Žj.Á³ÐNNÕ†�Íá»;[ëÍPÐvt¾¬»ø°9%KãÃÎuÐàÒqëF}ÈH7À4©Â¹{\ðäÒôö~Ø‘†ÝÛvéîM4Ù*CÖ	§þ¦,¼f'ñì‡^+ð ºË|pø¢¯Æw!;™@.èÛÞ½‡«¶"U—äzº~\±Ö´­>$éÁp•þ8WûÓîhm6Ù\ÃxàÄÄ`$ài»ßVþ7©e1übéÝA´8�Ö{(¼îùñî÷G´²	íJ@†ujó#�áÙ=8QÍËåÀš|#™l"z}Š�bºÆ…¸$ûMÜÄ%´ÁYÃ½ÚíŠÅ'!Z�ó�Hºá‹×Måß¯÷]%í‹µÜ~Œ£&ÌÍÉ{"ã‡iR Và­Ù.\jCÌùü[ÂïñÁÿSÂø×$â‹²ÐbÊA™+ûå±ÃÑv@®;ùôwW´-®l/š†™Cy=ëO\FN–2éïÚC¼øÅ}çusÝúÛƒüÃzoó?>¦?%èÏ…úNøÃw¡Ö­pŠN±H„¡/¨M5EùtIäQY—W½‘á
>­�ˆö¹1î${8Ó—á>W¯Þ¿Yàí ·ëÑz4w°Ëº§�H}Ô¢ÄzÊÑ˜èÁ†ârk�VžˆÈ¹ëUqóÕB®ËŽ¡@(~E¾pzos¥‘Ë> ÜÎ¥.Sú’áFsKÆê9Ì‡3a„—Œ4±&ÙœöÂÚ®¢/èû3àýáÞüèàÚ"Ê�‡$ò\s…ŸMŒÃ�¤~<E°ù�ÃU§N�#zÍæÕ¶ÿ-á7Þ û¦¾¶×¾èÑõ½€úô!Ës'°£ y°ñL_îñP*¹9¸ ôÇ&�'žÂLùi³¡¶‘yF¡{™ 1Ò£Ÿd<{ºq{´Íl± ud‚i1,”‡sÑNØR.RW!â›øù$ÿãÕÞŽ¨Ý�ú¤E#&A~Uå£2:®K¨œïÄ«ð…†Çúô2üÂ©ý@ü¥`æ®Öj“ZOTà%°é¡,<9°ã>ßTŽ=wgÝ"W!x/úÈSEñóå{€�›x$=ÓÈbtÊ<FëtØ Š¹“ç"¸bu$Ô*SAM¾J+ù!JGô�r‡=·€hf'"á;ùU¿Eï+Œ½�îù±ÈÅ1�¨@È>z>@=•ÝÄ.Ðú«æ�0ÛV’4,‹µ!kZ€d¶IŸêK­Î‘‘QIŽˆò‘/výGûXa¿ö»q'cü5ž?ä²95nÔNíÉ¨�«Q[í|ä�g´‘CÜX9ˆ± òÅõ8¿Ã?éƒœ:q£à*ƒøñ&¯
ZC1’¢¾Î‡Ö{—"YûÝ]Úv‡œÜ6J\]÷"³¼P‡g@³ÊÖ¹QcÆˆ¢Ç�²FKŽŸß3àW™?Õ±B!â+ìú;É7ël¥ÙW†Þê>�˜s”ã‚<\´Ý‘ºq(w©ë6OqBàGß`ÁK(ç]¹Ïc
/½:æ¬¥F“ÆåÍºkp‰ÒÝNXÔô™3;9FóýM6´?ŠnçÛÍ…�1a±ÉÕ}½ÏÎç,_Ö¥tQÐŠÎ±g­†Dlþ–qsŸ"ð³øú¯V‹¦€.¾iÓ€ŠMÂ~u¿%˜s s—kî~y…6GSBüBHJ¯Xd­iøeîÿÍ7‘ÌÙ½²ƒöw,ÜP;nÝÃ8¯exvY`œm€ÚA›}nð¹æ /•5;åÔê8PzúŠ´ Ñâm•=Æ´—n«^¹D  ùjÀ¬Øˆ	ìp§‹ìZ^Ë¹ù
€	S?"òó‡‰óh–D…Ù$.t·ú>.ìd1®·ÄQÐá²õÝpª¤>žæ¼e¨zzÚH •£_¡UžPÃñ]àEÎ�®þnÜœ-†w#·MÕ3EžÉŠâ‹øÜh±qg!É{ÜåüCôžñ^ÉŒéÍ[•ê«2lPd•�ª¸Û(�A#‰›B¦ÔÉ^6Ú‡ôPÎÉ†>¤] ž€á¯¿^Ð)ˆ˜”7†CgÚ²Œˆ®Ï“óÁ¢ïM‰q+l¡£Ô%’ML,(¥¡æ±E• o¡K µ¶„µ Å™öeïäÅdÛQ”+‰P½Žå1©ˆÀá-È¥dG�$ƒâ´êæŽ?¨š·¦ßý+�îþ$Û‘�Éê5P‹»‘k»„ê=Ö?
W0öÅs¨ÙœÊ	&[DóKü½7.¨‹Ö² `¼3#y”J{7¼P \æ®Hž©~èZ³9-?tøDÀÌ—ÿñÓÄlÞ:ýŠ6¸ÐšÅ{L„�î«g~¤#áŠìÍzŸQ]…X¸Ó<Bsu�Cˆ‚!46 {an
�2øôýEØ„Kc$æÌû;²¢ß™�k�¥ûp[a,Ï^?(1Ï¬ô=“Ø¶¸ôª²æÀÏ%'ÍžAÏö7óç~Y”û�¢<65®íl×ÑÌÉ>>)^ÛYÄ¢Ù„¢Z-Ûj1éÜnüE{:Õ7åœEÔeÎÜ·ÅÏi*:nÝ/V«Uaø™7ÅÕJº'S§õÜNi(B4îUéFÏ¾0.y{ÀÙî´ÝJèšPvíMYØK±©Ä½xjÃ¤Z0Éfni€( '52ý1©“9v˜xgaTœ®Q¶=Ž<óÌ×8!Ù™þ�N;TÊµÑÜó‰|šOª *“`/“b•mîlœôšäpèÛ;¥tZ¢:»\Ù§>Ý,·!{ë_Êl“sÉãt ˜¤çXëq(nkvh©\¨Ýv#Ç9ztðÍAªó3‰îû_øù¢
Í‡?�þz}Úå±)ïzxÁ5ü.¹èAç·Mìo”µ·ÔH'dq“�Äž[–Ÿg~#÷öøÀ¡AúV8Qžåø‘º.ý‘¾ûñµ.O–ËÞ_Ují“æ~n\�*4x–uŒxå»³L êNa$A À£…hÎ£ÒÇ&?ëgçYi®–qOvòÁ4´íAP‡:_.­¯ÒÍjËˆ{x=#ú(L‚%[»I‘&‹ó¯)×þhDœ+ÕK²a$U=qN/ÒuÜ�Õ^�ïKos‹ùç&ýdµùðm&òe™½* Ÿ¦çÊõ®SïžÛAp*Iþ¸RÎAF\Âà2™Å­#ózñP†ÛV os#üõ²úî=Ÿ„è*Ó¡]`¬Éc:âÙÆz³]+¾°|ŒµÛaûÜXtSkþ—×=Û»ï¾{Àç�ËoÄ"ÕÄV´6Sš’ìÙC·K¼]lˆf¡u�ÑEïu½úE"gäƒü7œ9Õî!S‡]vñV¨Ö¡KäÒV2oF’e“&È<º�fÛOÎ¼¼©y÷ªcQzrQìîöFÉ×Œ¸-K#Ðj%7ª¹]A£þòÃ
¿_)8‚�©›ÜÖ	}¿S&m¤‘bƒÆ>§Ò9³Ø‹Y
âq\+²Ôíâ«ƒRÑÈLÐ~1±eêäûÍÖs;,˜gâ5òcÅ[ô6žâ«ý¼_6" 7ø¹4ŽWúRPÓ^}»°SáþwcÎÜ"@)û†vûñ.ð¸~£U³_I+=+<ët£kICõ¶}H¿ZåGžÁU"ß}óåÈ²�åúô|úOa�¬GJ-a<�×�Ûøb©
½,4qk²ü§½wh£œ˜Myžìöˆõ“| tÆ9· ý­=Rë£A~{*a¡;Š»Ov|ª×A‹|�öwO9´a¼ã´Wh;o˜pÏòºs!oŠ´]§¶0¡¦hÅ“iNd©¨ÆØGy%Ä¥ûê â?|ù`tÞ´VlV’wÀvË8=n ?ðù‚ØD><RÒÙ"îÃ„À‰%sç8„äç” ÿþó»szþ*ÅÍ©qñ•Ãl¢MÕ¬igï²«ibÕ1unëª½‘IÛÆL>C~Rn ‡›¿Š³™Y�G®”ŒuGVœ/xÇÝ»EñìãÍóß˜Ò;èª)#gžûIw*ÞøY“?ÏùKÒZÝ„%Ô“Ž4ÖÚ÷�Çñ¹JN½.ÞP7¹yH~Ùq¥šÐaù*gÁæ0ròðÇ‘S1»“)�ÞY+!Zµ‹5®/Kf•Ú½!•0f•ÅmŽj…%´¬½<a§ƒ2‚ÏÎ8»Æ§á-‡íÞX�-edð{°ÆÍ·«®4¼¡¹ãÍd?_w8µCS÷,¡5 ›‰RÌ—Ã�áÛ~à°Á›¨(P˜q§Z+]^ð3=Ö*nª›ÌÙT7D%æ>0})2}†}²¦Í…_ÉÐ"ÓïŸ¾÷DI-È4®ô‹è3í3ûãk‹d/šÛ(öœ+`wà|Âú Q`ÈÓoi¾m7x,ÕÃE>‘D¦ºlm.}�îª,`’Ñ…¯›»½6Ö6å|º i$¬Ë®r!‡÷~`f
Év'À È)ËÅ:�Ï}cfå–z,ïÍ²¤h2cã£±XåW�¼±ïÎGÒÍ©¤µîÿ÷ë½ÓÝ†¤_wz}ÅV‹�eƒ§z5¨“wlyÁmÝGc`¤ø´˜Í§Wþ:æ»€Qßnã ×½i„ŒÑÔˆÙ&í^Š½/[t‚�-žüqÈ$,Ï?=²ßQ|ÛêÕ<{„yäÅî¾"J	7|<®Èà$épõ®+/îŽh¯6ù§�~n
»úö.æžr}8A.‡löðƒ½Ìl¯K"ŽÂ8ªë2¸½TŸëK�+øçK˜Èº1xEnµhÐ»>)È¹¢ ‡ä·…s<­ïG:v„ÇyªhCf¥å±Ô0š±÷¿!…‚�¬†Ï§¤QÅP–d uu93é”a×›ŽÆÀÝ ™Åæñô’­q}¶ô¸±ìr®™uïvìý� ÁyÀš¼J¦‚¯ +Ü¯ïAPAÎÔèº¡I¹‚i@N„BN74¹ÆÂ"�/1 {îT#ä¹wÍG�þzy¯U^þÒŸ'£à\4†‘4�~y4îp¨O3Ø|8C¿ã %‹^;w›æCô\® î/ÀÖÚR‡’Çd%oËãã£Wøûµ¿Öü¾²oÃöí6@¸÷Î½²ºo6ÝÖÕ%\]�vú¼ÈYbB±âìà&ÛååÔ%5‡Ÿ†ø‡4Lóã 1üe+ç²Àß’«‡­³‘«rˆÚBÊn¿<B641óo¦¹)`Ž!ôÅÍÐž?·ŸÇ¤À²—<Ìk)/·q„Ì=7ÌMC¸`Ü,ãý1¸wgwxþšãEówo8Se]_ œv™IÍpšåöBOæö[œ%½Ù=¿í•¾´�²YH”_ÞhÛX«ïzc”:óÓÿÁžÚïÃzr	Ñ�rPƒT‘†ó²ë­	Ùn—Ã@)©ÎJ£ÆÈs_elã±rÝmØO»ü“2ÔÓ¼Ê7<Ä¿^ +Æ2áR�qsXÒäà°ýz5KáèVjí–­5É-¶2±Öu1`ÎÞŽ@yßZz$qÜÿÍÒ�@	CH
O3Þš‚=JAŽþ‹a¦8úo›uA
T©rw´Z@‘i9úb|V0ØÁÖj¦8#ÖÈß<"xŸeÐß+áúãÀ°*ÀÜ`fPôËÆpÎ¦}z2ÈoëF‹á6CßAÃ¢‚×Cæì<U¹ÁTD['hD
¥°ð¸x~*ýiB+”°Öþ�€áÝ1¾Ÿ”HG=ýèŸ&}C²H½¢ÖÕ°ƒP9ˆF´orÆÐ˜ô‹Çö¢
¥4-¸*�?•»àÐu u�Ü/…�‘-›øNUwKã8vã_�
½r†-ë{Z[•0¯÷Pà‹•sødÙ¿\ä�Mï›Ý¾‰.=Ùójy³Û'�ggt÷0Ô ÔâyôwûÝÒþÔ¯µ@�ºùKÒ© Ü³žîYÂÓ­‹ô»>[4eâ¸ç¤W§ÖtêHxŸ
úƒ,`J2¤,Œ(YëMOî,(œÛ6mUÇBc´]¬7£0:fwü‡mÎ1ªêßóBã9ü´Y¯mns=àrÍd·{.sÉ<2¹G$þŒélJÆŸß Õ{Xé	*Tº‡ÂòÜRR)‰*ˆ3¥Å³’AyÏÖË™¦ÖŸªêE¶�ã5õÏçM@;ä$íT8LÀyß	u¢3ËVÖeí›”xÛ1`B:j>–×FžÛwÎwØæú©¬þ£¥Þ$F†¬/hI”o�>!bÛ–ó¬ÿØnÄÞ¸·‡­ºôNÒñz�?!³×Š0ÍR�%þ6M$¾Ñ>]}ÙlªÍ�8š	ãò§Ì?œ†]àž±†èŒÝyY×«}T)}�UFÜþ·Cb³=™ÐEa/Wòé`awÛ¶¥vgƒr³¼Ö<TÑ—ÈgèöÚëŒÞÛ6)tªdŠ%Çì"t0¹Z#y7ˆL«éÊ»)c¸#TVDÏÞo™Þuñ«aÓ&�ÛÞÞ­äS“ 0U`	4Å?GlÖ‹èX;�w^p¤øi3_›|£ö¶E};*ÐÃ*‡÷>ð! }O.%©ZÈá~òí“¾(¢‡±7¿;¹]Åé¿/	&­¹ ~ã`XçƒßT> Àzå¦í›±O�N¿îu†R‹ø0‰D	!` µ-B]ß�½Úçßi«»ÒÛÑÌƒkšÐ@W‰r`Ó;qNPZ¡nñsa‹cáa«er£ñ…ù;~½íÊÏô,åÓAøE¸Üí=5¸_Óz!lU©½bçxÐ¸5¹‘(ÚoEÈø.Ó=üžîÛ¶!óµ½×[Êw0K’Í]ÐA¹Ké¶á
33[ú,}êšÚ+‹4ƒ‰À€´ûAðW1ÖMÐ·Õ˜ÃEó£öl‘‹võ‘…É'6Åõù­¢,îð)•5$lx …³–^éA0Mo[oÙkmT.¹7¯€UHãŽDÜu1œcûQ_‘è‰Ýz„9ZÁ>=üoÊyYê©þÐê�p ¹ö:„µh]¢{}kã¸¶8¼c1M‰:¦�Qöç5 M|þ¤ö¶E~p[’_ß=—ÇÖ•]tŸ¡å–¦MB å¾¢‚ü¢Åòï·XW¯N@°Ç“(ŒËÅA6)èáhÃÊì�'õ€<®dÒë:ÄÃjÜ"Èà‹Ìžûô–¾·øµ·-vÇêG|&R˜#ÛéV¨è,õ½ègªÞSY«Z5�~OtV«ƒì¾³R|ƒùS8‡2_>J“�ûÐôJIï|mMWÐéCé›ÿôÌê^CºÊ··±½‹²°<µÒ%ã4õþ-÷º?IÄ¶âÛQkr`ÐG~Yž÷yŒ�Žr¿µ…ªõâ¾S•±lNÍ•LZŸ°mÓBE€Ó<²±ôó‡ïv"rËïrÙãØšˆ,‚¹ìeI+œfÀ5¤Løêxqè%4|ç×ÉBÞüôñ>–�Æ“ÔÝ$¤óxÆ¤}‡«eÏIGóîÐ0ùè¹6®Dr=˜{„÷øÅ¶æeíÞ¯ÿa±äúR¯÷‡/;½2+vÿ\sP\zýØ¼jb�“,¹`_’ú„rÝhôöðù*>ÎC»~ìš‡5<Æ›¨´ˆ‘µ£òÀXû'ß?Xµ^7èÚ–\6cn/ñ¨U>î*í=ÐüÈ4ÀiH2q	<“é�t›ÈŽÂþ>\“¾ÜŒ¯3à§�ýpÒÙ½ PjÑ—Ðo1°¿¡ù&B™¾Ï%haéë¸,»ÑxRÜÉÂ°ÜÅç\
\W¼çØ®çÚ8²è³ÈìW5e'õ+7	nÆ´_¯ÖùVi·ÝùZ2IÐTÐF|­E®-·0G½ˆWäÕÇ”Í¿%ú¶á“FÝGë 8¨z}îÉfC9w¦.Atre�ß‡Ù*uõGøQ74£�tÔ¬zöøÒÂ?¸ÌCÿ3lhvzf{ÕÔ�Ë-W8Ç¶ñ¬„Ã3¢¢E¨jnðþ{Êo[Gw'ëNÂØì&•.Ê²©:Èn[%Ì~?®†¬m,Ýžž4—r°ÏÒ†z�©óà…Ì²+2wË|ªtéÓŠÚ7cf¯ò´ÙÐv“D¼Ž—¨ÂöÇ-¡‡Žõ¹ïo²Yû*n[4Ý]r�Y…Ú&ÄÔ¶‡"x¿×vÈ…ÊFe¨ÙÖuÕ=ÆñÔýÂ…Ojo'¯a®Øj<Çå-¤“dî¾>wt§1RQFè#Ë±hiPõ�šðÏ“gO(ò"§	ýðØ^"vLö˜‘öÃ¡²¢’Œ%¥•ŠåÐ¹Gwéâáš½“s÷ø÷ß¶ŠÄWZ—6Ž<sr*çýf¿ßdcYm
”V“õ…žDDÃÖ¹ùøI	�ÜtQ0igâ©Òe¢¼5M§<‘C.hû,AÊ…Î·\‘<pòÄ2{ÊÄæ>ÉßÑ|Û®�^¼u)ŠÇ�Ì4n©ŽÍµm.§{›>“mŒKa¿X²ÇæÉ.æPøOÒ} O ’ß—ï×W»eÆMÝ.‚nX°×cÕd�··u*)×£ä(„"�á÷ý‹ÚÛYp±¡Dä&{:Ž’‡.˜üM¨ñä_å6ÒÓˆ_ªÛdžåú¹E( š¶8×ã„´Hña„	÷Ø�y4Ã}”‹vhnýcŒí•1—óÌù˜Ì45Æ0l½‚~ÅyÞµ È ¦‚¡�ŸŽêìuE¡w?\y2 �ÛD5ãFÆt˜Š{Ú™Gõ¾¿%‹ÇJéœQ>&"Ì\
Ô¡êþóƒôÖ‘»4Ø½O’Ã -…r=æF÷06ÊÝ µ«ì)»x}ÛæÍux§>�V_YÝýê½b2{á4gªî¥CòÜtÌ5åS¹n]íÍi2ëÆÿ”ã¿ô+å·k=û¢Ç•U‘óô
Œ©	×ØZ)Fc ãÈÍ¯JàJÕÇ<â¿¶}7O�ú}#»¦Æà�.è‹È+? |æ Ê.¿ÆÙ©£Á6 hPÇÑËzîaFþ£„é°aÙúp€¦~×É~¾£J•v1(™ßãê¾n‚n?—Ún„¹ÎãrÀ­(ÄÄû‡?Ç`êdE·HýpP”>†B°éaÛÜ:”m‰ñ¼�`Úí@=·çqq½ÛÔ²tòëC"å2¢Ï�ýï)¾sá$ky§�cË)4ä¿¶0©Á7¡ÐÃ!<)3–W?VM²™¹Âœš{‹&µíOr�äÓohâûMâ¼¯D�|ÉB2™8]Šey¸x?ÍšçÈ1`0€�¦œy“ØnéÀt‚�Ï]ÁDd‡’X˜]ù (J°�Þ{Ú.p«¡¾¤ëÅH€þNY@î}q<8å¥éÈ¹0-dŸ(sû—Ø™³F�=_?~œôüš>³E¡ô™±Ô”Æp “%l‡f –|ºž¬ßvP)�PÿD¨™ß/\³ÈšrýP‰Ù]ÌÀlÉÝF™_øÿhÁ·Û»Üabž›„»¬žš¦l©} ¸„5Å¡÷U`Ø.¹úÇÇ/ ©wBûJ@oX,F¡@S(IaÐî�‘¾�Ò�csÜÛ²0/¿$bº¾>öð5«ÐÙ#4ð�óÍR]bÛ†yàbÝÏã�Ä‡NL·�\~øÊt0ühBúÁaZêp”Ä™ûòð€HŽ$(÷½ÉG�'èCqNeê…-iØ�[¼ÆÊÕ6W´¥µ­ç¦‚•êÇØµ$|é¯ajTˆ‚ä¾bh®×Ði1ôX&žb£F·Ùñ¨cE�Óv%°Þôš‰EõºY}ˆMÍïp$èõþ«›žµëÙë2Laf£P;V3^è‘ÒÃUGË >7�ÀÔÞkM=·w	|›†gÛõÖ¿|õ·»Ø!t¸ßsÉ#[»ñ„ræUhðMÜÝ˜±ÖJ¯”�®ü#ƒ?Í‹ø‚	��/k 7)øõŽÞùzÐîŒ³}Ä²µ£â,…K%ç~Wn`³^DGIø¸fpz¡aÎ�rh¸}ÕVCbù•šyM~ß¼J_Ú�³À†ÏB7‡E¸ð%¼>v{ã]R5¡šw¹ä÷ÞÜbƒ[ý„¹—UYCÙ¯6»Ò£¬¥Xn],­V.ò(W-¹Ã§o–ÔÊi¿¸Èâïæ±Wj×vn×Ý—�A?”±W#Ì^©`ŠÉÄ��¤€”n÷Ëp»t'·WBUS÷ª¦¹ÑoÐA�ihïÃ&ü\ÃïchŒ¬S¨¾o¢ÿ3`ßUçnÇR§˜4Nš˜í�="œDžà†puÞqËG,’i¬Êé|Î™•Ÿ¥Sî�ô.\ê\ÌÓóã"Ëæ-Ü¦«;c°�VB
<éN‰soð=¦]-ó€Äsì=M7üv¿¶¼TÆ_äg™Â[9²zDûÎnÄëe‰íÆ~¥)9I,ó5½í6vÎ^j´NçªðˆUPôóiÛ+b‚™¦¯ÑG°½ê rbÎA�Ñ
>ø7¬pyˆ'Î#Ÿ¤_gÃ®×UÒÙsQü{ªoRèJÈRâ¥–ìšAëKÚöÒ9aAr£„ªU$ø†Ä#­?ªÆÞˆW ÿé½Câ¶8?5œDÖë §”Q·

ï%|}fÅ>l°d„ì!wóðÑKû{Ú?Ê·5r©Ê‡Â2"É_‰«{*Õ�%ð‚RÌV…ÜÞõx‹4â3â¹ùž@Ço¿mx^»Ý‡4„ª°â¸Bóµß£h²³–åhªæU<ûvÝgÐHPžæAÞÛ†k—À¿}——ql´\o‰ÐhY
Í(
JC•u¶E?3ar¥5,ÕBÑº
¾1~OñíÞVLz¶oQ‘^…IË¢°¦ñìq£à#P¼$žŒ ØÌ‹j¸^Ztéd½wMêuðm�#oÉ£�öç\a]¶ÝßLìÀ‹ák/Qu³�ñFçm[¢XÜ¤{+9é‘0þën$ÞS‚ëê
Úwôqm‹åßŸW³_¦¹2?ÀøW�dêŠ<‹7ª´ŠLÛÀ·Tr¿ÞŠà1¬<PwO-ˆ�Ž8âŒÖÛö¢rÝD–¹±2¤ÜÇ’€ ¹ì¨T„ÝªÌêAÙßìB"sHþô@UM|£ŽÔ²ÑÛÝÂÇÐ$húü„~”Í#vwŽâ+øJÆ¦‚h‹é³¨o1ÅœÒÛÖüƒ_CE¾î§±ÔÉiìÉ�%RÉ(™A6¬¶7†‘›áa=wFS˜Ÿ¨‰üê,'s0‡��œçõðØ‚Û›.»`[´ð¡¨Ò‚ïIHöãœºÎzJ»bú|Ñ�zSøž€ŸMßëšDJKÞ»ñ½˜ÌKñÞOÆ³íÑ1‰N·Ã‚æès»“–¢ˆîÒPp
ÉÈ|˜e5ÅðÅî{té7{UåXC"w»ÚÒªn½‰³K›~£9˜ ¢�®Ð„ ÕŽswäo‰¾qšCÐZ•0j¸¬('j/ãÑ_'�Ým/)Å¢‹àx·CQ?Ê“3è´ƒFo ta^°sò4�N¾Ã»ëp‡\÷AÔP	9g‡3ŒEÐbŠÙÆÍ\0âõw;yOB©ÏL42ÏÂçáK$Pä>Ý$9s£Îj²Qåë®^êÛœ»<Ìã†6/Lydöp‘QKsÎVzOlæªäE6,¡Täý3¿ŸåI×>í¼¿©äÕÚjÔ£6e1º)�´´ñ\ã¸u…é<"ü¦UìSóÔ«ãýcj“‘­œ«¸äÕúê\¤£¤ÕC	ÍñÊ+Ôj0ÎI_;Ãé#õ"ügÛÛKeÑ3ÀÜZ=L-\�ˆ|:Ò0HÂ~ÄÿŸµ/kVMºþ+ß½qŠyºTÀ•›7˜džùõ_¢{ŸBkŸî~‡èê8]UIúŒùdæZ¿¢œuÇ¸\fvÕj¡P»¢±œ÷ø4H:HNVÐœó�Bš,Hqù1FFö�<cgà¥G%w îÁyªþØú[å˜§&p|
ÂFy2ñ~v—�²Ä"�Yæ±Ò¹V}ë!âÅb6"]±À±îFj14ÂÇ »cÑLÂ†yu*¾ÐcïëŽÐ²Ö£{‰÷êV]Zº
]¡gDãì-"ç?È(Œ7«�Þa uB²üC“óx¸UçKWà7yÛnš-‡W±eWF[®+Û:Ã)Í Ñ¿…Åûé»¥Éˆ"ÙŒUNÖÞn‡¹¸aÚ`w]>üÞ9·6°¶ûÓvW
ø" �Å÷-ŒŠdôõlùâOmHÑ\_®Çb¶:CePh‡ÇjÍ¼2ü….�ól…¶ËÇžø˜¢ŸN]oN„­x¹Y¢p½>á¼®Äb¹x��3>h[2PÅÛµï§ð£ …õËí¡Gúù²„‡‹W?ikÞUkTÒ9¥Õ^å¶»Êìoæ2Ìm¨î˜¢z¸=« Šäþ=ÄxÔ�­‹Àõü4Š¿pš”nmÖ½Ø4^j%ø°ÎË½Ð	-Æ»yÇ»™±—MT{\ÿ± þam2›Ó¦�_g†WÊÈ‰f;\j¼‡sUhÅžAÅ£¥ÊÌg]ù£Ñ‘ÉhÔ¶{^“Û ëOû©è¼Å(—4çúCÑñ.ÆÍY~¼úCÙà�â×Ïýmkâc×¿éˆÔæMAí×¥™‰Â£ú¤ÜNÎ6ÚR�J^>§è9‚ æ<Ë£ð0×z§é4º«µ¹¡ tfìÐ^çñ†<«©üÐVù3;€:h_NÍMœDWx|~éóõ²ÞðÚºÐ´C�!ù<Øô*éŸ¼jLù>–^V¿ú�Çg×3ŠæTd(äß\e«ÎÐõ|Û�±I²7Z
£«ÝÜ1 ;ùcµ?§yjmâ¢�2Ò¹3Œu¶=ØíŽbò2£LxƒÆ©æcé#ùè×úû³üËE¸²ŸÃ­£éêH„4è‰à4“Ñ ¸U—¨X`÷XÐ–ÍêëzKú:õ¡ûmôûõ“ó¢&+Q[·Ôn‹Y5¥íCïÆ=Óv¬™¾�6~jÇsdáIç�[ïõÓ?§ž'Žò6ÖwžyCÑd{¿à+¡•·ž7ãûÇZlãŽoSV=m~þõ	�¼@ûÏWòèyj‚‚RƒyÐšƒ†;y×Ò³ã‰U>-Î˜Õv–Ö;¿‘ù<áû=fùþí&'ÎÎCd©R
Dƒö‡½T³jàfŽNë3Y»�k°}
Ûœº¿û¿-�>õóQ	�zÓ´Om‡m¿ñ}D‚öø:îŒ½	gVˆìšèºü8®1O¹ÓÏÃÉ$4Æ|¡ã~
”7¿(š½#ÏYÉ„·¤#9¾­xÀ�sñMí$o•Öø`¢ïoà¿}�ZœÂ¡¦ÂËÚI´ê¨Zž$ú³jHfm6pÊìÜÄû"=y)ùþRùm¸«Wˆ¡Õèêâz_²ø:nõ¸Ë.˜ê€.í¤y ó+"x�rÙõƒÀåg‹Wâ”Å»%å�Ãìvh²†¶J5KfâN7áé�ÚB,V¦ûþÌø2ü[�8–žg”¤”Gädöô™½Ö›µZ»Ðê¼Ï’ŠÊ”8Â:Ÿa�v”ŸŽ¼¿ÕíF{7-™_ÔÙ
á¡¯žn.v±¼K”ŽW;hr–=¦Çÿ¸žn"AöíÀ…4±LíÑ…íi»Â¨@™Tév{Ã¢åÌ›‹!kœ<±�f¹Ù×  •Èi}aÜ¸×…ï=Ê’AôÓÊÃ¹ÖÍ¡!Õ*c™+•{ðè<@isoÁåùÓ¥ôiqâê	‘›Ø”(•[´�V åÏ\ÛãQiÊlþÀ5–µ9ò“á‘õ	‚ÙÊýB�NÓû£~Òh ¾È‹›of»<ÚxO9ûÑ1
ÔDÙø {ÿé< <<ðõÁÛ‚þ‹{n¬c™sµ­W§Ê#ªÐÞ÷z­Ck7äšÃ}œ¡ù¨_{eýC=OÂ7s“ŸÝÃIìœÈ`�Âá:—+ú@¬�d‘3Û%&EÃù‹¬³~ZOšœÑK¸¨žµÔÚÛäæñËyªÌcŠu�þñôæˆwgšU‹82-©°µŸFóÍÜÄISŽ3ÅYjUDã¬lT<®¡Vq‰W‹ùQYÍÈZEÍœò?Bý×O¹|¾œ|Ô¤ Èo±Eˆä¯ïJ¹Ä]ÐGnÇ­n
æRî5Ê°W?0È¯Õ9urZøúß9¼ø[¾’\Ö%­*Äôp¬ÜV“hY‡û[ÔÜ·ä²Dfþt	ª¦”?ÍÊß¶&cH7™c;ÆÍ»ñÝ:#:šgäsš³GÞ½G ÿ�ãxýh ¿t»×£ƒ|µ£�HVýý±”BîcwÈgËZ�ŠCi¦‡VH%f}0þdoâ&›8³~Ièi]e¬d®·Ñ‰ZœœÙ÷„Ñ^¥Mo’ý ƒþ‡YêU<ÜÚíq–0ëŸÛ8=Ê˜ÛëºÀ�§ØdÑ“ðµ~Ú„“Ÿö&n†$[6`-çÝÚG€��Kcg5ë)qŒ6Èzj×�4~º‘?®Ç/Ü~i[õÒáÿÛ@ÑwQD=Wë›±¿�vÍéÈ]¸‡¶‹ø£	ùõ“ÿ¶5q/î7õ÷Á1�OÇ5An9‹¬�¶WäEKÝBÕîÌ’¼ÞÇŸÆhì^þ=£O¬â,ž/�‡êÁõ›-4=¬’E…Å¶¿�/N2Dº·ÈEwÏü´§'Æ& L1ëcŠ
ä£±lùäÒ6Û»Ò0òÎ,r¬0û•_âÕO‡ÙhÅŸè�¯<¦>�6ª–†~p6ÛC	ÅÆ¹Äß× “’aÀôªj geo¦|P'¾ñÃàÄÑEÝ�jW­=GtP=¥D3¦’‡³ÌÒ$•¢–YKÄºþé*��‘¡æ‰ÅwÕ‚›_í!8D‘íàänv¬¹y&févz†;ž”¼A‹GòocA{…òˆ;aŠ¿ZË¼‹g·ŽíF÷øT\îG€ð �©òãHBÝúõúz°6Ýz±*š—”[%—R;RÐmXÝ	f~ðµàT‘~îìOïÓxjnâdÝÍZB5Ž¼·Ä†»Û£íÒn!Â—ÂãààqÔÞÐYÝ|tO}ÍÎ7ÒöõB×bµ	}L¢ùË0Ûhý�.ÊU®\ÎvÜ…ËätÜÜM¡žéÌ{2áyÄ›8¦eFB¯V¶¡*Y]BÃø¤÷À—‡!î" }º¢îÛŸÜ–þq¸­'6O¥rP)ï°º\7æ²Çêr?ÅjÞi_JDt˜+è°B}"øéŒxed ·b\8ÙòqŒ±µ„Ä† ÝñÁ	ëz‰rƒ«6�•w@Ñ
Ëå‡Ðàkü~›š8ÇZYfØªøð
½B"¹ñÆ…®Ø¹¹7ƒb9¡âl± ð7?…”�_+U_|Ø $�Ö©¡…Z¾¶Ç¸æ›£/r}oíØä¼é
ëq A‡*÷ˆ=³_¿§œ_Îþ–�ÓªÓhuç5øð¸@]îN3d¸ú+ì¸ý�Ó“¦Ò‡=âÌ,%šöÒŒ3è%�µ¯£†T›ð`t”ž«wÔeA‰x}ž™Všü2ÅûÝ®¼Ï^úT{y¹ùno2°‘dRÄ‰¨xn¦Ÿ,è‹W—5Íîb8BGB|mGpòž‡6›ý­þr’'žÎYŠ¢‹qBË@öKú„_S¿rœt¸$eªÕ÷4:¬�êG·ÁËÉ©µ‰‹g|Ïo�ŒbÅ…ÕVE| HO´*…È]–GÖMïÒzUsr·/£D^O±¿WnW7¹~�Ú5œ0W¦ô/º¥'r¦Aø°@b</ð>.¯éO§å¤�ŽÌ‰Ã7ØEÑèU]ð�:\3rsd	³j›ÄË(9ÝcÖÿô¦l 6#ÔébÊÍRß,T·Ð� IÉ5ËUq%Öì•Í;à‡Qß­eœúœúP£|TF^½>P¡�¸¹_(8Ñ«ÇL$ó:ƒy©Ýj±órÃæÔÃÙÀ!Ëtþ´B‡ßýCãÄ«¢¡;„d­o~Ýì*€™Í*�“2(„æÍ):¶LNiç?�äß¶&îI^¾Ù°z¥o²|¾‚l–†	®W¹ÇÉ8ÙxµOðÄþô€“aÀöùz”by-ÆQÏ£‹ÚNæÈo%Ðê]-¦xsŒDó¸
o^ý¡Î÷Â7s'�ut'î‚
¹ì¤›ÓÃÌÄ!Yž\ï
KÍYö3ú|+Èã|÷¯Å ×™�àÉ7V¬ž²÷IŸ�Î=‡Ü¼•KÉ�g·k¾`¹‡4C#Ê*Uöâ��œx?äc�ãzÀbòJH9×t1°¶!Á‰X¥ûS‚Jþ¥ZÆéu¡ßÎõ	××ü`ý'0]’‰ŒËÊwUðRÆ|_V=¡Kßwk®Öé|WsQ^fpSúÃÖ¡€'=µ×bI¿_É�7¯] Ê%7¶\Çq�éw¤ô KÂù€ÿø-¬³<õ×<›y<­ÈÂéª’«€l„pInŒš^–ƒNxZ{OM>6¢ÐS(>«‘VLf7pE¸_�÷*vóëšquž6X®¢ŒDF£%×i]6/4ä}#CcÊxâ@�J+cÏ�Û=Ïógâéý¬¸ŠøIŽ%³à”•ü^�X†ú
 Ï-ÿ�üêt¼_�§�ëí¨y|Ïõ[ÜÏ.qråèÂòB‰-IˆŠ‡f\è;å*kŠ¹ï ®)7ghé‹=ÀzD<L«Db;ûÂõï«³¸æûÚ	'<ïU9ß»[Çz¹¨‡ÐßœëÚµŽ¸Ê3uôÞd<š®Ë8µ UAíÎf ¢Š çe÷U(À](S-ËU[ª>ƒÛi@a2Ë(q¿Ž"G¶YÊ~?Nþ­ñÉäò‹¹6¤z§Ü¥¬ßÑ››y™C§jµ5óD³@(Û2ã+õÇ+‚ï:(Á’“>±Uï÷~è‹ái×à�CÎz/Ÿ©‹�ïçS-2°+*¸àgzïá˜@Ðw1f\4_‘øób�ÕÜI>««äÚ0„-–	Œ ± Žrg§±ìß¡¦`_zù8«ÿlu2´WìQ^îÉ5`þ¡¹‰ÃCó%¼]ÎÊÊ?kLË1öA÷ÛxP‚óÏ^hÉ~&VBö�1’Ä´¾:hKí–žµíáäo]„%Ä›Åž¹)ÛÝ‡€ÌLNœ5®ÞuEÂ3±\ -a¸û›¥¢‡âÐÊý,¡»`WHu™§™ù¹î¾Æ"6÷·¼ø;MŠ\‘ÐÍ@}›‹¨A;÷bÿ Ñ$î¾œ�®FÊ×Òü“:��GÇÉ÷VŸ¾ÂEc îzA<‰®�â6ÞÍÔXH”ýa»"ru,µÇÙ.GöýÂý=(eþ€6@ây­‘MeU”þâóH¯Ô«�9rEvKŒÆ,r•úëbRsd{„NKú#/öOÓ°Ëÿ"þ�‹îÉz7‘¹3Š­%PÐüyÔ¹Ð�ÿ6ìUê"ëÛñäÌ>ï¢¯Qÿíú?íO&wyèém>JºÁ¦<SŒõ…€%>D]º¹•û<²²Ä.?ú(ÆŸQÁ¥T;`lìPÿ/ø›*¼ÿAÇ,T Ñþ{F&Ÿ àµFÅçsÚ d‡)‡€³ì…·J„plº;í"?ðŸWÕð¹ß=fÓå¨õÐ6¤Çê®¥&gå0f«h[T×dQÔ©â0¦FÖaöq°ù åu`‘n‚æY±ÄÞ+#Ðq·ÝÇ£Ëí%4Ó�aƒ¯à˜á-háB¸‡’ñ«fKž“}WÇ�ù€»zŽ„�Ó—ÑVÑ­.[ÄÔËÚáŒªá|`[íÝ·¨xñ\+Ó}\®ï6™ç‰ÐÜ�¦ï¼ÄçDU§+%’…å–‚^âòivßžÕªy,¤}Þs<2Ó5kñp‡C¶„•Í0ç3Îei8y/ï[ÌL½ÄsºùØ®ÐÐœ"vaÓdÒÄ¯á|=ÖY§í�Pt”ußZû™Y¯ù�{>.ØÞËlYaÚ›¯«tû¡ð3¶HÿÓâd©¹ºãÞ|�ÝÎÑ€HR«Ó.>´ñU ®�õ–š“«ù%ž†c‡úBz/­ÙW¦på˜`linfè»TYgp¯Ø]±›CÉ¾Kokãt´‚béçàO'ŽîPwé€à_œ¹ëq™Úe È<e«W*“D"©¦b¦ÌpTýœôqžãðÄw3ÿ¯¤ÓW{/íP»îKµ†üŽÐxÐ%0nÖ¢L9\ºó`vã�0—?ÍÓ?mNÜ5î.*óSÕJØ©§Xí)—¼RM„qJå/fêæ{öC+`C=/¬b
ÍÁ÷k{býäõ&¯e7°¹Ñn�Ày‡+9§Z	ø|‡Í€)XŽfÅÇñÛú”Ip<·ßw­E½ØÎI�Çûù68õ{ä¨Ù=p€$	~<_öÃý�fõ½Wvd /Fi¸±Ã÷­´;â€6Ÿ@iÁò(½9œÍ¸æŒ‡ã7(íH<ÏdÝ6VÓÛŒ„áErœc/'ìÆ'¤Åçé‚{Ïíýýeà·™
\£Ûý¥v›Äßí¥Œï)|y¹ì�gÛ’‰ŒU‚j‚`¢HÁ>>’�1„Ìö=Eªï¾1TÍÄôþ8Û)'Ä?�»âû¶·4ë?<Yl—«¼IÂ4b­Ï{¨øU�ˆ‚±NG±£Ë_¥‹-}Ã‡ÔIT£í>ßByËW“…¶8Æ =en ˜áméãXúÉàÄQ|Q×–H3º´º×ŒtQV¬¿m¹5YgöÍçåëûëý€juìÚƒÖPùz—¼7´×ú|md\w³õY6nÜ„àiWÛÂ,n¼�f�¢E�·�eS'PRÀM€X¤‰;á ;0d�|‰Ñ¿LÆ±Y‹ÆlöÍñDËà5ŽbÈ>Çl,Ñæ1iÑµ}ä¹o§Ý<3vÅG-/þ|o2†P9³ñ–]×Ç³½lEw >!aÓíö�
©}gÃ+^%‹:¿¿^@>'J[°4bfF½²wdÆÝp}Œ¸>GR¿p¥Y‚#iàv™|9W/ÁŒJrqq‰£ûºÉ&>I—‚ß¶wÖêîþ™qwÃÈ÷p'1¡\ŸtàèÚŽ×žæ†8±JR“*Ð²Bb!ÌWÛCê¥š¸�Y�Tü¨˜ý{"31AbØñÏ¿^òKÜCä·z!ÈÎ‰„ƒŽù¨??ÎðYŒo³"7¶Ôq¥y?OÖ¥­?˜œC ^6‡4Ò7KKÂ®ÞAˆ“^ÿÅyç¢§¥ó0¼�àÌâýÒúm9rG4õJú¬Íä$™¯tXfÑÍá.˜…%â"R†&¹t•z:2sv‘æ*âÍ÷óôGƒGËAâÊ�½}À%8%eÞÆYµ^KÈšÜ¡¹û®wðHYëÕ/Zx›�Ù,*˜³Â™Õ™œ½lqñd7xf:Š‰ õgc['9´ä™‡øž
ÿíæßæ&Nb\kâ 4Ì¨dõA^:hän+fÎQ•tè\y†íO{Œ'6dAÜçc" IöŠX¾òZëÝ2cc™?2Ø»;ÄHædnzŒñ(­³„yFÈË€Ii™_ÿhuâò²µ©{ˆS=*Ýà‘xþ^·¸xÛÓ-–˜Rœp¼{ïì­ýx¿¸/HÝêL6 Ñ9JŠŒA+Œî{!Žjã2ë Ó›<ûüŒ»ñšœÎŒk»M©gßÆ[ø¶Áÿ6™}hì…÷ž-ÈïÇ¤ªî‡T¡ÏÉêXg?{ä¾_î Ž^F„¼Û"ë,ƒ8‹~8[°Í€Ã- ì‘pï‹¬Ç_p¹‚ÍPØ!EÛnuŒŸÛ¼àÙéâq¹®³�Ì±,™ÉäÉxÁà- ´§í¯Gü,“açåÊÒ7‰bGÕsž•‡h¥Y\ºœ¹ì…=;Z	„×�³ÿèGy†`Æ6¹Ü›Õ;HÈès9=ï;÷R;¢Ú{Y¦ÙÆ5ÜµÀ¨Ô'¯¨üäûo£=rº�ë+ÀC`ë~soÍÞ54� âÁOžx–A3¶N%¡ djié\á£t_›û»ÍÞGú¦¿Æƒxs÷7¹ÒÌr¯;é'ÆÞá�tÜVòé,k"§³wªªÙ�¢ãÖì<î)0¾!^û�¿ÂQ—’ÅùÁ4«Ûl58ŽÌO2tŽgØwM‡©Š�žSã£‘ê'{“YSóÍºãŽŒEìÛ˜c}Nu©¬8iÙqÕc2ó¯Ò²+¥÷È9ƒ¡õ­Þ	&†}ã"d žF6ÒuuõpâQU¸xâ<>kW±GWËZ=ö‹Ày_g´`Û&äà¿DaÿA úâ®¦`£@KC
3¡ƒ‰ò@âoòÙ+^pµ+‹šáWnoR‡6¤RuÓwà¾ª™CÂ®‡ÝR6-ßCà Àìç¦|ß4‰�Åeo¯$ª6|QÔeL×v\ =rsq,züúåp8”ïWÏT«x’0Ž'Éë.ÇGºRHE 4ýE™6
41 …=Ïf±F«­³>®`ÈŸßb§Á4¨i+B áb±[„˜ß9ë¾‡ÿ½/MŒ)Z™äo9	åNË7’’ð—@t
ÂJèñí€Q–òH ´ö5Œü{Ž`ÓŽwÐ¢�<F¦mB�w¹pèkR£ú~œ·Å–ÒZyun«�Iÿ46q@Ýmç­�f1ñ»=ƒë¹P{:±ÙU‰ :VPÊFï×ïè`éU¯"ñ‚ùIKãL^­½Æ<¦[�²½s™ep�ä ¡ÙôáõJ/W½¸]¾Ç Ÿ¶&îUVôŠ¦nk†QåÙªæ‡8•nÝÑd¢•%;\LßñÉã{rd4ÙApc¨1…?;{Y²ä0ßFl8¨˜¶8‹—;7×Ã`vˆ¤T¬Ë-è¨ú˜’Ø OÎýy )ðzæLs9ÁvÍ0dŒoÝy‹ecÛÓ6Ê½sê—–ÏøÐ5~#KU¸ÈÇýÁöKëqDœÆPŠ1hÉ$XÒuIÏz&Q$À¼øTVªÛ’±•r~lÔ°¶·~•Q.úÃü~[ÉôösþÍ''só?þ±\G ÷‚„¹=B�«ÿz%èÆá„ëôõ«\ª{ö�R½'–Â’¬Z	d|+J¸wGUé¢ éû±”þ�í‰ûg3iù²^ ï>5…ë†„NôõÖ]žÅ#vã‹ºêóÖhH#~¸?>€Ÿ¹ôËùtQúm¶*™@‚]S¬î"EØÈª�A¾ÏY' ÍÈr×ýÇ¢ý—–'®÷DŒ/G�t¯§µ
Y-J-?_Œœ×š�>œ¼�Ú5$¤Úì=¶�º[#Ó-±ïxº°ÅQjŽñíQ•*.3wÖhQØ³ì4ã¬¢ôx6®­§»ïéÂ?�8<jIÏ4¶—Nê>bö@€spÙ–�™5ô ®|À˜F®Âúçîß#Ÿ±_¸ÏQ·utøÚZ�ÙÙd�ë™kÄí¾¹Ë{áº“n=�¥¼„Nûòêž•¬‰ÃïF'ƒ*ána¬T ?§ÃSs§»§aT_Ë*uIí±e8wI�W«�õWš	�‹9ÈWrû‹éæëj¢A”…r%Ar2ž	$½67þÎZì,»-R™¤=W<9Ò%T¯èJŸElŠ8îsÚ•Öæcÿ�¾8ù©Á…´OnKæ@GŒà,Fº4ß, (2tx¾…î�d.Õæý i“2xôP›…± g¿†ãñuÞNÑ•ú¥‰"ØÆÅ¶ÆhY;5³¹—~{j7=i³¬šb‘ú¾X¶Z¯Ï­çÄ40Ùºü%¶EXŸöè‹Î5´j]G Ttú¶Û\ãìX–óÁÐ0ýÈ7Kô{>Ž�¹G6€.A(¦2Vù¾÷!©]f2»"ã¦4
�Îh¦¼‡È[~Ê<"%�™O­¯Å l$#U J�‘#<Ô&QÒdÞ²íÅc8¢¤¶ë¥k�€üÑÝaóóìkr™ aËîÔ«pãÊÁþÓÔ 3€9	� ÛÑ0RÜ/�!XÚò€…ž&ùHK�Ò	¡0HQjó(Õ— ¤@o^Õ‘×é))�¸ý!Õ•×�=& ¿þ?J}….è*®Mt}�æ,f®¨Ì]»µj-+j/y~s;f¹2 ÿõþøƒÉÉò­³¤\“R�Ý¢<“�I¥ÜP÷¤â%ŠU~w9Q,ˆN¼=a
Ó…~Ê'vó»“àý-š@ÉéµÞæ¾À-\ª2)…ÐøßÆh²¬†ErQR üï¦Ý¸‡º´—Ý]f€6OÏû‚Ù<¹h@�"9œ!Ù_8é�&í±Ž	`$8.(·Ñ�PŽìœTB ÔxXÐ÷Éî±\bÎB\²Xú¢.þ‡ßœŒdž[ëþÎ64*ÜçŸ^¶ äs¹Ø­ÕUÚÝøÓ‰Ãù@‹ïg^4)RÃü•<„îê‰Ñ¥Bœšb73 Ì{Ù‹÷ÑÊ¯;ÐIgï¼.jþ ÙÝ‡&u‘ú ã“=—çc8(Ç Wž‡¿v\ ìaA§¥ŽžŽghà¶�-h ÓL=@c/hµAÒêƒÿé_Ù�¸½3Õm¤ÅÔõÓ+(¢zgF36´!2#3eŠ=øKEPÆÉ_¯ «Ò½Ñ	{:Ð±L7V2ÜxÝJšk7|âoŽDy?Vƒ×ã~÷±†¦†&Ž1×TöKÒÕgÇÄ)£]§él.¤5ÛŸv§PÚ
3áPex?À±‘1ò«×šx€!L¬�Un[I6Ž¸…Iò²:^nFWm‡FÛ‹ºþ�V(zÈIŠìIïQÐ×0ö…Os9KB.ä~åâÙM*Ý«¸ 
©’CgØêóœ#BYìý}÷£ù¯t dë'£ÂÛ‘WfÄ0ò•"¤!]„·G©÷µËÈ]š(ðGÍÇ(Ã€AqÊñ¾ÞDÐ“þVÕxoûéú+FßKÁäwW`ì’i7¬<jŸ	Þú¼?Ÿ�Å™Yï�&Ë}ÿ \ÎÀØq „RLÀÎò¢þý#¦�‡YzŠO�í²Ž3]›Cë�,,Ô`–ß2qiCãì�§Í�ûÑ>ß¨@ˆòþ«r#�ŠGFn —Ò ü�·z‰t…¤bV²ƒl•](#âÌ·‡lÑ£í-zô—³gE›ÁÙ,?
¼ßcÀo'Ueçç¦ôLÛµ©ùl~û57®×¨›sŽ
¥I¯h·¶YÒé7[±ƒ±¹ôT	ßC¾˜÷&Ÿ0:ÜaðØŸgA¥eY‚†/¥;îå¡[dË;Å‚=(–œD»0ÎÀ73Ã�ß‹*/�{¸¬@E-/úsüŸO1€LÌÐ:=Ðj ìƒiJ FßÑ¡6¹¿²�Ì;$ròêð¹@ã’ÿ¤”úæ—ƒ`å[´’Eqà!#qòe¡G‚ž
êL—òµ†£Ý€D#£¶€ŠREÝŸ¯Ö¼IéÙÝ¹{ÏÍòÒr/ïOÈ2(êP’O¥ÆR’{C´yÕÅæ.ÎÞ“P“Ã“RÄÃ%Ì!íšÖà¸{žÄÕÐÒñ÷@¦à~v‚'sÐ@tì‘	t¬ðMý¶1Uhx+‘dÐ‡zÈöM@Vy,N'�\RÇØ;¼Xäƒôáwä‚j%i›Œrª¯I~[± —ÑÕÓ¡ËÆ²pb¡ßÅå¾œÕWL3x@@)Äù>øB§µïìÀáÁŸ¤O—G2ƒé¹D“WÇ– Št•Iè¨¶×ŒÍÒÜ¨[‹/rÐ„Äyˆ¿¥bÐÂúà'Š
–Ï30Æ:ï)÷ËÃÞ3óÇL3N•1?°Å¢o~Kë#µ¸–»ìAr¶üà22hW±X×üE<o½·D©Ý˜åþÒk´7›÷@¬=–é€½3>Íñ¸vøv¨¹u{ÔÞ_jeæabBöóÅöè*<¾è¡÷zŽH‹(7Zvï¶¨¤Fw~ÏÞàÖ9hí>ÖÇÓê·Jã;w\b¶èZIØ§¢­ÈB(æy°M¹¢ÜqáJÄKÛsçcæ^6S«xf­¡awZñex,kÖ9€ªçdSH^¨´ÍÞn5D™ŒÞó%¾Rƒ³¦�>–ÚÓjØš@S´ÀçáÐõùÏ6 OVPç0Ÿï9l,j!:-Gg¯i{¸	µÉ/›�×ßk€3Ðy¢£à�:=wöÕÛ$°c�¼qÕ�ýB†#àläâ9
æ÷·iÑž˜'øâ§~ª>]}R�Z{Ÿ(`;+ZJp+P.‘4k%Ø)”Ž|Po«ª�²„÷Ž�$PQe¨¿OÀ·""ApX0ì]gNƒ�ß<¸¥Š›´¤ïÎju‹@Âcüýâ.+`(„®RØxO�P uý5îí_#åÛ¸UÞ“ù{Á2ÜòÌ¯w {{_¬Ë¨Ñ ÛÞ±zT¯§d§Rf3dýqU€*´lHj…Ï—Õ{ÛåzÝ8ŒÞ,gEëÞnéÌ¾jë|¡L€]©ôj	¨±Û}ßŸfßû‹oÍzƒ†=~Owáb¦]Ú†ÁtéÈå	®häœKƒ~:U,w{¯®A¡îÝÛ÷íw»Ï* ~‹åJônµ¹†+OˆXo;†ï1¿Ò2qï?¶[åŸf§{e©–Wó¨y9ùÈ–‹ÁÐD™Ri3q¥ðò�P:a�üã¬ø6û¤‚ñž©ï{›^ælªÚ*EEY�a—K¡ª]Ñ^‰í•£qäŽ\Ú°ú Iý=ËïG'ÀªÖ>©G@¦/˜§ÛóÒ¬7:¨P:šEÐå:Û}D(?øÌ¾Ý�å€<âÛfÌÉ…x†¹Ü¸šø¥ë†ûú8µxÐzó?òÄg(œ<_"L‰«Å�A;µÊ æÆ(XPäž9…I´Á±‡O,ëÆ×!ƒýï½mNÂZÐLò.DiË¾º�á„¿§©£’q4n&Æ€ŠT üù‘Û-«Ú	@½úÉ	²ŽÙŒ»¬?3}¹urR;u7œ5nåJÒ¼=ª³.ZVvÒ^€ì+#�°SŸÿÑìÄik=g€4àŠ—€ÊyNaØÒÍvG�‘¬�"¶¿’;/hÃú²¶Ò´dÁ´g‚ÃŸÏ³ß&K±é¢é<äBÒ"Ì¥3àg:®S,�6Nô÷íûÓ§¬á¡ï�Ž÷ßæw�B�óÍUCÄ3Y›Ä¼>âÞ�Àv–¯�9S Â­£÷%Ã¼œÁ*Ðæƒ<ÄzŒÌbÏƒmŠ§	©òB¿v*åAº	VPÚÖ‰m¨%G­YH8kgžž&" �,rª±¸
d´€tøª4|)Ÿã {ºŽ@>Hý‚†.–"M�réi+—ªò7ÉÚ´ÆùaÃ]ÓùRE÷¶Ðåš]=ô …ÈlAž>˜&*·2½bœ'}Æôï:R.¼ÝG¼Q!Ô‰aÉâ†/ç(%ÞÖ(V`¤\ÃsQ�Ý~_8`Ð€ä ô �|Ç_µW¡¾Ø™R#ËÁ· 4��2{ÂwMS5:¨;>L#Í>Ò,¶:™^¹ÙVÃWó=é÷KœYWm–lç¡­³Áj³è€äxãw÷÷E-f@wXêôÏ‡á8»¿Ó¸cþ†qm´µP£¡,–´¦ô¥°–·La! ó‚�Ál«>öí´«µ]^sÖ¯jNßí·ì{á>›&m
Y#x©ûî7èkïN~ÖZœ_4Ö	û¤Í¹~eŽšÚõ›ûžm`õÖr2Ÿ0†íé!�PAnÄBâÒ–îè�H¼ooVäCHPq4ÇaxsC_Š¸E½î§Q‹npÃ~¿�07qR=e¨|áÒS·¬+b;ˆ§ŠÒÓœ\é—þ4ÒÈÇ™aŠtž‹ïw4˜Pþ~<�ˆž/òïó{ì3?Þ�µá cÑ…w™5Z»Ž©ýŽ_ÈŠ ¬<w,ÐþÚ|TÁâþôÕ�èøû+N¾é?ß€žG;iw^ôJq/ñìÀÈbS°çÄ NÚVþð~Õ¡aaÄÎöëÌãÚy÷ÜÑœ<—{ÆNG;„Ž2iA¡Ã­ÍjCg‹å©ë µ¾ÞHïCó·åö}ÿðX°Ÿ]4ïÖ¯öSe�Å€
|qaµûñ‘™êì$€ÖAÐœ›~ÃšK_ÂËû£¸®!?ñÔH²ì'Áó ^ŒÉ”F­¶ŽÐÒ*qgqÉåŠ;·Å T(F66`Þð©÷…òm93¯£}¥d–»8<¬ZÃóH4Ž½¢;à×Yv›ã¼ŸØnq=]«÷Ð�œ=°~#ŒÕKÉ/éøÑ>lÕ‰Û´µ°wó¢í%
tbb>º4X—T+‘—èu¶¯AYc@ùò=�÷mTžª;À	FËÌ»éT0Äõ:’�EÝGŸ®55¾•µÚayJø�ú4ØòF|Ôâ ÀN,ÇõW¸Ï–Ùïû™ÉšŸK=P¿IëÞw÷Ž&)CL—Wâ€í@¨-~9§æÝÛ?˜œŒƒ-ì[$Sn+kµUs¦$WA‚¦N•&ªK˜œÃåvHO–Ü¼Ÿ†¿-¿Nï_/®Nxªÿ>)Èi ~ÄâÌ/Û! šA2Àçh�9mÿéd+i²¸Øa¶�2YLkq½²üK97Â£J,_Žÿó¯N~r¸ƒM gVÀ¦e!"ÙVëT#ÒÍ9¼äÅPÐ9‡¤êàg˜®ÔTú‘äùÀè^,YÄ[ÂƒvEØ–¨Äúa}¸›+¾ŽÜh±ÞæÞc&[ÿ¾Á6Íjï‡Cç&èˆê¸V�dÀ5®ª÷„laG^A�éÄî«æ›L,¯¶F,we«¥klèÖ¾½óâG§G‡Â¸  ÆÆ�î\ˆN¾�Î·‘Þol6C=”�cJÞ. AzËäêª÷’§TØlñˆûù=K­ØðhþGM	Z…QèºÁ¹_¦)AÆÅlÏÖïærÕÊUZIâBªó:©5î=b6½ß‹<”ÆmHLSRÞ/Ä¦Y¸UÈ7ÎWÞiºµÑ\ñ@ö�‡'õiw4A!c&ÀTd[Pä�¼ò<ç[ÿ*¿Û{·7�Øy,ÿÝÍ÷>¶àIÇã­ájX³=žQ×]?–cpÝŒ‡<»Ù‡£2‹?Rq}d:ikFß÷Ñþ¸U Ój2"[S+£|	2©Å¥‡ØÉ1@3'[»^§ÑG@¸†Ìâ�(oßÏ<Ðëô:G!�ëÖ¦DLÁ¤‰Àù·4ÉFÞßÑrßH*1“Öô@Ì<g3¤$Q?Rããy§âëÃ{š¤‡=	~>œ:ù:«!mô÷TÒ½àcŠ>t3¢Y¬Ðu2¤~_Ì“¡J¹®§*S¬��Þ»Áu­´«!=ú¼YÀßçéÀð(É[R½¶Êa¹ŽËˆ¼·†°§à•—Tj7˜Zª\ÃÙïÉæ&NrN
jé˜­XI–œ*jI<-ÑeÚÇ8"p×�FÙ\ºÒûI÷²j=LPøz]ÛÓ°ŽqÝ#íàÁõ¢‚šK’yf÷ÀymÖõø2L¡ˆãöÆŠWµÐ~O)¼¬vÝý;›7þð’ä•°%)¡÷|f6ë»Ý¯Í9šÆZNTÖ= š¡¿¿<þalò³7A9\Le•ìîz�tPûœö˜C&{w!Ïðà¸ Ì’ú°ÙÚÅ	 ¿¤%¾øTÞoþG�«‹�Œ“ëÝZ·ŽsD_¶«pKÄÉz¥†˜.‚ ÁË2ûÃÐÐÙ
¹¿ñÔäÛ"�ëú¾NM)šsGH¯sr²KIka‹AqZ«�y¶ÏðÆA�N6Ð Œ¿ÒÂ‡)(¾�?ðìõ�y»:†i`‘–„W´Æ4zñ4çØñíI�p“ÄgKý»Õ"ïß;ö=®
!,æq+f‹[Q²lzVË+É1Ñ%”ÊÞ¸ÜRÅ¼F+uC�ù°ô/³†át
÷¯ñ¼D ž¼Z‡Ç…õžTA^Ä"³Ç|Q/7§z›œW©é5àyß¿7x—·~·oæc<øiÙ3«/ZÿÑò{�ª¬b??cû½¦ÔðZ¾#ø:i»T�²g^Pú°µ¤²”ñšJÿ?àLF²Å¿‚gß þ…)è¢68	x5�»%xA M{Áæ@=yY¶'~•Ìu æÆõC“¥Ú([vÅeŒ9šÈ �G63fíw3£Dû\N{Àjyáñ™ðzÙƒ7¤;2ôôà2V�ÒyQ°VÇ-^ËD"å8É‹Úh¡ã²/5½±‹(O1F¾?ƒ�¼osâîV=‡3soŠSfð³£="êµ�–$OuKt	#ØÜfDêþ`zœ¡ž^0Nõ(pøºž_j¹ àAªÏuíƒ`%T®ÝÇø?žkêRãÏ;e+³'ÚY"¶»ÌÓ+ßËk Œ£pÄK¬*²Úp6°Ì9�ÇÏÿ½“á€ôÜî& ]ê¤<¬Ö½F(�ü@¼Ü.2×´±•œ¥gmì¿áÊ¨l3†ÚÏxÅÜ°Y¾À\J>—vJ(*P¯uëz­æ è…÷(ÜJ‹“í]í�è€dÃo þøÕÉO.ÅÕõ2/V¹M }ßÌ…Ë©ThJˆ�Æ½ÌÐH<‡Æ’@ží­ÿâ'„”Ïà™~?_x6º¦�Ý* ’q�Ž¿ÄÕY”;'“¾€R"»ôNÐ•Î^AÒ_ÍóÆþÚ³Râu6'ƒ±
t5îY–È„d”?D6¼öót,™u>&lþ�ÁÉP,v~xñ»Ë8üBO~q–ÉöÎ¯ª«}Ç×äâ¾?læým|¡üÁ.ò7×Ö7n	kQ#Ÿí54Hß6³ “ûÐ�ÛBOÊ�¦B­dYœn¼#8ñnwâ6»g ƒ"Š/îâÞˆ×[Û¿@ÍhNWÃzqØó ¯ÇôïŸÜþ:rˆwñrð&·¡Óš:Åsâ¢ÚÊ	¡å4ñ-ùaß@Õø6ëµh>†s²=Ó^ïã¯»%Å—«ï2äŽW;üJúÌ¥¼!à·šç™Ò\Wª6cxû™�ðCca±;Îõ”nˆÕÜ÷•¦¯9}3p�vj.Q^°#{xrh¥ÕÖ×Õ„_$Ôã£ubQÖ:k3Xû$Ø?vÛP
ûƒ—=àd?l·¸­H	‡í�Ö=•žÝ„‹c®Ó|žå_q!¾%hÐ‡òn®3D÷œ„�‰?ócÞ=tø]\E€{rVÕk²~ûöfiâŸ® œn8IRŠr‘È'‡ÐiY±¶çêqíFv¥­×éùÛà7átRŒ§ð½_rŽ[þæJ�ê¤Lr¹¼t³ŒQÀ‰¿@+iâuüdgâÖ6Ð%r‘’¹É²Ëë:×:R‘n{Ö€wÈ3aÆlñSœŒX<êf H`Üð#¥ÑÄ&ŽÎŽU ç=ª8ôÈj¸³|5}ÊcTX%q¯vKOPXôÃÅ/Ú%ü}m¯x¾”wŒ}ÖXfwQx|Ø„¹·’U=Ó7{RGNÎ}•â¯‘ëÍø12þùõü$_‰Æªîô0Ã÷I…8´‹ÂriÆOõ-©ÒŽ«w¼@íþ`Žù£ô¯Ÿ-M~©¿3òM&TB¾÷vh­x=zŠ2õÞhEiiƒn�ÝøÃo×Ÿ~½šž\Ê•—xàU<®u×¥¯†;^ëýp…//E�œiiÕÛìÉ³ùéÚ·¥‰kÂ}¨÷{ 78s¡Y˜Îµ+jôaoçìLÐü¶,bfBKßóDø}ýw@CÐƒ†ÆxóÃ^øTà-Æ€ª›�³$á¢˜Ã™ÓÐÝù¶X`ÀÿDÍùÈÇÙ$�3;j4tæ™Ä>èìçýË¯îƒÞTŠ ~±#î—óPŒ¢¦¬ü¥Æ5ùz¸uÄÍÖµÍH”B'¹„D’÷H–�ÅàŸ‘ð�_£àò÷“º+ï€WÇÍPÞ«Ž�-«y³(°ä˜É#ÞÕMˆ­€Î�kâ�¶&C�}ïÐ	ù=ã;Ä}ì¥*ãTP»æ¨HƒuidÑˆñê5—ÿ0‰tÊiZ•ˆÊ1 ˜‡“é{6pú…/"9´0NÓ$c25MwìÄÍ!à“®9BÇ$	ÙCÏÜn*¥ucNmƒ1Kú•%Q
öù›@>åíSÎa8tŒÃÄ34FÓ8ÌÇ«c:GV½P@Ývw�®¶6·°w*¨Œ¢¤ºvëS'9þÖ«Æ”ÊtÜþ³oLÆôü §&ë%éÜ„µQ�v·ñu vNÞ[Ð¦–»²‰èöçâ‚O!£Ðö“Ðìó¼–ô× ¢´Áþ‚�ãy5e°!�x¢Ï:ZÎ¬—‚Dì�’<RkFkÉ°ÍÏúÊ¶P2ŸœÓ½ˆã Â¹øì»‚µè™±h†S¿lu=×e Ox:cGÐªŸ7}^Š�ÀZ‹Añýå5)ò%Æ›šÝø‘£RûŠÙŸ»õëcÄ×õ Ia¦wtÜ{Z©9óÁ¨÷§ó9VEôà®r�ù¶80fl=cÔÉÚ›šŒ=Ü>´(çåÅÞ·pß�žÐ«~ ; Œl¢ÍUm¿áÃ'!õt ¨ý{)ð Ìq' lèZ”e[æáæ4³SqëUå±ë½ÒQî/ª„Éi÷œoø[®éÉ¶$ª;hG³Çç|~Š†\à˜d~0?4æºÀÌ5Ea¯êg—¹D3Ìy�å¬Z+<ìQ9ª÷r@"Ôª’.Ð™ù1·ŸŸâà©öÈQ`ÒÐïA‚B.3]FÿGŸ‚ß3vÎStÏÿ¢M†ö<eBÎäw–óý)
šBÆ3Ñ²›Î”ÂÅ4z“”ËK…IZŽA#D¢vò0Î�€o½HX~ž_Ã3š8&S¾­á>r±Ç�1fæEßl·4pK×äªsý°šÏ.üZj?7ðÈq‡òÔ¶¿xæ6àÀÂÝµ=#æº/²Ë
NpY¢(ºkDŸ;ÓùnkâÞMXhÒî	‰zcäâ@ºÆ"äó*�à&»ï…ú¶ Üý"hžš„Jùó‰üû¢„%Žâ$‹¡¿0@ÍA«=‡Ûo§Š
l wÑ¥n¨]
	¥³HÕn˜…úcï"ÁP,TJ=7?�Íïo�"ã$¹èáœYLîÙÕ¼:—¸s¤Ã›I-.·Ç®¾¢$#Ø)’mªýçNŸZš×JëÈ^GâÕR@_e™Ét‰@ OIBQéˆsâì™b™Ààÿ¶Õ¡7 #qn”bE-ƒÓf§+õt>°B#ô6âl z}¦îå½ŠÂâçÄ­xíà„v°º?#Äñ)Ð}¬-œ­¿8†’s ÖrÜ´áŽ)áÅv¹«*¯ŽbyaÚl«ðáŠ +‚èõRGP,ñs«þ*‡ýGÂA7Áxéá¤7=Äÿ×Ÿ"¿öŸ³'íA†@‚k"hþ‚3¾¹	'‰ôä˜ÆéægË?Ïhh2³äJÆDj£Ýg@&¸×Ä½	Ò{1wó¾ôôU�	·a¾ i•øsÿýNyý=¿pwR#xsæ9Þ¤–KÁ§�'b~7»i�ò"®Yx8ÊmHwƒ(Ya"°âtLÒèñõØ™ÌïXß¿E¾C0å+ÎQZïgX®ß‚µ·ÜêµÀSîîÎmÂ[œvË›¸)ŸÑ7ƒ/…âw{J¾cEm¯adFáå;À
Û€ŠO ²�lÂ¬]W �S·ûgàþ“=x¤ŒÛ�ÏÃ u8÷ä³.´ò Ÿ«y<+ëåf3\ ft0ïË‹ÊÅ¹x&þ`i2Y&U†ÝuUr÷2Aæ’ÞÖâ£áç)×°@<;4f³§üìˆÏÃ÷EÄ�Ç÷1n[ÖJs—ûe³¦ÜÝÔ3¯‡ËÕd»DE2–}æ_¦®=)=^–&®ÝÈÛÎ<Æ×vu4Ò¥©/#ò\däR>în[ÃåRWG<rwJž�â©A(Œ¿� ×ëö+õ‹äLò¤	 “��¸éÜø{ïH÷ÌÌ¶ÂÙ$*r¿>Œ“œæø%³+­:ÿÇ8|ì{?al® ¨‘îÕfžyÔ5ä¯~Y›½±«+Èqm1vCÌöŠù/MMœ“km•çæÕ¾îÄ¥X€
Œ¤ÇTìÛ´-2 'ó�d-"Ÿg%pu>Æ²û¨TlUãZEx} ŠiÓ³ËäÆa¿ž-�oÂ$€2ÿÜúõ€XºvZ-«£F3M¶p÷Åâóøý•/&ÖT(Ÿ”¬€yõŠýñ£ª>?^+¥Á•êU^=!‹CR®\«ßa«�ÒOÔ¶>b#¸(ž´Ößo•'‡Ó¶�ã…!I›ŽÃrÓøhƒPÍ±eÂmMöìøl·-RMóòÅÂ¬/á1—%|ðâ'µýôòýés8JB¨Iô/Îr<N%hP›™ÿ‡ŸƒÈÚPè…#¸_€ª€õlZŠO�"\�CIïªÒ^Éo¬Ý9½g-%ä€4l—žÅU{åžë$©ãg¾»^àož«ãë Ñ­lïÆÇGyÚ�˜b€…\Ñ'ïŽÐT–ÖÆòX‚ø…ÏûþO¦&‹X|Õ¾ë]IgW ÄV¼s�°ÌøH7ùÆÚNÜÛØ3±…)wZ(0³àéõ•,„6°Hß‹2ª8;l/Ç«JêaŽ;Ò­�ébVÞªÂõ1.œenâd~‡Ójìò~¤0nj¿íŠÐ[¸›;(2¸»jtëÏóõÓ*”À È>MgÀ´�T<Æà ,ú‹ã(‡Ø+P‹=Oó›DeW,Ù”;Buå!ñW8‹6:…Å™?Ó×s õ}µIŸÑÂÿâk“Ÿø@"¦Øh³</ý¬ZžVÍUtˆa¶°Ó1âÚÊ¼¶(ŸÛüO‹ã¿‚i)‘¯¹ùýÚ}Ò’�,<@9‡`-c€Ãï/ýpé`ˆÙµ˜^"ùµR˜Es7òòÄÙÎô#y¥ÑE àxÖþþï>> æ¸˜/"µRÙav“ø
�¾Ð€�¡0¶º7×b¦ºýªAŸAñâÃWíê?€˜Í“€;—k´’lìŠ6q’µp³Ìæ›øqj2-EP˜´ç³óÿîã“ ×ytg�+ E©Ç(mv‘™BÚy‘%ãÇ™ ®êß ¥©=ÏÜøðüµØ_ì×Óà
Òîø%¾²xn4¥Ù 2P ösÓßµÄ2 žßÇÙÑƒK¹þy<'æ&NŠÐ‘“®Q�æ¤áß3&ÂE›Ùx|ý¨µŒZ”±Úåó útò¹¡MîÙoµgMÑÓi£½ŠYèòuÂ™qTTÙºë/@„ÿÈ¬30	)Ož´œÿ°9qw}-Âb›à<áS'VßÄ=]9¶ÖðedâÑ‚	qâêBf?ƒàl“ÆÏ~Ï¿l<Ù^‹£á�g�eLßà¨ƒS.t‘è4VáS:°€·`Hææ�àì½>·éÈ(àŽSh·R¿P·'.6ýÇ·žƒ`|„ÇÃ£‘ G®µ-ŽÅiÆ¦Ÿàìµ ä\ðœK.E˜±ë4%ËHë™»ö‹E8Ö�<Wýÿ÷?2<i6VáeƒŸVˆ¨;t\‹žpí�¢¤*Ê³!Tàsƒ|ÆÇ_ß&×çÐ�|)P­Ž§‹TíOÐ§	d[¼±-˜;% w:Œ,·è]¶²ÇÙ¦¡Û’<=ÏÓíLÜ‚ät¿oyßÈ÷‘”
‰ ò;e½K1t–È«ùýM;PÎÝñŠù2}ò ?�ØwäQ‘B(ô×æòUÚ¶ê³Ðýš+xíßëk¦<fzÅ›Òui@³“¸<�9ªã†ç%óÙ�¸}Øê ®0*s�5#®â^<AŸuy+# Ê[H@cæ§©yháM€0«ü«A_Qâ4áF%¨ Ç2=Æéq‡ï¯çu�€’íy^ÀvÑû}XëUy¦À¿<~4^¿.í‰‡ª=¬ˆáæ«KÜÞA	Cº°û•°ïhÃºæ!h÷–«d:ß/s_Å‡'¹ DCãkª‘_Ü˜œvMzíÉ§¶$´ª2OÚC^ÓâŒ§É›×`ö‘‰¦ØM ÅÊL„"édß>õ:::=FVÃ|/¹¼®»ÚpØöÄ�RS_öé¥F¯ç›tbøçÅõ6_.O{‹ÿ[nÝGJM:Œàw!ÏX{œ¡÷²UãÐX×¹âÀâ›SgþææîM�«Lú â¥�y…�}´�|þ\ûüÙ &ÿ_�JìkÕ}xø\ ñ«Óa&�è5~Z·¢äØ U)®í˜kñÁ-M¦`Ÿ¤�¿¹[Öîº«1ssJ$´mtÛ&—™=×¤hSÌ/°—tX<ïá÷¾œ#À¿q‡Ç]	íg€z¦gùŠP¼«ëØ<¾4¨{ßŸ„=v~ç0w®‡cö_Øš¸‡ÉL]Rô,šok”ÌpJœŸ³xQ‰eç¹ÁR�•_ôV÷S¸‘§&G‘‘Q³çÕ6øèBÚ”³Wé'€LødjÏ½®ÍÖ¾å;ñŠ–@�­ÿÂÖÄ=1w¶K¡}Þ7F:ì»½%œ<[ß7Evœ!ÚÚixžÕS“þ7Ò›~ió&}îèlNÒÛæœ=fKÓÑ%o¯ê¤íÒþ¾¢£.Ð®û˜†©¡‰c7cÞ¬Ï|Ïeì»ZKÌ!ÂâlO2°â-€�=Ý sú§cÏ-�ÀãœÒÇyö¸õú~¶u"[%y,	ZWE�HOSX¹Q‰Üï1ÆÜŸ¿ðÛÐÄ±õr‹Ðó•xâ±›‰†t—ðUÐöÙ¼eÀ9®!ö™Sã‡7ª½ÙƒQð<ý¾ªuëFLÛˆ‘;ûà nNÇú~.îÌ€%ß€‘Š#*r>Ô€<ý–&®ñâRMé¾m.g –ÎbÞÄ³Êß_¬h_SéaÎºkVNvs ÝóOýfMfŒïWwrLoÎ'Òàéq)güb×Õ•lžƒ9È�3eÁ}¬Š©¡‰cI†*ÌV�W«vF¼Ïuzæ
£_Í ÝóÀTæ³kË,F±�é˜�ôÀý{ frœÏoè««cÃÕ9_`;©Ë»!÷Sîoðf¾ZÈÐØñÝþ_Ûš¸GfÔ¦•6g`ì| +²ÔD¡âR¨îgóL2ˆB˜gzw÷|éc�Æ.4Û=�ß€ƒ”<ò¬¹;±Ýò[¢"´1Öq»<"}÷ð%[ð´ßG­ï·_úfjâ"Hh‹âg[ªÔðÖ …5dŒ ƒË'E,’¬¶rO[fB¾sj1Íž	™¯ÅV­z°«YoÏ™åIÜï
Óüÿm]Ù’ª@ý «.²=‚l"Í"Ê ˆÃª#|ý„g
­û§BÒ!998'ÜçÔXFVÅZ»²¿›rÿ‡™…C•‘+Z#íó£“>n©›kßÊ—E¹šÔvB†a‘t?Wo®ÛÂØòï}2�ò[+é’*i Â+ùz»'GœÉG~èR!–×ùmÒØ�[ö†53�	6dHî¤(¼0çq­Çõ½ûäøõÊ¼æJ£;³…bÀä÷�)äRÏ	ÏPÅrêpà7Y=L§EÐlãló²QHafÆÑÊ²[coîö#[ö53îƒ&3Õ°+	oó¼Ô*œd‡Eæk‘nì«TÙi’Q7BÓß¢øDv|^Ï£s+hîä6¿Ä®!©mŒh.vZCr¸b`KÛ™¥º-Lª/Ïù53NRóËâ,u�ïäˆÔ¶ˆÉi—#E¢’ªRà ™#BišÎ‚JÜO)ùP0|Ã�¶¡ÔR¹^›vb—–Ÿôi•c"¢®1P”žZú-LÎ�f†¹ÊhÞoYòà½M¦¬ncçz¨?BÞ=tK±Ã^œN­?…Õ?È=ÎuÚ•?ß÷B–¾ŒZ-Â[X}�ª'.�ïL(æ�m÷xþH^mô©iú×UÛÄž?ãä‡@%!Š’$bc6eÁ™g–¯…Ð¡ rŸNA5gO;¥ÑuËH;åî[½|íqÊüàÌxÛç4��Wxœ=ÎmlSuÅñüïVWÙ
#2
5¨‰	Aš¬·Ýíº.1£ëíÃetm×R¶K[»v}¸[©ÝÖµ›'º#†8	™sH¶D	ÎDQ£&0AÔd¼€ÄŒ	!Y¤3Ù»óæóÍ}\²tŠÃbã$q8¬Q­ÁO¥ÐSvW4›iiUu).h·`@;�¹ç*éÔDÿ­¤CŽaIX�¯…QÌÖWQ¾¾™£­/gèkØ€º†¡i<�?ÄI¤Ý*Ì¹Ôô†ëÃeßzzÍ§ç°·MEžÖ9†yÆä‹4µû|»ŠÄ=[9„"ZÚ)çh0ú‡	5¬‰ü•¬ÂLòÞSÌð+ðgFG3™Kq°š´Cû�
•CåtupG§_oeÜ÷ˆáÑp%>ÃÔ›Ó|íqc½és¦‚A—=Ý%	¹Z©Û¤'û\1[[WZ«`ðòÉl?ÿÐ3øx«Å¹ý;yK‘7ekÄc3ù“ú`Ë€»ÃÀwyêjÂùH®· Ý½)O@jÉg$¹-µÂ-Ë¼®È£RÀ—q‹Eé½�Öœ{ ÙJˆQûc|;osÈ½é”Ó!yâ­Ž^·Ì­Ež¨éw$ÄŒ „xS�sg7ïïô*’+™²óù€sG!(·v¸}¢Ì+Üú„K{k’7W×#†¬Mo5…£®¨¹Æ™ÖÇwuÔZbþf{ÌKäÂ²ÍÑ–³¼·SÂÝƒU¨»¼ä	DÃˆTÜ&sµPŒØš²sS¼QCzcHIx^MtÅ¤‚bÉ‰é¸Ñµ‡cöN“!äN/?ù?#PÃˆ®çÞ=N�lgtâým·ñý}8Æ }štG×2\:¶š²Ç9œ_‡öñQNÞ£ŠSŸqØvv‚VO<ËÑÐäZNNOAGÌÏ|A‹äe¤ùêZ	*¾Õáú•14~w›~ÔÒÝÞaøòê§˜½öµßx¥{oU�þVÃ�Ÿ'è·Û/1:ûëeÎùÍPÏ‚ù…2ª^øþ)øî—Ñì½Ïÿ~“:5j>Ô`ÓÃ$ÿ3¬þ§NÓã…&xœû·�iñb¦®“œ¹�2­J|’Ã+¼LŒÌ¼ÃÂ½Í\sÌŠKSýŒÌ+‹²¼ý´ý�“|ò«\,ÊC'Ú
ƒt2L®uöÜ,àúÎ †¦sã�¢]xœC ¼ÿ™	™	°46QÄqƒM/Èƒ@‹cËhOKÿ100644 go.sum Uõ—uÃC9XhB™ŸÂ£‚@Ô³j/.MZãSxœC ¼ÿ™	™	°46:yf 2W’u±CµœÀ+†¯$Y100644 go.sum c¢Ìho.(~4uê¾æ¸£2a–³j/8†èé�—oxœ{¯ó^gÂF‹ÉyD&®åa42„7ž]$
 ‰�
à&xœ{¯ó^gÂFËÉyD6®=$
 @´2ã‡jxœû·�iÓ|¦Ù.›\Ý7ÿucô Xäá xœÛ4Ÿéú¦Ù.“œy,2­2�Ë\3]
MLÂŒòÜBsSüs<Ý³²�*ƒÝ|ªB¢"=\\B*M&Ú
ƒ42ZN®uöÜœíâî q»-ã�¥>xœC ¼ÿ™	™	°46Ú�DûÛØäÑn+¿NÓªÄT}™100644 go.sum J(1ÿ(×:©]ØNÄÉõÿ¨ùÞ9³j/•ãSxœC ¼ÿ™	™	°46�w,Žº×QŠ¾^éÄRƒ=›‰100644 go.sum ôÒLÙ;£–pÆig{wCve…³j/kmµà�axœ{¯ó^gÂF³ÉyD6®=$
 @™/àxœ{¯ó^gÂFóÉyD6®=$
 @¢0n�XxœÛ4ŸéÅ¦ŠÎ›³]Ü½0àâìxœ{1‡©wÓEçÉ5N<úæ
†VÞ†Q–É¾ŽF�YÚ!A‰úé¾ÆI•Éå%U9&¹%Ù¾ÁžA•…žQ‘“D@úÍ7ßpšà Ú å�¨
xœu Šÿ™	ï°•ØT…m8,àM%frªK©“Óa6NçãžÕï–æÚÏTÇç¦¥¢î6100644 go.sum Ä¼Yç³±W€©Ö1É•*r:NÄCÿ³jèÉs¼�Z›�,§ÇÀ™qV•hæn8“f3Ý¯4þã€xœC ¼ÿïï°
6v.Á–Èèòú
+BÀ¶ÆŽñÇö@100644 go.sum HPQ8xx,.î=eRcƒ”«¿Ë9³@/ïÙÞäRxœd ›ÿï™	°•>ôæA‘˜T•YDÜ"Û`.aD100644 config_test.go nU~Ø?0‡O‚8Î²¿rÚ³©*’I¨¹Éö
à0ùIæÖ26¯“<3oW&ƒà…Rxœ{¯ó^gÂFãÉyD6®=$
 @~,àxœ{¯ó^gÂFÓÉyD6®=$
 @�.í…IxœÛ4ŸéÅ¦NÆ
†V&.‰iNÞúÆy!¹~å^fE¾™•Ù©–î‘~†©™^…ie~UþA‰é“m…'8ó0O®uöÜœíâî 4{éZxœ{1‡éÅ¦N¦
†V¦‰îy^î�)yEeIEe®™–®�Qî!Þy‘~Ye~I‘^¦.eÆ&“m…'×8ñ0šn¾á4Á Ëì‚éƒ1xœI ¶ÿïï°ª=ïÏ~9€Ù…îX‹W%yF¿ê÷100644 stats_test.go rÚDmGÏhŽdE6b—BI�úI7“çˆ¨^FéXxI ¶ÿïï°ª=Ö §?*Öâ_ÿL§}Î ¢##à100644 stats_test.go C”J½ýúUN$¬|3®¸å�êi“çˆx(ÀèƒÌ'xœ;ÖÎøµ~Ã{‘Éâ“E%’œýCýB4´4u€,G×`gW_O?�í¤œüäl×‚üäíMŽXpqrºùû*¤–åÆƒåŠ’4õ6Ï”ÔÕ ×í$„ï#fx?ÀýõŸ„°ï//
// Only blocks that carry a time are counted. A single row whose timestamp
// parses to epoch 0 anchors the span at 1970 and swallows the whole index:
// Hanzo has one, and it turned a chain producing a block every 11 seconds into
// a reported 58,370 â€” 30,602 blocks over an apparent 56 years. Zoo has one
// too, reading 2,389 for the same 11-second cadence. Excluding them is not a
// threshold, it is a definition: a block with no time cannot bound a span, and
// counting it invents the interval it appears to measure“íØ WHERE `+blockEpoch+` > 0³Å0*ÁÈÅPg�³TxœÛ¹ÜwÃr5 è{îxÞ!þ§M±T°§&
// One block with no time must not become the whole chain's history. Hanzo has
// exactly this row: it anchored the span at 1970 and reported 58,370 seconds
// per block on a chain building one every 11.
func TestAverageBlockTimeIgnoresATimelessBlock(t “”#D#5) // five blocks, one minute apart“Ú#z“®“�$?%z',999,?,0)`,
		time.Unix(0, 0).UTC()“Ý“�
“\%DStats, ““Ð%stats")“ï!(float64); got != 60“i%<v, want 60 â€” the 1970 row is not a block time", got)
	}
}
ržBåˆègxœ{Â¹‡cÃTFkÏßÏLÎ+ÛýOK—gCÂ–‡ünç§˜™˜(¤åç•¤æ¥è¥ç3Ìôîkà¾¹¸¤æ�çÅƒÌ÷^¨©OþÅhY¿£[…móÙ¯­¶ä^™æ~0
ª?=_¯¸4—!‚ûÙ£Âœ×7*ËÎ}U^±Tð{G‰TEnbfÈôæus:[_—=v~Qø˜…!Ïl�ÈVåÉ½LfÙU‘K'Î_¿jw¸~Q‹	_úåž»¯¡z‹RsJ2sSã‹‹SAfDÉi5±ü˜$|k›êKV‘‡©ÔZO¡+-I-.©íxüÐ¨y‡îâÕ—>8×÷¼qì{Zi²ó2Ûœ„ÉbuSwnvãÑ’r¹ŒÙBý; †—$–ÃMxX~örz€öíÙªg¦È½Ímß¼K©g² ‹„í—2�«ŠœŠ<'´Š?’l¨¨³-�™PZ�ZT–Yœ_2ÁõÇ÷‰ÿÿäÉ³VÙq¾{^3GB4 Èj­Éá‡æxœ›ñÔp‚ÞFË»L“UØ…&«0«L¾)Æ3Ù€�mr³
³‚‚ÂäJfs�Üæ}Ìž,›¥ù¢˜À¢%®`z‡€“‚Âæ7µ\›'jÔ3 Ý5-º´	xí\ÛrÛÈv}¦¾¢•ŒA[„<sÎKäñTÙ–4VŽ­qIš™“8.$A	60 ¨‹uT•§ó©Sùœ|Ì|IÖÚ»»Ñ )�'©ä)ž‹I ±±{_Ö¾t7éäSz–™yš—[[ù|QÕ­‰·ÑøºÍš²ù8›Ê‡rRMóòlçcS•¼0›·ü+¯vf2²ÌÚ�ó¶]ðb%Wi{¾3Ë‹Œx¹ikP�{Íu9‰¶†[[;;¦iÓ6ŸœyYcÊtžMÍ¸NË©I›&kS•ÅµùõßþfòIU&ÍÅ™áÍ¢:«ø…DfiQŒ1�&1Ï0~f.¾Iþ�<6íyf~¨Ì8/ÓúÚ”*Ï²Ú4Y}‘5rûyQá¹IµlIèäÍ³'&K'çÊÁƒÆì›zY6àÅääå²4‹¬ÅÂá¶ÉÊ‹¡ÙËEu=ÏJ¡Ÿ�Oê¢¹©ê³Û�ì
7ëltƒ¡·»Nþ)ùç¡Ì€Ü–guÖ4Çxfjþ_Ø"/“´�œ�03ÓV`5mÉË	8Ï'YbN{Sû”e‹Æì\|³óÐÄy9Í®0ËG¤2öóMª9”až½9l†Ûfç<K‹öÀX“|l¶=SœàyÕ´*‘ËÏ?`ªÓE•—u‘]di…9Ô†*6uÖ.kê��ÿ˜àü{VíŠRæ°«zÞºH!ýžÊ“ƒ1„ƒº*[¼Ã)Roóyf óY~fbÇ¦Ê®Ï¢µrï8+ÁÌœ�ó<´¨:K§fVWs3Í›O00;�:û×e†y7•IIl*Š…&JÓ\¦ÌÜ\æí9…	�—yA·CR#³S7'2G�¯³I[QR�W�O³]“ÏÌdv–èˆ½¼6qU›¿Ûÿó›W?ï¿?9}vzøâýÞáñ�,X‘Co`"»Ê›–/ôt·UG#q˜wõi	CXÔÙz¡�Uåˆs$©IµP>ä†¨f
_;8¡5å�)èl©y!�.Fó
:Àˆh¼,§EF2n&B%/GÖ»¦Ù,] °./”7ÏD»œÁ%ªÎæ 65yé––E®;Ûÿ°óAýÂ€Ã³ªÌh¥d#-¯ÕðÊª…`E>É[ E��AHYº¡IŠÃÐjÄqI ó|:VçÃ¦s`;Zwã!qÀ;*@Ã4Ëz–Â/·ÚëEf:[nëå¤57[¨ÜÈïÖ@ù„e<|q>¶ëkÜ¨0!þ™5ðN½ª”Õ5nÅiq™^‹º€Ïpk@Ýéuð¬¸½—y±n €v¸u+Žx”]zþ�Sz‹ºç9ä_À¸Æ0Ž)¡)u.JTžp.Võu²5[–PŒ)Á¶©Wæ>4ñC÷j@k]Wõ�²£Dä»Ù}J©œ,Ç±‘mƒÈÂaúp-<e¾zj8Q<	)•øUln·3’ùÚ½èíÒ#Á�ÕÁ®©ñ/ÅübÀáø†uŽ+ïã�§OMÉëä›©šä‡XvGÜšŒ‚ðÊÑàÕ>‹M7MÐ BÄ3DHÂ¬ðÎêë¯1,9löò:é³Äéý)ß�;'ò €7á_+†Ù6IX=„¼£Ðõ$ú›0écñÅI�kE` C‹Û<À½ú2o²n¤7XbË±d�–ðös  UæÖNâ™ñú‚)Ç_LF0k&CóöY¤¸A­›z t\Ÿ¦mšÌ1ˆàM± ¢ Õ¾li1NV¤àåw?EµÏÍä¬ä•Z§‰N¯	²€@XqQ¨H8x7†”úA×Å@„6å¨è¶Í¢XÄ��ÙU[§6‘€{” Ú†ÚŒ³äŒ ­$GŸ+ÉªlðË+&Y GU€Ê„<ÉVvå]]@B:eÉIù²Ã0ÍÑ™Ùd'o5½É$­úMpÞh)"Âx¾¼2™„&L’²×Ë+q\N^
f ‹âè‡ýSŸòDÛ0 ´}$ApÌ�£�¬ƒá‡HKîî²Ž`ø+$«N¯z¯†G4AJ‚ü{–È¬Ÿ1Ù=‚I6Ö­7²=¢™uló�ésæ6ò¼xÊP FÁ|…´)›íøhÈ ¿óæèñÅ¾q€aè�Z|zK@2“!Ýþ©AœEpQùFæ£1|\cÞ¤´SÉ�aêu»\8ã –´LÚi(L%GÍ¡ÔLŠ,eæ�„²Ù¨ö•¹Aloß)Hš,+)äyºx«WyÑøæö~ÈD”Yœ{º*
$k|†Q‹ ª!XQ– XO �BAb 8¤HVA‡0�ç¦?1k%DYÀ±d¬‰d�}XÂúJ™R–Pí1É	=þòSJ4òkü%o•JÓE~].3�<ƒ‰
•U“œV¯ªË¬Ž]—ì_µq9´!g’ï#B¬<J¥þýqáþÎôÃe6^Èpíò56O”ÏëL@<p�ê§O¾-ß=áW‘÷úp;Ä`p ÛÁ€š}jÒÅøã|ŽÒÂ<ñ¯Ut
p«»F0Ç‹ÛxÖ†KµåóX—z[—JÍ˜©iÙ$Ÿå­kéG”g)]àŽ5eGÖŠÜR3÷Àá:A<I‡ñ3o7:ÆFVûATpò¥º'q‘¶Sã¿42â8kUÙd¿Ô9RjÉØìu)”:Ï@ÄÇIV¢6â+£Y˜’.“—ˆÆ0­!�º�#¤ƒH–ÚÑ)RgØ
_ùí ª=¢ÖŸC�ž�øt]4²ÅrŒ*`.5Bgãé?ŸÃ“2�˜¬Ðú.'3:ªÚf´ñ%f‰1·=µ3:x]¯T¦„-‰‡˜T‚@ŽÄ3)GRÄ"¤Î :ÀL)¶PÍ\²¦Ÿ§ŸPòŠÑC¹¬êOf²lÚjžFR‹ÂtQ5M>†É¸²J
S[{îº
•<4À“Ï1qH@’Šˆ5C!ijbÞ$×é™ðY†r’u÷LÍ#›À3LÜcZGü;L„Í	Rj#ò.èö%.ÇuÂ¿ q:)–g:F
	~]'wš`Ô+0û�'?1§_Îü	š‚Åû¡(’ôHœBk&aÎã¤ÿë_ÿ]¨à \{75GŒÐšiÃaÑGY �P!¿Wêm%$,Ò±`…žd3M–`åhkŒ
ôQ
Ü•ÚY»:°%ô�„’�Q\fÆ\•Õ²Ö1²�DfV¤Èß¼ »š‚Ñ$§¨4Õ°¢0ÅúõmþîmÔ@ÒÑ;ÆOÅB.ºFÆ¸Wb˜Újä0Y�HpïXtçf‚�·Ëšf	ñe¦Ä˜ëdÛ¬iÞÃA$‡ùÊèçÃÓý÷/^>;<:‰v¥h–ÿéÄ¶ýˆ£ýÓ_~<þS0ÆªûDb£\ÂÒîH]A\Øvž?;Ú_!r›î½øéøxÿèô½}†»i®ròþå�'§ÊŽ¼	4 ˜q5½Þ6ï9W6Z“×€‹ó´ˆ14K%e/�+ìÏ&Ï—tm<´œ)º�H}G—èÿU—ÉþÑÏÐñÏ�ˆù‚ð‚{äÉ¿”¸µŽ°}dŽP°L;Ó‹´™Ôù¢å;Ö0}›Ëj4á5¬pLÆžs21RVÍW5tÂ;\P²B×�„9;¯Ê1FšÁèLhïÀ"ÁßX³D"¸èWÊFFdÝÌÇÚKÚLÉÜØÀ`Æ	_ÃáæÑÈ1ˆ‡sc)ð[ss¹šæ	láÜ37ü?˜_ãçæôsë‹iªÂµØ%ã‘±¨Š
�³·�ÊA"�‰þÄg‘ûHXR<ÔV/Œ«mb¤Bf£ßÕŒ¶»)=Iö½b¢š ë’©iáu|>¥ Ë:Ê.1lnF_(` GîÄNÄxœ•ÀvÀ¼°®	¶¤N:}?ùD�Ò…DÌFä†¤«‹H©äb‡òÆÌÆ—èù~‡nhr¬ùkßÓ!Ã0"ðòÿµàg'¡¡ È"�Ü6d’¿ož'²öè*^Ý•K®¦’š4|oÊ#ƒ*Ü†5ŸF¦†IË¿ðžÀxíš<‰9D_ Ž†¾w¡{*a6%íqFÖ�™ŒÓc•pho|B±ùÿ	³ô•.ø�¥ü”°7Á‚Pxã¾Ë�t2AxQaé÷ÇÉ³î»Ü'8þTöYEÎŸŽ_É=‚Kx�È£÷\R0ïÅIXy.‹<bŠëº–ÃÉ¡KeÛK|Í¤³PÃÄÇÂ‘fVÏù"á.Ôž «ÝM¾à¬¨Æi—¯_1'™&›¯ÕIbÉŸ`&Íb Y.¸À
MûˆæïQ¨EŽ·±fhg¥è\¡<KO_0U‹9¾‚š�âI¢"	š£VËí-±\WùI$Oì)ÛÈ%Ø$ã=¡‹mûFA9wI“¾	„ŠÐ†qÍ–7&@ùÈì¦j .Cˆà­®ls	T4)`ÑAÜw	·0ŒBBß,+¥RŽ!…v™B“±¯ƒbj#Î¯ÎMèå­~D¿ù�ÓW¬('	Å
7Š¬0;]Á­Ú(†Y^CË]èê™«H˜Íäƒ¶}K³›k|¤§LD˜jdÛ|¬ªQt‹-Ú¦DÞ^ï’�1#Ýð-·Q—DoøÄå¹qZ£Ö¤×ÄÈn¬9NÒBŒn…TÒÓ$Iî HRúÊ„=f›�o»kPùç*IsOÓ/£››iËº´]nWéUJõÈ¾~›%Ø—ð²‰TºÈG¿ÅI…ãî¦f)%÷ð¥Kï:E6®EF¾NÕBö¬Z›ê}$Ã©®“”Ò×iÇÓæs'é•y{ÒžH_pº?bU¯sª­Â7éÂ’sjÝH/ gÕz7½`ŽÖRÖI®ÌÑ²8Í.B›^�°•Ø:5:\ÀáZÓa•P}¥€ÐzûÂS
&y'±•Inb‹ôÁdýW N(÷V¤óq~¶¬°.E˜Øð:Pìãoü‚_HJ�½UUHâ,�/M_,	v½jEk¬œ¢Lm�„
±]ô¿¾}ü9Ã2Ì&Anù…•¿¼¦‘>€žä-žž é,!ÅtÛ w8¿íZ!ÒØpðp}ƒZ©': ¶
ìÓæ¶€
WQ¢<e™�° v%@ýú×¿ÉœEN˜³¬Øý)[på~‘Ö2–up'G®®,K.¯¡o…~¶�LØmF%Å…DÄÂ­Ïož®`¼Ë#)yåz·äírmòh"¯ê�~&‚¨¨]#Œ�½`a„«-‚Ý+£f9žVÜŠ¶Ë¾Zè?ÛŒÿtÚærJ~™6o°Ï%¿²ê!¥G\eášÇýƒFt[’ýŒ°½	Œ2Ôìº> â«öˆ<è®{’XÌ/t-Î¨<cC±�$fµ†‰,^E;îSÉ¾�ü
z‚Ç”Y#tØ÷åâ—¢QÄ�h´­Õ}UC2\@È¯�GÁ²Ý¢çèÈ�Ø!÷ i–Ä/Þx®1>x:	ŸÆÞ
FËíà{-C hëo‹Îß<ŸdrºÑ·]Ô³(ˆeé{/KÕa$0G»Kñ‹G�}ðÍÎSm#¬®3ÓA´ârÀ_¾;(ÌÈ	y)¿ˆCgœë‚Éž·) ØÔÉµM}nÎôLZÿv€)Òq†æ?—€ šy£ØÃÚþ©.ñê*v"ÍLMd»¡» &ô­ùõ?þ32ÓºZ ¿XŠH.]se‰v‰ô(Æ‚¹<÷xØ7Ò^•¶�¤ñ¼8©À!W˜dQ#1/#’L+9wˆ±ìûGaê»Ñ=Ml™–×ÊÜ´ïÔŽ‰‰”˜'Û%6Å
Íìl¤ƒ1½µ�'Òðçôjn’l°»EËDÎÌH>Ž-¥EÖøÜÛ²ºëü!Áy*k,U­‚ÃEô�\ù>ù©ë÷åÄ?º¦!Pã *`¿CQèûÄš@.ÚâÃî}ub‰÷¦I¸a™`E´‰Uúü�¼n$gF<Ž—tF²?¥€Ù1Q®•ékÜÄÅ?•°É'æ2óejAGÃŒ³I
åNTAÏtÒ.ÓÂÐmž03;;ÍShƒæ¡#1ô"„jbì)Võ(Šqi­„ÍN6YÐÊs¯”õ¨´@O(¼‡=Ý`wH0Ô,ÝÍšÁ±'Og`‡3Î‘þJÉÛó™–à�ãt:çÞDrº?3_º±<Äún©"aç{ŒGØŠr.¢`!É-Ã nñ'Ž kìƒè62Ó¿±j	×…ås–'Dn‚ÜrÈ(ÊÔXâô«Ÿ~µí#Ýb²'i2˜j.„tÙˆ©©ƒˆ�ØÀ„}·ÃGLã<G V]�Ù_ÞžS(²ˆGS²«³X8´®b©ÐÿÔx G(|‹lêáS«w4>¸W¾}—¨Û~�ç�«aÌT\q:¢×·øv§;sÙ±dØÂ~R¬�qñÛ9–”§u>C	;ˆ±z;´´Øt'{a­…$ÌG "K›nC‹v?^5£ÖT6®}/²Âr¥T ˆÇð0Jc¼!ÛxË­sÑº¦íF7§Ëj·ˆZådTÌT¡�gpòÈ)9¯9Ü³+Og>±R`‹©Î‘C;µ‰ }é’W?†I·† Eâº{ìol›ÇÛØÿ\”¹‹Êf/²éÆ® úÅI ‘pXûs½8aÂn8ê
“2yÅùu3sÎj»&þ LÔÒ±jgï"�`^²y3K—Ø=*\t0ý-”ÎìZ»à€“
¶ÐrC%·K„;¤péTbIÒz€Æ–¨UÁ["æ¢[—’pE×;,¹Ò¥ol€�ÇµiE¼±mSÁâÈ¥ŠÉ��½Mn Ñ²ò±žïkCm*ç(±pääÎBÙyy±�¤¿…“yu¢å–=g FûQ
˜´þ•Ý“dñÐÂ!û}òþgˆ3«QeG½0ð!\Ø½“KV™Q*wŒ\kEdu	3Ûe®4ráÝ�Bý²F *ÁÆËr:âº‚U�†4öt]ÍÊE(@eÆÀX‰»Zx°#Ìé«½@Éˆ¯ØÜâw_0#DÌ‚:Øú€f€ÆË^……ÔÅæ[=Årù�„È*kqxöËð�vS©c£ZÈN¼ç+pŒ‰+W#BCÒS 6�G¸m^æ9FaCñàBä&�èÉND,Æ£msÊ+rPiñ ¼¾¶yéÐ¶Ï¡¦”(ÍÿìFûd;-ò½’��%çØê¨ŠÒ�´'¦ˆåz\B;¸zè�‡‚l¥,ŒK©ŒÝYöýSóX_¦;AÞâþ#£<3Ü}§˜D3·Œ½dVKÎœ#ì"R~ä&æ·6H­ŽGÚ<õ›H½i_dç"1ƒáT@2åÖ(�$òZT8•EPÉ÷Ø(pè}Ž†pN{Aì¾FµÏfàã·‰ 3=†åöo„Öê°¥PB£Èfè° <º¼áÇZàÍ¤jsYûuoQf*65¦£Kì«ƒ¹"�CNš,0…‚¥wÇÕl8f‘ïvË¬a¶”úV%XU”#µº>îj�;ŠJ\ˆs\ÕF8ëdá�À£“¥Th6ºTvw:8tŒØ�4nßÍF‚çÖäq×VÁËù„ã2xÀ^òã­ïqc©¥	þü]·tˆfYèÈÒ±I~¢I«5Z«8»6=SëÂþN‰RÇÈàÎÊü³VoÈ˜ÎìbHˆaIÕÿƒ}e&¶c0ÌIÝõ),R!tuÅŒmtëSÁ·/&äè„¡/¤Ö'$Ç›©;ˆýZ›°õn¾II5•£d™
°ZéÝÇ•'Æ¬÷ØQ©>=•ëŒÔ7ª€µz*ª¦Ã7Ø*vgé$eòæO,#›‹I2ÁÙ”Y‰P�‚’/ÿ †ð]Y9Ü·Ì,áõÂ¤òâ‚SØ†”ØBx!$˜8Þê›+ŠØj¾LÒm ëÓZm¢‘žÐ’žPö#XFÐöíYŠ&4ö·è	›xxdJB²† YçgçÜîª­$†3f×�¶ÄÂ¿Í÷{N*AËÆ1œ³Oït	¬
PR6¿B€�3µÑ±ß>ÁÖ9ß6æ&öÕ¬µ�Wýõ[È4Sþ'æãþ•L´ÞHo–QRvqa7!7Ïúæ6&ê¯=4o˜Êá"„?Œf|NêàÝáÂÍ<½g#Ò'Ò;A°(´>Ê—EAS×7œýK(_#òKÐf£(üŽ€s+ùJ^YÃ@ŒµÙ‹•žÍ]HÃ$w£¤™‹øé3°b‚A¸§¹<¤¹�ïŽapÐwæM|\"gø¯¯F©Â$.€%’y»‹‡™
ÆˆT‹«NÏ]­ÀñŽ×¼ùéœžÓ±™Ô—¾Ö2Ö½Huí4në'j‡ˆ¡Öo�ÐCsC±P*DoïûEz^]UèéUnzÇšÂÿëùóÚ gk„nÑÌ7ÿ7b�®„†&–ŠV$¢˜(ˆ“Í%:¤Åâíš•Þê4?Ë[ü:€t£¤üo«OY©�üm‘8ú-×!±.ÀÃ3�±x‡=;Ë9»�#Éœ•~|øÝ©)Üõ š†¨ÈÚkÙ÷ƒ„×ò0ÆAÛR2dYÝâ9°`bÄ’è›ÇØÓÎ¥L|JøÏ7jPT Niu„s�Ñß—nÆPÖˆé\ „Çf'ÿÖ¢‹œö¨È!–…1àAú@Vùí{óàóƒÍ0bÉvúÇOð÷wöøüè‘< ËÊÜ,o1*ž°¶"qîÅ2ßáóçC~ùÊÞzÜúÜ4³¶`%úà_{Q¬A•ÝÃE,¸…)ì°*Q†Æ¼@T“ÜKòÒâÁqÔžþ~¤îyä+³Ìr¡@)ûñÃŒ¶5qIÖI|[šE/Û&ÒbDw‚Û�˜0øŸ_/°@„Åi·ÓWS¢XÀŸŒfW°*‚D0Eîƒ¦‰D~òÅs„4(«ìÞ\Ñ¹B-ç¾žˆ©+¬ôéº %E&…"�ºÈgLÁýí.i¤WÐ¢µˆ�58OÚØ”€œà%}Å¹a§u>_‡âÔÒàMÍbØÁÊ.ƒEOÕJÛï“ž¨;ëéÏm¤\YÍÐ*ÇQK´¤eÿ…l:ôU±tnÝÙvñDnµ“ÌK¥ÂVŸlÅÊó0Œ)â"OÍ|ïèdGWšóêÒàØf>ÇÎéZåž£ôœ½^þ¶Kò¿crµ¥nÚïHú¡z`íÛ ’ec…õ$çÀ‰¾^&¯ðë,<×ƒâÞù©„räžíè®’â)Oèl†#iüEt_ì	zú˜õ&¶N)tk,½m!î±„¬Ø3¹ë­Ø>ûâ¾z˜FŽ£è–ÖÙƒˆ¶¯û]'~í ’)Nqc¢›P÷tx½˜èhÜ8~óB/vûm1{mO®çãªÐ{üå	GŸ‡çôª­Š…þžv§xã2í@Åv�á±ŒèZØÃw€\l>Æ�?ðLježI¬ÈÏ„p¤œÚ£ûË	&69pú	„Â£"Õ¤ÍÚT�¥ÎŸûï‰eC»z†Û1OK°}ï2›Ž¤êÞt±›îLjÐˆäHÜNª>ºšø%"{t•GV×‡ñªÒ;Íº>öã‡\ý`9éº>H.ûAØö¼á¥W#n‡)+Å�Ê]rÄh×ÒÀ.Rü¨ÑÍ|þ(�ƒxk�d@Ï:£È�g9œÈ•]þ<„üžˆ}ò/ÛÝÒÙ²ÐÍG²/~Œ"�ªÅÎlÔ³lŽ¡?eOq[€˜€¯¨Æ¨ýmyfõóßùíŽè±Ìu€ßhÂAåñÖà=I@ì—´ø„}D±¾©=2wÑyoàa*Ã�}¬&ÊÙ*pÂ_‘¿ÈŸ’}ô¨Ãþ4ƒø‡ÅüÖ6”Qº3.ùIª§?Å«„!îù/^—¸ÑãZ¸7x£\úÊ–¯£�O"
	"net/http"
	"net/url±TUGbrowserRPC(c)“®GgbrowserRPC picks the RPC URL to hand the SPA. ChainConfig.RPC is the address
// THIS PROCESS dials, which in-cluster is a service-mesh name; emitting it here
// shipped `http://luxd-headless.lux-mainnet.svc.cluster.local:9630/v1/bc/C/rpc`
// to every browser loading explore.lux.network, where it is unresolvable.
//
// Prefer an explicit public_rpc; otherwise pass RPC through only when a browser
// could actually reach it. Returning "" is deliberate and better than a URL that
// always fails: the SPA can hide the affected action instead of offering a
// control that silently errors.
func browserRPC(c ChainConfig) string {
	if c.PublicRPC != "" {
		return c.PublicRPC
	}
	if browserReachable(c.RPC) {
		return c.RPC
	}
	return ""
}

// browserReachable reports whether url names a host reachable from outside the
// cluster. Cluster-internal DNS, loopback and RFC1918 literals are no3t.
func browserReachable(raw string) bool {
	if raw“<&u, err := url.Parse(raw“Ð'false
	}
	host := u.Hostname()
	if host“<&“3E“=A||
		“ =svc“= 
internal")“ìB:host == "localhost" || host == "127.0.0.1" || host == "::1“A&ip := net.ParseIP(host); ip != nil {
		return !(ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsUnspecified())
	}
	// A bare single-label name (no dot) is a cluster service name, not public.
	return strings.Contains(host, ".")
}

// ³H5ÉâÅàÔxœ{¯ó^gÂNF£Éy„7ž]$
 AûK¾žxœÅYmoÛÈþ,ýŠ_¨D¦œà®8¨ð‡Änq)lÇµ“…a$+reñBíòvIÙ>Ÿ€þˆþÂþ’>3»¤(Yr.h�æƒE.‡³óòÌ3³L)Ó/òF‰¹Ìu¿ŸÏKc+÷{‘Ò©Ér}3úÙaa:¯è§07ô£U5šUUI×®²ttYåsõqq“W³z’¤f>*ê»i>šgƒ~4™Ts£/•]ä)m]:1?>».¬T÷¥r¢2¢š©Üq‘ë©Iúi×ßwâP\]Ã†:­ÄC¿×¨õV	l÷Hw¿w&çJàßJ(l¢éÁÔX?]¿÷ÖJ�­ÉMx¥´jšß±`:Cè„+jÈ/±ÿCô	gÉ§*-£¡ˆè&üFË!?ÿÕ˜Õsº	¿Íó™Ô¿v$üm{ÕH•Òº•ß5$²äP_VÒV§ÀqîR³Pö˜[‡°‘ñ²(Ämnuã¿=¹(ëÊì[u“»JYÇi*Tæ�v‰x[˜ô‹µ®ò‚²%üöþ¹È¿H�®Ô]%r'R©SU*KúÓZ§"¶âÅ©¹¯¶(¥œs,9$ú)i€5òãÓP¸E*Æ‡éœ7½ðœ~àK*Ëª¶@B�ÓÖ{¦ncH$áM„SÝ•…±Ê"´ˆ{¯Çr¼ßrßÁA»�Eüêà¡¯Âél°.Ï
õ�M]Åx,‰ºð–$ïõ¹R6¦Å%®„w˜‡âg“kDbLÁ�éõò©xVù¾güÒt¹¤?€qrìVÓ8º"M×Ý4î!ÕþŽ÷:ä8ÆPÐÆÉ›,³áò¬0 u6)­™¨3“©x›�WÂUCòKö6*k)Æì!g9ü‘Ÿ
è<ì°¦‰©Ì�¢GoŽÅÞ"Ûf	šØ<‚[®kJë2”èA–¥ÒYÌ·C6Ò¨�m;ruP­¬ö²µºÀ¶…Ò^Ï€\�¸HX!¤bÀÆ­°µƒÍ€{�ƒ7Æ"óHS¿çT¡ˆ©–¡BÛ€Š_jesª>¡qûÜ1ãq-NhO4p¤D¾+7¤—QrZ´E
Ùû­UÚá,èg®bÿ´Ñûà¥à¤Û]™«¬ÏŒ«"ïÔ-`Ì0Ð¢_ghN¤S”r´�ä²1¦Ž1�À‘.¯$ó¯5yÏ¾÷{ôwØ Ç–é¢“ê—Ñ¼2¢çDzÜnTE&¾;Æ
@…äu@¶%ãì•‚ZÀmÒ«)*ÂŒæ-†â™Dû^E~)ºNâ€­ÈÂìYStQˆÚP4*iÏ‰�Î‡€œâ\8ñïþKxCœxvò��^�23hN«|¡Š{QYtvØ’°º÷Z‰‹ó#F'íÇNÓS�R ,êSbñ*yý}òúÇ—xÍ«ÿ–è{CŸˆ>,ù3tL`ÚX˜"S–wâ‰Ùæ²O�’éÌ;¼=‰D/kS	¹@Nå¤@*§Ø˜êš6'2)Y¡æ~ƒ¨‰þÇ�¨™ÞÇ“†Õºh`“NrÂ±ùBaòQ»Šü/aâêeéÃñ2;PHÔÝä€„¬Ü°Ê•¨Í5;„î×é}­I¼Uz*ËÖ¶$Æ°uåA¬êšµÉŸš›�Ô èXXƒy/ÏÖòlýñbÞ–Š …®ˆ7 ÏÄá¡ˆ¢­Và9Oe	�×®Æ?^³5xÐwx?Ž&Ì‰¹E+mî/Ržª7À°w,ÞýhÂà9påYÍïÄJÃ£—$�¿´ØìÊÑþ€îè@Ù£f%öÞÓf¼ƒ;VSYI>³ÉO²Yˆ9ÜLc~ûÈèi~Ã\b¯1�ª¸à¡‚Z4¯°´‚Êæ…u²Ýs£Å«Ñ$ÝŽP¼-§åa8!ùµÖ^¾4µMéAÄ#;¯;Qb�ÉCßÝwB¹éI~Èˆ÷Ü`EwìÉjWîÅ½Ðž¨iNm
•JûmÛòP*»ï»"q›'
ª‡³ø&	¾ÍŒ~ŽÆU—¡gu©âwô½ÇLÐmvOS¥ÏÂ9çÛ}ù¶†EñÅ4Müö7Ì–Êh”æÄ›|ATW—;Hc5O¸MÞê<ÚA^[ØgÒ¡ŸŽŸthò_PÐä«4ù*	M¶³Ð&ÉüöÛS”D1(Ó�'�æ›'J.¸É€Zc¯a°]ýÿ‡Êþä½Z§—Ý¤øu¾Ù Éod�Â³Ç|ƒ6I‡‡£™¢Ã,Â'}9`§õfäóU�Ò¾_M‚NÍ–�Â‹0¤U¶V¤�ÎÅ­ßÎ3X‡ƒæÁc;U=J	©?ÐØÃ¤5W’<Ej=ÓåÎ�v*$íN˜mo i¹¡SÁô™	¨­üÁSÄDíÛžô6 Ù²�1™ÚŠ\inYßÑôžñýC8ËŽÅkñBtN³¨Ù‰ÉîIþóCDßŽ¨ÆÑëä ReŽ_£¹Â´‡Ë¿ŸØ­w8Ð—9wÑøêzù¹ß£@µŒê­ÀÓUÁPÀÇº"O%yë¿RÛ‚Á9þBIt�˜¬leVÏc>v„Þ1yy8iœBW¦�õÎT?}øpÞb«¬<J	gŽóúýÁÁè‡ƒƒNIW‹uœâdö^J�¤Ñ¤¶Í�t%ýÆ^BU@îze‰Lús2]0Œ­bjÍœ�ýÛ©xwÌÇIÿ”ùÎ*ê‡ôMáž…ZŸ1“6KMÖ$EA6pÂgÃ¶Ò¹|JÇÖ[Bl£CÈÂ"PŠfŒ>Ð¶“ZÜ…/mt„åmcAz‹L�…éÊ¡�pB ÀÒÁ³y<VQ'ŠYY§Gƒñ
QÉF_{Y.ð5‡�w~Y×žIúdšNë<PÐ·â¡ÑµÇ¼†Aè\ßbm.-AM:Tî_ºÜÙV´Mæurq‚.M‡ú\ÁkuVC_W«¶n“NO§bJ|�H›¹Yt1Ê|Ù_¯3ïj�àÊþÞñ—Ë÷gûÍù”!ÚF€¿)2+bnž6óUmq˜õd²ú$Á$Bˆ�QcèYÖž$µ.A>¬HKxÖ¢5".npâß7&ÂöCöa	Ûwh†vºj¸íØ_úù‰¿²«¬å9Ú;9•ÖÍdá©ë	æÂ-¿N¢_!êõN†å¸þ>zõ+qðcð$Óvímp÷ˆrý×Þ�òÕWýÞE‚N¾Äg2lyñ]£÷'n¤âEçÕÞ©rŽþ“#pKxiîWù­e³È¡¥e¿óÝ’Ó§�¹iãÖìAâ—âï¼	�¿jnóŠ½xâ­Ý!N£%`È =zÇþËQ÷ý$ø7è–[ðaããjï??¢æŸ’#xö)	Öî3¾U�bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"
)

func TestRegistryAddAndList(t *testing.T) {
	r := NewChainRegistry()

	err := r.Add(ChainConfig{
		Slug:   "cchain",
		Name:   "C-Chain",
		RPC:    "http://loca@lhost:9650/v1/bc/C/rpc",
		Type:   "evm",
		Source: "config",
	}“ t.Fatal(err)
	}

	if r.Count() != 1 {
		t.Fatalf("expected 1 chain, got %d", r.Count())
	}

	chains := r.List()
	if chains[0].Slug != "cchain" {
		t.Fatalf("expected slug cchain, got %s", chains[0].Slug)
	}
}

func TestRegistryInvalidSlug(t *testing.T) {
	r := NewChainRegistry()

	err := r.Add(ChainConfig{Slug: "BAD SLUG", RPC: "http://x"})
	if err == nil {
		t.Fatal("expected error for invalid slug")
	}

	err = r.Add(ChainConfig{Slug: "../escape", RPC: "http://x"})
	if err == nil {
		t.Fatal("expected error for path traversal slug")
	}
}

func TestRegistryMissingRPC(t *testing.T) {
	r := NewChainRegistry()

	err := r.Add(ChainConfig{Slug: "test", Name: "test"})
	if err == nil {
		t.Fatal("expected error for missing RPC")
	}
}

func TestRegistryRemove(t *testing.T) {
	r := NewChainRegistry()
	r.Add(ChainConfig{Slug: "test", Name: "test", RPC: "http://x", Source: "admin"})

	if !r.Remove("test") {
		t.Fatal("expected Remove to return true")
	}
	if r.Count() != 0 {
		t.Fatal("expected 0 chains after remove")
	}
	if r.Remove("nonexistent") {
		t.Fatal("expected Remove to return false for nonexistent")
	}
}

func TestRegistryUpdate(t *testing.T) {
	r := NewChainRegistry()
	r.Add(ChainConfig{Slug: "test", Name: "old", RPC: "http://old", Source: "config"})F

	err := r.Update("test", ChainConfig{Name: "new", RPC: "http://new"}“ t.Fatal(err)
	}

	cfg, ok := r.Get("test")
	if !ok {
		t.Fatal("chain not found after update")
	}
	if cfg.Name != "new" {
		t.Fatalf("expected name=new, got %s", cfg.Name)
	}
	if cfg.RPC != "http://new" {
		t.Fatalf("expected rpc=http://new, got %s", cfg.RPC)
	}
}

func TestRegistryUpdateNotFound(t *testing.T) {
	r := NewChainRegistry()
	err := r.Update("nonexistent", ChainConfig{Name: "x"})
	if err == nil {
		t.Fatal("expected error for nonexistent chain")
	}
}

func TestRegistryMDNSPriority(t *testing.T) {
	r := NewChainRegistry()

	// Config source takes precedence
	r.Add(ChainConfig{Slug: "cchain", Name: "C-Chain", RPC: "http://config", Source: "config"})

	// mDNS should not override
	err := r.Add(ChainConfig{Slug: "cchain", Name: "C-Chain", RPC: "http://mdns", Source: "mdns"})
	if err == nil {
		t.Fatal("expected error when mdns tries to override config")
	}

	cfg, _ := r.Get("cchain")
	if cfg.RPC != "http://config" {
		t.Fatalf("expected config RPC to persist, got %s", cfg.RPC)
	}
}

func TestRegistryDefaultType(t *testing.T) {
	r := NewChainRegistry()
	r.Add(ChainConfig{Slug: "test", Name: "test", RPC: "http://x", Source: "admin"})

	cfg, _ := r.Get("test")
	if cfg.Type != "evm" {
		t.Fatalf("expected default type=evm, got %s", cfg.Type)
	}
}

func TestRegistryLoadFromEnv(t *testing.T) {
	r := NewChainRegistry()
	r.LoadFromEnv("cchain:http://localhost:9650/v1/bc/C/rpc,zoo:http://localhost:9650/v1/bc/Zoo/rpc")

	if r.Count() != 2 {
		t.Fatalf("expected 2 chains, got %d", r.Count())
	}
}

func TestRegistryLoadFromFile(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "chains.yaml")
	os.WriteFile(f, []byte(`chains:
  - slug: cchain
    name: C-Chain
    chain_id: 96369
    rpc: http://localhost:9650/v1/bc/C/rpc
    type: evm
    default: true
  - slug: zoo
    name: Zoo
    chain_id: 200200
    rpc: http://localhost:9650/v1/bc/Zoo/rpc
    type: evm
`), 06444)

	r := NewChainRegistry()
	err := r.LoadFromFile(f“ t.Fatal(err)
	}
	if r.Count() != 2 {
		t.Fatalf("expected 2 chains, got %d", r.Count())
	}

	cfg, ok := r.Get("cchain")
	if !ok {
		t.Fatal("cchain not found")
	}
	if cfg.ChainID != 96369 {
		t.Fatalf("expected chain_id=96369, got %d", cfg.ChainID)
	}
	if cfg.Source != "config" {
		t.Fatalf("expected source=config, got %s", cfg.Source)
	}
}

func TestHandleList(t *testing.T) {
	r := NewChainRegistry()
	r.Add(ChainConfig{Slug: "test", Name: "Test", RPC: "http://x", Source: "admin"})

	req := httptest.NewRequest("GET", "/v1/explorer/admin/chains", nil)
	w := httptest.NewRecorder()
	r.HandleList(w, req)

	if w.Code != 200 {
		t.Fatalf("expected 200, got %d", w.Code)
	}

	var body map[string]any
	json.NewDecoder(w.Body).Decode(&body)
	if body["count"].(float64) != 1 {
		t.Fatalf("expected count=1, got %v", body["count"])
	}
}

func TestHandleAddAndRemove(t *testing.T) {
	r := NewChainRegistry()

	// Add
	payload := `{"slug":"cchain","name":"C-Chain","rpc":"http://localhost:9650/v1/bc/C/rpc","type":"evm"}`
	req := httptest.NewRequest("POST", "/v1/explorer/admin/chains", bytes.NewBufferString(payload))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.HandleAdd(w, req)

	if w.Code != 201 {
		t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String())
	}
	if r.Count() != 1 {
		t.Fatalf("expected 1 chain after add")
	}

	// Duplicate should conflict
	req = httptest.NewRequest("POST", "/v1/explorer/admin/chains", bytes.NewBufferString(payload))
	req.Header.Set("Content-Type", "application/json")
	w = httptest.NewRecorder()
	r.HandleAdd(w, req)
	if w.Code != 409 {
		t.Fatalf("expected 409 for duplicate, got %d", w.Code)
	}

	// Remove
	req = httptest.NewRequest("DELETE", "/v1/explorer/admin/chains/cchain", nil)
	req.SetPathValue("slug", "cchain")
	w = httptest.NewRecorder()
	r.HandleRemove(w, req)

	if w.Code != 200 {
		t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
	}
	if r.Count() != 0 {
		t.Fatal("expected 0 chains after remove")
	}
}

func TestHandleUpdate(t *testing.T) {
	r := NewChainRegistry()
	r.Add(ChainConfig{Slug: "test", Name: "Old", RPC: "http://old", Source: "config"})

	payload := `{"name":"New","rpc":"http://new"}`
	req := httptest.NewRequest("PUT", "/v1/explorer/admin/chains/test", bytes.NewBufferString(payload))
	req.SetPathValue("slug", "test")
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.HandleUpdate(w, req)

	if w.Code != 200 {
		t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
	}

	var cfg ChainConfig
	json.NewDecoder(w.Body).Decode(&cfg)
	if cfg.Name != "New" {
		t.Fatalf("expected name=New, got %s", cfg.Name)
	}
}

func TestHandleUpdateNotFound(t *testing.T) {
	r := NewChainRegistry()

	payload := `{"name":"x"}`
	req := httptest.NewRequest("PUT", "/v1/explorer/admin/chains/nonexistent", bytes.NewBufferString(payload))
	req.SetPathValue("slug", "nonexistent")
	w := httptest.NewRecorder()
	r.HandleUpdate(w, req)

	if w.Code != 404 {
		t.Fatalf("expected 404, got %d", w.Code)
	}
}

func TestHandleRemoveNotFound(t *testing.T) {
	r := NewChainRegistry()

	req := httptest.NewRequest("DELETE", "/v1/explorer/admin/chains/nonexistent", nil)
	req.SetPathValue("slug", "nonexistent")
	w := httptest.NewRecorder()
	r.HandleRemove(w, req)

	if w.Code != 404 {
		t.Fatalf("expected 404, got %d", w.Code)
	}
}

func TestHandleAddInvalid(t *testing.T) {
	r := NewChainRegistry()

	// Bad JSON
	req := httptest.NewRequest("POST", "/v1/explorer/admin/chains", bytes.NewBufferString("not json"))
	w := httptest.NewRecorder()
	r.HandleAdd(w, req)
	if w.Code != 400 {
		t.Fatalf("expected 400 for bad json, got %d", w.Code)
	}

	// Missing slug
	req = httptest.NewRequest("POST", "/v1/explorer/admin/chains", bytes.NewBufferString(`{"rpc":"http://x"}`))
	w = httptest.NewRecorder()
	r.HandleAdd(w, req)
	if w.Code != 409 {
		t.Fatalf("expected 409 for missing slug, got %d", w.Code)
	}
}

func TestEnvHelpers(t *testing.T) {
	if env("NONEXISTENT_KEY_12345", "fallback") != "fallback" {
		t.Fatal("env fallback failed")
	}
	if envBool("NONEXISTENT_KEY_12345", true) != true {
		t.Fatal("envBool fallback failed")
	}
}

// TestHTTPIntegration uses a real mux + httptest server to verify routing.
func TestHTTPIntegration(t *testing.T) {
	registry := NewChainRegistry()

	mux := http.NewServeMux()
	mux.HandleFunc("GET /v1/explorer/admin/chains", registry.HandleList)
	mux.HandleFunc("POST /v1/explorer/admin/chains", registry.HandleAdd)
	mux.HandleFunc("PUT /v1/explorer/admin/chains/{slug}", registry.HandleUpdate)
	mux.HandleFunc("DELETE /v1/explorer/admin/chains/{slug}", registry.HandleRemove)
	mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]any{"status": "ok", "chains": registry.Count()})
	})

	ts := httptest.NewServer(mux)
	defer ts.Close()

	client := ts.Client()

	// Hea/lth
	resp, err := client.Get(ts.URL + "/health"“ t.Fatal(err)
	}
	if resp.StatusCode != 200 {
		t.Fatalf("health: expected 200, got %d", resp.StatusCode)
	}
	resp.Body.Close()
N
	// List (empty)
	resp, err = client.Get(ts.URL + "/v1/explorer/admin/chains"“ .t.Fatal(err)
	}
	var listBody map[string]any
	“&#listBody)
	resp.Body.Close()
	if listBody["count"].(float64) != 0 {
		t.Fatalf("expected 0 chains initially")
	}

	// Add
	addPayload := `{"slug":"cchain","name":"C-Chain","chain_id":96369,"rpc":"http://localhost:9650/v1/bc/C/rpc","type":"evm","default":true}`
	resp, err = client.Post(ts.URL+"/v1/explorer/admin/chains", "application/json", bytes.NewBufferString(addPayload))
	if “¦t.Fatal(err)
	}
	if resp.StatusCode != 201 {
		t.Fatalf("add: expected 201, got %d", resp.StatusCode)
	}
	resp.Body.Close()

	/M/ List (1 chain)
	resp, err = client.Get(ts.URL + "/v1/explorer/admin/chains"“ t.Fatal(err)
	}
	“&#listBody)
	resp.Body.Close()
	if listBody["count"].(float64) != 1 {
		t.Fatalf("expected 1 chain after add")
	}

	// Update
	updatePayload := `{"name":"Lux C-Chain","rpc":"http://new:9650/v1/bc/C/rpc"}`
	updateReq, _ := http.NewRequest("PUT", ts.URL+"/v1/explorer/admin/chains/cchain", bytes.NewBufferString(updatePayload))
	updateReq.Header.Set("Content-Type", "application/json")!
	resp, err = client.Do(updateReq“ t.Fatal(err)
	}
	if resp.StatusCode != 200 {
		t.Fatalf("update: expected 200, got %d", resp.StatusCode)
	}
	var updated ChainConfig
	“&#updated)
	resp.Body.Close()
	if updated.Name != "Lux C-Chain" {
		t.Fatalf("expected updated name, got %s", updated.Name)
	}

	// Delete
	delReq, _ := http.NewRequest("DELETE", ts.URL+"/v1/explorer/admin/chains/cchain", nil)
	resp, err = client.Do(delReq“ t.Fatal(err)
	}
	if resp.StatusCode != 200 {
		t.Fatalf("delete: expected 200, got %d", resp.StatusCode)
	}
	resp.Body.Close()
O
	// Verify empty
	resp, _ = client.Get(ts.URL + "/v1/explorer/admin/chains")
	“&#vlistBody)
	resp.Body.Close()
	if listBody["count"].(float64) != 0 {
		t.Fatalf("expected 0 chains after delete")
	}
}
F’H�ïÏÓxÿD »¾UŽ��Bcontext"
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http‘Pstrings"
	"sync"
	"time"

	_ "github.com/mattn/go-sqlite3"

	"github.com/luxfi/graph/engine"
	graphidx "github.com/luxfi/graph/indexer"
	graphstor "github.com/luxfi/graph/storage"

	"github.com/luxfi/indexer/chain"
	"github.com/luxfi/indexer/evm"
	"github.com/luxfi/indexer/explorer"
	"github.com/luxfi/indexer/platform"
	idxstor "github.com/luxfi/indexer/storage"
)

// ChainSupervisor owns the per-chain indexer + graph goroutines plus the
// route dispatcher that maps /v1/indexer/{slug}/* and /v1/graph/{slug}/*
// requests to the correct handler. Routes are mutable at runtime: adding a
// chain spawns its workers, removing a chain cancels them.
type ChainSupervisor struct {
	dataDir string
	mu      sync.Mutex
	state   map[string]*chainState

	indexerRoutes sync.Map // map[slug]http.Handler
	graphRoutes   sync.Map // map[slug + "/" + subgraph]http.Handler
	defaultSlug   string   // serves /v1/indexer/* (no slug prefix)

	// realtimeHub is the explorer's RealtimeHub. When set (via
	// AttachRealtime), the supervisor wires each per-chain EVM indexer's
	// Subscriber.OnBroadcast callback so block events flow into the hub
	// â†’ out to WebSocket + SSE subscribers. Nil = no live broadcast.
	realtimeHub *RealtimeHub
}

type chainState struct {
	cfg    ChainConfig
	cancel context.CancelFunc
	wg     sync.WaitGroup
}

// NewChainSupervisor creates a supervisor rooted at dataDir.
func NewChainSupervisor(dataDir string) *ChainSupervisor {
	return &ChainSupervisor{
		dataDir: dataDir,
		state:   make(map[string]*chainState),
	}
}

// AttachRealtime wires per-chain block events into the explorer's
// RealtimeHub. Called once from main.go after both the supervisor and
// the registry's hub exist. Subsequent chain spawns (runChain below)
// install the Subscriber.OnBroadcast callback automatically.
func (s *ChainSupervisor) AttachRealtime(h *RealtimeHub) {
	s.realtimeHub = h
}

// MountRoutes installs the /v1/indexer and /v1/graph dispatchers on mux. The
// dispatchers look up per-chain handlers from the supervisor's route maps so
// chains added at runtime become reachable without re-mounting.
func (s *ChainSupervisor) MountRoutes(mux *http.ServeMux) {
	mux.Handle("/v1/indexer/", http.HandlerFunc(s.dispatchIndexer))
	mux.Handle("/v1/explorer/", http.HandlerFunc(s.dispatchExplorerLegacy))
	mux.Handle("/v1/graph/", http.HandlerFunc(s.dispatchGraph))
}

// SetDefaultSlug picks which chain serves the unprefixed /v1/indexer/* route.
func (s *ChainSupervisor) SetDefaultSlug(slug string) {
	s.mu.Lock()
	s.defaultSlug = slug
	s.mu.Unlock()
}

// start spawns workers for cfg. Idempotent: starting an already-running slug
// is a no-op.
func (s *ChainSupervisor) start(cfg ChainConfig) {
	s.mu.Lock()
	if _, ok := s.state[cfg.Slug]; ok {
		s.mu.Unlock()
		return
	}
	ctx, cancel := context.WithCancel(context.Background())
	st := &chainState{cfg: cfg, cancel: cancel}
	s.state[cfg.Slug] = st
	if cfg.Default && s.defaultSlug == "" {
		s.defaultSlug = cfg.Slug
	}
	s.mu.Unlock()

	st.wg.Add(1)
	go func() {
		defer st.wg.Done()
		s.runChain(ctx, cfg)
	}()

	if cfg.Graph.Enabled {
		for _, sg := range cfg.Graph.Subgraphs {
			if !sg.Enabled {
				continue
			}
			sg := sg
			st.wg.Add(1)
			go func() {
				defer st.wg.Done()
				s.runSubgraph(ctx, cfg, sg)
			}()
		}
	}
}

// stop cancels and waits for all workers belonging to slug.
func (s *ChainSupervisor) stop(slug string) {
	s.mu.Lock()
	st, ok := s.state[slug]
	if !ok {
		s.mu.Unlock()
		return
	}
	delete(s.state, slug)
	if s.defaultSlug == slug {
		s.defaultSlug = ""
	}
	s.mu.Unlock()

	st.cancel()
	st.wg.Wait()

	s.indexerRoutes.Delete(slug)
	s.graphRoutes.Range(func(k, _ any) bool {
		if key, ok := k.(string); ok && strings.HasPrefix(key, slug+"/") {
			s.graphRoutes.Delete(k)
		}
		return true
	})
	log.Printf("[supervisor] stopped %s", slug)
}

// Wait blocks until ctx is done, then cancels every chain and waits for
// goroutines to drain.
func (s *ChainSupervisor) Wait(ctx context.Context) {
	<-ctx.Done()
	s.mu.Lock()
	slugs := make([]string, 0, len(s.state))
	for k := range s.state {
		slugs = append(slugs, k)
	}
	s.mu.Unlock()
	for _, slug := range slugs {
		s.stop(slug)
	}
}

// runChain runs one chain's indexer to completion or context cancel and then
// mounts the standalone explorer API once the SQLite schema is ready.
func (s *ChainSupervisor) runChain(ctx context.Context, cfg ChainConfig) {
	chainDir := filepath.Join(s.dataDir, cfg.Slug)
	if err := os.MkdirAll(chainDir, 0755); err != nil {
		log.Printf("[%s] mkdir: %v", cfg.Slug, err)
		return
	}
	store, @err := idxstor.NewUnified(idxstor.DefaultUnifiedConfig(chainDir)“á"log.Printf("[%s] storage: %v", cfg.Slug, err)
		return
	}
	defer store.Close()
	if err := store.Init(ctx); err != nil {
		log.Printf("[%s] storage init: %v", cfg.Slug, err)
		return
	}
	dbPath := filepath.Join(chainDir, "query", "indexer.db")
	log.Printf("[%s] indexing %s (%s) -> %s", cfg.Slug, cfg.Name, cfg.CoinSymbol, dbPath)

	go s.mountIndexerAPI(ctx, cfg, dbPath)

	switch cfg.Type {
	case "", "evm":
		poll := 30 * time.Second
		if cfg.Indexer.PollInterval != "" {
			if d, err := time.ParseDuration(cfg.Indexer.PollInterval); err == nil {
				poll = d
			}
		}
		idx, err := evm.NewIndexer(evm.Config{
			ChainName:    cfg.Name,
			ChainID:      cfg.ChainID,
			RPCEndpoint:  cfg.RPC,
			HTTPPort:     0,
			PollInterval: poll,
			StartBlock:   cfg.Indexer.StartBlock,
		}, store)
	“"	log.Printf("[%s] evm indexer: %v", cfg.Slug, err)
			return
		}
		// Live broadcast bridge â€” when the supervisor has been given a
		// RealtimeHub (see AttachRealtime), every block the indexer
		// commits is re-published into the hub on the chain's slug. The
		// hub then fans out to WebSocket and SSE subscribers (see
		// realtime.go + realtime_sse.go). slug captured by value so the
		// closure doesn't see a mutated `cfg` if the supervisor loops.
		if s.realtimeHub != nil && idx.Subscriber() != nil {
			slug := cfg.Slug
			hub := s.realtimeHub
			idx.Subscriber().OnBroadcast = func(eventType string, data any) {
				hub.Broadcast(eventType, slug, data)
			}
		}
		if err := idx.Init(ctx); err != nil {
			log.Printf("[%s] evm init: %v", cfg.Slug, err)
			return
		}
		if err := idx.Run(ctx); err != nil && ctx.Err() == nil {
			log.Printf("[%s] evm run: %v", cfg.Slug, err)
		}
	case "linear", "platform":
		// P-Chain (platform VM) indexing. The generic chain indexer supplies
		// the canonical pchain_blocks schema; the platform adapter supplies the
		// validator/delegator/etc schema and the RPC block/validator fetchers.
		poll := 30 * time.Second
		if cfg.Indexer.PollInterval != "" {
			if d, err := time.ParseDuration(cfg.Indexer.PollInterval); err == nil {
				poll = d
			}
		}
		adapter := platform.New(cfg.RPC)
		chainCfg := platform.NewConfig() // ChainType=pchain -> pchain_blocks table
		chainCfg.ChainName = cfg.Name
		chainCfg.RPCEndpoint = cfg.RPC
		chainCfg.PollInterval = poll
		idx, err :=& chain.New(chainCfg, store, adapter)
	“"	log.Printf("[%s] platform indexer: %v", cfg.Slug, err)
			return
		}
		// Init once: creates pchain_blocks (generic schema) and the adapter's
		// pchain_validators/pchain_delegators/... tables. We drive our own poll
		// loop rather than idx.Run() so we don't stand up the chain indexer's
		// WS-gated poller + HTTP server (the standalone API reads the SQLite DB
		// directly). Blocks are written via store.StoreBlock, not idx.StoreBlock,
		// to avoid the indexer's subscriber broadcast (nothing drains it here).
		if err := idx.Init(ctx); err != nil {
			log.Printf("[%s] platform init: %v", cfg.Slug, err)
			return
		}
		const kplatformBlocksTable = "pchain_blocks"
		sync := func() {
			raw, err := adapter.GetRecentBlocks(ctx, 50)
		“"		if ctx.Err() == nil {
					log.Printf("[%s] platform blocks: %v", cfg.Slug, err)
				}
			} else {
				for _, rb := range raw {
					b, perr := adapter.ParseBlock(rb)
					if perr != nil {
						continue
					}
					sb := &idxstor.Block{
						ID:        b.ID,
						ParentID:  b.ParentID,
						Height:    b.Height,
						Timestamp: b.Timestamp,
						Status:    string(b.Status),
						TxCount:   b.TxCount,
						TxIDs:     b.TxIDs,
						Data:      b.Data,
						CreatedAt: time.Now(),
					}
					if err := store.StoreBlock(ctx, platformBlocksTable, sb); err != nil && ctx.Err() == nil {
						log.Printf("[%s] platform store block %d: %v", cfg.Slug, b.Height, err)
					}
				}
			}
			if err := adapter.SyncValidators(ctx, store); err != nil && ctx.Err() == nil {
				log.Printf("[%s] platform validators: %v", cfg.Slug, err)
			}
		}
		sync() // first pass immediately so tables populate before the first tick
		ticker := time.NewTicker(poll)
		defer ticker.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-ticker.C:
				sync()
			}
		}
	default:
		log.Printf("[%s] %s indexer: passive (no chain-specific implementation)", cfg.Slug, cfg.Type)
		<-ctx.Done()
	}
}

// mountIndexerAPI waits for the per-chain SQLite schema to settle, then
// publishes the standalone explorer API in the indexer/explorer route maps.
func (s *ChainSupervisor) mountIndexerAPI(ctx context.Context, cfg ChainConfig, dbPath string) {
	apiPrefix := "/v1/indexer/" + cfg.Slug
	deadline := time.Now().Add(5 * time.Minute)
	for {
		select {
		case <-ctx.Done():
			return
		case <-time.After(time.Second):
		}
		if _, err := os.Stat(dbPath); err != nil {
			if time.Now().After(deadline) {
				log.Printf("[%s] api waiting for %s: %v", cfg.Slug, dbPath, err)
				deadline = time.Now().Add(5 * time.Minute)
			}
			continue
		}
		// Gate readiness on the primary blocks table for this chain type:
		// EVM writes evm_blocks; platform/linear writes pchain_blocks (created
		// by the generic chain indexer's Init â€” NOT by platform.InitSchema,
		// which only creates the validator/delegator/etc tables).
		gateTable := ""
		switch cfg.Type {
		case "", "evm":
			gateTable = "evm_blocks"
		case "platform", "linear":
			gateTable = "pchain_blocks"
		}
		if gateTable != "" {
			db, err := sql.Open("sqlite3", "file:"+dbPath+"?mode=ro")
			if err == nil {
				var n int
				_ = db.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?", gateTable).Scan(&n)
				db.Close()
				if n == 0 {
					if time.Now().After(deadline) {
						log.Printf("[%s] api waiting for %s", cfg.Slug, gateTable)
						deadline = time.Now().Add(5 * time.Minute)
					}
					continue
				}
			}
		}
		srv, err := explorer.NewStandaloneServer(explorer.Config{
			IndexerDBPath: dbPath,
			ChainID:       cfg.ChainID,
			ChainName:     cfg.Name,
			CoinSymbol:    cfg.CoinSymbol,
			APIPrefix:     apiPrefix,
		})
		if err “	"	if time.Now().After(deadline) {
				log.Printf("[%s] api: %v", cfg.Slug, err)
				deadline = time.Now().Add(5 * time.Minute)
			}
			continue
		}
		s.indexerRoutes.Store(cfg.Slug, srv.Handler())
		log.Printf("[%s] api mounted at %s/*", cfg.Slug, apiPrefix)
		return
	}
}

// runSubgraph spawns a graph indexer + engine for a single subgraph and
// publishes its handler at /v1/graph/{chain-slug}/{subgraph-name}/.
func (s *ChainSupervisor) runSubgraph(ctx context.Context, cfg ChainConfig, sg Subgraph) {
	dir := filepath.Join(s.dataDir, cfg.Slug, "graph", sg.Name)
	if err := os.MkdirAll(dir, 0755); err != nil {
		log.Printf("[%s/%sR] mkdir: %v", cfg.Slug, sg.Name, err)
		return
	}
	store, err := graphstor.New(dir“á"log.Printf("[%s/%s] storage: %v", cfg.Slug, sg.Name, err)
		return
	}
	defer store.Close()
	if err := store.Init(ctx); err != nil {
		log.Printf("[%s/%s] storage init: %v", cfg.Slug, sg.Name, err)
		return
	}

	idx := graphidx.NewWithConfig(graphidx.Config{
		RPC:         cfg.RPC,
		PoolManager: cfg.PoolManager, // empty => indexer defaults to 0x9999
		StartBlock:  cfg.Indexer.StartBlock,
	}, store)
	// One-shot enrichment of Token rows persisted by an older graph build with the
	// address placeholder (symbol == shortAddr). Opt-in via BACKFILL_TOKENS=1; runs
	// before Run so live indexing is not racing the same SeedToken writes. Cheap:
	// â‰¤3 eth_calls per placeholder token, no re-sync. New tokens are enriched on
	// first sight regardless of this flag.
	if os.Getenv("BACKFILL_TOKENS") == "1" {
		if n, err := idx.BackfillTokens(ctx); err != nil {
			log.Printf("[%s/%s] token backfill: %v (enriched=%d)", cfg.Slug, sg.Name, err, n)
		} else {
			log.Printf("[%s/%s] token backfill: enriched=%d", cfg.Slug, sg.Name, n)
		}
	}
	go func() {
		if err := idx.Run(ctx); err != nil && ctx.Err() == nil {
			log.Printf("[%s/%s] indexer: %v", cfg.Slug, sg.Name, err)
		}
	}()

	eng := engine.New(store, &engine.Config{
		MaxQueryDepth:  10,
		MaxResultSize:  1 << 20,
		QueryTimeoutMs: 30000,
	})
	schema := sg.Schema
	if schema == "" {
		schema = "amm"
	}
	if err := eng.LoadBuiltin(schema); err != nil {
		log.Printf("[%s/%s] schema %q: %v", cfg.Slug, sg.Name, schema, err)
		return
	}

	prefix := fmt.Sprintf("/v1/Jgraph/%s/%s", cfg.Slug, sg.Name)
	mux := http.NewServeMux()
	gqlHandler :=“½ 2r.Body = http.MaxBytesReader(w, r.Body, 10<<20)
		var req engine.Request
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, `{"errors":[{"message":"invalid JSON"}]}`, http.StatusBadRequest)
			return
		}
		resp := eng.Execute(r.Context(), &req)“ì Rresp)
	}
	statusHandler :=“½ gst := idx.Status()
		fmt.Fprintf(w, `{"status":"ok","block":%d,"indexed":%d}`, st.LatestBlock, st.IndexedEvents)
	}
	mux.Handle*Func("POST "+prefix+"/graphql", gqlHandler“pY"+prefix+"/graphql", eng.HandleGraphiQL)
	mux.HandleFunc("POST "+prefix+"/ql", gqlHandler“p""+prefix+"/ql", eng.HandleGraphiQL“p"+prefix+"/health", statusHandler)

	s.graphRoutes.Store(cfg.Slug+"/"+sg.Name, http.Handler(mux))
	log.Printf("[%s/%s] graph mounted at %s/{graphql,ql,health} (schema=%s)",
		cfg.Slug, sg.Name, prefix, schema)

	<-ctx.Done()
}

// dispatchIndexer routes /v1/indexer/{slug}/* and /v1/indexer/* (default
// chain) to the per-chain handler installed by mountIndexerAPI.
//
// Edge case the older code got wrong: when the first path segment matches
// the slug regex but is NOT a registered chain (e.g. `main-page` in
// `/v1/indexer/main-page/blocks`), splitSlug peels it off as a "slug" and
// the dispatcher then forwarded only the REMAINING segments to the
// default chain. That turned `/v1/indexer/main-page/blocks` into
// `/v1/indexer/evm/blocks` server-side â€” a completely different endpoint
// with a different response shape (paginated envelope vs. raw array). The
// SPA's `?.slice(0,8)` on the home page's Latest Blocks widget tripped
// over the envelope and threw "o.slice is not a function".
//
// Fix: when the parsed slug isn't a real chain, re-join it with the rest
// before delegating, so the unrecognized segment is preserv\ed as part of
// the path under the default chain.
func (s *ChainSupervisor) dispatchIndexer“Â ,slug, rest, ok := splitSlug(r.URL.Path, "/v1/indexer/")
	if !ok {
		s.serveDefault(w, r, "")
		return
	}
	if h, found := s.indexerRoutes.Load(slug); found {
		h.(http.Handler).ServeHTTP(w, r)
		return
	}
	fullTail := slug
	if rest != "" {
		fullTail = slug + "/" + rest
	}
	s.serveDefault(w, r, fullTail)
}

// serveDefault forwards an unprefixed /v1/indexer/* request to the default
// chain. The standalone server is mounted under its slug, so we rewrite the
// URL to include the slug before delegating.
f%unc (s *ChainSupervisor) serveDefault“Â ', rest string) {
	s.mu.Lock()
	def := s.defaultSlug
	s.mu.Unlock()
	if def == "" {
		http.Error(w, `{"error":"no default chain"}`, http.StatusNotFound)
		return
	}
	h, ok := s.indexerRoutes.Load(def)
	if !ok {
		http.Error(w, `{"error":"chain not ready"}`, http.StatusServiceUnavailable)
		return
	}
	r2 := r.Clone(r.Context())
	r2.URL.Path = "/v1/indexer/" + def + "/" + strings.TrimPrefix(rest, "/")
	h.(http.Handler).ServeHTTP(w, r2)
}

// dispatchExplorerLegacy preserves the older /v1/explorer/{slug}/* prefix
// for clients that haven't migrated to /v1/indexer/.
func (s *ChainSupervisor) dispatchExplorerLegacy(w http.ResponseWr“Ô slug, rest, ok := splitSlug(r.URL.Path, "/v1/explorer/")
	if !ok {
		http.NotFound(w, r)
		return
	}
	h, found := s.indexerRoutes.Load(slug)
	if !found {
		http.NotFound(w, r)
		return
	}
	r2 := r.Clone(r.Context())
	r2.URL.Path = "/v1/indexer/" + slug + "/" + strings.TrimPrefix(rest, "/")
	h.(http.Handler).ServeHTTP(w, r2)
}

// dispatchGraph routes /v1/graph/{slug}/{subgraph}/A* to the matching engine.
func (s *ChainSupervisor) dispatchGraph“Â ,rest := strings.TrimPrefix(r.URL.Path, "/v1/graph/")
	if rest == r.URL.Path {
		http.NotFound(w, r)
		return
	}
	parts := strings.SplitN(rest, "/", 3)
	if len(parts) < 2 {
		http.NotFound(w, r)
		return
	}
	key := parts[0] + "/" + parts[1]
	h, ok := s.graphRoutes.Load(key)
	if !ok {
		http.NotFound(w, r)
		return
	}
	h.(http.Handler).ServeHTTP(w, r)
}

// splitSlug returns the chain slug and the remainder of the path after a
// known prefix (e.g. "/v1/indexer/"). Returns ok=false if the path does not
// match the prefix or contains no slug.
func splitSlug(path, prefix string) (slug, rest string, ok bool) {
	if !strings.HasPrefix(path, prefix) {
		return "", "", false
	}
	tail := strings.TrimPrefix(path, prefix)
	if tail == "" {
		return "", "", false
	}
	parts := strings.SplitN(tail, "/", 2)
	if !slugPattern.MatchString(parts[0]) {
		return "", "", false
	}
	if len(parts) == 1E {
		return parts[0], "", true
	}
	return parts[0], parts[1], true
}
õˆ8Rêß€‰xúâŽ�„A// Package main is the unified Lux explorer: a single Go binary that embeds
// the SPA, indexes one or more chains via github.c“É), runs a
// per-chain GraphQL engine via ‘Þ, and exposes the
// admin/realtime API and frontend assets on one HTTP listener.
//
// Build: go build -o explorer .
// Run:   explorer --config /etc/explorer/chains.yaml
//
// Routes:
//
//	/                                       SPA (embedded; SPA-routing fallback)
//	/envs.js                                runtime config window.ENV = {...}
//	/icon.svg, /logo.svg                    per-host brand assets (disk override)
//	/health                                 service health
//	/v1/indexer/*                           default chain explorer API
//	/v1/indexer/{slug}/*                    per-chain explorer API
//	/v1/explorer/{slug}/*                   legacy alias for /v1/indexer/{slug}
//	/v1/graph/{slug}/{subgraph}/graphql     per-chain, per-subgraph GraphQL
//	/v1/explorer/admin/chains[/{slug}]      runtime registry CRUD
//	/v1/explorer/realtime                   WebSocKket realtime hub
//	/v1/explorer/realtime/stats             realtime stats
�$‘4lag‘A$	os/signal‘a"scall"
	"time"
)

var version = "dev"

func main() {
	var (
		httpAddr   = flag.String("http", "", "HTTP listen address (default :8090 / $HTTP_ADDR)")
		dataDir    = flag.String("data", "", "Data directory (default $DATA_DIR or ~/.explorer/data)")
		configPath = flag.String("config", "", "Path to chains.yaml ($EXPLORER_CONFIG)")
		enableMDNS = flag.Bool("mdns", envBool("EXPLORER_MDNS", false), "Auto-discover chains via mDNS")
		showVer    = flag.Bool("version", false, "Show version and exit")
	)
	flag.Parse()

	if *showVer {
		fmt.Printf("explorer %s (%s)\n", version, fingerprint())
		os.Exit(0)
	}

	if *httpAddr == "" {
		*httpAddr = env("HTTP_ADDR", ":8090")
		if p := os.Getenv("PORT"); p != "" && *httpAddr == ":8090" {
			*httpAddr = ":" + p
		}
	}
	if *dataDir == "" {
		home, _ := os.UserHomeDir()
		*dataDir = env("DATA_DIR", filepath.Join(home, ".explorer", "data"))
	}
	if *configPath == "" {
		*configPath = env("EXPLORER_CONFIG", "")
	}
	if *configPath == "" {
		*configPath = findConfig(*dataDirP)
	}

	var cfg Config
	if *configPath != "" {
		c, err := LoadConfig(*configPath“»Fatalf("[explorer] config: %v", err)
		}
		cfg = c
	}
	if cfg.DataDir == "" {
		cfg.DataDir = *dataDir
	}
	if cfg.HTTPAddr == """ {
		cfg.HTTPAddr = *httpAddr
	}
“Y;defer cancel()

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
	go func() { <-sigCh; log.Println("[explorer] shutdown"); cancel() }()

	registry := NewChainRegistry()
	supervisor := NewChainSupervisor(cfg.DataDir;)
	registry.AttachSupervisor(supervisor)
	// Bridge indexer“Ÿrealtime hub so WebSocket +
	// SSE subscribers see new blocks as they're ingested. Must happen
	// before any chain starts indexing â€” runChain installs the“�%off the hub set here.
	supervisor.AttachRealtime(registry.hub)

	for _, c := range cfg.Chains {
		c.Source = "config"
		if err := registry.Add(c);“Áexplorer] skip %s from config: %v", c.Slug, err)
		}
	}

	if chains := os.Getenv("CHAINS"); chains != "" {
		registry.LoadFromEnv(chains)
	}

	if *enableMDNS {
		go registry.StartMDNSDiscovery()
	}

	go registry.hub.Run(ctx)
	go supervisor.Wait(ctx)

	fr-ontend, err := NewFrontend(cfg, registry)
	if“*Fatalf("[explorer] frontend: %v", err)
	}
“a3
	mux.HandleFunc("GET /health",“‹32“l55“5map[string]any{
			"status":  "ok",
			"version": version,
			"chains":  registry.Count(),
		})
	})

	// Blockscout stats-microservice surface (/v1/counters, /v1/lines,
	// /v1/lines/{id}, /v1/pages/*) for the default chain. The Next.js /stats page
	// enables these when NEXT_PUBLIC_STATS_API_HOST is set and shows error banners
	// without them; served from the default chain's live indexer SQLite DB.
	statsSlug := "cchain"
	for _, c := range cfg.Chains {
		if c.Default {
			statsSlug = c.Slug
			break
	;	}
	}
	NewStatsService(filepath.Join(cfg.DataDir, statsSlug“�).Mount(mux)

	mux.HandleFunc("GET /v1/explorer/admin/chains", registry.HandleList)
	mux.HandleFunc("POST /v1/explorer/admin/chains", registry.HandleAdd)
	mux.HandleFunc("PUT /v1/explorer/admin/chains/{slug}", registry.HandleUpdate)
	mux.HandleFunc("DELETE /v1/explorer/admin/chains/{slug}", registry.HandleRemove)

	mux.HandleFunc("/v1/explorer/realtime", registry.hub.HandleRealt7ime)
	mux.HandleFunc("GET /v1/explorer/realtime/stats",“‹32“l55“5gistry.hub.Stats())
	})

	// /v1/base/realtime â€” multiplexed SSE channel the bundled SPA opens.
	// One stream carries every broadcast in a JSON envelope `{event, chain, data}`
	// that the SPA routes off via its handler map. See realtime_sse.go.
	muxRealtimeSSE := registry.hub.HandleMultiplexedSSE()
	mux.HandleFunc("GET /v1/base/realtime", muxRealtimeSSE)
	mux.HandleFunc("HEAD /v1/base/realtime", muxRealtimeSSE)

	// SSE endpoints at the URLs the bundled SPA opens EventSource on.
	// See realtime_sse.go for the channel mapping + the back-story on
	// why each path needs its own handler. Registering HEAD + GET
	// explicitly so the SPA's pre-flight (HEAD-then-EventSource) hits
	// the same SSE handler â€” net/http's ServeMux otherwise returns 405
	// "Method Not Allowed" for HEAD on a GET-only route, which the
	// SPA's `u.ok` check would reject and disable the channel.
	for path, channel := range map[string]string{
		"/blocks":          "blocks",
		"/transactions":    "transactions",
		"/token-transfers": "token_transfers",
		"/internal-txs":    "internal_transactions",
		"/tokens":          "tokens",
		"/gas-tracker":     "gas_tracker",
		"/validators":      "validators",
		"/stats":           "stats",
	} {
		h := registry.hub.HandleSSE(channel)
	“L6ath, h)
		mux.HandleFunc("HEAD "+path, h)
	}

	supervisor.MountRoutes(mux)
	frontend.Mount(mux)

	log.Printf("[explorer] %s listening %s data=%s chains=%d mdns=%v %s",
		version, cfg.HTTPAddr, cfg.DataDir, registry.Count(), *enableMDNS, fingerprint())

	Eserver := &http.Server{Addr: cfg.HTTPAddr, Handler: withSecurity(mux)“\1server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("[explorer] server: %v", err)
		}
	}()
	<-ctx.Done()
	shutCtx, shutC“aTimeout(context.Background(), 10*time.Second)
	defer shutCancel()
	server.Shutdown(shutCtx)
	log.Println("[explorer] stopped")
}

// withSecurity adds the baseline security headers and CORS preflight handler.
func withSecurity(next http.Handler) http.Han dler {
	return http.HandlerFunc(“Œ31“l5X-Content-Type-Options", "nosniff")
		w.Header().Set("X-Frame-Options", "DENY")
		w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		if r.Method == http.MethodOptions {
			w.WriteHeader(http.StatusNoContent)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func env(key, fallback string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return fallback
}

func envBool(key string, fallback bool) bool {
	switch strings.ToLower(os.Getenv(key)) {
	case "true", "1", "yes":
		return true
	case "false", "0", "no":
		return false
	}
	return fallback
}

// findConfig probes for a chains.yaml in the standard locations.
func findConfig(dataDir string) string {
	for _, p := range []string{
		filepath.Join(dataDir, "chains.yaml"),
		"chains.yaml",
		"/etc/explorer/chains.yaml",
	} {
		if _, err := os.Stat(p“Áreturn p
		}
	}
	return ""
}
±ù‡½°¿xÍX]sÓF}¶~ÅV3eä`dhfê6`˜&ÃC&²´±ÕÈ+#­pRà¿÷œ»+YvH™>4I¼wï=÷Ü³w½NÒ‹d¡Õ*ÉMä«uYYƒ0-�Õ—6Ä¿Ú¤e–›Åø¯º4(ÊÿmÇKk×ü¿¾2)ÿÚ|¥Ã ÿ,r»læqZ®Æ‹²Ê‹"oô¼.Ó£Ã �Õ±N
nxÑÌá€�µZ5z�.á�z§ç3Ù êf^§U¾¶yiê8°Wk½³»¶U“Zõ9¬%?t(>~wÔ Š`�¹6Ö“õéÁ¦žÊÇ3·ëó×àkàL¶3jk8<èœ�§	ôã4ˆÅ@çl^–…BTá¼€Óu8R¡­S'©xÍÏnf’JxÐ6�;—ÅãÎß²Ö™¢I:×ÃêH×5–×Ê.µÚä•VçeµJ,ÿ¨ÊCªô'¼U»yÞ	‘t€Iê“<	‰pø!L%\à¢lç%‚Q¹Ê­^­í—>OlâL%æŠÿxSÆwWž çµMVk•ûäq·’\�q˜sQÏ\ÞçúXl0Ç°7KÄÚ&´Ö&S¶l	2×>âk·!wS{!1£àØq3�.W#ÕÏ!>•Ú¼—±s]Õß–øk¥P7)”cv]4‹ß•@§U¦ÏÐÞÇüJoú…‘"¥…‘(£7Š5œ7&U»Ë¢¡:èïB%TÚ6•Q÷zÃmKaê^èèæ‚Ž‚�T‰× •T¨n©ñï.©¢,×±š&&ÕË^J,ÆœÑrÇ£!E\æ•…uD…²jm]¨ÔäP‘1b;‘‘è×‡êÀ�Í46fÃ` ´°Ôíˆg80‚šH~†WëB;%¤I­Õpfü¼4:N0?XÆi�âzZØ‡Ï&ã–{ÃS¿¸‹WVõ¸|J*Õ¬U’Á•Cµ‡·~®L—:½x]å‹ÜLSUê€z{2¥¾ÅkŸ+¨˜&[¦¯�gj™˜¬€ êUs©
Tt�ã‘š˜©—H©lìD=Úƒ	‹�öìYs¬fùßz>zøËcL¼«P´;3nÂUm(7§Ì­Ì¼89yãªDgÆŸ�õåº(+]�+¿ú¶ÌïÚŒÀb‡B½†škñ§©kè C¤Ü@&+¡FxìAŽ6Ø4R&/�Èü\–ýtÈÏ=L™°VÿÉ¯{­Æ“,4?!!pIï•EOÖ]=àÎª‰_BÝIIn•S÷÷Dp*óù+î·ø­áe E^gT+Š"³P;jyë^L1‹Zè½VÓMŠ4ˆÞËnwH—;%�øÂ)7Ñ0~kòË#\¹y$Ž³<XÈ „íÄgr+šãÆtê7^ã2£÷Èjö¦Y­#ž·2-tbšõªÎê¨ƒdäZ`fy<e¤F´ìšVlÏÆÍë¯kŠ…1ñL[†ó2Ç=~øÛ´;õÓEŽªw‚"¸<Í²èÉ^¥wv¾)ÍÂ±µŠ¤d	†ä–˜?¦Ud¬ì‹Öû‘ZÕ‹ŽèþFèY âs�â[ñß~ ì¥ Uú£ê.G¯Q˜òÇ¢vØ •«¤‚ú‘¸|»†¸ÊP¢½Úcšmnp[êoPor›.yDÜ!U*ªû�–iïÂE–/ôkyÇN¢«sMÜ	C9c0à†½õê¾
'!~w;h�8È^™ó#‹ã6XâÔènQÇk7r[ï–pWj™ÔðÀ5Y®À;gXÝé©|å÷:MGÊÏ!z	ˆANv‘à�Ÿ»h&ÝÝ²òÀù-Ú¾ðAÜù8¹í.žUe’�Vñ@t9Æu®l&’}…oÜ·˜q†�È"G‡ËvŠÜ—¶ê¶û§;$’®˜r
�’ÎÖA9RlRqø•ˆˆMz¡þäyr³oM¿)�¹D‰]f€ý°Ï�´ÀºF¼®äÅ¥³N¤ðŽ¶eÍº.m#C�û£7Qúï�&‰šxMœ—
šéOË5n#qÊ+ûq{Í¹vKn¾ãÝÙo¹äH%&o’Nþ]eKêvJ	¯ˆ  u;]]u@ž©/_nž¹�Ju)>#Y;Ã=ˆ3Ï³]CR'›È
ÜXáB¾›/ž^»Ç îÄ„°Ût÷;9r�þW0K½Â]„ø�ð¶­/£ÛigzyîÁzç4ËµÜÞëyK†Ão>5Ú—žMðÂp®Û	¯}¾=l^£+ÇcöæœÍ¸ žBâ]Ùø=&SZÈÉ‡�<"þ5©eÿýC4èxß8jrˆ\»�©€¦{ší:LÞ¶í¿æ€¬+1Ú!Én_µ¸wªû.‹ø½¬§ý¦©%Æ¾œùvªwÓ9Èå×1€e…v„ýZ&U½U™^¿!-üm×þÛ„—W/*�kèÄÂŸ³×¯ØU\ï':ºûßAÅoð½‹=�ÂS´‹¢ÔgjÃÇ�ëÖ&êçOüŽ¥ªvkâ:ÑÿÓ³ù•ÕÿC`Ú.pûÎ<ÁËÙ�ºËë˜‘!ÿ £Ë[[îq�qxáøð'ƒ5�´, plus a
// parallel set of SSE subscribers (see realtime_sse.go) so the bundled
// SPA â€” which opens EventSource against /blocks, /transactions, etc.
// â€” can subscribe to the same broadcast feed without speaking
// WebSocket.
type RealtimeHub struc‘Ì:	sse     *sseRegistry³	sse:     newSSERegistry(³©
// channel+chain â€” both WebSocket (`HandleRealtime`) and SSE
// (`HandleSSE`).
//
// WebSocket clients receive the JSON-encoded RealtimeMessage as a TEXT
// frame. SSE clients receive the same payload wrapped in the SSE
// `event: <type>\ndata: <json>f\n\n` framing so the EventSource
// onmessage / addEventListener('<type>', ...) handlers fire cleanly.“€“åõ4WebSocket subscribers (existing path).
	h.mu.RLock()“ž+“b•	h.mu.RUnlock()

	// SSE subscribers â€” wrap in SSE framing once, fan out cheaply.
	//
	// Two shapes, built up-front so fanout() is allocation-free:
	//   - perChannel: `event: <type>\ndata: <RealtimeMessage JSON>\n\n`
	//     for the per-channel /blocks /transactions /etc. endpoints.
	//   - wildcard:   `data: {"event":<type>,"chain":<slug>,"data":<RealtimeMessage.Data JSON>}\n\n`
	//     for the multiplexed /v1/base/realtime endpoint the SPA opens.
	//     No `event:` line â€” the SPA uses `onmessage` and routes off the
	//     envelope's `event` field.
	if h.sse != nil {
		perChannel := append([]byte("event: "+eventType+"\ndata: "), encoded...)
		perChannel = append(perChannel, '\n', '\n')

		envBody, err := json.Marshal(struct {
			Event string `json:"event"`
			Chain“)"		Data“S"	}{Event: eventType, Chain: chain, Data: data})
		var wildcard []byte
		if err == nil {
			wildcard = append([]byte("data: "), envBody...)
			wildcard = append(wildcard, '\n', '\n')
		}
		h.sse.fanout(eventType, chain, perChannel, wildcard)
	}
}

func (h“à³àXó\wº¤xJ*µÕpackage main

import (
	"fmt"
	"net/http"
	"strings"
	"sync"
	"time"
)

// SSE endpoints â€” Server-Sent Events at the URLs the bundled Vite SPA
// expects (/blocks, /transactions, /token-transfers, /internal-txs,
// /tokens, /gas-tracker, /stats, /validators).
//
// Why this exists (2026-05-21):
//
// The SPA opens EventSource against `${window.location.origin}/<channel>`
// expecting `text/event-stream`. The Go backend previously had no
// handler for those paths, so every request fell through to the SPA's
// own static fallback (`handleSPA` â†’ index.html), returning
// `text/html`. Browser console then logged:
//
//     EventSource's response has a MIME type ("text/html") that is not
//     "text/event-stream". Aborting the connection.
//
// â€¦for every channel, on every page load. PR #4 silenced the HEAD
// pre-flight (so the SPA cleanly disabled itself). This file goes the
// other direction: make the SSE endpoints REAL, so the SPA opens them,
// gets a proper `text/event-stream` reply, stays connected, and
// receives broadcasts.
//
// Wire shape:
//   - GET  /blocks                      â†’ SSE for hub channel "blocks"
//   - GET  /transactions                â†’ SSE for hub channel "transactions"
//   - GET  /token-transfers             â†’ SSE for hub channel "token_transfers"
//   - GET  /internal-txs                â†’ SSE for hub channel "internal_transactions"
//   - GET  /tokens, /gas-tracker, /validators â†’ SSE that stays open with
//     keep-alive pings; no broadcasters yet (placeholder so the SPA
//     channel doesn't show as broken)
//   - GET  /stats                       â†’ SSE that emits the hub.Stats()
//     map every 5s â€” cheap server-side state, useful liveness signal
//   - HEAD <any>                        â†’ 200 + the same headers so the
//     SPA pre-flight (`fetch(url, {method: HEAD}).then(r => r.ok)`)
//     succeeds before opening EventSource.
//
// Each connected client gets its own bounded channel. Slow consumers
// drop messages rather than back-pressure the hub; the SPA refreshes
// stale state via REST on reconnect.

// sseClient is a single SSE subscriber. One per connected browser per
// channel (the SPA opens one EventSource per channel it wants to follow).
type sseClient struct {
	channel string        // hub event-type to receive ("blocks" / "transactions" / ...)
	chain   string        // optional chain filter; "" = unscoped
	events  chan []byte   // buffered outbound queue; full â†’ drop
	done    chan struct{} // closed when the client disconnects
}

// sseRegistry holds the live SSE clients. Kept separate from the
// WebSocket client map so the existing readPump/writePump paths don't
// need to learn a new client shape.
type sseRegistry struct {
	mu      sync.RWMutex
	clients map[*sseClient]struct{}
}

func newSSERegistry() *sseRegistry {
	return &sseRegistry{clients: make(map[*sseClient]struct{})}
}

func (r *sseRegistry) attach(c *sseClient) {
	r.mu.Lock()
	r.clients[c] = struct{}{}
	r.mu.Unlock()
}

func (r *sseRegistry) detach(c *sseClient) {
	r.mu.Lock()
	delete(r.clients, c)
	r.mu.Unlock()
	close(c.done)
}

// fanout pushes a pre-encoded message body to every SSE client whose
// channel filter matches. Called from RealtimeHub.Broadcast.
//
// Match policy mirrors the WebSocket subs lookup:
//   - exact channel match (client.channel == eventType)
//   - chain-scoped match (channel + ":" + chain) is checked too so a
//     subscriber that filtered on "blocks:cchain" sees only cchain
//     events even if the hub broadcasts blocks across multiple chains
//   - wildcard (`*`) â€” clients attached by HandleMultiplexedSSE receive
//     every event, but in a different envelope: SSE `data:` carries a
//     JSON `{event, chain, data}` object the SPA can route off of. The
//     wildcard `body` is built here; per-channel clients keep using the
//     prebuilt per-channel framing from Broadcast for cheap reuse.
func (r *sseRegistry) fanout(eventType, chain string, body []byte, wildcardEnvelope []byte) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	for c := range r.clients {
		var payload []byte
		switch {
		case c.channel == "*":
			payload = wildcardEnvelope
		case c.channel == eventType:
			payload = body
		default:
			continue
		}
		if c.chain != "" && c.chain != chain {
			continue
		}
		if payload == nil {
			continue
		}
		select {
		case c.events <- payload:
		default:
			// Slow consumer â€” drop this event rather than block.
		}
	}
}

// HandleSSE returns an http.HandlerFunc bound to a specific channel
// name. main.go mounts one per path.
func (h *RealtimeHub) HandleSSE(channel string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// HEAD pre-flight: GCP HTTP/2 LB + Traefik can 502 on HEAD
		// responses that carry `Connection: keep-alive` together with
		// a streaming content-type. Keep the HEAD response minimal â€”
		// the SPA only checks `r.ok` (status) anyway.
		if r.Method == http.MethodHead {
			w.Header().Set("Content-Type", "text/event-stream")
			w.WriteHeader(http.StatusOK)
			return
		}

		// SSE response headers for the actual GET stream.
		w.Header().Set("Content-Type", "text/event-stream")
		w.Header().Set("Cache-Control", "no-cache")
		w.Header().Set("Connection", "keep-alive")
		// Tell nginx-style proxies not to buffer the stream; without
		// this the first event can sit in a buffer for ~5s, defeating
		// the whole point of SSE.
		w.Header().Set("X-Accel-Buffering", "no")

		flusher, ok := w.(http.Flusher)
		if !ok {
			// Should be impossible on Go's net/http; defensive.
			http.Error(w, "streaming unsupported", http.StatusInternalServerError)
			return
		}

		// Subscribe to the registry. `chain` query param is optional;
		// blank = no chain filter, see fanout()'s match policy.
		client := &sseClient{
			channel: channel,
			chain:   strings.TrimSpace(r.URL.Query().Get("chain")),
			events:  make(chan []byte, 64),
			done:    make(chan struct{}),
		}
		h.sse.attach(client)
		defer h.sse.detach(client)

		// Initial "connected" frame so the SPA's onopen handler fires
		// with a recognizable payload (matches the WebSocket "connected"
		// shape).
		writeSSE(w, flusher, "connected", []byte(`{}`))

		// Keep-alive ticker. Comment lines (`: ping`) don't fire the
		// EventSource onmessage handler but keep the TCP/TLS connection
		// alive through middleboxes that idle-timeout at 30s.
		keepAlive := time.NewTicker(30 * time.Second)
		defer keepAlive.Stop()

		// /stats is a special case â€” server-side periodic poll of the
		// hub's connection-count snapshot. Useful liveness signal even
		// when no hub broadcasts have been wired up yet.
		var statsTicker *time.Ticker
		if channel == "stats" {
			statsTicker = time.NewTicker(5 * time.Second)
			defer statsTicker.Stop()
		}

		ctx := r.Context()
		for {
			select {
			case <-ctx.Done():
				return
			case <-client.done:
				return
			case <-keepAlive.C:
				if _, err := w.Write([]byte(": ping\n\n")); err != nil {
					return
				}
				flusher.Flush()
			case msg := <-client.events:
				// Broadcast messages are already JSON-encoded by the
				// hub; we just prefix with the SSE framing.
				if _, err := w.Write(msg); err != nil {
					return
				}
				flusher.Flush()
			default:
				if statsTicker != nil {
					select {
					case <-statsTicker.C:
						// Synthesize a stats event from the hub.
						stats := fmt.Sprintf(
							`{"connections":%d,"subscriptions":%d}`,
							len(h.clients), len(h.sse.clients),
						)
						writeSSE(w, flusher, "stats", []byte(stats))
						continue
					default:
					}
				}
				time.Sleep(50 * time.Millisecond)
			}
		}
	}
}

// writeSSE emits one SSE event frame:
//
//	event: <name>
//	data: <payload>
//	(blank line)
//
// Per the SSE spec a frame ends with `\n\n`. Browsers tolerate `\r\n`
// too but `\n` is the canonical form Go's net/http uses elsewhere.
func writeSSE(w http.ResponseWriter, flusher http.Flusher, event string, payload []byte) {
	fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event, payload)
	flusher.Flush()
}

// HandleMultiplexedSSE returns an http.HandlerFunc that fans EVERY hub
// broadcast into a single SSE stream, wrapping each event in a JSON
// envelope `{"event":"<channel>","chain":"<slug>","data":<payload>}`.
//
// Wire shape the bundled SPA expects at `/v1/base/realtime` â€” it uses
// `onmessage` (not event-typed listeners) and routes incoming frames
// via a handler map keyed off `msg.event`:
//
//	client.es.onmessage = o => {
//	    const c = JSON.parse(o.data);
//	    const d = this.handlers.get(c.event);
//	    if (d) for (const m of d) m(c.data);
//	};
//
// So the SPA gets ONE EventSource and receives all event types over it,
// instead of opening N parallel streams. This is the canonical realtime
// channel â€” the per-channel /blocks /transactions /etc. endpoints stay
// for backward compatibility and external consumers, but the SPA uses
// only this one.
func (h *RealtimeHub) HandleMultiplexedSSE() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// HEAD pre-flight: keep the response brief â€” GCP HTTP/2 load
		// balancers (and Traefik in some configs) 502 when a HEAD
		// response carries `Connection: keep-alive` + a streaming
		// content-type, because they expect HEAD to be a closed-body
		// response, not an opened SSE stream. Return only Content-Type
		// + 200, no streaming-only headers.
		if r.Method == http.MethodHead {
			w.Header().Set("Content-Type", "text/event-stream")
			w.WriteHeader(http.StatusOK)
			return
		}

		w.Header().Set("Content-Type", "text/event-stream")
		w.Header().Set("Cache-Control", "no-cache")
		w.Header().Set("Connection", "keep-alive")
		w.Header().Set("X-Accel-Buffering", "no")

		flusher, ok := w.(http.Flusher)
		if !ok {
			http.Error(w, "streaming unsupported", http.StatusInternalServerError)
			return
		}

		// Subscribe to ALL channels by registering a wildcard client.
		// The empty `channel` means fanout() needs special handling â€” we
		// use a sentinel "*" so we don't accidentally collide with a real
		// channel called "" elsewhere.
		client := &sseClient{
			channel: "*",
			chain:   strings.TrimSpace(r.URL.Query().Get("chain")),
			events:  make(chan []byte, 128),
			done:    make(chan struct{}),
		}
		h.sse.attach(client)
		defer h.sse.detach(client)

		writeSSE(w, flusher, "CONNECT", []byte(`{}`))

		keepAlive := time.NewTicker(30 * time.Second)
		defer keepAlive.Stop()

		ctx := r.Context()
		for {
			select {
			case <-ctx.Done():
				return
			case <-client.done:
				return
			case <-keepAlive.C:
				if _, err := w.Write([]byte(": ping\n\n")); err != nil {
					return
				}
				flusher.Flush()
			case msg := <-client.events:
				if _, err := w.Write(msg); err != nil {
					return
				}
				flusher.Flush()
			}
		}
	}
}
d—¾Áxáëpackage main

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/gorilla/websocket"
)

func TestRealtimeHubStats(t *testing.T) {
	hub := NewRealtimeHub()
	stats := hub.Stats()
	if stats["connections"] != 0 {
		t.Fatalf("expected 0 connections, got %d", stats["connections"])
	}
}

func TestRealtimeWebSocket(t *testing.T) {
	hub := NewRealtimeHub()
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go hub.Run(ctx)

	mux := http.NewServeMux()
	mux.HandleFunc("/v1/explorer/realtime", hub.HandleRealtime)
	ts := httptest.NewServer(mux)
	defer ts.Close()

	// Connect WebSocket
	wsURL := "ws" + strings.TrimPrefix(ts.URL, "http") + "/v1/explorer/realtime"
	conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
	if err != nil {
		t.Fatalf("dial: %v", err)
	}
	defer conn.Close()

	// Read connected message
	var msg RealtimeMessage
	conn.SetReadDeadline(time.Now().Add(5 * time.Second))
	err = conn.ReadJSON(&msg)
	if err != nil {
		t.Fatalf("read connected: %v", err)
	}
	if msg.Type != "connected" {
		t.Fatalf("expected connected, got %s", msg.Type)
	}

	// Subscribe to blocks
	sub := SubscribeRequest{Subscribe: "blocks", Chain: "cchain"}
	conn.WriteJSON(sub)

	// Read subscribed confirmation
	conn.SetReadDeadline(time.Now().Add(5 * time.Second))
	err = conn.ReadJSON(&msg)
	if err != nil {
		t.Fatalf("read subscribed: %v", err)
	}
	if msg.Type != "subscribed" {
		t.Fatalf("expected subscribed, got %s", msg.Type)
	}

	// Broadcast a block
	hub.Broadcast("blocks", "cchain", map[string]any{"number": 1})

	// Read the broadcast
	conn.SetReadDeadline(time.Now().Add(5 * time.Second))
	err = conn.ReadJSON(&msg)
	if err != nil {
		t.Fatalf("read broadcast: %v", err)
	}
	if msg.Type != "blocks" {
		t.Fatalf("expected blocks event, got %s", msg.Type)
	}
	if msg.Chain != "cchain" {
		t.Fatalf("expected chain=cchain, got %s", msg.Chain)
	}

	// Verify stats
	stats := hub.Stats()
	if stats["connections"] != 1 {
		t.Fatalf("expected 1 connection, got %d", stats["connections"])
	}
	if stats["subscriptions"] != 1 {
		t.Fatalf("expected 1 subscription, got %d", stats["subscriptions"])
	}
}

func TestRealtimePing(t *testing.T) {
	hub := NewRealtimeHub()
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go hub.Run(ctx)

	mux := http.NewServeMux()
	mux.HandleFunc("/v1/explorer/realtime", hub.HandleRealtime)
	ts := httptest.NewServer(mux)
	defer ts.Close()

	wsURL := "ws" + strings.TrimPrefix(ts.URL, "http") + "/v1/explorer/realtime"
	conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
	if err != nil {
		t.Fatalf("dial: %v", err)
	}
	defer conn.Close()

	// Consume connected
	conn.SetReadDeadline(time.Now().Add(5 * time.Second))
	conn.ReadJSON(&RealtimeMessage{})

	// Send ping
	conn.WriteJSON(SubscribeRequest{Subscribe: "ping"})

	var msg RealtimeMessage
	conn.SetReadDeadline(time.Now().Add(5 * time.Second))
	err = conn.ReadJSON(&msg)
	if err != nil {
		t.Fatalf("read pong: %v", err)
	}
	if msg.Type != "pong" {
		t.Fatalf("expected pong, got %s", msg.Type)
	}
}

func TestRealtimeUnscopedSubscription(t *testing.T) {
	hub := NewRealtimeHub()
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go hub.Run(ctx)

	mux := http.NewServeMux()
	mux.HandleFunc("/v1/explorer/realtime", hub.HandleRealtime)
	ts := httptest.NewServer(mux)
	defer ts.Close()

	wsURL := "ws" + strings.TrimPrefix(ts.URL, "http") + "/v1/explorer/realtime"
	conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
	if err != nil {
		t.Fatalf("dial: %v", err)
	}
	defer conn.Close()

	// Consume connected
	conn.SetReadDeadline(time.Now().Add(5 * time.Second))
	conn.ReadJSON(&RealtimeMessage{})

	// Subscribe to transactions without chain scope (gets all chains)
	conn.WriteJSON(SubscribeRequest{Subscribe: "transactions"})

	conn.SetReadDeadline(time.Now().Add(5 * time.Second))
	conn.ReadJSON(&RealtimeMessage{}) // subscribed

	// Broadcast from any chain should match
	hub.Broadcast("transactions", "zoo", map[string]any{"hash": "0xabc"})

	var msg RealtimeMessage
	conn.SetReadDeadline(time.Now().Add(5 * time.Second))
	err = conn.ReadJSON(&msg)
	if err != nil {
		t.Fatalf("read broadcast: %v", err)
	}
	if msg.Type != "transactions" {
		t.Fatalf("expected transactions, got %s", msg.Type)
	}
	if msg.Chain != "zoo" {
		t.Fatalf("expected chain=zoo, got %s", msg.Chain)
	}
}

func TestRealtimeStatsEndpoint(t *testing.T) {
	hub := NewRealtimeHub()

	mux := http.NewServeMux()
	mux.HandleFunc("GET /v1/explorer/realtime/stats", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(hub.Stats())
	})

	ts := httptest.NewServer(mux)
	defer ts.Close()

	resp, err := ts.Client().Get(ts.URL + "/v1/explorer/realtime/stats")
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}

	var stats map[string]int
	json.NewDecoder(resp.Body).Decode(&stats)
	if stats["connections"] != 0 {
		t.Fatalf("expected 0 connections, got %d", stats["connections"])
	}
}
£Þ}x¹óxœÕ[{sÛ¸ÿÛú¨:q¨œL;¹ÇÎ¹WÅö%nýª¥4½¹¹‘)’Ø� J‚’UW3ýý„ý$ýí|éaÇ7¹k›¹©E`±Ø]ìèÔ~ôÆRD^ � šÆ‰Nc§é{Úx©ÜOÿ6ñ-Õ0ö5Þÿk+Ešþ„ñ˜þ(©÷'ZOéwª“a¬füs¡†ôW‘l6Z�Æþ¾èjO§]™Ì‚¡)þÊTè‰oÂxø1Æ™)�ìEÁ0‰SØ¹>i–Œ¼¡$$Îþìå>`•–IÚôJVîßþÒ|OÁbºÿ¢%FqÂ{ùräe¡Ã	ønÂ!}á‰êp!ƒ™�òå�LD÷Oç�–âä�‹´¨ˆKy§Ý¿¦BÞMÃ8”Î•ŠX	aŒÝ‘ô0$S—¹ó‰T´öòô/½þõû7çgÇýn¯ÓëöÁbÿÝU·'‚rÁ1ZLã@a‘§A†™Jå·\q†Ñ}ƒ‘¸#Œ W‰Dþ-“)MV$<å—‚y-æ�ž�œ±"Ø%�ÄsÂ<2IâÄÈC)Zé4»q$±¹‹¹TZÌ“X�›âÁ"±œ È›ú�/T¬E{¾È¡—¥’æ	#uB ê¹�é4V~¬�­ˆŸCpsÅ¤Ú‘ýû4ÌÆKË*xg‰>ˆÃ¶ÑH&DW:ñ¦’ Èôà€L¤�ÑT/ [Ù j’Æ`ÇÐZ¼�0zãq"Çž†BN€´�Œ¦™†ZŒ’8rõ¬¤bŸ?tâ©Ôê Vù�çûà,Åi7ôôÔ•]'ÙP‹ûÆŽ?¸öô„ ×ÆN”	úGÖâ^`Ã;‚à¡0?÷äMcÉ¦s)ç5„ÚKÆR§ë*9æJ‹Åd/÷dÐ'A²Ì
5I¹”]Ðr£LW÷pj¤¶Ä‹`%‘¤ðb·:~o
ówiÉ‡A(z`_ñT*:@ÈÞß‹F&8»PÖÔÊÐiJ|èœ‹(ö%ŸœGØÊ•ôÀJ’~Ù±0œŒAxò.nÁº)ï‚TFæª™o7ñ 9ÀFGhÞŠè$U›I€á©Lö¬�Yd?Ùp(¥ŸZ:i]L-æÜi	ÇgÛØY‹ä—ºQæžƒf§…S—ÐfÁCïUhƒF ¿9
ûaM.tnÓ`cgI*ÃxÅá‘ m®@¹ÓÄ/ˆðËf[4GA(›_Ð":•/šß‘@�’x·?ÈÒEŸœ4¼ÂÑ—M³/¡[ßŸ¼“ÝÕíJ}áÝÑ~Çà3u^•‹A ®qNëõ*:Ì‡ððÄäFÜÌ6a(Ô,ç×(Ôù7Í'JÎŠìÀÄŽ<l$`H²7Ž²»Üm“{1þ$÷‹ViÈKŽãÎåÕåÙ1TNylYœB³Æ	fŠt‹h1aƒNU'¼i þýÏ`ƒæ¨˜÷g¯Ø«ðžFUçAÂK¾†*¼$ü2\f’…R8Xå¾À¹ó¡—$¦þ^OàÁ¨ ŒâgÑT"4ä®9!/ŠöÂKKÏÍs{ìB�4�s3×½î°_ÉÂ¤ÅiQ )%ÈQè‘D‘*Ù	S¤óÈÚ@ƒòB0† †ña~Ÿ·2œH<Pú¬o5!>k§(^P¢áÒ„¼ÈîØˆèpß±ÿø«�æÛÓ^I#t?u�wa”­íðF*LRòÀŠ\Ê•5Çvè�U‡+KÎéû1xNhV=°Æ7ueÍ5^`ìÑ…Õà¶Š W™{´¸†zËq>Ñ²æ¼‡â20Ì‘|DžNÄïÃìn˜ÃÜ£°š¶¶ab,�[¡WbìÙ‰(þ™è%n)y=l~ó¶±óg/ÌäÆùÍH/ÐáfM3ò^Qº±	$£™vÁýRâAÀ'2&Á”ä¶ì—3 \V9»¦¦Â×	ÂÓ6Îä™*‚éÅÛaú:^ÁV!œ¥�é4‰ï‚ˆ6ÄqØ\’iß+'W)'=S£øáCõcÙ s±&õ‚›E¹é€ÄGt#Ó8ÌLJ÷ãOu‘$åÜ&V�9ç,y5ß„¤8Å;].‰gEZ¹8ðÛlÃ‘S¡?t¯.�¹`xÃt*?ÐDBÉªO‰Ò–<ì‚ýâÜ}Ç¹‘Ó¢ í4Éä�,ïõ@8¥8¼0zÄ’)ìZ´†1Ú…„ƒ4é"5<¥2Ãó–k~:³šõvò>œRô1†º5/"§Ž _$F¨:CYd›`ç›¯ˆ‘™‡üÎ|6v°Ùoò†¡xûêà¥‰¶¼ŒK‚QpGé›²q'ðÁp0
HD&P"Ê$˜Gfïrúò'Ê„oâ9J�ÓóÓãž8¾zÙsP.~su!¨Öaô�áÐSÎ.ùÎ<[±¼{³1GŠR¨.è2Ó©-!G’R+ É„jm2†À–p’~Ê…(¤‹šÓ–Z3d	„ ZME:õ%¦—É‹TDÒSÀ(Íñ I¥ÅÁÝîù„Q™Ê@8”ÁÎ‘ñÆsÌ‡šRÞ8½)Uj±ø»Lb®Q!8byÛÉ­p\9CTÙ(ÿ*çÆ‡ÜQ z]üñîzÝü$’~¿:ç§ÝãSçâìÒ)$ÐjÔæ:©Ï™S+‹5”˜H/ÍÑYRv--»LŒÉVyJ|+^‰üÃùí‘!ºšûpbj?,£ƒï1l…`>Ì(÷^æâ{(| À ‘m�¡�§Ô°Kí†÷½c€ 2"¥ƒGpBv?No…94ÎïJu`•‡»Û“Ó±’ÔjLºž²Û±æ)*NçseÈçTN±]l=ã
Á5åÒÑÚh›h¦rK£¼©úºZ�(äâ¨ˆ÷‡Ýr;¾OQÊ9hü·GXZ(z‚;ª’xž¶++‰Ãû‚3Âöéõ	—T„°,6lS ¸#ï£\!ûÀ°0êñBêñ8Ì	+4�R^ºó ÇÊ\§+¥/6š‡5Ð7\/„Ö	'Ês2�Ü´T¾c¾Û•´àžDx(¹‰ùöƒ#û¡EÚíNA©9Íg>\¾¡`ÙªªqŽ—É=M-ª.Ýdv�yòzê»-F%6y¿1tËôBÊ²Õ5…ò¦³­Ä¿¶ANdéÕÉ§L4ò"ß}³HÊóðk	»_.ËZ“E`+?»¹‰FŒU÷ÑØÑwj3H-I µ6C�!›�)J0Üº#m5;Eñ˜@ÿýÙÉ¡hêX{a¿(Y¶¹�€ÏäÔ
{´P+É&;aXöP¨-âSX±•"j‰e{… ÎCÛÓüÚæ+ÕÅ:	µÞÚc„PŸ.”ZúO%æ8_ø0A½:1Ã0ó9Å€ëeQ­¥<îˆamYMui�Œ“ ÕØ]— Ô9P[Åb£"ÜÞÙF�ûrDô°ZVêÔ2‹€jLn87^¤6©çD U5"&÷3-9ÿ¹Ì½Shæ¢	�DÖãqÆ]iÛ‘®¶Í LËÇC°´Mß¼£æMþ¼LÄùør·£*•µÔýžoBæ¦ip-“oÂÉbù<V§JA#³Î�n*9À4éëâ½Ì¢&Á[šÄ~6Ì/•úå°(`î›'�ŒøÛ†Äì`#�•‰:y5ƒü"kžDáò‘(ÃÝ’§„ä×†ö¦+:FæB1˜êO0P¸E[mbè�b(£Wed)Î¾P�×ú'6Jc™•­V|N¹a¯6ñ”m+ÇWnúD›Ë¥…ÍòŸÆæ=”§œIàs†äR+š}’CR¡ÐfÚ"þHÓ%s�ÿg¿ÁÄCéÀe¬¿‡£ð×Ùât”¤›©�Šn™L¾š|†t¤Þ¸çÿ=¬5î—mA|
âv…S5U“lhð< N„†Í‡jµuG$fÑ‘¸µµT^8ÏŸý°÷,Ú{æ?o—ÅC»Z Ýoµ©ÜJ%>¼;½9­T¿ß‰·7Wï¯Å›„/®nNNoøçm…ÂªŸùTúîS)¬¹-þpuvY¥| ®.…vMTÆAaó“0Þ*?ÈÝ2¯ *šR¯•l�Ô_~óõ¯­>?wÎM§	²•¾yfU¬‚!beµë(jMw×$æNeÒó`#ª„;¶£Û8Fh{·RÍdSã·v‘¥Ò\Øzö�(0÷‰Ñ”¯l*‰07öJµ�zb†lDÊï¼35OPOQ]·ÀW7®¹Ë/c�Æ*gÖ\MÆŠîûæ¨ût,´D~:ŸHºáaNOiÁ0Kø.úßÿü—¹çïz½kñêà lçï½ééC~}äˆºm ù2²Hë¶Öè•óùŠ.ºsŽéòt!µi5pŽD',…3B:±o(Š;T¤¨!C$šZx|ÊÄÁ>ØE
ÀïL¨×$ÍýTJ%5Ü~âY©øëƒLí®È}Z)È±³V(R�Ÿ—.Õ8[�(spe$’i³Â'øy!5ÞtW,’ü9˜ðóA_”]�zë«ÒÔ2f²¥£eš
„³õ¤àÞX‡m�o«~Ûå’q=RWVÅ¹i¯JqÌ«6Êzc©Ìà$[°#XÞ<[žOåŸN2:©7÷ÆÖ½«~ÎÔ?!Ê¯ÒËù|Q �vr£>rí+ò£ Iµ+nÍÔ-?Ó1¥ÈÜKü”ªƒ×F¤ªƒ3�jx nöÅ5MJ†‚¼zDOƒæ“ 4WÕ¬Vž?óÔØùZIz°èG|†aö)Nƒy%—ñõ;‹™ÉàÞßœÛ]Ë}K·×l½ |I³É®ðªô9æ‰˜ÛÑqàÌlCìÈø�Ý]¡~LgÌì‰Ó[òÆfðwìX	*¢oz»D	 K‡dþšä–)D­t~vqÖ3¾ì$îš–ù ÿõ¹2ÀÕÜ³0¶ù{¹î6V]Ø8ît{ek[tºâì²wúöô¦ETO³d§¨ÎˆOã•á #Oør"*ZF©)F×;»8íö:×æéÀ8Þ³oCè^êFÏ·ÌR¿„Ý?éôNéËbÖ˜±q'—±MÊˆÔ”3PD'×DŽ.ž½±áÒèµÅœïúR¤ƒéTú�û&€Ã£}²e¡àPæ°
üz‹lÐœ]‹,�¹L³Ð¼×!yº†S~ÂÑíqw×ÈaN·Ê|]GpÔM¥8m¯l P˜,õ°LS�…¶É 'mA9‡Ò}ó!—ÂÌØKû¬…æ'Räªô~²?’¶&5¥úæöaÅÏ#Õô’±­ZYoX[ÌÎ8²»ÁcvwMWDgpÁPõ—bß|U·A6=#†/€ÉæþÖQ}+¾kÒ<ÓV4§éÁ¾UØiey‘=['rÖ	Õ&D,Lê„>x@°®ënË]ºÈ*>¼ûÁ�šþhJõRxPE/=éþ,Eà‡"’IüQ6á"(÷ÝÆY�Nøš‘s¹&w–Ì�¥j*.;ªF™±‰Áµd�^XTôLÔã÷¥‚“õ¿hƒl,&�OB"„æ“7£l'ŒÇîµmÔqçÐÒ:BÙ!ýCñlÖd!µ>§câ )ŠÂév NœÖZÑ¼~ÉRm­¯õ\–Û/WR­Üà`Kç…U½g£¢:Eßq3¼¸qÙ©fa“d~ßËÊ[	~w	¿ÑÍ/oZÛ®krvæÝõ®Å½[R¶[�¶kwÄ/¢r×’¹á¾gË™²C¤œ”ÔÏt‡^Ñ*“öjÈÊ¶0ó½9ý5¼à s¦šÄ¾é�Ûµ×xÍŠ[ÃœùÊg³Ž)þ[®+\¦J©4s7hFY@Œ%wŠ·â*Qþ‘”/¥ëéÛ3O¯ŸØÓ*ô<ÔŠšBƒ«x:õß½Üú¿¸ÀÚávH[ô77G>Kûç³užÒüi‹/È·}Ž’Ë¼HªÜìý*·zæ‚hsE÷E¿ü5_�²•’ñ!Ê~¥»6KÞÆÚt3yÿ‹÷oôÿì�1ö•œ¯Õó{ƒÖ’MkðáË†Ou¢Õë�ÿ-gJ	)ò«¯&›ž(Ýn| ö‹ù Ûöf^æ¯eòf�¥ûgôƒ*´÷�!W…š.³¾ÜY€GÜ QQ±´ªhš{ì~Ý<›ó�©¿úŠÄ'öêøÕ«xWüÝzšÒ,ÿJŽ!ïÄ€ùxO$°Û„A±Lpackage main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"net/http"
	"sync"
	"time"
)

// StatsService serves the Blocks“°API surface
//“Ï“í3,
// backed directly by the“¼A//
// The Next.js explorer turns on config.features.stats when
//“P#(it points at this backend). Its /stats page
// then requests /v1/counters and /v1/lines; without them it shows two error
// banners ("Something went wrong" + "Some of the charts did not load because the
// server didn't respond"). The indexer's own /v1/indexer/{slug}/stats endpoint
// is a different shape and its chart endpoints are empty stubs, so we serve real
// aggregates here, computed from evm_blocks / evm_transactions / evm_addresses.
type StatsService struct {
	dbPath string
	mu     sync.Mutex
	db     *sql.DB
}

// NewStatsService targets the default chain's indexer DB
// ({dataDir}/{slug}/query/indexer.db).
func NewStatsService(dbPath string) *StatsService {
	return &StatsService{dbPath: dbPath}
}

// conn lazily opens a read-only handle. The indexer writes in WAL mode, so a
// read-only reader never blocks ingestion. The DB may not exist until the
// indexer has created it, so open is retried per-request until it succeeds.
func (s *StatsService) conn() (*sql.DB, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.db != nil {
		return s.db, nil
	}
	db, err := sql.Open("sqlite3", "file:"+s.dbPath+"?mode=ro&_busy_timeout=3000")
	if err != nil {
		return nil, err
	}
	db.SetMaxOpenConns(2)
	if err := db.Ping(); err != nil {
		db.Close()
		return nil, err
	}
	s.db = db
	return db, nil
}

// Mount registers the stats-service routes on mux.
func (s *StatsService) Mount(mux *http.ServeMux) {“counters", s.handleCounters“lines", s.handleLines“lines/{id}", s.handleLin“pages/main", s.handlePagesMain“.pages/transactions", s.handlePagesTransactions“pages/contracts", s.handlePagesContracts)
}

// ---- Wire shapes (match @luxfi/stats-types) ----

type statsCounter struct {
	ID          string `json:"id"`
	Value       string `json:"value"`
	Title       string `json:"title"`
	Units       string `json:"units,omitempty"`
	Description string `json:"description"`
}

type statsPoint struct {
	Date          string `json:"date"`
	DateTo        string `json:"date_to"`
	Value         string `json:"value"`
	IsApproximate bool   `json:"is_approximate"`
}

type statsLineInfo struct {
	ID          string   `json:"id"`
	Title       string   `json:"title"`
	Description string   `json:"description"`
	Units       string   `json:"units,omitempty"`
	Resolutions []string `json:"resolutions"`
}

type statsLineChart struct {
	Chart []statsPoint  `json:"chart"`
	Info  statsLineInfo `json:"info"`
}

func writeJSON(w http.ResponseWriter, code int, v any) {
“ñ5w.WriteHeader(code)
“ªv)
}

// ---- Aggregate helpers ----

func (s *StatsService) count(db *sql.DB, table string) int64 {
	var n int64
	// #nosec G201 â€” table is a fixed internal identifier, never user input.
	db.QueryRow("SELECT COUNT(*) FROM " + table).Scan(&n)
	return n
}

// avgBlockTimeSec returns the lifetime average block interval in seconds,
// derived from the full timestamp span so it stays meaningful on a sparse,
// demand-driven chain (per-window deltas collapse to zero when idle).
func (s *StatsService) avgBlockTimeSec(db *sql.DB) float64 {
	var count, minTS, maxTS int64
	db.QueryRow("SELECT COUNT(*), COALESCE(MIN(timestamp),0), COALESCE(MAX(timestamp),0) FROM evm_blocks").
		Scan(&count, &minTS, &maxTS)
	if count < 2 || maxTS <= minTS {
		return 0
	}
	return float64(maxTS-minTS) / float64(count-1)
}

// dailySeries buckets a COUNT by UTC day over the trailing `days` window.
// timestamp is a unix-epoch integer, so strftime needs the 'unixepoch' modifier.
func (s *StatsService) dailySeries(db *sql.DB, query string, days int) ([]statsPoint, error) {
	since := time.Now().AddDate(0, 0, -days).Unix()
	rows, err := db.Query(query, since)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	points := make([]statsPoint, 0, days)
	for rows.Next() {
		var day string
		var value int64
		if err := rows.Scan(&day, &value); err != nil {
			return nil, err
		}
		points = append(points, statsPoint{Date: day, DateTo: day, Value: fmt.Sprintf("%d", value)})
	}
	return points, rows.Err()
}

// ---- Handlers ----

func! (s *StatsService) handleCounters“I,db, err := s.conn()
	if err != nil {
		writeJSON(w, http.StatusOK, map[string]any{"counters": []statsCounter{}})
		return
	}
	blocks := s.count(db, "evm_blocks")
	txns := s.count(db, "evm_transactions")
	addrs := s.count(db, "evm_addresses")
	avgSec := s.avgBlockTimeSec(db)

	counters := []statsCounter{
		{ID: "total_blocks", Value: fmt.Sprintf("%d", blocks), Title: "Total blocks", Description: "All blocks indexed on the chain"},
		{ID: "total_txns", Value: fmt.Sprintf("%d", txns), Title: "Total transactions", Description: "All transactions indexed on the chain"},
		{ID: "completed_txns", Value: fmt.Sprintf("%d", txns), Title: "Completed transactions", Description: "Transactions included in a block"},
		{ID: "total_addresses", Value: fmt.Sprintf("%d", addrs), Title: "Total addresses", Description: "Distinct addresses seen on the chain"},
		{ID: "average_block_time", Value: fmt.Sprintf("%.1f", avgSec), Title: "Average block time", Units: "s", Description: "Average interval between blocks"},
	}
	writeJSON(w, http.StatusOK, map[string]any{"counters": counters})
}

// lineInfos is the catalogue of charts this backend can serve, each backed by a
// real SQLite aggregation.
var lineInfos = map[string]statsLineInfo{
	"newBlocksPerDay": {
		ID: "newBlocksPerDay", Title: "New blocks per day",
		Description: "Number of blocks produced each day", Resolutions: []string{"DAY"},
	},
	"newTxnsPerDay": {
		ID: "newTxnsPerDay", Title: "New transactions per day",
		Description: "Number of transactions each day", ResoClutions: []string{"DAY"},
	},
}

func (s *StatsService) handleLines“I,sections := []map[string]any{
		{
			"id": "blocks", "title": "Blocks",
			"charts": []statsLineInfo{lineInfos["newBlocksPerDay"]},
		},
		{
			"id": "transactions", "title": "Transactions",
			"charts": []statsLineInfo{lineInfos["newTxnsPerDay"]},
		},
	}
	writeJSON(w, http.StatusOK, map[string]any{"sections": sections})
}

func (s *StatsService) handleLine(w http.ResponseWrite“^id := r.PathValue("id")
	info, ok := lineInfos[id]
	if !ok {
		writeJSON(w, http.StatusNotFound, map[string]any{"error": "unknown chart"})
		return
	}
	db, err := s.conn()
	if err != nil {
		writeJSON(w, http.StatusOK, statsLineChart{Chart: []statsPoint{}, Info: info})
		return
	}

	var query string
	switch id {
	case "newBlocksPerDay":
		query = `SELECT strftime('%Y-%m-%d', timestamp, 'unixepoch') d, COUNT(*)
			FROM evm_blocks WHERE timestamp > ? GROUP BY d ORDER BY d`
	case "newTxnsPerDay":
		query = `SELECT strftime('%Y-%m-%d', b.timestamp, 'unixepoch') d, COUNT(*)
			FROM evm_transactions t JOIN evm_blocks b ON t.block_number = b.number
			WHERE b.timestamp > ? GROUP BY d ORDER BY d`
	}
	points, err := s.dailySeries(db, query, 365)
	if err != nil {
		writeJSON(w, http.StatusOK, statsLineChart{Chart: []statsPoint{}, Info: info})
		return
	}
	writeJSON(w, http.StatusOK, staQtsLineChart{Chart: points, Info: info})
}

func (s *StatsService) handlePagesMain“I,db, err := s.conn()
	if err != nil {
		writeJSON(w, http.StatusOK, map[string]any{})
		return
	}
	blocks := s.count(db, "evm_blocks")
	txns := s.count(db, "evm_transactions")
	addrs := s.count(db, "evm_addresses")
	avgSec := s.avgBlockTimeSec(db)
	daily, _ := s.dailySeries(db, `SELECT strftime('%Y-%m-%d', b.timestamp, 'unixepoch') d, COUNT(*)
		FROM evm_transactions t JOIN evm_blocks b ON t.block_number = b.number
		WHERE b.timestamp > ? GROUP BY d ORDER BY d`, 30)

	writeJSON(w, http.StatusOK, map[string]any{
		"total_blocks":           statsCounter{ID: "total_blocks", Value: fmt.Sprintf("%d", blocks), Title: "Total blocks", Description: "All blocks"},
		"total_transactions":     statsCounter{ID: "total_txns", Value: fmt.Sprintf("%d", txns), Title: "Total transactions", Description: "All transactions"},
		"total_addresses":        statsCounter{ID: "total_addresses", Value: fmt.Sprintf("%d", addrs), Title: "Total addresses", Description: "Distinct addresses"},
		"average_block_time":     statsCounter{ID: "average_block_time", Value: fmt.Sprintf("%.1f", avgSec), Title: "Average block time", Units: "s", Description: "Average interval between blocks"},
		"daily_new_transactions": statsLineChart{Chart: daily, Info: lineInfos["newTxnsPerDay"]},
	})
}

fun+c (s *StatsService) handlePagesTransactions“I,db, err := s.conn()
	if err != nil {
		writeJSON(w, http.StatusOK, map[string]any{})
		return
	}
	var txns24h int64
	db.QueryRow(`SELECT COUNT(*) FROM evm_transactions t JOIN evm_blocks b ON t.block_number = b.number
		WHERE b.timestamp > ?`, time.Now().AddDate(0, 0, -1).Unix()).Scan(&txns24h)
	writeJSON(w, http.StatusOK, map[string]any{
		"transactions_24h": statsCounter{ID: "new_txns_24h", Value: fmt.Sprintf("%d", txns24h), Title: "Transactions (24h)", Description: "Transactions in the last 24 hours"}4,
	})
}

func (s *StatsService) handlePagesContracts“I,0writeJSON(w, http.StatusOK, map[string]any{})
}
[N?ïúÇ`x¯Pð±L“ �Mnet/http/httptest"
	"path/filepath"
	"testing"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

// newTestStatsDB builds a minimal indexer DB mirroring the evm_* schema columns
// the stats service reads: evm_blocks(number, timestamp[unix]), evm_transactions
// (block_number), evm_addresses(hash).
func newTestStatsDB(t *testing.T) string {
	t.Helper()
	dir := t.TempDir()
	path := filepath.Join(dir, "indexer.db")“¸!path“¡t.Fatal(err)
	}
	defer db.Close()
	_, err = db.Exec(`
		CREATE TABLE evm_blocks (number INTEGER PRIMARY KEY, timestamp INTEGER, gas_used INTEGER, gas_limit INTEGER, base_fee TEXT);
		CREATE TABLE evm_transactions (hash TEXT PRIMARY KEY, block_number INTENGER, gas_price TEXT);
		CREATE TABLE evm_addresses (hash TEXT PRIMARY KEY);
	`“¡t.Fatal(err)
	}
	// 5 blocks over the last 3 days (30s apart within a day), 3 txns, 2 addrs.
	now := time.Now()
	for i := 0; i < 5; i++ {
		ts := now.AddDate(0, 0, -(i % 3)).Unix() + int64(i)*30
		if _, err := db.Exec(`INSERT INTO evm_blocks(number,timestamp,gas_used,gas_limit,base_fee) VALUES(?,?,?,?,?)`,
			i+1, ts, 21000, 12000000, "0x5"); err != nil {
			t.Fatal(err)
		}
	}
	for i := 0; i < 3; i++ {
		if _, err := db.Exec(`INSERT INTO evm_transactions(hash,block_number,gas_price) VALUES(?,?,?)`,
			fmt.Sprintf("0x%064d", i), i+1, "25000000000"); err != nil {
			t.Fatal(err)
		}
	}
	db.Exec(`INSERT INTO evm_addresses(hash) VALUES('0x01'),('0x02')`)
	return path
}

func TestStatsCounters(t *testing.T) {
	s := NewStatsService(newTestStatsDB(t))
	rec := httptest.NewRecorder()
	s.handleCounters(rec, httptest.NewRequest(http.MethodGet, "/v1/counters", nil))
	if rec.Code != 200 {
		t.Fatalf("status %d", rec.Code)
	}
	var out struct {
		Counters []statsCounter `json:"counters"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
		t.Fatal(err)
	}
	got := map[string]string{}
	for _, c := range out.Counters {
		got[c.ID] = c.Value
		if c.Title == "" {
			t.Errorf("counter %s has empty title", c.ID)
		}
	}
	if got["total_blocks"] != "5" {
		t.Errorf("total_blocks = %q, want 5", got["total_blocks"])
	}
	if got["total_txns"] != "3" {
		t.Errorf("total_txns = %q, want 3", got["total_txns"])
	}
	if got["total_addresses"] != "2" {
		t.Errorf("total_addresses = %q, want 2", got["total_addresses"])
	}
}

func TestStatsLines(t *testing.T) {
	s := NewStatsService(newTestStatsDB(t))
	rec := httptest.NewRecorder()
	s.handleLines(rec, httptest.NewRequest(http.MethodGet, "/v1/lines", nil))
	var out struct {
		Sections []struct {
			ID     string          `json:"id"`
			Charts []statsLineInfo `json:"charts"`
		} `json:"sections"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
		t.Fatal(err)
	}
	if len(out.Sections) == 0 {
		t.Fatal("no sections advertised")
	}
	found := false
	for _, sec := range out.Sections {
		for _, c := range sec.Charts {
			if c.ID == "newBlocksPerDay" || c.ID == "newTxnsPerDay" {
				found = true
			}
		}
	}
	if !found {
		t.Error("expected block/txn charts advertised")
	}
}

func TestStatsLineData(t *testing.T) {
	s := NewStatsService(newTestStatsDB(t))
	for _, id := range []string{"newBlocksPerDay", "newTxnsPerDay"} {
		rec := httptest.NewRecorder()
		req := httptest.NewRequest(http.MethodGet, "/v1/lines/"+id, nil)
		req.SetPathValue("id", id)
		s.handleLine(rec, req)
		if rec.Code != 200 {
			t.Fatalf("%s status %d", id, rec.Code)
		}
		var out statsLineChart
		if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
			t.Fatal(err)
		}
		if len(out.Chart) == 0 {
			t.Errorf("%s returned no data points", id)
		}
		if out.Info.ID != id {
			t.Errorf("%s info.id = %q", id, out.Info.ID)
		}
	}
}

func TestStatsLineUnknown(t *testing.T) {
	s := NewStatsService(newTestStatsDB(t))
	rec := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/v1/lines/nope", nil)
	req.SetPathValue("id", "nope")
	s.handleLine(rec, req)
	if rec.Code != 404 {
		t.Errorf("unknown char)t status = %d, want 404", rec.Code)
	}
}
Õßèž»Óx;Äêpackage main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"testing"
)

// Full evm_blocks schema, matching what the indexer actually writes â€” the
// shared stats harness only creates the columns /v1/counters needs.
func newBlocksDB(t *testing.T, n int) string {
	t.Helper()
	path := filepath.Join(t.TempDir(), "indexer.db")
	db, err := sql.Open("sqlite3", path)
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	if _, err := db.Exec(`
		CREATE TABLE evm_blocks (number INTEGER PRIMARY KEY, hash TEXT, parent_hash TEXT, nonce TEXT,
			miner TEXT, difficulty TEXT, total_difficulty TEXT, size INTEGER, gas_limit INTEGER,
			gas_used INTEGER, base_fee TEXT, timestamp TIMESTAMP, tx_count INTEGER);
		CREATE TABLE evm_transactions (hash TEXT PRIMARY KEY, block_number INTEGER);
		CREATE TABLE evm_addresses (hash TEXT PRIMARY KEY);
	`); err != nil {
		t.Fatal(err)
	}
	for i := 1; i <= n; i++ {
		if _, err := db.Exec(`INSERT INTO evm_blocks
			(number,hash,parent_hash,miner,gas_limit,gas_used,base_fee,timestamp,tx_count)
			VALUES(?,?,?,?,?,?,?,?,?)`,
			i, fmt.Sprintf("0x%064d", i), fmt.Sprintf("0x%064d", i-1),
			"0x0000000000000000000000000000000000000001", 12000000, 21000, "0x5", 1700000000+i, 1); err != nil {
			t.Fatal(err)
		}
	}
	return path
}

func decode(t *testing.T, rec *httptest.ResponseRecorder) map[string]any {
	t.Helper()
	if rec.Code != http.StatusOK {
		t.Fatalf("status %d", rec.Code)
	}
	var body map[string]any
	if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
		t.Fatalf("bad json: %v -- %s", err, rec.Body.String())
	}
	return body
}

// /v1/stats is the canonical flat summary. Values must be plain numbers so a
// caller can compare total_blocks to eth_blockNumber without unwrapping an
// envelope â€” that comparison is the only real check that the index is current.
func TestV1Stats(t *testing.T) {
	s := NewStatsService(newBlocksDB(t, 7))
	rec := httptest.NewRecorder()
	s.handleStats(rec, httptest.NewRequest(http.MethodGet, "/v1/stats", nil))
	body := decode(t, rec)

	if got := body["total_blocks"].(float64); got != 7 {
		t.Errorf("total_blocks = %v, want 7", got)
	}
	if got := body["head_block"].(float64); got != 7 {
		t.Errorf("head_block = %v, want 7", got)
	}
	if body["indexed"] != true {
		t.Errorf("indexed = %v, want true", body["indexed"])
	}
}

// A missing DB is a real state (fresh chain, indexer starting), not an error:
// it must answer 200 with indexed=false so a health check can tell the two
// apart, rather than 500ing.
func TestV1StatsNoDB(t *testing.T) {
	s := NewStatsService(filepath.Join(t.TempDir(), "absent.db"))
	rec := httptest.NewRecorder()
	s.handleStats(rec, httptest.NewRequest(http.MethodGet, "/v1/stats", nil))
	body := decode(t, rec)
	if body["indexed"] != false {
		t.Errorf("indexed = %v, want false", body["indexed"])
	}
}

func TestV1Blocks(t *testing.T) {
	s := NewStatsService(newBlocksDB(t, 10))

	t.Run("newest first, default page", func(t *testing.T) {
		rec := httptest.NewRecorder()
		s.handleBlocks(rec, httptest.NewRequest(http.MethodGet, "/v1/blocks", nil))
		blocks := decode(t, rec)["blocks"].([]any)
		if len(blocks) != 10 {
			t.Fatalf("got %d blocks, want 10", len(blocks))
		}
		if n := blocks[0].(map[string]any)["number"].(float64); n != 10 {
			t.Errorf("first block = %v, want 10 (newest first)", n)
		}
		if h := blocks[0].(map[string]any)["hash"].(string); h == "" {
			t.Error("hash must be populated")
		}
	})

	t.Run("limit is honoured", func(t *testing.T) {
		rec := httptest.NewRecorder()
		s.handleBlocks(rec, httptest.NewRequest(http.MethodGet, "/v1/blocks?limit=3", nil))
		if got := len(decode(t, rec)["blocks"].([]any)); got != 3 {
			t.Fatalf("got %d blocks, want 3", got)
		}
	})

	t.Run("limit is bounded so a huge index cannot be dumped", func(t *testing.T) {
		rec := httptest.NewRecorder()
		s.handleBlocks(rec, httptest.NewRequest(http.MethodGet, "/v1/blocks?limit=99999", nil))
		got := len(decode(t, rec)["blocks"].([]any))
		// Assert the cap AND that rows actually came back â€” `got > 200` alone
		// passes trivially when the handler returns nothing, which is how the
		// scan-type bug slipped through the first run of this suite.
		if got != 10 {
			t.Fatalf("returned %d blocks, want all 10 (capped at 200)", got)
		}
	})

	t.Run("before pages backwards", func(t *testing.T) {
		rec := httptest.NewRecorder()
		s.handleBlocks(rec, httptest.NewRequest(http.MethodGet, "/v1/blocks?before=5&limit=2", nil))
		blocks := decode(t, rec)["blocks"].([]any)
		if len(blocks) != 2 {
			t.Fatalf("got %d blocks, want 2", len(blocks))
		}
		if n := blocks[0].(map[string]any)["number"].(float64); n != 4 {
			t.Errorf("first block = %v, want 4 (strictly before 5)", n)
		}
	})
}

// Every route this service mounts lives under /v1 and none under /api.
func TestStatsRoutesAreV1Only(t *testing.T) {
	mux := http.NewServeMux()
	NewStatsService("/nonexistent.db").Mount(mux)
	for _, p := range []string{"/v1/stats", "/v1/blocks", "/v1/counters", "/v1/pages/main"} {
		if _, pattern := mux.Handler(httptest.NewRequest(http.MethodGet, p, nil)); pattern == "" {
			t.Errorf("%s is not mounted", p)
		}
	}
	// /api/v2 is Blockscout's shape and must not exist here.
	if _, pattern := mux.Handler(httptest.NewRequest(http.MethodGet, "/api/v2/stats", nil)); pattern != "" {
		t.Errorf("/api/v2/stats must NOT be mounted, got pattern %q", pattern)
	}
}
%˜¼³³ùxÍ\koÇ’ýLýŠ;C[Ú	‚ÝU¢Ø’ìè^KVD%þö�Ó$çŠœ¡g†z¬!`?-°_ûö—å—ì9UÝó )Y¹`Ü\q¦ÕÕ§«NU×d�.¢‰5ó(I·¶’ù"ËKluº£,-íuÙÅŸqTFÃ¨°ýâÓŒ¿m:Êâ$�ôÿZd)ŒçÒn–Mø+µeZ–þ�ü÷"*§ýq2³üƒŠ2GyWÜ¤#>*“¹ínmu>˜î$)§Ëa8ÊæýyT–i’í`ê¤´ß²Eóýly=Nú“<ZLû6�$)ÆèÈÏ$¾n�Ôl™¤±½¶¹oZ”Y~g[¾„‚6Oìê�¦PWÑ]'ô-ìåüþ÷×‹Y–‹Lw²˜Eå8Ë9–w‡Ø~ÆJðÞÖV¿oö)â`¹°ùeR`½ÙUZ˜rjžìˆüÆõ4O�hÐL²<[–Ðia³¥´æH|hMœØÍÑÔæ%* Eaú—Ïû~þÏÅl9¹í?1QËÝ%ÿXF²Ÿ–¶(1r&¢Œ²<·£ÒLÑefóÐœqªÂD9 ºgÖ`ª|™-»&Š	Cq,]dâºŒy•å6/¶MnçÙ¥´s�FQ:²3YÏ<Ü*ovM; è‚|ÞêüIn²[�ùÒÈ?Ämxñ®·:EA#†:øMÛ½"òø�u:qËÑ®ÑÂ@léE½ç‰	Ò…;»æÆlê€]êö»øw±ŠfWFˆí8ZÎÊÆ†d*þÀ”  ¥6÷ê‰	ÒÌpÃÌ"·ãä˜ép«m4£ªZM¢p±¨_æ¬~šwS›bh�Ë$’Î/Ê2M}£Þ¶lqQ#ð*É!†E›=ö8üº�aËa1Ê“!ðð6}™gQ<ŠŠÒŒ¢ÙlãeŠÌgþ°—6Å¾�gÙ†p�‚!‘Q~ÿÏÿ6 ‘öÎhQŸšÁà�
t¡9IffÏ@³äÒš¡Ÿ.Üê4uñÄ¯
ŠÙºÝRÕ;Nu;øŒÆT¿âk?KÇÉd«£ø3ÎÂ†ûÇWËt´Õ¹’Öºáï¢¤|�Ã¶àØŒ{µ¿rˆGJQ�ì<ËJó 8ì†[c¾¡ÐwÏ<Y�  ·å2OÍã•WxãÏÆ®ŸhÏä,ìbÉóèÂ›ODoÝªÚ(1
ŠÚ&µ¶¶ÚÕ„TLc3B³\`õŽ¸çÙr$i8ÉL4.a«†Y9]"‡¡1Ìí$ÁæÝ Ý@Ž±×øB�¦*ì¸Æ™™ –HÔb†¨ëq”$…f3íKÐ�–e—Ë7n—‚bmz¦­¤`jš ìÑLaŸ{fêÔ{œÁX:CâDÓsÜ8þmÝ°ìÔÃ{šó©åâj£_˜Y–]˜å¢qv�ÙÆ¤Þ©Î•P( +)~¢È8šèÖ=Ž®Î®C™pýÜ˜±ùW <¼¹Ý™s=0æ÷i«±æ Ò›'b[4{ÇËkQ¥Æ6è64Ñïn›¦!æ¡ŠÐ/ûHyC¯G7ÐÀ#ò#:ëùÆN¢ÑÍ¦�ÔKÞ/ÇkòtV»0°åAÃØ/’Ñ¼ß4�]uhUƒÏY¦jß¡ðæ²ŸèæÜ§Ôö,�ø
õ*óeøf5€r ±†@{âXøM~Ii«ÑHeÇYßtÇÉylŠc`7Cs[Òçn&-Å‹§&šñÍ “ò…‘³0Ávïd‹ûV"#4ÌrvÕ*¯/"›ÛßÅBt*ío”Šõý÷|AØ^TÇYKš·Î¨¼Þ†›"×àÞÜ¿˜ÕäþÑKø±	ŽG„¼Ú?–ÍpÞÏ˜w—*ñãá‡Œ‹YÖDƒû*JPŽ±èÐ!Ã<~ŒE´veÏt»n	­çÒ�‹”E´×GÙÂ«Iø"Žƒç�f•®%íu0,¬¶8ÈRK(@>o$UÈx‚Ç·xWÉ(pSžõXD" übBEäQŠ …ª×†°ªrH
iÊ…~ULZÝ;j6I—p+ †U`QtQÍ5tVV·¶in%~òj1”’ë”%ÉdÞ³�€/Ü>— ÁWpè0ŽX„G;]#—	¹	‘|?v³ÅÎ^¹
ZŽù^”ýÕ—AÛ™--lž ‹Cg,J^ƒ_mPñ‰ ³yÞ:ŠZ1²¤8‚„"lQäðÀ‰¡³¡l¸º±ðŒxwÛæT{Óƒ_Ïf"$½°7^aàŒ”œXž=aý‹S¡º�4çbž‚P+’�ÚæŒNšî3Ñä(H v‹§ˆ|ÃS„´å8èþV;½÷˜-[, êG,ºêRW®¼µ€Q.Á:q:È±c¡Êi…ÛüÆÙòŽhóAðçd;Ê÷62	Ñ8§ò¶g_ã|Y÷;xú£+G¿2éÐOÁÓ(´î·÷ªÆmólÛÌlêñBëE|‹ÑÔsë�¤@‘AöL�ÀÖQˆÏ¨Ö5¸È04hSéá0GÕÊÒ]µêMCD’˜ò5�/ž1�‹@ž€ÛÐÑèú½¡¦~á(SªV¸†²%œ†4ŽpP-8¡FêæÅé‘ÒL:ÖÁÏo�Ÿ0âyÄ}ußNxYiI¼¡ÛØùîIÖÂXÛà³)áŸ³„êwL^:Š‹‚Vpl.­³"<¾ˆ“üÅløQ°wÿòÝw½ï¥ÍWˆy€Az´–ïÍœ=wÍ£K@˜Vx€-Ùf/LÑôwL8 ºnJ— µü’&ãÄÆ�t äÀ=Æ’UbBNðû„âdHË<@,oÍ!\¸?Ë
qKµjDêð(…!ÂF<@nf *-ù¢Vâá)’^ë[VoC‘E~åvHÃxØ…Ö6B^ÓQ<*Lð¨è™�?©a©w…�DslÿÚ67óa6Û6*½.œ6Ž6yô‘òY ¹áÎê†x7I$F:g’è@èmÁ +Z» À"ƒ/ ¿}fžæ
Âè;"ª·‘}Ý,á)Z!©˜_F3ƒ½uôƒÍâ
62Âi”ö`™#4ÊR2µ�c¸½Ú«¡ë¤Ù3œ],5­5`W©‰H'RÀŸŠ?"¿³OcAõ1~ÙE—Õ«£y¡¯¤ñÑcÞÎÙéþa/ n@B:â‰¼ùéüüô9UíøLž5±k¨@yª——/I�Ù¸¹êúÞÂ*ò¨"NÊ:l°HoþîÂkuŒEi0}oZÙäA’ì÷ÿøÈó´c;äë
mx>A7Ðs†1Îêô�	
kW¢X$„Ô±iˆÏ!à6×î0Ósò%±¥;‹åp–S¸Ò*	À 6œå0ÁÊÓ?h°*°^§fÑ¬¦hëW@"¨Î.¹/æžÒœË¯Eañ¤ªgEdG ÕðÆ ÖKx Ieº%ÀÜà-|º-Ò¯ç@	‘d2™ ùˆ-þh€þm"°^H8qsÛa½3ˆ`1ÀtXgÀ¿¶²ã}&!Dc�¡:TNik<>_¨•cƒ›!»—ÄšX ïõém”wÉ¹áèa•š«Û«û†íAs¢µ>’•gâüwß»À|·åm#¬ÝQx¶„1YµñP&IÏažC‹M[²f~yŽà´ï:C05jgÈ˜G9md•¯§¡Ä‰8Ý£aÿÂüzÜSÔÃ¬pÍÄ¦6OFŽî¹ÁDÊb–XdD9Ž@>J³”™#³ô�c„# ä{iSMÅÀ)c37ßêP m‚íÉò>™ÿDþ²åÈG‡Ì™²Õ~ÕÁŒÑé�êÿ/àU€CàÕBO Þ«#<E�û ^+mCé1y.È³°§j§no€\S4F¥	"sIþÙ|ßp®ž44ýZP×xÝtk"¹.‡.kp>bÙ5Ùz®Ï�†¦©X¹×�·Åw¡½}Î b¡ÃÈMÀX2+ÝÖKà‘­íIHL$;	™î'¸]¯
gEß=© ZôÃ04¢gdìßÁ¾ætWÙRnµ¼~0-)Ò˜D.©R1›´=šé+f1Ë$÷LeÊ¹’¯;w^¦wƒØlêƒ=5ôëz“’›€ý¨ ÖÐÁE/uyàÒ¸âšÝÀƒˆ¯×Û­«<)‘ç2¸;Qçèâ¥Å6RZ¥Züú™†h&ºÌ	YüŽÑV7õoxÊ)Ù£Äˆ¼#3Ð‹í9OÓ°“Ä7ðòP£vˆ—ï¨J8—</8a3È#tx÷%ì¹Î3uòèªbt=ák[žÙR…: Úïž÷›€Ïg›íþ†ÈËêö;Ï„ø·Î­Áå¢°eLí2Y¹x_�ˆ!½Zx;Üfú\B4¿á¾²† Šô"¿4kž\ô†"«$—úV<,d¦Ç°$‰¡Œ¤NºÓ©ø«1ÃÐQWtÁ”Ð›¼†þ‡PR¼üÉ&“©Ò×a¨?ü«s°}À~¾ØÅpÕÿ¤µ\Â{•6C€šÏxý#ÿœ_ï3a ?êwGÚ[Þá‡s 6¡toøÃ¿Ø»¿À€FœdW�Ÿ‹� þ©	�PèÆ)SÀø�n –tØï¤˜ànƒ*º«ÊGñ†*åúÈƒ©ÐòoùW-½GË §ãWïÂ]ƒLôP‰ï–·6Àk²²‚L
ÅÿñŒÂ¦ÂâŽ“œg;*`_æs'°š³!Å<áð	Ùb‰*\­Z+&W;áìcñÿ`aá‚uíÕ¹<	hz9«ÆóÚŒû·�43Ï™À¼ �Ñ›{F«Í¤yX�³¨¸Áöõµ®†-Ýú0#¯ÒùvM]Å�¯@$‡eÓñ.]<òN±°#ä>FPRNsœ2	i{ ‡žœë_d\\SXfÅ4­µ¬7Éô<õei;	Åt²-Ë™K'2£å£¨û“Z¸àä¸n]ý*áU_àÝ—×Z–˜übzË§'àûX$ÉÈh‘h‚–Xh_ÒÁÿzõ¡@ž–”»†�½\Q|çSÇ¸(©`“J6ƒDS¼Ø÷ZÀø‚WÈ�ü©IŽ1A”àd"YéL9²m4t�Ë¤¬eØØº6P¡Žê—áÐëHƒ>dãéÂ¹ŒG°Ž+Ù'�Pä ˜xVœnüiºG5ÎÜ4œ
Ð¼æq%Ÿa%�\	êòd!-=dÂxN¹@2
½”@55„AX×A’€8Ê+ßW, ¯A“oÒb&PBÉ¤FB¤Íù=›”ƒæ±
î#<”‰Š“·çlë-ºD™	Žè/0�^‘f)ì“g¬¸²{+‘òM¡Kä‚ÊXMa)ë©26se�º#úÕzà;ziÙËÅ‘b”Z[Úa_‡Àºˆ‚Ï®Åà—¨¡ß"ót}5fa"y·ûT¡ó´ûã<‹í^ž1)°fßfXÜ¹Œr&�âŽÿ|@@ÃŸ™Â<ººƒÃ7‡ûçfÿí/'çÁ“žyuööØè|æ(Ü�m÷ÓáÙ¡!8ö¾­~m^œ˜qÑÞ�XyµŽ^8À�Qð8uhÖ	\.¥hÏ<›zÈ±Ú`Â×dðö¥)�,ø�*w¬°ÁM¶Æóî¥Sä—ÕycË`FÄ]:HíBT/÷%g.þîHs¸/™fF%Ž¤›…IÜYS>®È=ª_3U.Å×ü¥ï�ÉÔÄ±¼”®Õ#i�G­³ö®Œ5_òFÌ3­&e}Èþ¬9XŒ¼fç<ÿøØ·Õ‹GŠÜJR@¯9°7¾*Oîæ7ÛeñyQsõ¨è?iá§RµâÝŠ÷ìÈ'ùe_¹êKç~áë´°U/ŽMû�2H„xrCÉ(ºíÙ™3u•8”‡eÒ²ÿÙqdoûŸý ;<v·ýû<zCÈzsÜ¶ûe‰?CÐ+1œ+¾óÒ
öHd…q¥O8Bi€MíX«k,ùÀ¬þÝ—Xn’ÊcÖÛƒ" ¡:–"I’s×r5¾^™˜�6]TÝ3õ?öÆª)Ìö†(ýnQ |”QÃÉÉú™«…Y’½´«×	¹+œ¹K< Q8Åýq”B¸K¤Ai<Øfè€RŸòÆìýÉûrãh7¸Efž]ÿþÁ0°‰õ%	‡qö�ŒË½àldrkŽœ<ŽðC!&õå<&t×UIîîq8ú<ï»’BÕ¡d™^.pŒ+äM˜”J3<FN¿H®]õ"ÏF¤Iœ×‚°,&½Š@Z2Š?åY‘Š7Â¬Z9Gö"Õl3�bÜôƒä~Z&¶dÊæM„ÂÕ$-Ì„+®Ìxãžêx›Ú�bŠD’M‘^ž21ÙØœgH;åÙB3är8h¹Ìˆp9>‹¡:]ûp™ÌPÀ‚Ò%.L*jQ¶‡ú]–†G#;ÕÆA!·�ôÊ˜+/Q'”#ÑõvQî@MLn½|±ÿ—WGoÞ|8û—Ã“ÁÞóïå®^Ft!!tÔ«àŠ$ã§d,�FüEžVP_kc]‚’KÔNm´ gÂ’ÿ¯ÿýÖØrú�õ•²¾–°%—ÎäÈíƒ>TÿÚ+ìkVNU…d©¨�jÁ„úL¢UêdÄ¼Â�€ãY„âÚ+D ÈGÙô2è®,&PN÷¹–^¡)$pÖ�Ç‹•``d3Y“Äóí@^\þ&3#RD³;=¦	¼ø{�âvÄÙ<ðP S3iõ�ñc·¼]5´Ž*•í*±ÚœsÁÌÆþÍw2bÚœŸ\#	•$Ž-€riÕÜ)MšzU±çÎÖ?v�jcv]³=°’+ó\.pñôÌ,®Oþ�ìé¹ùáó�¼’ÖçÈ‹á²ñÚ·Ïð)‘«ÁÔM#‹{NX¸‚<‰R@¬»Ñœßz@òZi�u–Qü'Ù¿@{¯£dÓ&º™}º[YÚdƒCÜê¨™?>/ÃÁÂÕ;ÕÔGêœh–{ªœ8ëp±r)®%µuÅ¸ÌÝL>ÍÜw26ó¼WÚZ^ Ylß1p„»Ï])ï™~7¢¡2rœYÇ¡]°7/oež!^E¬Ž<±¾ßÆÞýðÃ7’f“ÛOn4‚ZËü²‰Ð8@-ÇÑAz¡þ£ûºÊ;²ºÃ<Ïdâ�Ÿ»€^–ÝÝß>wç°üŽhUbš?Þžtoßß~t5ÇÌ,‹—Qì—‰*Â(ÁìíÂA7<¼¶#ä4 àÊ/µ�ê4"º]!S+ëGeË +MÒr‡i&×.
°fÉHrQúM€_ó!?ï¢îz(ªäŸçEà°ÿžíú›DÓŠXÚU’¦ü€ÂW…Øg¨[¥ƒŠ³‹îvW2ÝÝGñ¶+¬‰ùƒêFè„ð…ÞÈo¥1H¾!ÑµÖ%ÞRÞ=};87Ý§zP+(ž_Ä!D¬Œž«Ý^ÞÑ‹'Y5ùšõÜÉÏo6ô^�ôÏ'4ÕŠ ð£3|­Gí—nÏY@Ô.Ž´"&ÖP>­Lo³–ž¥ø,îÚd˜”d´£¨ÏN¿ÛŸfÛ*Ë-ª3Ä4î¡ø©Kš·fipŸ"E��ZZRÜVNUª+ýZýÞþ$Ê§&×”íZùÀ1SÆ]SõHQÉIêTìqo
Ý7JªVR£!à‡¬­‘üÇP&ÅsÆW"#†êàÝºìÆgØÁÄ
;‡OH°Ø‚C	1"=±×fˆ’¦Â">"½cµŠH‚¢èà#?�ÙYÀ6¡Eê?6—[½ík¦ï#ÌLë¡_–-,?¤ãÍïxlÀu#Ó%…îú°”òxeCø‰bœ,¿uZ…Ü›œ¿8:9:yí×$¤o¸&§mšEˆjùI¸_R,¤#´š!íæ—â.pwŠÊ&ëŽ|q(/-âdŒzvòd«JmÇ5^Ò8ÒI�ñF(’ É$…u‰ÑíµÝxv‰kjÞúEyñâ÷\68}�`âã�aclƒgÛÿÚûè+š¦ü†›‚j«üuñK± �<ae1EÊéˆ«	yŸ]Ns°Ùn¦£{IIëêº~¯’ë¾¬½‹µº)‘ª%"e&‚~M¹óW¤‚¸ç>À3|q A�wõ ê€ŠŒeŠ[ïl’‚0al‡[ §U>GDê?ÁH‰ àÇÂ¨�fÂŸmÜSâìñæ"Ã?B&ˆ].±¨‹è=Ôáh9{ƒëÑr
Ú<!L•‚9ÔÅõ²¢½4‚’Om¤ÐãŒùÍ=újŠO‹›¿w�Èü§aÐ´¨tîø¦‰¥2ä¨ø|õx‰8"BU/ÇÇÂDJ®­Q|YµÑ&U);*?Ü°ß«úŠ‡b¸ûãÍOîùÜHœ·›no¹ó39"t<¾èZ„€5-´( R`0‚×MÓ‚@ž\‘€á`Ø1N‘¤£ÙGœ0bëu Þ—þj+àAÌT1Ô¼ÍjÓc¹ºénÝÙ�ö§6„ÛÕaÁfv	¶ƒÖ¤Úë®PÊ“¬|E ­àøó_7¹Ê}`!àÃˆm\ß9½:‰ÎA:oVg'FaÞ~I£K€‘×D+räßPà±,¯oòY´Ë¿©Nã¡Ö±X© *ñá>ì8ÇÅÔ©~Ù¡™_ul}éì|ã±ì­ÇaëK½ÊPIbÇùhJãSòH¨bù¹»¢�·a#Tíñ«dùF~�^¢tž€ÛÐC ›Í%Ý‡ÂÍrýÓŒ[µ®ë&(ð€ÚdufÕÔbªùËÆ¯ßïöï ‰œøJ„¹7é£�V†*ÉN\8Š(d�9,—lx€#“IþÈN‹‘§Õßp$ªóä¼˜
ìvY:ÂâÔ¾NîÖ¾¸éôÜòu�Ÿq@·yÂÀžpÛ|«Ö„IëžAÎäa£ã-ZéöÛ³÷Õ‰×Ïßãp×¦LÖÓ4dè­S{ýÅÕ|ÑÍjQ³`õëjÔ
æ”…ñîœ$š<F2…ŽÙè÷èòß®¸Hñ_ãp†Ã±ò¶©g<“Ïð‘l¼ØG¬/ƒ¨XÌ™¥Õ€é+±‚þ#'ˆ ¹hŠæMµl	i�š®Êm	i¹2Q3?µ“ÔI�ßðúsºæP.C£ÿùlHñ%›Pzv²ŽÓÖ(b-¤qíUç V$WÍAïÂ"û;,ÂØc£*@è¥á1ƒ¨�¢ Ò¾ >Öß€3D{.xv’y¸ª|î[Áê?§P¿õ(ÆGüžðvëÿ 5.ò<ë	´_x› dÿ“�¢�°T*	// Native-coin balances come from the chain, not from the
			// index â€” the same RPC this chain is indexed from.
			RPCEndpoint: cfg.RPC³P*C‹3‡ä
„ýxœÛÃ±‡cB¨Õ¿d…è•S-nîŸZR¥à±“½áŒ¡��™‰‰‚K~rvjQZfN*ƒÔŽÿ�y=Oû®Tëbðøâ\V³ic?£Èò[j“³~Èi›ÍûûgÔÕ’í_vl^É˜Çèð=ßêè®�Q+³~¬™u_ÛM“¥ ÑjpQjzfqIQe|Ijq‰^z>ÃÜ/M‘ÂŠœ—;Õïo¶™ÿ›3ûäpæ§ |}Hî€2x~ �ÿ¼¼�{3Mu½)·:)ë8Pâï—Ç\½ö±�Iûç “nv»þ*-°`´áFÏ”³©n@ªç4&Ù		ha|y:Þ‡«I±}Ï100644 registry_test.go #1yÙÍµ;všÁæˆ»huÎÌ“WåÅJ7{¤x$ Ûÿ40000 workflows ×›v}úòØI†ÌG¢»\8ýþÎL§x' Øÿ100644 release.yml  Pé¦Èab-Qù@ô«çÂ½
QÀ	"·™x­VínÛ6ý¯§¸HŠ"IM)ÎG±¥00'V#‰mØNÖ¢+RZ¢e62é’”/Í°‡ØîIv(ÙIšb†ù‡!Qäý8÷œ{¹NçÅÅw³\a(åŽ³œ/ð(§<´1¯‡»áö›Í0XÖ©iI�©Z#7t¢i$7’–š½6Ó*_ÐßþEÒ‘f.,Eóz´ER¥â†ßÀÎ(×É�MtáX¢§3î¨†›5Š&‚çn‚¡æ6übkÄUJ3aØD[G#ã_W'Q<ð¬ˆ!™�®0ÊÒÞö^yÎ‡ØV™ÖöáL�ñÿÖç´O F¼íÊìácTtSK ”ÅT(GÙ$1¡ÔÑ}¹‘i“=D¢�Ý#Ò�2w46zJyq7–«-4—œZÈX˜±ÌEXi	“‚þˆnµ¹‰pfu"‚fÿ„Nº×WqÐîvõpçm ?8_›ú�à1-Q(9–°‰ÀßÐ#Ô”>›¬JäÏÂDµö´‹A‰V¶˜â<·¾¦S�9pšI¥°(e:Ä"mT‰•&`
LØ	wkËtWn±ºî£š�î�’\{°kå(—*³eÞKôMðEiØ2À›'‚RiDâä\ÔÈj*ÀC¾Þ¿/h%<ÁhÇ7ÑF:î…Áq¿{�ps®²ƒW÷O>0ž#AÍA…›0%È+„S1ú—â³âi
¤•f•“,IhZØœaÙ¯¹t¢|Ì¤ó5©0ÙzDÍ:3rÎA8O×)_ÐÜuËDú�VçsáÓ™.†�¥]É•™Ñw‹IG›¥{#g„t!šbÊÒ%F[Ëª•‰;ž8èPVœV:4¥‚S-lHC@•Š1/@Ð“öðôòðzØ=‹;? ¯Ä8ó‘ñ¥iPœzÍ!¢Â©¾ $U}<ÑÓ³Rá	$GŸ³‰Ó7Ÿi´ ´¤y¸˜æaw®@á^¿}ÕÆ€5)F!ôî™Î– E[µï?@6%»~\^Òëù‡	W¿k.£­¥¯Nwpyqt�½p«ßíi¾ØðßãytÝëwßø¿ÍŸ7O�ÿƒà×nÿ¬ÕîSdMu{VÊ„@Á
#”ìH«±Ì
ÐØ–ú ­ -èi„n‰z•G±nU®yú™ÐA+®úòÊé,—	¸½Ncá’	¥…�pËceÇùñ)ð
|$ú„¥–/”k¸ÅL4¬ðd©É´QÒ¥fÄ×ZOcž[A¿„ŸÓGb¦PQuÂFå~úôÎ³[-7úÍ>;t.ä
µf¹ñœ
“‡kçfö ŠîO4z
uðj#Á¨ùÑôæ/Ï8¸Je�àiwL�†ž·Œ`,éõëåó@ƒª4!U0¡Å�æáyÜjÔÉ¿Uu]c­ëstúø¸}¿Ý»t/ûGñÚ“ÑªÁ3gP?à*�ˆXžŽsžÙÆ°b·ÄÞ£ËHb Z©UãÕý²­=¬ŒáŒ¦ão5c‚C…Ÿ”�êNNE¹ZuÒªiì†;õk�	g‰0ðbj,;d.G–n3á*0ó“±Qf„–þ}(QaM„ñËó÷‰Ç+�iZà*A¬E¬ ·ûû»;èxÕ�%¸Äý§·«îùåEL‘¿Ç”ºl5‡Íkè¥Q.ÑépØ»n¶ZýÆÁOÛ?oñû^wSù|7Ï‡§eó@úR9Ü^xÞØÝÀÌƒìûþÅ:nÃ½Ab*ÕñÝWåè¢U&Kìk—‘' øWfä/.¥»åå†¾}CÄ n!ûzÝvgH×V9­}
þ�'+Eä‰Vxœ›.Ü.´¡Ÿeò~–�ÉÎ¬ê›g±þe O´…²Üxœ•X}�Û¶ÿÛþ¬ÑöfËYSÈ�Ü]Û.é-NÚÁÍGK´Å†’²ãôCì3ìƒõ“ì÷�”,ëœ3�èD>oü=¯TÅÓ|#XÁe9Ê¢ÒÆ±ñp0Zn„‡¶ô¿ñ©ñçFW6‰,ç{^¨dûd4œ‡ó9;×åZn˜´Ìå‚9]Í”Ø
ÅÀ§´†¥ž 6ÜI]2¥y&2¶6º`iå6ñ‡n_‰F˜u¦Nû<\pÇ/¤aþ‡eYnXüÝßÙ(Å2“ft7üøöíÍ‹,3_¦Î�«–$D¾p°)�âO’[OÄ3Vo…Q|Ï°ÀÖÚ°ÅÍ‹ï˜(*·gÏñ\‰Œ§Kµ^^fbÍkå˜a=é+Z\f�„z-ÜN›Ö½¿�¯]–2Rõ¹‡/Š|ë_#€‘: Úûàª´ÊRXÆ™Å±•Î`•ª-«„™…×´¶Nò7ï»„½ÍÉáY�8@lH€¶Ÿ¬ *ð5WV³Ú

D„Íy%¾ck)TfÙJì5@X¨z3Í1omºº˜¿¹9Ÿ¿EÌÄ¸LW¤—+FàñªR{¶ËEé#-%!¢H–*Z•hÂªsðCl‘!�G¢÷ï~µº„Û±3j½C�ÖÈG°ü4W‰�>—_k|uuáwdéž~Ë.Ž¥ÌF®vœÊi}t´¾>¿æCUl‹)ËøfÊÍÍ”Y­xÉ§l%]ªe9P¶Ðv8 5àoR˜²CXËÞþxµ`7o~:¿\,ðpmÂ®ÊYŠqpµË¹#–R›‚+x†{AV˜­LÅ¬6gtz6Võ§l–ž)ˆMð6£¢ƒHNì6M¢¼Dé”«³gOŸ<žL½¤].Óœ4¤Ú—QÂ‘�²ÌÄ'
6Å»Åå5Ùç4¿2zýÉp@Ç9ËTé«­Ýy]7õJÉ4‚°£“‘¢(Ï£UFJÛ	[TJºmÄ²)Ç:±xaÈP–ó-…o¥­t‚ñ±–F¢tÖ›­K²‚¥¼,µcf×{¶Ò.?k%¡¼°§Ì¡“äàDu¹£v6ŸÁúÇïÿ>æ<I’». ¥nÏÝ0a±a�að¦póXiO}Ýæ*%®5-V­˜†kŽdËv-èÜ¡ úL��re`èžUgöÇïÿB AÑ˜7‚CÔJ‰ñ„‘@£K'Ê,Ùèi4|» ú9Â.C¦^NìgJP»„¿§¨fÎÛ7ŠAprQñËâ.ÇÒv¶IQÊ.Td¤Õb_¬´êóPÂõsÕ¯�ëFkõ
™¹�KzvcgY„­ÖÞîšÏô‹Ë Mç”0V!YtQÁ!lüøÓ3ü&m�ú[›<ÑÃÁeI¨‡Þ´Òdy£]„�Q×æff7…ú!cÓÑºŒ�.·ÐµIÅÉºëwFG(ÍÂ!CQŸ2Qn§Œ:Î4´!Œ&WñHñ¹ �i¡ñÄ=ÿpÆý`x•C¹¶¢Ú£mh¹+ª1³¿q üù¸ù7¢|÷?%ª¿AM@ÔêÃó{Á]�ÄmE9¿|$+¶ƒþF3
ô±¢ÁÆÈ2› Y	Ô0	‡øº{<&Ä¶Úrh­7Z©+$°Ù¢y7>nâW©¥Œ{#Özg±k®ïdÉ&a£'�i"ÅÈfÜK”7šŠêÐI³šqËí–1¢‹'P8vr3¨ŠîäIþ~Íl½ò.¶ñìÇ¬‡“Ò)æ„÷øqò´Öu²iÑÈÇ$×üÝž¬ÙkàjNœ©e.tíÛMã9jÆÙá<i.
Îj¸ÐÛ|û×¹ç›öŸÑ¼s?ÿÜ¨šûýÍ/0¢¨‰f@¡±ª¥r3€E4$ÌKÅä¨3¶Ú³èêU‚¢D=k-£Qn0˜$×ß^’ ð�y�¡¥.¥Ýñj¶ýf„Å×'�­oÑ°DZé¤@)Xçè`Â¥É„ù`­¸ËÃL�xeUwl!:¸­™êŽcµ�è:ù*@Ð#ˆŽÚÈ¯w‡�hŠd/ú¡ÜjFÉá8Öméž°wWÌ×
2ÂæzWÚD�¹³ây~­7Ú�Æ8½õ355â0»dÒ~À$Û>Í(Þj™ÀåmïÂ›„l?êÚ‘8#Èç^±”dÉÍ>¢,í�—c|\û¨©žæ?nª‡F{Ìÿ"MÑÏ5n£=~îw–)m5~àêc1ç÷ÔT˜!±³$€;¡2#ÿ4‹ÂÎIÒòîÍõ‰Ãz-µQ£Îaií�QÇ†’ÖÓ"¼ÖžZë‹Øë5PåŒV6Nc¡³4ÑÆr¹Éþ¡à æœ:I‡ž˜C\Ä¥Œî®Çv®ãÎ¡P6+=Ï*¥w×¸„>�Àig©°Õúµ¡íõhê'Eøžq$bÕÐž «¹ªÓ¬‹tEYl¿4Q2á*æ§Wº¹ªfÆ¶H-”!k„°ºæ+¡ú“V;Þi4ÓtÃê“g~õ j|ïÜN››iïÚ¤–§:”(ê7Ü¡iûkt¥8S¨S´G·&ÜÎwÂÐ€Ž;@…L]Ð±¿°|_Ñå íCQñV[¾¦;£6¨7š1u§8˜ %ùÎ4ñ)_×ö¸ó¾æ#è¶ÜYöœ…�XÉ+\�ÎÃ`<¾ûç{>ûíñìÙm|În??ž>}rÿõ]øªE])~0 ‚i»ßª¦ä[(·ìëË×?/~ñ†Ì¥¿,¦Þ*12ãd¹Ç!®ë2íÈûþ¼3aãóf¶5F›	ù›>jùwvöœi›àv”Q%ñŒ“á@®ýæWÏY)1Œ@‚”ñÚç{´ÄÂ%—$p=ùšÿÈž±G»ÑÔå…CÐýp@€¥ëMdmeC±ÿ0÷®,¸±9Wã`ÔŸ@KŠÿC}â‹úi¼”¤}c#È”$~Ù"Á‡×÷ò6¡Ë�äÒ{`ü`wJ›?\·“Ü¸Ò}™™|wÄ¾êRòŠ#GÞg=Vú�äÝö?pð1ôþQv{†€ñ1âãƒ=úTä”��
™÷¥($S)øÐ<rºÑxÕYmoÛFþ,ýŠ	qÉQ9ŠñµE€ªð‰ã\zH|��\q0|ñZ\I¬ù–]Ò¶ø¿÷™Ù¥DÉfì½Í‡ˆ¢vwÞžyff]©é¹škÊUZ‡i^•¦¦p8t1-“´˜?ûÕ–E€³¼æ�¬œóG¡ëg‹º®ø¹´ü¿Ñs}%ßmm°Q^Úe1†øu^Vçó8-ž-UžÅßÃÑpøìí- x¯,féœ=KmI‘ÅþLÓ”¤º¤´HôU<¬—•ÞØIÍ´¦/ÃÁQÖÌ	ÿœl:e­'�ÅÛ€X¤>T®o®,ð¶])ÏX)ºýü
Òëç?P{¦(õ)MÚÕ«ïØqø~��ÞÒÂTÓv1?bÝ6äÆ:6¯](Ï§é‹<¢DÍ#ªDTDWò9¼Ò3Õd5�•e†Ã¼Íð"¿mj¿BêQÙ˜)änùHÞ¶ËÇ��JD"ÒÅEDyRØˆT’"×ë¨Ù×)b”ZªAªÆ™¾Ðý÷Å»·,�iŒ¦Yi\m,¡ïÑ°Žá‡ÛÒñ‰<xPœºè‰É¾ë(°_ÔfI‹2K3²‚œâTe�¥´¶dš¢Np[«Zwä6w„;vD{QŒ¡÷ª®µ)ÈhöÝçò[X^RV^j3UV“Êª…*š\cý�Ëj¡‹xx¡Œ¬nÏØÅ)œ)ñ»ÆÖ{e^Á‹áéÿŽÕø·�ñ�'þs|òe'zþýõ_N;yr¨ç)X²ÓÜn´JÆVÍtÔÚ9Î›Z�­RÇÖ¥Ù°zuÂÚð¼a$gj|øË»¦ÖWÃ�s7X¡:f‹‹ùÉSñŒóPÉöÓø ôÍòôP«Œýý¦9óî;Ð—²s%y
­kÎò‚t^ÕKv‡XgM1¥íáˆœìÕ	Hw£®‚žl�÷^ñ	4?×áíê�"¬ƒÂÖÒ::‡üÛµ×üE’xÝ´Y,¦CmIh§3ç
D$-.T–&„·*ãÐ,I_Á2K3SæØ"�/´W&-M
Ã­dž7;4[fŽ
„ÓÙ¼Ëx#/¦Bò£6ãwªž.Ž$X¼+>B„FL�­³@àñ>+=ƒVW	ããÏAD«-ì >œ_0ŸíîRô�>ƒ�>7©ÑÉ:Õ©ÿDa¾õ‘,Ä½¢ D‡:‚ü˜8oâ·åô< 0mH^},2÷Rçš•ç4AZÅµÇ­)'?ñ/ì 6upDÊP‰ÿLš ƒ9òUež4°Î›í‰’ÕdîèÉJH‹/Å~Í£Õ–s›«E)xd
GQàÆ„.RE�mÇû`\†Ì9ð‡…y‘eT‚rÁ*‚;A~Õ(ƒISeéÉEa>úX%üŽÊQÉgK>cÓÀ(ïÝÑ¸£™¸Øèq’Ú)ûx9¡Æé�)[�­Ö*{ÊÈ@ƒmKl-ìð»Ïñ"ÍZTlÇ—v=-}q‘™ðð•ø=¨«Ü�[Ž9AKàxKác;òŽÜÝŠ„O ŸâkÛÑÊ•¢C�ÃX4/™ZógŸ!¿‘�ýÙíö…’�ŽcG®’H÷K€O7�Ï‡õ�<ì½®3•Y-ií”ÛL‰DEÕã+#¦­½…°³„µPåµgK�6�ZêéÝCÙyä¯ÖçY¿KÜ]—ˆó{ðN’êt»—�n9ªÃ�«´-ÊÌÖÉÚzæ(ŸIÒMr9Š�-EÈo»‚h~Ü&Öž][i°&ã=n�ÐŽbßŽ°Ú†°öW'ÏëŠší‘é)¸ecÙ¶qzÛjºÓý·á`cMËšH«Èm®+|Wœó.»þ©kÐûU	¦;S›º Q(þpüI–IeÞRh;•Fl¼?†:’¾\G´Î=ŸŒ]—EÔI ·àýµ© ù©´ÃýÙÂÐm¶Ë-yÜeUÙÔ\3¥MÚ8!¢�ˆÀÑ+’!Ý¹‰Ûh)³ªÀ¼ØRˆ ‘Û%UUºHB|ÁJŸØ {Óñ‹�êòim,Ïè›Ï¸4Ïàn5—Ç»< ÇÀ yÖãálUÙ0Ð+õ¶TÉktj2ÖdøÂr‰ïßd²™¡€}%&�3ÂJÕ?luú6eÃÎ,mŒö3a‘²þ±ð�ÈTd�¸·¥ñ;q{‰ÎaB�/AK,HŽw<rÌkÊŒµ:eûXäÊØ…ÊB§Í“Ùè§{H­°I÷‹õ(™®Q2‹%¬˜1m{&°¥ë�Ðî0tp“ÉžØ¿ª $o�p6Fc …Q­S¤Ë“¶ø¦¾}dcÏÓ
:»&ÜÉ…Û.¸…‘¹uµ¿m`< ‘�[Pºã¤c`,µ†Ž\0 ²ÚšÉZxíiiïÍ‹ŸÑ•ÂFBÈ&$÷ôÔ—Yyˆã8psÑ-BçÈ³Ç
kÈ‹J¥‚3#IëÚ¡O¬yGDA¸!AVîú#lüÁ¤ùQ¥¦ËÔø¸5«b7€7Ñª¢pÍ)¬Â°ƒmÈ9� i—çObŸÉòý;ÁÇWØ,+—aìDyriÜVÕŒ•:ê‡_rSÄ£_«d×Rè¼s"¢ÜÝgà+“dßy÷çq�å…2çðêïcv  à7€ßM¤ÏæÄ:Nm�îÇÁM¨»;�éÍ‡ïér/ãIoä’Ç½è)K¤,ýëèßýT¸Þ^_‚élUVÿ‚éWÜYèÏôÔÿò¹ÁÕŠ ñ2~rÓ&ÅG¨ãB¨Çì?D;@™‘)'-w7	Hñm_ŒQ~Ÿ/-±õr»Çî€*–€À‘z€±)v%”]FBIÂK¼õe¯¯G¾68cøF`u}�aòÒÇì´Éò.—0}“G˜ÎÌ$w½5þ•vÆ‹Gß©«—KÌ#\]Ø!âíø%Ô‹è‡�Ÿ�F±[>ÁÑ7&‡ìómï=ý &•9Ü&¢\_ŸF.´G¸^kìK•â2€#	—:RåÆò–öÐ—º5«ß¦ÉCð0¸ŒdIbŒS‘‰ ð[Ç}5.­Wös•t·(áèèZ9„Ôo‡n¿¦¨ñµN¾ŠnÝBïœÂî§Á¾	Ÿ2Ârõç·œ‹ÿ¨¬Ñ¡»|‡úˆ²¬Xßúô‚KÖµ÷IÁ=Ñåú�?u‚tÓÙÄ�1Xæß›Ùù õçÄAY¿æ—áü‡ç„Ÿõ"ú$m!÷t2›AØC¬è×ÏÚÌ‡‡^Î8¾÷W4®d@º=2¨µ|+å<-ÝÕCœý„L°¾VùCh´�?´\Îj,Š=þhÉnâ«GW¡*)û¿º]jâêJ’4xªUûš:£:�J‘T1“;legacy `chains: [...]` YAML shape used by tests and the
// minimal config. The full Config struct in config.go is the canonical schema“p²“¨Œ“E<	sup    *ChainSupervisor // nil = registry-only mode (tests)“ZÂttachSupervisor wires a ChainSupervisor so admin/config/mdns adds also
// spawn per-chain indexer + graph goroutines, and removes cancel them“¡AttachSupervisor(s *ChainSupervisor) { r.sup = s }

// Add registers a chain. mDNS-sourced chains never override higher-prioritLy
// (config/env/admin) entries; admin/config/env duplicates return an error“¡³œ“¸.“B.mu.Unlock()“Kd“ï.mu.Unlock()“K(“0“{!.mu.Unlock()
		return nil
	}“©1r.mu.Unlock()““ñ?)
	if r.sup != nil {
		r.sup.start(cfg)
	}“/	t“¹	%.mu.Unlock()“Ú	,r.mu.Unlock()““
)
	if r.sup != nil {
		r.sup.stop(slug)
	}“5
““").mu.Unlock()“á“	“"³8$(	updated := entry.Config
	r.mu.Unlock()
““u@
	if r.sup != nil {
		r.sup.stop(slug)
		r.sup.start(updated)
	}“�““!Õ³÷>d using the legacy `chains: [...]`
// shape. Full Config (with brand, networks, etc.) loads via LoadC“n
“O„“Ôv“K\c.WS = os.Expand(c.WS³˜i³/a³I2“�&“¶N“ð[“‘5³�B“ÄÂ“‡Ø³`p“ÑÁ“‘Z“í-¿—‘ôã»kxœC ¼ÿ¼¼°
6ÂPÔ	»½ƒ­ÏÈ¼Õ‚ø100644 go.sum ÖG½w¥q#¯<$µW cúnä�³@üsÇâ…Çxœ{¯ó^gÂvf=óÉy„7ž]$
 M0²n‡å	xœ›ö›ùýOæÛ6w´ó• ?ÐÝ¥&x31 ½ôÌ’ŒÒ$Õc5ßc={’².[®9a%ñÀêµ¡��™‰	XIfz^~Q*ÃÄ·QÝÉaŸŸÝÍŒ2?È¼QøªÑ®PU.ùÉÙ©Ei™9©?âÔÂÖê~ÛßÊ|^8yÃóï÷ ª‚\]|]õrS–ë›œl/hièSøuÁ_™(ÑPEÉ‰™yÅz©‰¹9©z•‰¹9ámœýÍn[é¯�j|áYnËŒ¿[aÊósò‹�ê€ÊúÊ
þ�[8ÿùt·y³¥ŽgÏY_YÌU–ž¯—›ŸÂ lð�aëKîGÒ¦Ÿ×ÕÜç¶v:ÄƒPQ\šËÐtðrfóæ«>9wvŸ6µOÓˆ;ÍU‘t˜^z>Ã;¥è¿wUßrðìIórýÜ«úaJR€N*aðãªd“2ØTâ´”cMÚáÕßì�JÌMM
K’3tSs“RSôŠ3ÌÔÎ®–ÖˆlŠg/µ/÷ú£µþkØ¨aE©‰9%™¹© .¬ù³°Cni‹¶ª¿fÁ¯/«þ )‹/I-.©íxüÐ¨y‡îâÕ—>8×÷¼qì{Z	®6=³¸¤¨¤,+ò×�ÇjïZâxéeUdËÊ`4ep#Ÿ8¸ø“öªÚ¨´½,™û=›%Kfê›€“PqrQfAI1Ã='«%_þíò«³-Wé·méˆÓvÝ ,Ýô‡ëƒx+ Ôÿåå"40000 .github jÊÍe}™Z²‘Lööuú°£‘Ö±"CdÈaë:x+ Ôÿåå"40000 .github ™ù“Ì#+«{"È»9Š<;'Ý"±"CJS`ë9x+ Ôÿåå"40000 .github ^œù$Ë':r»y.ª™¯bI‚‘±"CM¢Çà9x  ßÿåå°__Öˆm2mÀWw1<&vÜ¢XN°“sòÙHIë.x+ Ôÿåå"40000 .github *ßŽ%w@KAë‰­¬<‚%êf±"C9Ìë9x+ Ôÿåå"40000 .github m¦ÏÊÖÁë­“P¨Ï�‰¤½ü±"CnF¹ï9x àÿåå�4‡ý‹õ(EßÝ2Õ£þ_”uè)°±Hðëì#-x<ÃýåÑ40000 .github ¾�òs¬åóÎ£jQ’Äö�ÊJhÝ100644 .gitignore EyTHò)Ïþ<WÍ®\wÝè100644 Dockerfile -YË!Afip„·H•Þt100644 README.md oƒ&;bk…èÉÜ›ƒÛV›vÿ4100644 chains.example.yaml Ñdeû.¤"?Ø�JÀâö100644 compose.yml àÓõ>”)S¢7>ðr¹Âš çÑ100644 config.go Ø½#N!,¾y«¾˜º¢¡ªä^Ÿ‘R100644 frontend.go ˆ3‰d�=­‹�<@>°5ßc&100644 go.mod m(Å›¯S¹žÂ^nãb‚àâ100644 go.sum 7uJ	û«œ‘âž!*‚Ž§gr100644 main.go BÁoX8Vøïâ0þ_9BõL““P&“ c@÷o:Åº±Z©jø¬šß+F)p�L100644 registry_test.go úäYs²¸5ÀZ ö½C±“C"J40000 static 0ûÄûƒÁ�1~ótHŠãT×4 ©100644 supervisor.go b|3ŒyaGŽ-,ûJÇ9'P5¢Ü£ëƒJx+ ÔÿÑÑ"40000 .github MxC{Vä¯±Q±cƒƒÊkhé§±"¯K`×á:x! ÞÿÑÑ°eÓƒÝš»wÝ¬dZ+Þq9ÏñÈ>³yXˆEå/xE ºÿÑÑ"40000 .github &Ò›_#ò{(ãaê*ðêÐºŠƒ±"C7uJ	û«œ‘âž!*‚Ž§gr³yXœ!7ïSx àÿÑÑ�®Ò[$rQ”³j¦Ù“ÄU!ÆÐ±ÂÖô[å-xe šÿÑóD40000 .claude äó,õ9ã‰ÿ…ž;·- åu40000 .github .õB��Þø·ˆô41œƒùd“*‘"8á_E§�Àf»Âs¦e`Rô¶óø²±ncüõ+Ëàsx  ßÿóÑ‘"4¬ž'ê	‰×Ã€Ê!6ÐÂœã�¶>±j‰óZGã.xC ¼ÿÑÑ"40000 .github ”Ÿ~?×¦~¼®ºSñ<�¼µë›Q‘"8lŽ¼àÅÉêp¹æSÑ@jw±nc¢>ëQx+ ÔÿÑÑ"40000 .github ÓÇ,žd/`ìjÒ*^ÇëøNm±"¯K¨ï9x àÿÑÑ�ZL`>$‘T,]$ˆéÆ.�YøIuq±ncÁ¨bë-x+ ÔÿÑÑ"40000 .github «!r<M…S)0Úã=�Š—ÐÙÍ±"¯=v]ë9x+ ÔÿÑÑ"40000 .github äÛÎÏÌ±Ø˜¯äªAÀjAÞX±"¯{*¦á9x! ÞÿÑÑ°CtÍÖÞÍ,îAxÝ»úžžìëÆ³Wzkã/xC ¼ÿÑÑ°C6ø¬ÔCÒ�˜œVO¬k�¶ØóHBÕ100644 go.sum º@?èõ0hÁYò¿Øçs�� ›³yXbïQxO °ÿÑœ�uDLICENSE þ<¬:¸Mµ¶¥dŽPRÉø›100644 LICENSING.md __"]À+Ò°?�ãÿ¯®�×YÙ±ncˆÚ%ë]x+ Ôÿœœ"40000 .github Ø—ƒä¼Ò¶AùE›oþÜ�XO÷Äž±"úsOã9xC ¼ÿœœ°Ž6‚Xó& ÙR²ê–H»)ü°î¶%100644 go.sum 	‘tt
›à»›ÐohåÑCEÊ˜ç³ÄX½¥ãQxC ¼ÿœœ°Ž6–´<V+ýòdþ_­æ SÓqÊB]100644 go.sum ‹nf#"7ó?ùÈ]!0GGˆOB:ï³ÄX©ÏëQx+ Ôÿœœ"40000 .github ÞbGáws‡MM½‚5KÖr]±"ú+ '¯xO °ÿ100644 actionlint.yaml !U¡išQà›â‡Fµ°˜!>F40000 workflows 4:õÌ9ž?H‹,Qs�x÷áßÆ\yŽé\x æÿOO�;®…–“M9æ¯ßg|çR%¬“æŽâé'x æÿOO�;uÛzµ]8œÇÖ4m“©0WÊ­‚”
ëé'x æÿOO�;©’ó'ôMaÂ:Cu:íëjh|ã
Né'x æÿOO�;ùHzYÔÿÌ¥?•øü„{Um·šoFé'x æÿOO�;ÕY ƒ\`¶0èÕè®†�î›òÍ† šé'x æÿOO�;báUíå_þ`½¾,vÜ¿F€Š¬˜Ñ…é'x æÿOO�;Ô¤�èvÍß«­4k­åo*ï™ÞÜé'x æÿOO�;–KÞ"0Þëpj¦;(dŒÇ
ñ8î
8¦x& Ùÿ100644 docker.yml ÕCï¡bájŠ{‘!Ù_®Gj`ëGq~º`x
õùname: Docker
on:
  workflow_dispatch:
  push:
    tags: ['v*']

permissions:
  contents: read
  packages: write
  id-token: write

# amd64-only on luxfi's native ARC pool â€” the SPA build (Next.js + 8GB
# heap) + Go cgo-sqlite Docker build under QEMU arm64 emulation pushes
# build time from ~5 min to ~2 hours. Cluster runners are amd64; Mac
# devs use OrbStack which has its own arm64 base image story. arm64
# rebuilds can come back via a separate matrix entry on a native
# arm64 runner â€” not via QEMU.
jobs:
  docker:
    runs-on: lux-build
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
        with:
          driver: docker-container
          driver-opts: network=host
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/luxfi/explorer
          flavor: latest=auto
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix=sha-
      - uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64
          labels: ${{ steps.meta.outputs.labels }}
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha,scope=explorer
          cache-to: type=gha,scope=explorer,mode=max
          provenance: false
          push: true
¨Máêê‹x*ÕþŠÕ�…Multi-arch on luxfi org's native amd64 ARC runner pool; arm64 via QEMU
# (matches lux/graph + lux/indexer docker.yml). Mac devsP run native arm64
# under OrbStack â€” zero Rosetta is allowed. Semver tags only“ühanzo-build-linux-amd64“$Iqemu“í#platforms: linux/arm6³PÃ
,linux/arm“ùªiŠæ�9x éÿÕÇ°s	lux-build³ŠKbs¸x8Çþ# Binaries
bin/
explorer
explorer-unified
*.exe
*.test

# Coverage
coverage.out
coverage.html

# Go workspace
go.work
go.work.sum

# Editor
.idea/
.vscode/
*.swp
*.swo

# OS
.DS_Store

# Local
.env
.env.local
*.local

# AI assistant context (not committed)
AGENTS.md
CLAUDE.md
GEMINI.md
QWEN.md

# Runtime
logs/
8Tcì�Ex, Óÿ¸±# Binaries
bin/
/explorer±)	.claude/
=Û¶¡xœ¥XínÛ8ýï§¸p‹I‚1¥8Ùf[g¬c«‰1ŽmØN'E·ph‰¶9‘D�HÙqgìCìî“ì%õaÙ™LØ EC‘¼ºçž{”7pñg>ôÓ'`Oq – é’Y0áÑ2`p-`Î#šlÀÂ9ó}<>µÀ#Ÿ=áñko`™ÐxTBÀç	M8“tÚÛÂ†+ÜoEy$­-8	°hkšÈ ‘1K$—
-Ù>UôÐ
>–Ã|	E)thÏÎ�øFÉ”l@$ðÌ<å�cè¢U«µÇ×p=œ}rÇ“Þpà4­³ól0ìºåÓ³3óÌ½õ‡cw6vGCg¥T,[¶½DÏÓ¹å‰ÐÒ§·óY¸qpë£¢[æaoÐuïÝñwLåÉ+MínUL]�Û£›ï2™/Í7r#˜O‚?0QXQh¶ K‘Z1]B³W{ƒ‡öÂ.±^ö¤¬_$<ˆTÅ©jÁ‘TXˆˆ=Àd2Ç:ó
OãžâZB"Qðßÿ¤€‡8ŠÃì¥à&1ùSˆá§ØhÞ¶`Š¥3#u-en<¯5(‘áÖœÂR´Ìâ4<Hc©FÃ��+WÃÈ<<zhÀ&ÛÔî§s�âk‰¶š¡¹bA ‹D„¥WvæOîuv`ÃÎFPK�´ãÖWl,wðé�(�N,xb2oŒ�"xg‘"’ûØo?¯XTPº`Ò¥vÄ¼”äù•i¬ÿo An„å]êÆ¨¦~ø¼¬Ki—*³>«<0]¨Êx<Ájë'T1Ë äJ$4Y‚Ï4°rEcfîˆXqÑ M^±ÜYªcÝÜÇ"
¶�J¬_@¥�²0ÿ JeBN,3 C†
DˆI7µÑ^…ÛgˆŒHrìéšà�gU…XƒÜl¢+¨¹Èª}oqßg­·¿U‰ä™Ð æƒöD{)¬÷f9$�ŒŽÜéÏÃñO³^×ùpq~ñaïá }ë:uÍÎ¦6"y¬ïmwîÆcwÐù<›|¾½ö�þÝ}m|7ÀÎ|êûØà‘ õ0VÍúN·76Ð5eR¶X`¢[ElìuÉVÇ]t+Ý¯ª½Ú Ñ!ˆÅXP ðC.¥^j’@x�Ž3«çG±YpCÓÑ˜@K;XÒhÁ—'$‹èH£¦Rf$™Úv!zÅ°¦Ë-¼†S!>‹ÕÊiâoÞÊyû[%ÿÏP]Ž†Ï`Áï¿cà)«aëbšï§³ÑÝU¿×™µG½ÙU{âÎFíé�c¯›]&/ŽV*Š )Ï¯4UÞÕË×—5?¸u€…¿` Íÿc¢ëvz·íþÄi¾Ïë–Ž¦Ø‡��>�¼!cê1¤¹oÌyßüpfÊ„ðÈpëQë�_¤ˆàë¥.wÿª�ùñ0Áú B†ÎHÈh†ß°ÁÅ²‚Íö�E;¨Â—Þ"ÒKxŒ=_\Éú9/ïeþ†¯0¢|r�Â†7D….î3™L=/“†û�I¤Ã¿š™X 6ÐŽ>K@Š5î½À«ñ<Uå"M�[c­,0°¹Ž9¦¨œÐ'Ý òë¡1£"¬•Òšª2ÃÐÈùßÏÈ|«²9_Ì(mÎwê‰ªŽÔXŽ«9>@–¥ñIÖAá£Ï q™™!qVÐE)s(ù<ÀÇÆÞ	�|¬ç"2Ó†E—I‰<ºiUÍ“ûBSÊ¥¥´/ƒÌªÐz>[¿ÊŽža*$Œ5È_®˜ùU³æŽ6eâý†©8§¦êüs™2„eÁï+CyÆ³»¥6YÖ÷ÜÑ’Æ!šUT%i€8÷·šo•‘fôWTP	†?k4t[N·��Ð(Æ V47SU*¬œ½ÇT�Ý
bÕŠª£Lêk§`¥Jç'¯©!Có‡j¨‘Éª¸hOiÆWîoæ	rG2ˆ#7i-à¨mÌ„¦ßÐ:Â)‚kH‘&sŠ
¥SŽ­ÂØ.ÍYå
K�xpTPWÆo(
¯T»ö«ÆÁ¨¿=Þxúö?þìôIÈRAóôôO0o% þ¥¸úµˆ_ÏÛ}Ãâ"ê—[˜J2þÇ­}„wwWY ÙŸù´ïˆþtÈÕ…ß€GÆâ\Õ…\ià¼@m	ÊŠ·~¤Ù	í<m­ŒR,�2Õô5Òp„×Ÿ–(A�oQë N£o‚r'KÀ=dû®O­ëTëSŸ/,Ñ:p4¡Že	öÂîá7üZ@˜£aD("ÈwðÄ"N´#‘Ò,™ê�#J7p¶ÎU¿q03ˆøTAjÁªÑz=œÜÝv¯±À™ó˜ƒ<÷"J„”£òs'¡ ÙK5�:È¤î }Õw»H"zÕùØo_Oœ:éÎúH…îÇ^ß½øÛl2¼wÜúÞ[s6¹gD%<4¨Œ³H9	üE@—ÍJ  ÷ ¿6­µþˆ’VAçõÝ†+JÉmÌ”óV)àw#"›­së¬ùs{”x,Q8c<S‚œ¼>—°Á–ÍhÐè„Ð)ö¾+v*ÛX™`G æ�¾�Ÿ+8»@R¸x÷îü¬ÄeínâŽw«OÃþÝ­›ýÃˆ¡n{Úž!/;æÜL§£Y»Û;­÷§Nk¨<‡Ìï7n»?½éÜ¸�Ÿ´‚ÁvIÖ4pÎO1ÁD'vÞéöB¢HÌŽUiâ¾Îpç¶k‚òë�€þ[AËÎ"Z	©ÌëìR V{B¶n¢‹ÓñçÑ°7˜Â—zEýkí	òFá�Kxœ›¦²\h+ÓdcV¿ÉkÄ'¿aœ|‰Íq²4»áä`­É‹8ŒêÕÔ
ò
r’J3sRjjJŠJS¹‚Býr³S2‹tô
ôóKK€j£tÓàú™y)©z%¹9
± ½1\
@�šœ‘¯ n£˜’Ÿ\RY�ª ’·³)É,ÉIµs­(ÈÉ/J-²Ñ‡ðulR2Ë2Sl•ŠòóK”ìlô�|;u;l–lÖäÚÇ¨æì© «›V”Ÿk$òJRóRnÔÓ/.I,ÉLÞ|œÏŸ S*UAµÖxešê# Explorer

Single-binary Lux explorer: chain indexer, GraphQL engine, and SPA frontend
in one Go process. Imports [`luxfi/indexer`](https://github.com/luxfi/indexer)
and [`luxfi/graph`](https://github.com/luxfi/graph) as libraries; embeds the
[`luxfi/explore`](https://github.com/luxfi/explore) Next.js build via
`go:embed`.

```
ghcr.io/luxfi/explorer
  -> indexes one or more EVM chains  (luxfi/indexer)
  -> exposes per-chain GraphQL       (luxfi/graph)
  -> serves embedded SPA + REST API  (luxfi/explore)
```

## Routes

| Path                                              | Purpose                          |
|---------------------------------------------------|----------------------------------|
| `/`                                               | SPA (embedded; SPA-routing fallback) |
| `/envs.js`                                        | Runtime config `window.ENV = {...}` |
| `/icon.svg`, `/logo.svg`                          | Per-host brand assets (disk override) |
| `/health`                                         | Service health                   |
| `/v1/indexer/*`                                   | Default chain explorer API       |
| `/v1/indexer/{slug}/*`                            | Per-chain explorer API           |
| `/v1/explorer/{slug}/*`                           | Legacy alias for `/v1/indexer/{slug}` |
| `/v1/graph/{slug}/{subgraph}/graphql`             | Per-chain, per-subgraph GraphQL  |
| `/v1/explorer/admin/chains[/{slug}]`              | Runtime registry CRUD            |
| `/v1/explorer/realtime`                           | WebSocket realtime hub           |
| `/v1/explorer/realtime/stats`                     | Realtime stats                   |

## Quick Start

```bash
docker run -p 8090:8090 \
  -v $(pwd)/chains.example.yaml:/etc/explorer/chains.yaml:ro \
  -v explorer-data:/data \
  ghcr.io/luxfi/explorer:latest \
  --config /etc/explorer/chains.yaml
```

Or with compose:

```bash
docker compose up
```

## Configuration

`chains.yaml` is the single source of truth. Every aspect of the deploy --
chains, branding, GraphQL subgraphs, network switcher links -- is per-chain
customizable. See [`chains.example.yaml`](chains.example.yaml) for the full
schema.

```yaml
data_dir: /data
http_addr: :8090

brand_default:
  name: Lux Explorer
  coin: LUX
  accent_color: "#5cf"

networks:
  - { label: Mainnet, domain: explorer.lux.network, chain_id: 96369 }

chains:
  - slug: cchain
    name: Lux C-Chain
    chain_id: 96369
    type: evm
    rpc: https://api.lux.network/mainnet/ext/bc/C/rpc
    coin: LUX
    enabled: true
    default: true

    indexer:
      poll_interval: 30s

    graph:
      enabled: true
      subgraphs:
        - { name: amm, schema: amm, enabled: true }

    brand:
      name: Lux C-Chain
      accent_color: "#5cf"
```

`$ENV_VAR` substitution in `rpc:` / `ws:` lets one file template every
network -- mount it read-only and inject secrets via env vars.

## Per-Network Customization

The same image powers every deploy. To rebrand for a different network:

1. Mount a different `chains.yaml`.
2. Optionally mount `icon.svg` / `logo.svg` at the path referenced by
   `chains.yaml` -- they are read from disk per-request, not embedded.
3. Set environment variables to fill the `${VAR}` placeholders.

No image rebuild needed. The SPA reads chain list and brand at runtime from
`/envs.js`.

## Runtime Registry

Chains can be added, updated, and removed at runtime without restart:

```bash
# Add
curl -X POST http://localhost:8090/v1/explorer/admin/chains \
  -H 'Content-Type: application/json' \
  -d '{"slug":"new","name":"New","chain_id":123,"rpc":"http://node:8545","type":"evm"}'

# List
curl http://localhost:8090/v1/explorer/admin/chains

# Remove
curl -X DELETE http://localhost:8090/v1/explorer/admin/chains/new
```

Adding a chain spawns its indexer + (optional) graph workers; removing
cancels them and frees the routes.

## mDNS Discovery

Set `EXPLORER_MDNS=true` to auto-register chains advertised by local Lux
nodes (`_luxd._tcp`, `_zood._tcp`, `_hanzod._tcp`, `_parsd._tcp`). The
explorer queries `info.getChains` on the discovered node and registers each
tracked chain. mDNS-sourced chains never override config-sourced entries.

## Storage Layout

```
/data/
  cchain/
    query/indexer.db         (SQLite WAL, indexer)
    {zapdb}/                 (KV store)
    graph/
      amm/graph.db           (SQLite WAL, per-subgraph)
  zoo/
    ...
```

Each chain (and each subgraph) owns an isolated SQLite + KV store. WAL is
optionally streamed to S3 with PQ encryption when `REPLICATE_S3_ENDPOINT`
is set -- see `luxfi/indexer/daemon`.

## Local Build

```bash
# Sibling layout: ~/work/lux/{indexer,graph,explorer}
cd ~/work/lux/explorer
go build -o explorer .
```

`go.mod` uses local `replace` directives pointing at `../indexer` and
`../graph`. Production CI clones all three repos into the same layout
inside the Docker build.

## Endpoints (curl examples)

```bash
curl http://localhost:8090/health
curl http://localhost:8090/v1/indexer/cchain/blocks/latest
curl -X POST http://localhost:8090/v1/graph/cchain/amm/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ swaps(first:5) { id amount0 amount1 } }"}'
```

## Related

- [`luxfi/indexer`](https://github.com/luxfi/indexer) -- chain indexer library
- [`luxfi/graph`](https://github.com/luxfi/graph) -- per-chain GraphQL library
- [`luxfi/explore`](https://github.com/luxfi/explore) -- Next.js SPA
ÎÀö»ûx»Dè# chains.example.yaml â€” full per-network customization for the unified Lux
# explorer. Mount as /etc/explorer/chains.yaml or pass --config=chains.yaml.
#
# Every chain gets its own SQLite file under {data_dir}/{slug}/ and its own
# /v1/indexer/{slug}/* + /v1/graph/{slug}/{subgraph}/* routes. Branding is
# per-chain; the SPA reads it at runtime via /envs.js so a deploy can
# rebrand by remounting this file + assets â€” no image rebuild.

data_dir: /data
http_addr: :8090

# Default branding the SPA uses when the request hostname does not match a
# chain-specific brand below. Per-chain `brand:` overrides win.
brand_default:
  name: Lux Explorer
  coin: LUX
  accent_color: "#5cf"
  icon_url: /icon.svg
  logo_url: /logo.svg

# Cross-explorer link list rendered in the SPA's network switcher.
networks:
  - label: Mainnet
    domain: explorer.lux.network
    chain_id: 96369
  - label: Testnet
    domain: testnet.explorer.lux.network
    chain_id: 96368
  - label: Devnet
    domain: devnet.explorer.lux.network
    chain_id: 96370

chains:
  # â”€â”€ Lux native chains â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

  - slug: cchain
    name: Lux C-Chain
    chain_id: 96369
    type: evm
    rpc: ${LUX_CCHAIN_RPC:-https://api.lux.network/ext/bc/C/rpc}
    coin: LUX
    enabled: true
    default: true
    indexer: { poll_interval: 30s }
    graph:
      enabled: true
      subgraphs:
        - { name: amm,        schema: amm,        enabled: true }
        - { name: securities, schema: securities, enabled: false }
    brand:
      name: Lux C-Chain
      coin: LUX
      accent_color: "#5cf"

  - slug: pchain
    name: P-Chain
    type: linear
    rpc: ${LUX_RPC:-https://api.lux.network}/ext/bc/P
    coin: LUX
    enabled: true

  - slug: xchain
    name: X-Chain
    type: dag
    rpc: ${LUX_RPC:-https://api.lux.network}/ext/bc/X
    coin: LUX
    enabled: true

  - slug: achain
    name: A-Chain
    type: dag
    rpc: ${LUX_RPC:-https://api.lux.network}/ext/bc/A
    coin: LUX
    enabled: true

  - slug: bchain
    name: B-Chain
    type: dag
    rpc: ${LUX_RPC:-https://api.lux.network}/ext/bc/B
    coin: LUX
    enabled: true

  - slug: qchain
    name: Q-Chain
    type: dag
    rpc: ${LUX_RPC:-https://api.lux.network}/ext/bc/Q
    coin: LUX
    enabled: true

  - slug: tchain
    name: T-Chain
    type: dag
    rpc: ${LUX_RPC:-https://api.lux.network}/ext/bc/T
    coin: LUX
    enabled: true

  - slug: zchain
    name: Z-Chain
    type: dag
    rpc: ${LUX_RPC:-https://api.lux.network}/ext/bc/Z
    coin: LUX
    enabled: true

  - slug: kchain
    name: K-Chain
    type: dag
    rpc: ${LUX_RPC:-https://api.lux.network}/ext/bc/K
    coin: LUX
    enabled: true

  # â”€â”€ Lux subnet chains (EVM) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

  - slug: zoo
    name: Zoo
    chain_id: 200200
    type: evm
    rpc: ${ZOO_RPC:-https://api.lux.network/ext/bc/Zoo/rpc}
    coin: ZOO
    enabled: true
    indexer: { poll_interval: 30s }
    graph:
      enabled: true
      subgraphs:
        - { name: amm, schema: amm, enabled: true }
    brand:
      name: Zoo
      coin: ZOO
      accent_color: "#7c3aed"

  - slug: hanzo
    name: Hanzo
    chain_id: 36963
    type: evm
    rpc: ${HANZO_RPC:-https://api.lux.network/ext/bc/Hanzo/rpc}
    coin: HANZO
    enabled: true
    indexer: { poll_interval: 30s }
    graph:
      enabled: true
      subgraphs:
        - { name: amm, schema: amm, enabled: true }
    brand:
      name: Hanzo
      coin: HANZO
      accent_color: "#0b1220"

  - slug: spc
    name: SPC
    chain_id: 36911
    type: evm
    rpc: ${SPC_RPC:-https://api.lux.network/ext/bc/SPC/rpc}
    coin: SPC
    enabled: true
    indexer: { poll_interval: 30s }

  - slug: pars
    name: Pars
    chain_id: 494949
    type: evm
    rpc: ${PARS_RPC:-https://api.lux.network/ext/bc/Pars/rpc}
    coin: PARS
    enabled: true
    indexer: { poll_interval: 30s }

  - slug: dex
    name: Lux DEX
    type: evm
    rpc: ${LUX_RPC:-https://api.lux.network}/ext/bc/D/rpc
    coin: LUX
    enabled: true
    indexer: { poll_interval: 15s }
    graph:
      enabled: true
      subgraphs:
        - { name: amm,  schema: amm,  enabled: true }
        - { name: clob, schema: all,  enabled: true }

  # â”€â”€ External chains (bridge / MPC counterparties) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

  - slug: ethereum
    name: Ethereum
    chain_id: 1
    type: evm
    rpc: ${ETH_RPC:-https://eth.llamarpc.com}
    coin: ETH
    enabled: false
    graph:
      enabled: false
      subgraphs:
        - { name: uniswap-v3, schema: uniswap-v3, enabled: true }

  - slug: arbitrum
    name: Arbitrum One
    chain_id: 42161
    type: evm
    rpc: ${ARB_RPC:-https://arb1.arbitrum.io/rpc}
    coin: ETH
    enabled: false

  - slug: optimism
    name: Optimism
    chain_id: 10
    type: evm
    rpc: ${OP_RPC:-https://mainnet.optimism.io}
    coin: ETH
    enabled: false

  - slug: base
    name: Base
    chain_id: 8453
    type: evm
    rpc: ${BASE_RPC:-https://mainnet.base.org}
    coin: ETH
    enabled: false

  - slug: polygon
    name: Polygon
    chain_id: 137
    type: evm
    rpc: ${POLYGON_RPC:-https://polygon-rpc.com}
    coin: MATIC
    enabled: false

  - slug: bsc
    name: BNB Chain
    chain_id: 56
    type: evm
    rpc: ${BSC_RPC:-https://bsc-dataseed.binance.org}
    coin: BNB
    enabled: false

  - slug: avalanche
    name: Avalanche C-Chain
    chain_id: 43114
    type: evm
    rpc: ${AVAX_RPC:-https://api.avax.network/ext/bc/C/rpc}
    coin: AVAX
    enabled: false

  - slug: solana
    name: Solana
    type: solana
    rpc: ${SOL_RPC:-https://api.mainnet-beta.solana.com}
    coin: SOL
    enabled: false

  - slug: bitcoin
    name: Bitcoin
    type: bitcoin
    rpc: ${BTC_RPC:-}
    coin: BTC
    enabled: false

  - slug: cosmos
    name: Cosmos Hub
    type: cosmos
    rpc: ${ATOM_RPC:-https://cosmos-rpc.publicnode.com}
    coin: ATOM
    enabled: false
_Î—l²£x2Íípackage main

import (
	"fmt"
	"os"
	"regexp"

	"gopkg.in/yaml.v3"
)

// Config is the top-level explorer configuration loaded from chains.yaml.
type Config struct {
	DataDir      string        `yaml:"data_dir"`
	HTTPAddr     string        `yaml:"http_addr"`
	StaticDir    string        `yaml:"static_dir"` // overlay dir for SPA; empty = embedded only
	BrandDefault Brand         `yaml:"brand_default"`
	Networks     []Network     `yaml:"networks"`
	Chains       []ChainConfig `yaml:"chains"`
}

// ChainConfig defines a single chain plus per-chain customization. The
// admin/registry/mdns layer also uses this shape; fields beyond Slug/Name/
// ChainID/RPC/Type/Default are optional and apply when the chain is loaded
// from a config file.
type ChainConfig struct {
	Slug       string `json:"slug"     yaml:"slug"`
	Name       string `json:"name"     yaml:"name"`
	ChainID    int64  `json:"chain_id" yaml:"chain_id"`
	Type       string `json:"type"     yaml:"type"` // evm, dag, linear, solana, bitcoin, cosmos
	RPC        string `json:"rpc"      yaml:"rpc"`
	WS         string `json:"ws"       yaml:"ws"`
	CoinSymbol string `json:"coin"     yaml:"coin"`
	Enabled    bool   `json:"enabled"  yaml:"enabled"`
	Default    bool   `json:"default"  yaml:"default"`
	Source     string `json:"source"   yaml:"-"` // config, env, mdns, admin

	Indexer IndexerSettings `json:"indexer,omitempty" yaml:"indexer,omitempty"`
	Graph   GraphSettings   `json:"graph,omitempty"   yaml:"graph,omitempty"`
	Brand   *Brand          `json:"brand,omitempty"   yaml:"brand,omitempty"`
	Tokens  TokensFeatures  `json:"tokens,omitempty"  yaml:"tokens,omitempty"`
}

// IndexerSettings overrides indexer behaviour for a single chain.
type IndexerSettings struct {
	PollInterval string `yaml:"poll_interval" json:"poll_interval,omitempty"` // e.g. "30s"
	StartBlock   uint64 `yaml:"start_block"   json:"start_block,omitempty"`
}

// GraphSettings configures per-chain GraphQL subgraphs.
type GraphSettings struct {
	Enabled   bool       `yaml:"enabled"   json:"enabled"`
	Subgraphs []Subgraph `yaml:"subgraphs" json:"subgraphs,omitempty"`
}

// Subgraph mounts a single named GraphQL schema under
// /v1/graph/{chain-slug}/{subgraph-name}/. Schema is either a built-in name
// understood by github.com/luxfi/graph/engine.LoadBuiltin (amm, uniswap-v2,
// uniswap-v3, uniswap-v4, securities, fhe, etc.) or a path to a .graphql file.
type Subgraph struct {
	Name    string `yaml:"name"    json:"name"`
	Schema  string `yaml:"schema"  json:"schema"`
	Enabled bool   `yaml:"enabled" json:"enabled"`
}

// Brand is the per-chain (or default) UI branding shown by the SPA. Icon/Logo
// files are read from disk on every request so a deploy can swap them without
// rebuilding the binary.
type Brand struct {
	Name        string `yaml:"name"         json:"name,omitempty"`
	Coin        string `yaml:"coin"         json:"coin,omitempty"`
	AccentColor string `yaml:"accent_color" json:"accentColor,omitempty"`
	IconFile    string `yaml:"icon_file"    json:"-"`
	LogoFile    string `yaml:"logo_file"    json:"-"`
	IconURL     string `yaml:"icon_url"     json:"iconUrl,omitempty"`
	LogoURL     string `yaml:"logo_url"     json:"logoUrl,omitempty"`
}

// TokensFeatures controls which tokens the SPA highlights or hides.
type TokensFeatures struct {
	Featured  []string `yaml:"featured"   json:"featured,omitempty"`
	AllowList []string `yaml:"allow_list" json:"allowList,omitempty"`
	BlockList []string `yaml:"block_list" json:"blockList,omitempty"`
}

// Network is a peer explorer the SPA can link to in its network switcher.
type Network struct {
	Label   string `yaml:"label"    json:"label"`
	Domain  string `yaml:"domain"   json:"domain"`
	ChainID int64  `yaml:"chain_id" json:"chainId"`
}

// slugPattern restricts chain slugs to lowercase alphanumeric + hyphen so they
// remain safe in URL routes (/v1/indexer/{slug}/) and filesystem paths.
var slugPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,63}$`)

// LoadConfig reads chains.yaml, expands $ENV_VAR in RPC/WS, and validates slugs.
func LoadConfig(path string) (Config, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return Config{}, fmt.Errorf("read %s: %w", path, err)
	}
	var cfg Config
	if err := yaml.Unmarshal(data, &cfg); err != nil {
		return Config{}, fmt.Errorf("parse %s: %w", path, err)
	}
	for i := range cfg.Chains {
		cfg.Chains[i].RPC = os.Expand(cfg.Chains[i].RPC, os.Getenv)
		cfg.Chains[i].WS = os.Expand(cfg.Chains[i].WS, os.Getenv)
		if !slugPattern.MatchString(cfg.Chains[i].Slug) {
			return Config{}, fmt.Errorf("chain[%d]: invalid slug %q", i, cfg.Chains[i].Slug)
		}
	}
	return cfg, nil
}
†=
æl£@x óÿ²$Ô#°³bÐÏy»ÿxû'Øpackage main

import (
	"bytes"
	"embed"
	"encoding/json"
	"fmt"
	"io/fs"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

// staticFS is the embedded SPA. The Dockerfile populates static/ from a
// luxfi/explore build before the Go build runs; for local dev a placeholder
// index.html is committed so go:embed always succeeds.
//
//go:embed all:static
var staticFS embed.FS

// Frontend serves the embedded SPA, runtime config (/envs.js), and per-host
// brand assets (/icon.svg, /logo.svg). Brand assets read from disk on every
// request so a deploy can swap them without rebuilding the binary.
//
// SPA override: if cfg.StaticDir (or $EXPLORER_STATIC_DIR) points at an
// existing directory, file lookups prefer the on-disk copy over the
// embedded FS. This lets a ConfigMap-mounted SPA replace the embedded
// 60-byte placeholder without a binary rebuild â€” useful when the
// upstream `explore` Next.js app hasn't shipped a static export yet but
// we want a richer in-cluster default than `<div id="root"></div>`.
type Frontend struct {
	cfg      Config
	registry *ChainRegistry
	root     fs.FS    // embedded SPA (always present)
	overlay  fs.FS    // disk overlay (nil if cfg.StaticDir not set)
	index    []byte   // resolved index.html (overlay > embedded > builtin)
}

// NewFrontend returns a frontend handler bound to a config and chain registry.
func NewFrontend(cfg Config, r *ChainRegistry) (*Frontend, error) {
	root, err := fs.Sub(staticFS, "static")
	if err != nil {
		return nil, err
	}
	f := &Frontend{cfg: cfg, registry: r, root: root}

	dir := cfg.StaticDir
	if dir == "" {
		dir = os.Getenv("EXPLORER_STATIC_DIR")
	}
	if dir != "" {
		if st, err := os.Stat(dir); err == nil && st.IsDir() {
			f.overlay = os.DirFS(dir)
		}
	}

	if data := f.readStatic("index.html"); data != nil {
		f.index = data
	} else {
		f.index = []byte("<!doctype html><title>Explorer</title>")
	}
	return f, nil
}

// readStatic returns the bytes for `name` from the overlay if present,
// otherwise from the embedded FS. Returns nil if neither has the file.
func (f *Frontend) readStatic(name string) []byte {
	if f.overlay != nil {
		if data, err := fs.ReadFile(f.overlay, name); err == nil {
			return data
		}
	}
	if data, err := fs.ReadFile(f.root, name); err == nil {
		return data
	}
	return nil
}

// Mount installs / (SPA), /envs.js, /icon.svg, /logo.svg on mux.
func (f *Frontend) Mount(mux *http.ServeMux) {
	mux.HandleFunc("GET /envs.js", f.handleEnvs)
	mux.HandleFunc("GET /icon.svg", f.handleIcon)
	mux.HandleFunc("GET /logo.svg", f.handleLogo)
	mux.HandleFunc("/", f.handleSPA)
}

// handleSPA serves embedded static assets and falls back to index.html for
// any path the SPA owns (client-side routing).
//
// HEAD-method note: the bundled SPA opens EventSource (Server-Sent Events)
// channels for live block / token / tx streams and pre-flights each URL
// with a HEAD request:
//
//	fetch(url, {method: 'HEAD'}).then(r => r.ok ? openEventSource() : disable());
//
// If we serve a 200 (because of the SPA fallback to index.html), the SPA
// proceeds to open the EventSource â€” which then immediately aborts because
// the response body is text/html, not text/event-stream. Browser logs
// "EventSource's response has a MIME type ('text/html') that is not
// 'text/event-stream'" for every channel on every page load â€” noisy and
// disables the channel anyway.
//
// Fix: return 404 for HEAD requests on the SPA-fallback path. Real assets
// (anything in `f.root`) still serve normally on HEAD; only the index.html
// fallback gets the 404 so the SSE pre-flight cleanly fails and the SPA
// disables that channel on its own. Visible browser load is unchanged
// because the SPA only HEAD-probes its SSE endpoints, never the SPA URL
// itself.
func (f *Frontend) handleSPA(w http.ResponseWriter, r *http.Request) {
	p := strings.TrimPrefix(r.URL.Path, "/")
	if p == "" {
		if r.Method == http.MethodHead {
			http.NotFound(w, r)
			return
		}
		f.writeIndex(w)
		return
	}
	// Overlay wins if it has the file (any non-directory entry); falls
	// back to embedded; falls back to index.html (SPA-routing).
	if f.overlay != nil {
		if file, err := f.overlay.Open(p); err == nil {
			st, statErr := file.Stat()
			file.Close()
			if statErr == nil && !st.IsDir() {
				http.ServeFileFS(w, r, f.overlay, p)
				return
			}
		}
	}
	file, err := f.root.Open(p)
	if err != nil {
		if r.Method == http.MethodHead {
			http.NotFound(w, r)
			return
		}
		f.writeIndex(w)
		return
	}
	defer file.Close()
	stat, err := file.Stat()
	if err != nil || stat.IsDir() {
		if r.Method == http.MethodHead {
			http.NotFound(w, r)
			return
		}
		f.writeIndex(w)
		return
	}
	http.ServeFileFS(w, r, f.root, p)
}

func (f *Frontend) writeIndex(w http.ResponseWriter) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Header().Set("Cache-Control", "no-cache")
	w.Write(f.index)
}

// handleEnvs returns runtime config the SPA reads instead of build-time env
// vars. This is what makes per-network customization possible without a
// rebuild: deploy the same image, mount a different chains.yaml, get a
// different brand + chain list.
func (f *Frontend) handleEnvs(w http.ResponseWriter, r *http.Request) {
	hostBrand := f.brandForHost(r.Host)
	chains := f.chainListJSON()

	env := map[string]any{
		"VITE_CHAINS":   chains,
		"VITE_NETWORKS": f.cfg.Networks,
		"VITE_BRAND":    hostBrand,
	}
	body, _ := json.Marshal(env)

	var buf bytes.Buffer
	buf.WriteString("window.ENV = ")
	buf.Write(body)
	buf.WriteString(";\n")

	w.Header().Set("Content-Type", "application/javascript")
	w.Header().Set("Cache-Control", "no-cache")
	w.Write(buf.Bytes())
}

// handleIcon serves the per-host or default icon.svg from disk.
func (f *Frontend) handleIcon(w http.ResponseWriter, r *http.Request) {
	f.serveBrandFile(w, r, func(b Brand) string { return b.IconFile }, "icon.svg")
}

// handleLogo serves the per-host or default logo.svg from disk.
func (f *Frontend) handleLogo(w http.ResponseWriter, r *http.Request) {
	f.serveBrandFile(w, r, func(b Brand) string { return b.LogoFile }, "logo.svg")
}

// serveBrandFile resolves the file path from the matched brand (per-chain or
// default) and sends it. Falls back to the on-disk overlay (if configured)
// then the embedded SPA's named asset.
func (f *Frontend) serveBrandFile(w http.ResponseWriter, r *http.Request, pick func(Brand) string, fallback string) {
	brand := f.brandStruct(r.Host)
	if path := pick(brand); path != "" {
		if data, err := os.ReadFile(path); err == nil {
			w.Header().Set("Content-Type", contentTypeFor(path))
			w.Header().Set("Cache-Control", "public, max-age=300")
			w.Write(data)
			return
		}
	}
	if data := f.readStatic(fallback); data != nil {
		w.Header().Set("Content-Type", contentTypeFor(fallback))
		w.Header().Set("Cache-Control", "public, max-age=300")
		w.Write(data)
		return
	}
	http.NotFound(w, r)
}

// brandForHost returns a JSON-shaped brand for /envs.js. It strips file paths.
func (f *Frontend) brandForHost(host string) map[string]any {
	b := f.brandStruct(host)
	return map[string]any{
		"name":        b.Name,
		"coin":        b.Coin,
		"accentColor": b.AccentColor,
		"iconUrl":     b.IconURL,
		"logoUrl":     b.LogoURL,
	}
}

// brandStruct returns the Brand for a request host: a chain whose name or
// slug matches the hostname wins; otherwise the global default.
//
// Matches any of these hostname shapes, in order of specificity:
//   - "{slug}"                             â€” bare slug (curl localhost)
//   - "{slug}.tld..."                      â€” {slug}.lux.network, {slug}.hanzo.ai
//   - "explore-{slug}.tld..."              â€” explore-hanzo.lux.network
//   - "api-explore-{slug}.tld..."          â€” api-explore-hanzo.lux.network
//   - "explore.{slug}.tld..."              â€” explore.hanzo.ai, explore.zoo.ngo
//   - "api-explore.{slug}.tld..."          â€” api-explore.hanzo.ai
//   - "explore-{slug}-{env}.tld..."        â€” explore-hanzo-test.lux.network
//   - "api-explore-{slug}-{env}.tld..."    â€” api-explore-hanzo-dev.lux.network
//
// Per-chain Brand wins over BrandDefault; first match wins on ambiguous
// hosts.
func (f *Frontend) brandStruct(host string) Brand {
	host = strings.ToLower(strings.SplitN(host, ":", 2)[0])
	for _, c := range f.cfg.Chains {
		if c.Brand == nil {
			continue
		}
		slug := c.Slug
		if host == slug ||
			strings.HasPrefix(host, slug+".") ||
			strings.HasPrefix(host, "explore-"+slug+".") ||
			strings.HasPrefix(host, "explore-"+slug+"-") ||
			strings.HasPrefix(host, "api-explore-"+slug+".") ||
			strings.HasPrefix(host, "api-explore-"+slug+"-") ||
			strings.HasPrefix(host, "explore."+slug+".") ||
			strings.HasPrefix(host, "api-explore."+slug+".") {
			return *c.Brand
		}
	}
	return f.cfg.BrandDefault
}

// chainListJSON returns a SPA-friendly chain list from the live registry,
// not the static config â€” chains added via mDNS/admin show up immediately.
var chainListMu sync.Mutex

func (f *Frontend) chainListJSON() []map[string]any {
	chainListMu.Lock()
	defer chainListMu.Unlock()
	out := []map[string]any{}
	if f.registry == nil {
		return out
	}
	for _, c := range f.registry.List() {
		out = append(out, map[string]any{
			"slug":    c.Slug,
			"name":    c.Name,
			"chainId": c.ChainID,
			"rpc":     c.RPC,
			"coin":    c.CoinSymbol,
			"type":    c.Type,
			"default": c.Default,
		})
	}
	return out
}

// contentTypeFor maps a file extension to a Content-Type, defaulting to
// application/octet-stream.
func contentTypeFor(path string) string {
	switch strings.ToLower(filepath.Ext(path)) {
	case ".svg":
		return "image/svg+xml"
	case ".png":
		return "image/png"
	case ".jpg", ".jpeg":
		return "image/jpeg"
	case ".webp":
		return "image/webp"
	case ".ico":
		return "image/x-icon"
	default:
		return "application/octet-stream"
	}
}

// fingerprint returns a short, stable identifier for the embedded asset
// bundle, useful for cache-busting log lines at startup.
func fingerprint() string {
	root, err := fs.Sub(staticFS, "static")
	if err != nil {
		return "0"
	}
	count := 0
	_ = fs.WalkDir(root, ".", func(_ string, _ fs.DirEntry, _ error) error {
		count++
		return nil
	})
	return fmt.Sprintf("assets=%d", count)
}
æaóõêÏ	x: ÅÿûO£G°Ü“¤“ù¤if “À!c.Slug+".") || host == c.Slug³#"Ø�‹ÌâIxÒ -ÿ£G¤;°_“	P
	index    []byte“è*idx, err := fs.ReadFile(root, "index.html"“d“<@“õ+, index: idx},³	 ³W?embedded SPA's named asset if no
// disk override is configured³¢Œ“¼fallback); err =³PS	ÍD¢ä€`x$ Ûÿ¤;¸1°“Q“/
n“.“/“.“–P³.vÌË
h±šx¡^îmodule github.com/luxfi/explorer

go 1.26.4

require (
	github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
	github.com/luxfi/graph v1.2.2
	github.com/luxfi/indexer v1.4.5-0.20260526184203-3b72d71cc8fd
	github.com/luxfi/mdns v0.1.1
	github.com/mattn/go-sqlite3 v2.0.3+incompatible
	gopkg.in/yaml.v3 v3.0.1
)

require (
	filippo.io/hpke v0.4.0 // indirect
	github.com/beorn7/perks v1.0.1 // indirect
	github.com/cenkalti/backoff v2.2.1+incompatible // indirect
	github.com/cespare/xxhash/v2 v2.3.0 // indirect
	github.com/cloudflare/circl v1.6.3 // indirect
	github.com/dgraph-io/ristretto/v2 v2.4.0 // indirect
	github.com/dustin/go-humanize v1.0.1 // indirect
	github.com/go-chi/chi/v5 v5.3.0 // indirect
	github.com/go-logr/logr v1.4.3 // indirect
	github.com/go-logr/stdr v1.2.2 // indirect
	github.com/google/flatbuffers v25.12.19+incompatible // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/gorilla/mux v1.8.1 // indirect
	github.com/gorilla/rpc v1.2.1 // indirect
	github.com/grandcat/zeroconf v1.0.0 // indirect
	github.com/hablullah/go-hijri v1.0.2 // indirect
	github.com/hablullah/go-juliandays v1.0.0 // indirect
	github.com/hanzoai/ltx v0.5.1 // indirect
	github.com/hanzoai/lz4/v4 v4.1.22 // indirect
	github.com/hanzoai/replicate v0.7.2 // indirect
	github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
	github.com/jalaali/go-jalaali v0.0.0-20250521085720-bf793ab67800 // indirect
	github.com/klauspost/compress v1.18.6 // indirect
	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
	github.com/klauspost/crc32 v1.3.0 // indirect
	github.com/lib/pq v1.12.3 // indirect
	github.com/lmittmann/tint v1.1.3 // indirect
	github.com/luxfi/accel v1.1.9 // indirect
	github.com/luxfi/age v1.5.0 // indirect
	github.com/luxfi/cache v1.2.1 // indirect
	github.com/luxfi/codec v1.1.4 // indirect
	github.com/luxfi/compress v0.0.5 // indirect
	github.com/luxfi/concurrent v0.0.3 // indirect
	github.com/luxfi/crypto v1.19.17 // indirect
	github.com/luxfi/database v1.19.0 // indirect
	github.com/luxfi/ids v1.2.13 // indirect
	github.com/luxfi/log v1.4.3 // indirect
	github.com/luxfi/math v1.4.1 // indirect
	github.com/luxfi/math/big v0.1.0 // indirect
	github.com/luxfi/metric v1.5.8 // indirect
	github.com/luxfi/mock v0.1.1 // indirect
	github.com/luxfi/zap v0.7.0 // indirect
	github.com/luxfi/zapdb v1.10.0 // indirect
	github.com/markusmobius/go-dateparser v1.2.4 // indirect
	github.com/mattn/go-colorable v0.1.15 // indirect
	github.com/mattn/go-isatty v0.0.22 // indirect
	github.com/miekg/dns v1.1.72 // indirect
	github.com/minio/crc64nvme v1.1.1 // indirect
	github.com/minio/md5-simd v1.1.2 // indirect
	github.com/minio/minio-go/v7 v7.2.0 // indirect
	github.com/mr-tron/base58 v1.3.0 // indirect
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
	github.com/ncruces/go-strftime v1.0.0 // indirect
	github.com/philhofer/fwd v1.2.0 // indirect
	github.com/prometheus/client_golang v1.23.2 // indirect
	github.com/prometheus/client_model v0.6.2 // indirect
	github.com/prometheus/common v0.68.0 // indirect
	github.com/prometheus/procfs v0.20.1 // indirect
	github.com/psanford/sqlite3vfs v0.0.0-20260519004904-f9180fa2acc9 // indirect
	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
	github.com/rs/xid v1.6.0 // indirect
	github.com/tetratelabs/wazero v1.12.0 // indirect
	github.com/tinylib/msgp v1.6.4 // indirect
	github.com/wasilibs/go-re2 v1.11.0 // indirect
	github.com/wasilibs/wazero-helpers v0.0.0-20250123031827-cd30c44769bb // indirect
	github.com/zeebo/xxh3 v1.1.0 // indirect
	go.opentelemetry.io/auto/sdk v1.2.1 // indirect
	go.opentelemetry.io/otel v1.44.0 // indirect
	go.opentelemetry.io/otel/metric v1.44.0 // indirect
	go.opentelemetry.io/otel/trace v1.44.0 // indirect
	go.uber.org/mock v0.6.0 // indirect
	go.yaml.in/yaml/v3 v3.0.4 // indirect
	golang.org/x/crypto v0.52.0 // indirect
	golang.org/x/exp v0.0.0-20260529124908-c761662dc8c9 // indirect
	golang.org/x/mod v0.36.0 // indirect
	golang.org/x/net v0.55.0 // indirect
	golang.org/x/sync v0.20.0 // indirect
	golang.org/x/sys v0.45.0 // indirect
	golang.org/x/text v0.37.0 // indirect
	golang.org/x/tools v0.45.0 // indirect
	google.golang.org/protobuf v1.36.11 // indirect
	gopkg.in/ini.v1 v1.67.2 // indirect
	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
	modernc.org/libc v1.72.5 // indirect
	modernc.org/mathutil v1.7.1 // indirect
	modernc.org/memory v1.11.0 // indirect
	modernc.org/sqlite v1.51.0 // indirect
)
Szµdï6¢/xo�ü¡#“!�*3‘+h0.1.0“Ñ‘«	!0.0.0-00010101000000-000000000000“Ñ‘ê
0‘õˆQProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260215031811-a0ab0b218a8“!
“}š!decred/dcrd/dcrec/secp256k1/v4 v4“`“Hx2.5“A³Ü½replicate v0.6“Á“T10801064154-80525e88d958“A“¡5“qib/pq v1.12“Q“]<0.7“q ge v1“`“À“ä-“<]ontainer v0.0.4“q“˜“Õ database v1.17.44“q
ids v1.2.9“q“	“R$“_	 “À“r:“R!“Í02“Q!“	/v4 v4.9“Amagefile/mage v1.1“a“E	S4“�
“€	“»	“€
“ã	*“›
2“Á“È
�ierrec“Ã(“®“‡t7.5“A“®procfs v0.19““I51127171934-4e34e03a991a“A“˜Qsuperfly/ltx v0.5“±“2“±“n3’^2’$otel/metric v1.42’$“¸2“ÈF2 v2.4.3“€crypto v0.49“ñ“Z312153236-7ab1446f8b90“€#4“ñ	net v0.52“ñ“é-2“ñ
text v0.35“ñ“[
3“fJ“ÕA0“h“3[48.0 // indirect
)

replace (“Ñgraph => ../graph“Ñindexer => ../indexer
)
"Y6ì…~x, Óÿ“!‘°{“æ›³¿Ž“x²rypto v1.17.45“�³…	€=ì;x, Óÿ‘ô�“1.2.0“ ‘¬1.4.1±Õ‹9“`“³ƒ	<mÓg:x øÿô›°;¦·ßx÷�rc2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd h1:ZLsPO6WdZ5zatV4UfVpr7oAwLGRZ+sebTUruuM4Ra3M=
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo=
cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
filippo.io/hpke v0.4.0 h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A=
filippo.io/hpke v0.4.0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 h1:MhRfI58HblXzCtWEZCO0feHs8LweePB3s90r7WaR1KU=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0/go.mod h1:okZ+ZURbArNdlJ+ptXoyHNuOETzOl1Oww19rm8I2WLA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 h1:FwladfywkNirM+FZYLBR2kBz5C8Tg0fw5w5Y7meRXWI=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2/go.mod h1:vv5Ad0RrIoT1lJFdWBZwt4mB1+j+V8DUroixmKDTCdk=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.3.0 h1:wQlqotpyjYPjJz+Noh5bRu7Snmydk8SKC5Z6u1CR20Y=
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.3.0/go.mod h1:FTzydeQVmR24FI0D6XWUOMKckjXehM/jgMn1xC+DA9M=
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.18 h1:9vWXHtaepwoAl/UuKzxwgOoJDXPCC3hvgNMfcmdS2Tk=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.18/go.mod h1:sKuUZ+MwUTuJbYvZ8pK0x10LvgcJK3Y4rmh63YBekwk=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A=
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 h1:MIWra+MSq53CFaXXAywB2qg9YvVZifkk6vEGl/1Qor0=
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/dgraph-io/ristretto/v2 v2.4.0 h1:I/w09yLjhdcVD2QV192UJcq8dPBaAJb9pOuMyNy0XlU=
github.com/dgraph-io/ristretto/v2 v2.4.0/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM=
github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc h1:VBbFa1lDYWEeV5FZKUiYKYT0VxCp9twUmmaq9eb8sXw=
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/rpc v1.2.1 h1:yC+LMV5esttgpVvNORL/xX4jvTTEUE30UZhZ5JF7K9k=
github.com/gorilla/rpc v1.2.1/go.mod h1:uNpOihAlF5xRFLuTYhfR0yfCTm0WTQSQttkMSptRfGk=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
github.com/hablullah/go-hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k=
github.com/hablullah/go-hijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ=
github.com/hablullah/go-juliandays v1.0.0 h1:A8YM7wIj16SzlKT0SRJc9CD29iiaUzpBLzh5hr0/5p0=
github.com/hablullah/go-juliandays v1.0.0/go.mod h1:0JOYq4oFOuDja+oospuc61YoX+uNEn7Z6uHYTbBzdGc=
github.com/hanzoai/ltx v0.5.1 h1:jWamJSHg1RtvcarevSJ/W5lyPJfRB4vBYXDuxyyOup0=
github.com/hanzoai/ltx v0.5.1/go.mod h1:rWgoDyQiB5d+GOGYncKrQu6uS7Gnoh0xlm1hCU5rq5E=
github.com/hanzoai/lz4/v4 v4.1.22 h1:NjK1Umwaxb5lnoFB44iGPtGj58y/PLucNcCc2gE8q4I=
github.com/hanzoai/lz4/v4 v4.1.22/go.mod h1:/iX6TGHL94Q9MkMmvHKKHlyopHn06RjEW7Bz3h6TJEk=
github.com/hanzoai/replicate v0.7.2 h1:PulFMIVVbvxz/LmBSKlj/o+T7nZmPGd2L2YjYVv5V1U=
github.com/hanzoai/replicate v0.7.2/go.mod h1:U0WdEoFFNw4lS9CRysXtsWOqtkzjIi2Ggv+C8Nsz8+4=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/jalaali/go-jalaali v0.0.0-20250521085720-bf793ab67800 h1:lvIuaX7hO0eO3Rlev+cVnlsoExR3i/JXxu88zt4JHPg=
github.com/jalaali/go-jalaali v0.0.0-20250521085720-bf793ab67800/go.mod h1:Wqfu7mjUHj9WDzSSPI5KfBclTTEnLveRUFr/ujWnTgE=
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/lmittmann/tint v1.1.3 h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I=
github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/luxfi/accel v1.1.9 h1:Tsk6gXj2uKE19501bD0ajRYdeCHIlTGb6jYyLc+F8hc=
github.com/luxfi/accel v1.1.9/go.mod h1:K00BcnLzEYMPHwCFq8Tf/450ApmTs9xBVvYOnofJCkc=
github.com/luxfi/age v1.5.0 h1:G69HbSV4R3vKEH9B0CulnRaMdSdf4RalMgP8xKmxHeI=
github.com/luxfi/age v1.5.0/go.mod h1:iAYAxgvrXxcy746+Ovh/eWWDuF9teJLNcCSSOX9RYW0=
github.com/luxfi/atomic v1.0.0 h1:xUV60MuzRvXngaQ1sM0yVC2v4TRoLlUGkkH7M9PS4yw=
github.com/luxfi/atomic v1.0.0/go.mod h1:0G2mTlQ6TXWHICUHrUUPu1/qAiIyR4gSZ2tva9ci/bI=
github.com/luxfi/cache v1.2.1 h1:kAzOS55/hmYeNKR+0HAKv4ma48Y6JjkI8UQeqdZ8bfI=
github.com/luxfi/cache v1.2.1/go.mod h1:co7JTxZZHpKT31Yh01LFp5aZOxmoUg157FhBLQdQHVU=
github.com/luxfi/codec v1.1.4 h1:Yl8ZalMNkqo7cD6R9AjczAajkLOmsjyZ9+DASVYHrvg=
github.com/luxfi/codec v1.1.4/go.mod h1:oGQ3j6E8c2P0pL0irYtWkrB1hmDUFIE0puXHK4gV5KI=
github.com/luxfi/compress v0.0.5 h1:4tEUHw5MK1bu5UOjfYCt4OKMiH7yykIgmGPRA/BfJTM=
github.com/luxfi/compress v0.0.5/go.mod h1:Cc1yxD2pfzrvpO32W2GDwLKff+CylHEvzZh2Ko8RSIU=
github.com/luxfi/concurrent v0.0.3 h1:eJyv1fhaC0jMLMw6+QS774cUmp7GK+ouMgvLCqnC7cc=
github.com/luxfi/concurrent v0.0.3/go.mod h1:Aj/FR5NpM0cB2P4Nt3+tz9+dV6V+LUW4HuMgSjwq5hw=
github.com/luxfi/constants v1.4.7 h1:e/Qs+DQP3pugle3Zncq6fZCxKgqqtbyD/z7Gm4ZjsYg=
github.com/luxfi/constants v1.4.7/go.mod h1:hOszZ2NDQ8gMZKncfcZ67PXkb5OIbnwAzXC3oFbQwW0=
github.com/luxfi/container v0.0.4 h1:BXhF82WyfqVP5mjlNcr7tP0Fcnvl0Ap1rkiu+rq5XuM=
github.com/luxfi/container v0.0.4/go.mod h1:Z3SpmMF5d4t77MM0nHYXURpn+EMVaeu1fhbd/3BGaek=
github.com/luxfi/crypto v1.19.17 h1:l2LLu7UFyICtJVfraLDLRi+lFGiDXKHSL18M9/m1gsQ=
github.com/luxfi/crypto v1.19.17/go.mod h1:INjdZtke85k8hX/QAmTMAY8bbZ4gzGZQLqURg3xf6Gk=
github.com/luxfi/database v1.19.0 h1:sySH5eS8ihJtyc+yb0LgTignGwJ9H4W9bv+nboCuIm0=
github.com/luxfi/database v1.19.0/go.mod h1:o1ccjgVKMYbVBfi+6+sDvx4swGFgGnCGFQ4gL/oJ+kY=
github.com/luxfi/geth v1.16.98 h1:w187TtKuGStf3tm2bshuHVKBv2Frjx0lT54kQVXyNHA=
github.com/luxfi/geth v1.16.98/go.mod h1:6kEzSExdk9CPQDPXALt6P3HfQqBq7KF1Jrrv9gBpxbU=
github.com/luxfi/graph v1.2.2 h1:APDihqCknjMeo06M3Xi9WAuyIMYOWMwsGmvKw9JElEU=
github.com/luxfi/graph v1.2.2/go.mod h1:/wuzuD5jiziLSecONTi3GOeuzLhAsflOqMerEawuFgw=
github.com/luxfi/ids v1.2.13 h1:lEotq0WUpMLcc/5X5MbE3n73jdfNo8IlFvTDfUXg+9A=
github.com/luxfi/ids v1.2.13/go.mod h1:QWjJvggX/nRkmrz1AGSaF2Y6mnOvz3g7lFOzs3Zd+JM=
github.com/luxfi/indexer v1.4.5-0.20260526184203-3b72d71cc8fd h1:RBI4m3Y+MmIsb1JO7JzBi/HffCVavhzt814qQbToGFk=
github.com/luxfi/indexer v1.4.5-0.20260526184203-3b72d71cc8fd/go.mod h1:h5U3td/esXhbkDojcpvFQtSCQ9USrYMnnmtNk56ei+Y=
github.com/luxfi/log v1.4.3 h1:xkUKRWvQ4ZwvlUC2e0/RTtHYZOYSMvSQ9W9lbjwBmiI=
github.com/luxfi/log v1.4.3/go.mod h1:myIkufyiQomSQH34K981kbz6cG4WUoerRUh7F4XhlQI=
github.com/luxfi/math v1.4.1 h1:1t9bCCsEqnl9yIKrShlbs80DBKyYTWdnzkVfBqEeO7Q=
github.com/luxfi/math v1.4.1/go.mod h1:QvbRxauQyE1w4lvbcLSe6c8yeJz2Zj1Bq1rayGgs2tA=
github.com/luxfi/math/big v0.1.0 h1:Vz4c0RsZVPdIKPsHPgAJChH/R3p15WHRUz7LkLf+NIQ=
github.com/luxfi/math/big v0.1.0/go.mod h1:BuxSu22RbO93xBLk5Eam5nldFponoJ73xDFz4uJ3Huk=
github.com/luxfi/mdns v0.1.1 h1:g2eRr9AXcziPkkcd24M+Qu9ApEpoKKjfI79QSNqv0rQ=
github.com/luxfi/mdns v0.1.1/go.mod h1:dbp5f3h3aE7CGzwbaWzBM9cwdcekhmSrWhQevgYhhNA=
github.com/luxfi/metric v1.5.8 h1:axPwfq+erOlIue7IJp5g+hMcMtVhYHja9gJAaT3+KNA=
github.com/luxfi/metric v1.5.8/go.mod h1:fO2giazkg4NDtr72JM/QXJBYebplAMeWC1JoZyNDvKw=
github.com/luxfi/mock v0.1.1 h1:0HEtIjg1J6CWz+IUyP6rsGqNWTcmxjFnSQIhaDuARwY=
github.com/luxfi/mock v0.1.1/go.mod h1:jo35akl3Vtd8LbzDts8VJ0jmSVycrd1/eBi6g6t5hKU=
github.com/luxfi/pq v1.0.3 h1:pFlQm1+5FuKTDUh2y/23bXWkN4I2Rc5iuxJypwDFFMs=
github.com/luxfi/pq v1.0.3/go.mod h1:8bppZcRElfrVt0n3nYCZW3iX1TvhvzNbdjNdK1irgIE=
github.com/luxfi/sampler v1.0.0 h1:k8Sf6otW83w4pQp0jXLA+g3J/joB7w7SqXQsWmNTOV0=
github.com/luxfi/sampler v1.0.0/go.mod h1:f96/ozlj9vFfZj+akLtrHn4VpulQahwB+MQQhpeIekk=
github.com/luxfi/utils v1.1.4 h1:8OY0jXCkpp6OotN1Y++6DATJkvtEwzq2k0p56imJlAk=
github.com/luxfi/utils v1.1.4/go.mod h1:c3yz1RjzrB+cs5GZm+q1T3/2cCKElO9vxm9yRRtgSEM=
github.com/luxfi/zap v0.7.0 h1:wiOXfctxiNk0EAiaEPURTXemvO8fyaGViIkoQVWs7Kw=
github.com/luxfi/zap v0.7.0/go.mod h1:WOBoeqEh0BCApg1wfxoadOLMXZUC9T1ksOABGoEp8os=
github.com/luxfi/zapdb v1.10.0 h1:1lLHEmkyC0BucnA/zjQYsMkUVxuEo2vQkEaQGjYfuuc=
github.com/luxfi/zapdb v1.10.0/go.mod h1:Qukh3hDRD0MnxA6z+a28JTnXhN85AiLLgp6TYr4QAMc=
github.com/markusmobius/go-dateparser v1.2.4 h1:2e8XJozaERVxGwsRg72coi51L2aiYqE2gukkdLc85ck=
github.com/markusmobius/go-dateparser v1.2.4/go.mod h1:CBAUADJuMNhJpyM6IYaWAoFhtKaqnUcznY2cL7gNugY=
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.2.0 h1:RCJM0R1XOsRs+A3x3UCaf3ZYbByDaLjFeAi+YCQEPhs=
github.com/minio/minio-go/v7 v7.2.0/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0=
github.com/mr-tron/base58 v1.3.0 h1:K6Y13R2h+dku0wOqKtecgRnBUBPrZzLZy5aIj8lCcJI=
github.com/mr-tron/base58 v1.3.0/go.mod h1:2BuubE67DCSWwVfx37JWNG8emOC0sHEU4/HpcYgCLX8=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nats-io/nats.go v1.44.0 h1:ECKVrDLdh/kDPV1g0gAQ+2+m2KprqZK5O/eJAyAnH2M=
github.com/nats-io/nats.go v1.44.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.68.0 h1:8rQJvQmYltsR2L7h8Zw0Iyj8WYNNmpwikoQTZXwfVeA=
github.com/prometheus/common v0.68.0/go.mod h1:4soH+U8yJSROk7OJ//hmTiWKsxapv6zRGgTt3keN8gQ=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/psanford/sqlite3vfs v0.0.0-20260519004904-f9180fa2acc9 h1:9bBMbcwroL46feESdJWjRX0GV+k8o/P9gAg9UX6Vz7U=
github.com/psanford/sqlite3vfs v0.0.0-20260519004904-f9180fa2acc9/go.mod h1:iW4cSew5PAb1sMZiTEkVJAIBNrepaB6jTYjeP47WtI0=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/studio-b12/gowebdav v0.11.0 h1:qbQzq4USxY28ZYsGJUfO5jR+xkFtcnwWgitp4Zp1irU=
github.com/studio-b12/gowebdav v0.11.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/wasilibs/go-re2 v1.11.0 h1:9j4uCAJyO3IJHygeOdUJNzm6sX3P1+3xuKTbCITQrvc=
github.com/wasilibs/go-re2 v1.11.0/go.mod h1:cDOlK/pNYGbGKRNa+NnXDJ1c8R63tWjnTjFKzoSbLv0=
github.com/wasilibs/wazero-helpers v0.0.0-20250123031827-cd30c44769bb h1:gQ+ZV4wJke/EBKYciZ2MshEouEHFuinB85dY3f5s1q8=
github.com/wasilibs/wazero-helpers v0.0.0-20250123031827-cd30c44769bb/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/exp v0.0.0-20260529124908-c761662dc8c9 h1:4d4PbuBNwaxMXkXI8yiIYjydtMU+04RHeuSxJdgKftM=
golang.org/x/exp v0.0.0-20260529124908-c761662dc8c9/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.154.0 h1:X7QkVKZBskztmpPKWQXgjJRPA2dJYrL6r+sYPRLj050=
google.golang.org/api v0.154.0/go.mod h1:qhSMkM85hgqiokIYsrRyKxrjfBeIhgl4Z2JmeRkYylc=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss=
gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.34.2 h1:mxsy2FdrB6+qG3NfXefz1AmWv0ehOSDO4jxgxd7h9yo=
modernc.org/ccgo/v4 v4.34.2/go.mod h1:1L7us56+kAKu04p25EATpmBBvhbcqqZ85ibqWVwVgog=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc=
modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.72.5 h1:m2OGx9Ser1VvTS4Z9ZJlWs+CBMxutLaTiAWkNz+NB9U=
modernc.org/libc v1.72.5/go.mod h1:np0N7KDJ7eUtMZmOqVZNldrZyG+DHLl2B5pg8Hbar3U=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.51.0 h1:aH/MMSoayAIhozZ7uJbVTT9QO/VhzBf0J9tymmmuC/U=
modernc.org/sqlite v1.51.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
ÂËtâ¦�›xb�å÷›°‡°nProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260215031811-a0ab0b218a81 h1:TBzelXBdnzDy+HCrBMcomEnhrmigkWOI1/mIPCi2u4M=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260215031811-a0ab0b218a81/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQk³``“m“4ö00 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc“Z10³ŒP22.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug“¾!92.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0“`#“Ð"³‚"­“}126.0 h1:YxjIynlo3iKtpf6GeQngeB5RbexIKPPKsZIoq5EHe3E“1“}196.0/go.mod h1:PBvHNQ7cXBJejTWDWxXWvtUwjT5O66pTC7npxLWu8H0“q2+³B2eG10801064154-80525e88d958 h1:qxLoi6CAcXVzjfvu+KXIXJOAsQB62LXjsfbOaErsVzE“î3*10801064154-80525e88d958“04\05 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE“¼4&5“ã4D³ß6Ž01 h1:x1nbl/338GLqeDJ/FAiILallhAsqubLzEZu/pXtHUow“�91“¸9û20.7 h1:ksHieAp50umwqxqgyHk9WiOmXM54kia3IEDb5H7FsM8“!=Baccel v1.0.7/go.mod h1:iZD3oxffiMEIT/KvzD8bgwC/cBn4AYlMW3QJpbRa4RE“!=“M;24.0 h1:tU5q65RQSQdaVq64Z/DVUhiPQMoMPQT6pr41LsvG0AQ“!=“M;4“¡;K“ß<“�<™“*>³æ=V03 h1:bcqUO8twMOhHWCgN/RbjfWulVPbvUQcxDkssaRrCx4g“!=“/?73/go.mod h1:ENkJ121cmDEkwQPDiKK4QhnTnW9u37PGpepbrdVcAmc“!=“,@“è?¨00 h1:VtH6kvZrCEjCnkHPkhExDU+GJ0ZulYX4rnA+lXJdJHw“Á@ 70/go.mod h1:ee525i8Recbpb0jVTDZYZBr1MmvJ27OITJHZ/nlNMBw“!=“~A37.44 h1:hfiTls7sqbweW+o4iaZqB8P997paC+vpgWmhN6v5MJ0“!=“~A:7.44/go.mod h1:6Ey5y3I0WNLHbxIlIdFqUuKfBg+b0fAgTA8FgRgQ8zg“!=;geth v1.16.69 h1:CHO6xTZ+A+3itk94ts4uyVRJajNVP3RxWTjJp5qGOl“ñIBgeth v1.16.69/go.mod h1:8eEO1hW5sa6OH2VeCMaCPnRz28JBxFvCPBCWPLsU2c“ñI“|B00 h1:jwOXnlDGfx2TjqE5GhP5T3Rv9cGh7jHSfV6DceggmAc“!=“|B70/go.mod h1:bf35zaU3Nm5tiJj1d5SoZP8DYYJl4aCVp32rOY5vj2w“!=“C	09 h1:+yjdhXW99drnd2Zlp1u/p8k3G23W3/1btJQ4ogHawUI“!=“C	79/go.mod h1:khJOEdOPxd22yn0jcVrnbX1ADa0GHn5Y74gvCzN5BYc“!=“ÀC04 h1:r/c47r6WEK+EfkTeYCj3tWKTH5dUWQauuLUmK/jDvBw“!=“ÀC4“ZDI“ÀCC4105612-65220f1104cd h1:s34y2+uPyyqsXuJKWQu5bL2Y1sWevkBJr2xF7eRkrFc“!=“ÀC4105612-65220f1104c“YDJ“ÀCC4130605-aade4dd9fd4c h1:OCaB/KxgU8hrKtIBMwSoA8mt2Pf8B+23UhKQf0EbRDo“!=“ÀC4130605-aade4dd9fd4c“ZDI“ÀCC4134132-0c0ba56e32d7 h1:vgt+Q5m+lJzLLIuErWkjg2lbbKE3wtoZD9X41MoRokg“!=“ÀC4134132-0c0ba56e32d7“ZDR01 h1:rIfFRodb9jrD/w7KayaUk0Oc+37PaQQdKEEMJCjR8gw“!=“ïD	61/go.mod h1:64IE3xRMJcpkQwnPUfJw3pDj7wU0kRS7BZ9wM7R72j“ñI“�E12.4 h1:iVz7s0gToCNUU/2cLT8gUa8MLzc0YRp4jBmeXBeKdX“ñI“�E92.4/go.mod h1:i+Am9YvFsqDE75ZW8MPE62XCXGukXNiN/7mD9SKxxZY“!F"“òE¤/0 h1:VB3mQcETc9j5SY1S6lAgFtuGr/rjWuDgPYnxS+OKWM“ F“�F	60/go.mod h1:/3dheKVjUk2yiS/ocH1IDzeLXOIe+kpVsErIGDOZdi“ F“.G01 h1:6tVarXMnNR3Xzud8FYUHjtXabTll3HI/OEqG0tgLAcI“!F“.G71/go.mod h1:PkD4D4JoGuyKtfUkqPNYkrg9xKrJeNVZFdW/5XvAe/A“!F“ H
“ÝG–“I²03 h1:yDEuuJ6bm9H8DzOzzveXI1AZlu/AeSpHNRrY1akbyc0“!=@utils v1.1.3/go.mod h1:lskchZiYYRBFsSyYg3NWLPw211uRyxIs3/wAzrfSI“ðI9zap v0.2.1 h1:5uU3Z47Nu/FNcLqYeKZ4B0f+0ZDF79/2M/9FRN/1/Hw“!=@zap v0.2.1/go.mod h1:CobyEg6MNO6qZ5wn3kVEw6QCxFVZvvGMnEKsC5sJZtI“!=>zapdb/v4 v4.9.3 h1:KV1DrbZqtZsZvrWyPw5D8WHR0DxzqW2jHjqx6obxgoM“!=zapdb/v4 v4.9.3/go.mod h1:BrpLaDeYy+smON4ASDPCFKWrQ58CXhy3WNEPGna7vuU=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mar“›K×04 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE“ÿL“¸L4“ÉLY/0 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE“¡L“hM60/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3“¡L“N	³ËM“%R22.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o“R92.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc³oR£ierrec“Ñ0cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4³«V@“ºY³fYF17.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI“ZCommon v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw“�\"319.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws“�\":19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw“]]+G51127171934-4e34e03a991a h1:r4YWl0uVObCbBFvj1VsIlyHzgZwZOHvY1KdRaQjzzUc“]]+51127171934-4e34e03a991a³ ]é“~a³ bPsuperfly/ltx v0.5.1 h1:vGUeBhKvBKZ2s2TsTrOSahz+m0PswfqOWDAD+ICiiYY=
github.com/superfly/ltx v0.5.1/go.mod h1:Nf50QAIXU/ET4ua3AuQ2fh31MbgNQZA7r/DYx6Os77s“9d#22.1 h1:J4X2hrGzJvt+wqltuvcSjHQ7ujQxA9gb6PeMs4qlUWs“9d#92.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ“Že 23.0 h1:LFhBNzoStM3wMie6rN2slD1cuYH2CGiHpvNL3UtcsMw“Že 93.0/go.mod h1:AafrCXVvGRJJOImMajgJ2M7rVmWyisVK7sFshbxnVrg“Že?nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2exJQ“ŽeFnottinygc v0.4.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo³!hi22.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho“iBotel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc“i“*l12.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/“i“*l92.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI“i“8m22.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY“i“8m92.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc³€mL03 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0“ün63/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s“q“op“	pÔ349.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4“q:49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA“ÇqE312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA“ÇqL312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ“Ÿr“Hrn24.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI“Ÿr94.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY“t³Ws¤22.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0“t952.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36S³|uþ96.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg“Žw342.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo“Žw42“Åx 25.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8“ýx95.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA³èy!23.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s“ðz83.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk“Œ{ø26.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk“k|96.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E“‘³}“ÅƒÄ“ô„—27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis“½…97.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0“†22.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw“†92.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0³­†Q02 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo“.ˆ2“Hˆè0.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo“þ‰³Ï‰4J1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4“M‹í348.0 h1:ElZyLop3Q2mHYk5IFPPXADejZrlHu7APbpB0sF78bq4“mŒ:48.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig³¿Œ8È7ƒÙâ³sx íÿ°‡êƒ°¾@“ÐB
³ŽB"AR$ìè!xH ·ÿêƒ‡‚°A?/1 h1:6NXqd1aDXvhIN13U0O3UvehDjG/9fQ0tb+w8ZpTjWs“�>“Ü?1³u@uAc´á�ñAx îÿŒ=½0°€³«>³>wü²¡xÅYínÛÈýM=Å„€w©D¦œ`·X¨ð�$n±)Çµ“…a$9²¸¦Hf†’­ÍØ‡Ø'ì“ôœ;CŠò×nŠÍ‹ÎÜ¹çž{gR'éer¡Õ"ÉËÁ _Ô•iT4B]¦U–—ãŸlU†˜-þÕJÝŒçMSóÙ6-›|¡Ã.òf¾œÆiµËëY>^d%fƒñXe‰^Tå©6«<åÖµU‹ƒ£SeýH³®µUM¥š¹Î�Ÿ®òrVÅƒUÒøõVí«³sè°Lõe´b�V
ÛÝ‘=Ž’…Vø·™ätR%?Ì*£`§¯LRf[ó¦2R=Ë¯eb:‡ë”-–˜ƒý¿„aqlÒ:©�/þ7¼É÷Ÿ«jó�/üŽßö{ñyóÏ²:ÿ¼Ì³vÂ<)î‰p¯œ&Oí¬:1v#HÞ8‡œr#±8mÓ¼…÷r›V+mÖjjª+‹ ÐIQ¨Ë²º*Û(¸ØÐ+É²©v�¾Èm£�•¸z:Sâ«WE•^Zµ,›¼`8•[`ÖßZ€¨´*}Ý¨Üª4)S]:‹³e™ªÈ¨§¯éÜÙÃ¬‡ê®²Ñ�1P`s$<%ÒâÎ€‘v|)»JÕd_!~Àû-eþ; š4©›¥ÑÏ ˜Eî‘¾Š0#ö(ƒ;õu]TF¸vá™÷Oàÿ•øñ4«¤ˆžï=ez`)ŒÎ†ÛsaY¡ßãsµl¢?Ý�‰ÄqšÄïÊc­MDE5ž”3˜ƒ#õS•—ðþ´ª
qLä3õÄ�ÒÈ 0v•|¼áà<>Fþ6³(<£êçŠ	´cUÒàïd‡(¤áÌœ‘âÆñË,Ã–òxÚRœ‰kSMõQ•iÑîö$'DÒŠóoÄ.è¨�¡�¹u,QŽ†–Á'ûªtD÷{t…jj–ä@ùÂÌµ³òÊvQ‚xQ�pËË%ÃJë¹ù#©k]f‘¼ŽD	ÌF~ÜçÉð�¤ÇN¶•Ø¶Ð¥“3¤i ’d 'k¸TíXÀ¸QfY‚âæÀ=Ø#Q•Aä¼A`u¡Ie7>C;‡ªÏKmr$f¢JøDJTÌÅ)ÓÌ1©�‚ÆŽ˜’H¹Òç’s×ýðvYÚ#5È2ûÊªÜ�y#íÃ™Ù)Í+Ûx^8XN Ì¤B=ß
4§‰Õ9êJ|Z{²¤LÆãp”å„xgþX‹õbû  F-zL�¾†w#Š~ŽÁ+c~GTB)º!0ß` BŒ=òz »cbSÁÍã‹›ÈÎV›TÄcóæ`¤>Ò:î{º¡ð<Ž¼îEvàÄ.é ¥xÊz‘ÜÓ{â¼×%,iÕ¿ý¡fv[õîèð_ž„ÊD2Ð–¤M¾ÒÅZ5¥8Ž¥ïJ­NŽ_:¹Ÿ¢Ñüª�åH.€”…L­žÇ/¾‹_üðËz¾ÆûNÑG¼ÄþœBµ‰ªŠ‰BÅ­ºŸ"Aî3Ù…G'éÜŒpÜDÅ-•U£’È#™å3¯¹9É¤.’i»WhC¼¿Åq‡ú"I×8¬Ûh'¢FŽTuID8¯�…î—˜8;GZ:0>ÁRÝ,KT�,;o"ä¶®J»¥¡çxÂÑW¿^íëT’­Ò·IÝéGèÆÎH½VRA¼ZÁ-þd·ä�N1 :¶`äÙÖô0ý,V‹.Uœô§ÀHÒ<Sûû*]¸¥¾KÛÖ›áÞ±ìlòÃ9DÐi�pæÙø}uX]¡”¶ï'!OõK0ˆ3,Tˆz¸‚ÊEÇa 6u^ºïéíg«gB í®âí÷èh=h#qãHä¬çf²ƒ=Ð³dY4œùÄÄ?&í@$eR*n$«_Wå,¿�¢x
«&ÒËâAšŠ#È“±zÈlØ&Û+9MÁ·cp§‡ønsÝ	u”u�"ÿ´Zš”Bö¡ŒyÍ‘c­ÎFyËmç,ƒø¢;äþ¬èhŠËê )wŠ¡Ë<µd£ÚfjŒ¤�r+úZ¢ÖfW^„ÜS0!zÔâª$ºœ¬*¿EåZÖ¾há´Ñ%Ù(|w© _íœF�q%ÂpL¸=`>}EÉ"Û¡Ÿ&Ã±iXw.RDÊNñ"_‘ì–<£tÄÖ£�MGa;v`3nÏÂÞ§è«_=ÿL{Ô 	=ísÐ4ýïIH=ÊB~Æ#4ägÜæ!! &”'¢_~yŒ” {$Ô‡“C¦òV‡óXÒa¸ì‰À)9¾‘ß"=Ìüÿ°Ùÿ€ƒœUÂÁ 2?À‹¿Ï8·˜ò+y§�
îjü-ºM;ÒÍ5Ï³Sâø	]8ÇÛ®›Y´XKôëMs¡ªrÓ…Ådœß§áÚ}ÜŒ}úF†«èXg†RztîìrA]øÈÁf¯™ƒ¶:‘“„¦XÇuÐAº;�ãn'´7lg<r—è!rè„[h¦ –GS6X9£[íNâ· Ùñ�™Là´Èu)Uë6ðñkyÿâ�³õB=ùþè‹“×´ÊÖL�O_BÞ/±øLÂñ^8
‘š“ç£p¡Ñðá1ÄïG1ëŽ ¼¼H6œœ�ß| í¶è]üÙÑiãÌà3lŠ“]‘§	­u7Y(.îº*ÆQþD'h1#jÃçPVï4àˆŒ­¡ÅÉŠsÇøæÃÈÊjfžñ^âXõãû÷Ç]/¶‰Ê��Hä$®ßíí�¿ßÛÛÌY’ô5ºF\¡¼+¥^Ðx
ÁÍ„_ÖZr¯/QåO�Û™¥2�ÛˆÎ™¸o‘‹«†�ÉÌT�à?Þª7r¢t_…ðŒfEL¦85 §ìÈÄG•š'&ÃE!>ºuöà+Ñ½H¬Íg<¹^±­\. BQŽQÉ}‘ÝÖ¸×#u¸s$ò
6pRÔ¶§#}£þ£ƒÃ_Z�@Y"pH `ÑÉwKÐK5,l‘ëUàp8!�K¦¢.‹�¸ä||q²Z„C…êÐjtGüµëH¶¥g	¯U3×ÿõ·Õ�·i6-! æNz-ƒðhïb ¿/s‰˜$—–òáf#wimF›x±ŒOq©@„Co,dìCÉ«ŽúÂ.}­»T3±(à®Ó˜L1ŒGŸûîPŠ}ëPÁ¨\ÃHZÑŸghÖ‘SàæaÊ%€“¨¿�¾;Ú
æ(/:Èµ¢°"N/ÞR¿<Zšb„T!™ø�àBAH„—!CuëÀ#lRwCáIjûHÄ¦{CZÊ±ÇH\ìÃ•zÎÔ1˜òÛc–Â-~ûÍ�€ÎZn;Ç…”›Âƒ@nâ5ºbÏs$Ìø-.qç¸Têz„¹Ð3ÊB'ê÷ˆúû;Dý ÃŠ_ÿ½ºLˆ¼ÃG™¶¯o‹»;”ë.|}!„tóœ¸Ò¸/õ‰›„nzˆªüE
©zÚ[¼ÕÖò?Bœ²í¢…•U7í âP¹pöÃ‚šr€ë]zN*ÅðçPô�SáîÅ¦§˜-Û�7=vÚöÐÒŸÎÞR&àú�w=n¨p,ÛZ{û�·›tóœˆÛä|�¢ñþ¡Kxl“sx óÿ’4ñ3°•³¶\%½±xÅZûOÛÈþ9ù+¦–öÊì¦eY¤Fâ‡òèc/¥ÂÞGUQOÀ·Ž�µ·â¿ß93¶Ç±'$öJ­pÆ3gÎœÇwã©?úæ_I1ñÃ¸Û'Ó$Í…Ûí8—w¹Ì<Èx”a|ÕÿO–Ä4Ë¼�çSó™° §Á„×Mýüº?#I4L¯AÇént»ãY<CœÊ«0ËÓ»7Að&Žðìæâg=ÕnˆïÝN*»âXÎ÷¯Ád±À•ŽLù]êa¹Ë¯÷“x^aQç,š]„ÎhDëœÆŽý‰ä±ý—<›OOöiL8t¨A¿%#?ºN²|ðzç·Í¾¼Íû—£þ~?�ŽxþðnªˆÈ›	œ%³t„!gÄ›ÓØýF·Žñ÷bWÄaDçèäÞ[?÷#Ãx�`Nêí'³8w7hæ+sÞØuäíTŽràŸ¢'®’\ü8½j�&Åï3Uê±jøóæ�B{±m•Ñ<%4½]†íêtxO ¡Çñ�…íõ$ŠTZtöÞˆ³£ów`„ÕUèêÖ1$½Û”t%@È<IÅÿCÅ¢ s:Zx¤(Ü¢éí=¯/³‘?•O±?ùƒÈSÿF¦™l´Éóc˜epú)ÅÉžÚÊØ1!Ç‰b�¢ÄØÆÿ©œ$7ruÞ;V´ðÜ0†ž(}Ñ&pzX;Ù‹ÔÓœ¨Ó2®”þXY‰š$òD¤2Ÿ¥1ô4“êt_Ý4ÈpÕMí+Âç2!’€I£à$NbyÄ“qî¬ÌÐØ�2Év\[NpÒ&ÿóiàçÏ ÿ$"ªù¢* Á�5@îÅÀ¤ÙQJè‰}BfÙÚc9_$LC†›/ÔÑøª'’oj·w2wµ¾•à…‰Ãƒšˆ©c`p 56c™UM�¸cð$fL"&HÇ˜´‹	&lêµŒ3dBDbcZ:à,#‰ ³[M[ :V V’>Nò·t²5°¡+ÓÎZUö6ˆ*w±CÈÇƒã³“4LÒ0¿[ýÝN¿/”i‰Œ´Èýo2ÓTŽd€¬F"³°~‘3Yæu“/~;¦Õƒ�	xÙu2‹63 Aš†¶.Ål	9+r0	bŠÎ¥×ñoÃYVˆ‰ókZ°! €_Á¦ÐÇÑQ’�ëÂð-Í$^·[¶^nóõšDJ›N�†kØø�û³(§„luÃ°+ýñ¦!&U‰…8d�§”Ñ&�@�Fä˜¼‹‰‚ Vo?JüàmšLã›µ$a®Óyáàá4¸÷ß$Y>íßIÂù²¿µwË&€-8õÁ›In[€+Nð•FóèAÈ�'÷†r2=ST�1pQ˜x¿'aìbZy1E£Ì»ó'¡Q’yÿ æH¦<î‰Ï_¨&r¿ªiƒ®/9yèdBP<ÂÓ/Â` ^ïüºóšÇ€êñ°œy.™Ã@Àø—6’'&)X€Fø½Úâç_ÕÞ[››øÇƒo®µÇ³«í¿nôÄæÎö6éÔV’U°VÓËX¹aÞ’èÝÈ°o)Ý»d-¨�Õ‹F*À²ªr�zøß'Óøp@Ü³m&\|—g†La_a'. S7cÃ`©‚Ø®šµ€Š
6}ä½‘\¯¨^‡Tì/d€·f$2ðTþI>GOÀèËù©üs†G×yw8Ä*§ó
•ö4JR™öyiŸeIÁå3Ž6o’%i Ù§SÏ8+ò/ì¨¡gŽò:`ð…ýÛÔ†W†®Ô–f·sã§â2	îÐ!™~F§ÅØ?¾ëv¨B'9�è�€‹¹·‡ižúíþ��#=}Fs U¾óÅsÇQâç;ÛËë}ž½ûJsu)Ô¨XT­º(º¾X£�‚LK»�©æ’ô×ïÈƒ¢]Ðs[œ�SæBuDE)¾´mâŒ`.Å¿û¯À�%qòéìA“àîIo6ËôŒõâjö7 là½—>ã�Q°ŸÄTg½¤(JöæO§Q8òó0‰UckV²0J×¬fmßlmªDø‰ŒZý%³ñô	ˆõ{6šZÈ´ÒÕm!]¸øA `‹“ßƒ™:¢,P‚:Wâ·øãÿQú‹5ÝÛ>yVåÜÛ›¯mÎM¯¨ïò°¹:¼@¹ÎRÉ†¬˜Õ/“w]dŒ°Â4~þð£™t•kQÊÁ GJ›£ÿTÈæñµc[Ío|{ŒùÕè®Öéh†¡çêD|Z«QG8�hÀ§WG³˜zÕùrœêë‚b%°j·�¢Žx68ÓZyn“RqI�Ùòy8†b\C×–ÇE†ÔesvÒè.&,$H´–¡Õf—ë7J£¥¶%4C~ÔlŒÞÅ}ºˆy(Ôµ[�Iˆ!¦‘„51f¹AlonÛ$�WíÀÚ¹Â±Çˆ|YÞ°
0›)K;:›³W•ßrŒ~ù!ê{–fÅk«Ì8%ØCn÷ûÙ§ãçJÀj¥ò=!%2kçS‹!½wÌFë6Ry
é—8m×nx|d}�Â•úÒ¸þøÄ©r-&Pç8¤ÿPFSÜÀP¢Þ.Ó÷Ðz/#ê²5­+Ñ:rŽ?þóÃÙððxxñ÷Ã]¼Úúuû7Êupí]â®t…Xþ4Ñ ™A|#ŠWx@KF'¤¤Y¼ü€{NëÛ[L{»Võ3M,[Jv/I";]ºÂaÊôÐà—Ö¶‡ì�Q¥¾OÀƒ¼J¹`³mRE&®ï&³[ñK™¹‰L¦hñR;Âñ�H“Ýz{ÕUéµ¦2R}'NeB·j×Þ´§†r
g´çÇÙ-5·ðJ—ÂoqÅÎåµ°æ©Pl±—Q?·P!ßX‡¬º�Êù"ýïdÅ÷M–T@j!§ðÞÎ–•¢‚èŠèEˆþ5´Šz‚î¶Ý9«—‡Ù4‰3ÉAô
Sñ3%ëç6†º½›ëŠÓÝX½æ,Û‡ô™µ6<õèÖ(Çs?Ÿe¾3H¾‘KêþÈ Ò¡þ�€Úÿ|í–óç fUÁ¦’º°ÌA/‘L5óö£$“üAÅ(
Q%“qñ0ýàq8ªiÈ… 4›öøËÌRó=ºhÃ‚óÓ#ø‚Sˆ[�Û¯ÐôITÀt<{³Fé�Ðâ“ˆzÓ†Ø2h�¯ª¾®Žˆ£PGL¸èçÔ¡©d;�µ7µÚ	©…aK*ôl#ñ9hfÕI*Öj^ë",M&k-oªÃ8ÌC ü�Ñ>€w;~œ¬×b«D»ÛÁ×3hw/”U+7‰zŽît;‚m•S†Wêé_éhÃûÅ^Ž“³4ú=¶Äº:1…ð°ßÅž×Œ·l1ä­k¼ºñóW˜ïb£ó,tQV¥�/ëo©ÀÐíÌøº¾n¦º;B ´´+c9où¾‹ìLÑ²÷ßu2Ð#Ìj¬w�®ûÞ-1»ªÔ^ÂÕø&c+7^»sÙa‰[Òû1C^„E¶fËf÷œÍ£sì &Të‚å€ãÒë´©7ÑO¿¯>Ñ0µnæ{fQ0C†bô
LZÌ¹ê¤ÊHæ¸³d´Ä,ÊúsË(úƒdƒúÔnO«;ÐÄIÖÓ]©#O@ýCå·E‹ z�&f{J°,„þ ³hÃ%À”!P}¬¥ä£¢à}÷ÿ	�%l”Cx óÿ�VÆU°n³ÅX&²E¾ûx¾7AÈpackage main

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	_ "github.com/mattn/go-sqlite3"

	"github.com/luxfi/graph/engine"
	graphidx "github.com/luxfi/graph/indexer"
	graphstor "github.com/luxfi/graph/storage"

	"github.com/luxfi/indexer/evm"
	"github.com/luxfi/indexer/explorer"
	idxstor "github.com/luxfi/indexer/storage"
)

// ChainSupervisor owns the per-chain indexer + graph goroutines plus the
// route dispatcher that maps /v1/indexer/{slug}/* and /v1/graph/{slug}/*
// requests to the correct handler. Routes are mutable at runtime: adding a
// chain spawns its workers, removing a chain cancels them.
type ChainSupervisor struct {
	dataDir string
	mu      sync.Mutex
	state   map[string]*chainState

	indexerRoutes sync.Map // map[slug]http.Handler
	graphRoutes   sync.Map // map[slug + "/" + subgraph]http.Handler
	defaultSlug   string   // serves /v1/indexer/* (no slug prefix)

	// realtimeHub is the explorer's RealtimeHub. When set (via
	// AttachRealtime), the supervisor wires each per-chain EVM indexer's
	// Subscriber.OnBroadcast callback so block events flow into the hub
	// â†’ out to WebSocket + SSE subscribers. Nil = no live broadcast.
	realtimeHub *RealtimeHub
}

type chainState struct {
	cfg    ChainConfig
	cancel context.CancelFunc
	wg     sync.WaitGroup
}

// NewChainSupervisor creates a supervisor rooted at dataDir.
func NewChainSupervisor(dataDir string) *ChainSupervisor {
	return &ChainSupervisor{
		dataDir: dataDir,
		state:   make(map[string]*chainState),
	}
}

// AttachRealtime wires per-chain block events into the explorer's
// RealtimeHub. Called once from main.go after both the supervisor and
// the registry's hub exist. Subsequent chain spawns (runChain below)
// install the Subscriber.OnBroadcast callback automatically.
func (s *ChainSupervisor) AttachRealtime(h *RealtimeHub) {
	s.realtimeHub = h
}

// MountRoutes installs the /v1/indexer and /v1/graph dispatchers on mux. The
// dispatchers look up per-chain handlers from the supervisor's route maps so
// chains added at runtime become reachable without re-mounting.
func (s *ChainSupervisor) MountRoutes(mux *http.ServeMux) {
	mux.Handle("/v1/indexer/", http.HandlerFunc(s.dispatchIndexer))
	mux.Handle("/v1/explorer/", http.HandlerFunc(s.dispatchExplorerLegacy))
	mux.Handle("/v1/graph/", http.HandlerFunc(s.dispatchGraph))
}

// SetDefaultSlug picks which chain serves the unprefixed /v1/indexer/* route.
func (s *ChainSupervisor) SetDefaultSlug(slug string) {
	s.mu.Lock()
	s.defaultSlug = slug
	s.mu.Unlock()
}

// start spawns workers for cfg. Idempotent: starting an already-running slug
// is a no-op.
func (s *ChainSupervisor) start(cfg ChainConfig) {
	s.mu.Lock()
	if _, ok := s.state[cfg.Slug]; ok {
		s.mu.Unlock()
		return
	}
	ctx, cancel := context.WithCancel(context.Background())
	st := &chainState{cfg: cfg, cancel: cancel}
	s.state[cfg.Slug] = st
	if cfg.Default && s.defaultSlug == "" {
		s.defaultSlug = cfg.Slug
	}
	s.mu.Unlock()

	st.wg.Add(1)
	go func() {
		defer st.wg.Done()
		s.runChain(ctx, cfg)
	}()

	if cfg.Graph.Enabled {
		for _, sg := range cfg.Graph.Subgraphs {
			if !sg.Enabled {
				continue
			}
			sg := sg
			st.wg.Add(1)
			go func() {
				defer st.wg.Done()
				s.runSubgraph(ctx, cfg, sg)
			}()
		}
	}
}

// stop cancels and waits for all workers belonging to slug.
func (s *ChainSupervisor) stop(slug string) {
	s.mu.Lock()
	st, ok := s.state[slug]
	if !ok {
		s.mu.Unlock()
		return
	}
	delete(s.state, slug)
	if s.defaultSlug == slug {
		s.defaultSlug = ""
	}
	s.mu.Unlock()

	st.cancel()
	st.wg.Wait()

	s.indexerRoutes.Delete(slug)
	s.graphRoutes.Range(func(k, _ any) bool {
		if key, ok := k.(string); ok && strings.HasPrefix(key, slug+"/") {
			s.graphRoutes.Delete(k)
		}
		return true
	})
	log.Printf("[supervisor] stopped %s", slug)
}

// Wait blocks until ctx is done, then cancels every chain and waits for
// goroutines to drain.
func (s *ChainSupervisor) Wait(ctx context.Context) {
	<-ctx.Done()
	s.mu.Lock()
	slugs := make([]string, 0, len(s.state))
	for k := range s.state {
		slugs = append(slugs, k)
	}
	s.mu.Unlock()
	for _, slug := range slugs {
		s.stop(slug)
	}
}

// runChain runs one chain's indexer to completion or context cancel and then
// mounts the standalone explorer API once the SQLite schema is ready.
func (s *ChainSupervisor) runChain(ctx context.Context, cfg ChainConfig) {
	chainDir := filepath.Join(s.dataDir, cfg.Slug)
	if err := os.MkdirAll(chainDir, 0755); err != nil {
		log.Printf("[%s] mkdir: %v", cfg.Slug, err)
		return
	}
	store, err := idxstor.NewUnified(idxstor.DefaultUnifiedConfig(chainDir))
	if err != nil {
		log.Printf("[%s] storage: %v", cfg.Slug, err)
		return
	}
	defer store.Close()
	if err := store.Init(ctx); err != nil {
		log.Printf("[%s] storage init: %v", cfg.Slug, err)
		return
	}
	dbPath := filepath.Join(chainDir, "query", "indexer.db")
	log.Printf("[%s] indexing %s (%s) -> %s", cfg.Slug, cfg.Name, cfg.CoinSymbol, dbPath)

	go s.mountIndexerAPI(ctx, cfg, dbPath)

	switch cfg.Type {
	case "", "evm":
		poll := 30 * time.Second
		if cfg.Indexer.PollInterval != "" {
			if d, err := time.ParseDuration(cfg.Indexer.PollInterval); err == nil {
				poll = d
			}
		}
		idx, err := evm.NewIndexer(evm.Config{
			ChainName:    cfg.Name,
			ChainID:      cfg.ChainID,
			RPCEndpoint:  cfg.RPC,
			HTTPPort:     0,
			PollInterval: poll,
			StartBlock:   cfg.Indexer.StartBlock,
		}, store)
		if err != nil {
			log.Printf("[%s] evm indexer: %v", cfg.Slug, err)
			return
		}
		// Live broadcast bridge â€” when the supervisor has been given a
		// RealtimeHub (see AttachRealtime), every block the indexer
		// commits is re-published into the hub on the chain's slug. The
		// hub then fans out to WebSocket and SSE subscribers (see
		// realtime.go + realtime_sse.go). slug captured by value so the
		// closure doesn't see a mutated `cfg` if the supervisor loops.
		if s.realtimeHub != nil && idx.Subscriber() != nil {
			slug := cfg.Slug
			hub := s.realtimeHub
			idx.Subscriber().OnBroadcast = func(eventType string, data any) {
				hub.Broadcast(eventType, slug, data)
			}
		}
		if err := idx.Init(ctx); err != nil {
			log.Printf("[%s] evm init: %v", cfg.Slug, err)
			return
		}
		if err := idx.Run(ctx); err != nil && ctx.Err() == nil {
			log.Printf("[%s] evm run: %v", cfg.Slug, err)
		}
	default:
		log.Printf("[%s] %s indexer: passive (no chain-specific implementation)", cfg.Slug, cfg.Type)
		<-ctx.Done()
	}
}

// mountIndexerAPI waits for the per-chain SQLite schema to settle, then
// publishes the standalone explorer API in the indexer/explorer route maps.
func (s *ChainSupervisor) mountIndexerAPI(ctx context.Context, cfg ChainConfig, dbPath string) {
	apiPrefix := "/v1/indexer/" + cfg.Slug
	deadline := time.Now().Add(5 * time.Minute)
	for {
		select {
		case <-ctx.Done():
			return
		case <-time.After(time.Second):
		}
		if _, err := os.Stat(dbPath); err != nil {
			if time.Now().After(deadline) {
				log.Printf("[%s] api waiting for %s: %v", cfg.Slug, dbPath, err)
				deadline = time.Now().Add(5 * time.Minute)
			}
			continue
		}
		if cfg.Type == "" || cfg.Type == "evm" {
			db, err := sql.Open("sqlite3", "file:"+dbPath+"?mode=ro")
			if err == nil {
				var n int
				_ = db.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='evm_blocks'").Scan(&n)
				db.Close()
				if n == 0 {
					if time.Now().After(deadline) {
						log.Printf("[%s] api waiting for evm_blocks", cfg.Slug)
						deadline = time.Now().Add(5 * time.Minute)
					}
					continue
				}
			}
		}
		srv, err := explorer.NewStandaloneServer(explorer.Config{
			IndexerDBPath: dbPath,
			ChainID:       cfg.ChainID,
			ChainName:     cfg.Name,
			CoinSymbol:    cfg.CoinSymbol,
			APIPrefix:     apiPrefix,
		})
		if err != nil {
			if time.Now().After(deadline) {
				log.Printf("[%s] api: %v", cfg.Slug, err)
				deadline = time.Now().Add(5 * time.Minute)
			}
			continue
		}
		s.indexerRoutes.Store(cfg.Slug, srv.Handler())
		log.Printf("[%s] api mounted at %s/*", cfg.Slug, apiPrefix)
		return
	}
}

// runSubgraph spawns a graph indexer + engine for a single subgraph and
// publishes its handler at /v1/graph/{chain-slug}/{subgraph-name}/.
func (s *ChainSupervisor) runSubgraph(ctx context.Context, cfg ChainConfig, sg Subgraph) {
	dir := filepath.Join(s.dataDir, cfg.Slug, "graph", sg.Name)
	if err := os.MkdirAll(dir, 0755); err != nil {
		log.Printf("[%s/%s] mkdir: %v", cfg.Slug, sg.Name, err)
		return
	}
	store, err := graphstor.New(dir)
	if err != nil {
		log.Printf("[%s/%s] storage: %v", cfg.Slug, sg.Name, err)
		return
	}
	defer store.Close()
	if err := store.Init(ctx); err != nil {
		log.Printf("[%s/%s] storage init: %v", cfg.Slug, sg.Name, err)
		return
	}

	idx := graphidx.New(cfg.RPC, store)
	go func() {
		if err := idx.Run(ctx); err != nil && ctx.Err() == nil {
			log.Printf("[%s/%s] indexer: %v", cfg.Slug, sg.Name, err)
		}
	}()

	eng := engine.New(store, &engine.Config{
		MaxQueryDepth:  10,
		MaxResultSize:  1 << 20,
		QueryTimeoutMs: 30000,
	})
	schema := sg.Schema
	if schema == "" {
		schema = "amm"
	}
	if err := eng.LoadBuiltin(schema); err != nil {
		log.Printf("[%s/%s] schema %q: %v", cfg.Slug, sg.Name, schema, err)
		return
	}

	prefix := fmt.Sprintf("/v1/graph/%s/%s", cfg.Slug, sg.Name)
	mux := http.NewServeMux()
	gqlHandler := func(w http.ResponseWriter, r *http.Request) {
		r.Body = http.MaxBytesReader(w, r.Body, 10<<20)
		var req engine.Request
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, `{"errors":[{"message":"invalid JSON"}]}`, http.StatusBadRequest)
			return
		}
		resp := eng.Execute(r.Context(), &req)
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(resp)
	}
	statusHandler := func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		st := idx.Status()
		fmt.Fprintf(w, `{"status":"ok","block":%d,"indexed":%d}`, st.LatestBlock, st.IndexedEvents)
	}
	mux.HandleFunc("POST "+prefix+"/graphql", gqlHandler)
	mux.HandleFunc("GET "+prefix+"/graphql", eng.HandleGraphiQL)
	mux.HandleFunc("POST "+prefix+"/ql", gqlHandler)
	mux.HandleFunc("GET "+prefix+"/ql", eng.HandleGraphiQL)
	mux.HandleFunc("GET "+prefix+"/health", statusHandler)

	s.graphRoutes.Store(cfg.Slug+"/"+sg.Name, http.Handler(mux))
	log.Printf("[%s/%s] graph mounted at %s/{graphql,ql,health} (schema=%s)",
		cfg.Slug, sg.Name, prefix, schema)

	<-ctx.Done()
}

// dispatchIndexer routes /v1/indexer/{slug}/* and /v1/indexer/* (default
// chain) to the per-chain handler installed by mountIndexerAPI.
//
// Edge case the older code got wrong: when the first path segment matches
// the slug regex but is NOT a registered chain (e.g. `main-page` in
// `/v1/indexer/main-page/blocks`), splitSlug peels it off as a "slug" and
// the dispatcher then forwarded only the REMAINING segments to the
// default chain. That turned `/v1/indexer/main-page/blocks` into
// `/v1/indexer/evm/blocks` server-side â€” a completely different endpoint
// with a different response shape (paginated envelope vs. raw array). The
// SPA's `?.slice(0,8)` on the home page's Latest Blocks widget tripped
// over the envelope and threw "o.slice is not a function".
//
// Fix: when the parsed slug isn't a real chain, re-join it with the rest
// before delegating, so the unrecognized segment is preserved as part of
// the path under the default chain.
func (s *ChainSupervisor) dispatchIndexer(w http.ResponseWriter, r *http.Request) {
	slug, rest, ok := splitSlug(r.URL.Path, "/v1/indexer/")
	if !ok {
		s.serveDefault(w, r, "")
		return
	}
	if h, found := s.indexerRoutes.Load(slug); found {
		h.(http.Handler).ServeHTTP(w, r)
		return
	}
	fullTail := slug
	if rest != "" {
		fullTail = slug + "/" + rest
	}
	s.serveDefault(w, r, fullTail)
}

// serveDefault forwards an unprefixed /v1/indexer/* request to the default
// chain. The standalone server is mounted under its slug, so we rewrite the
// URL to include the slug before delegating.
func (s *ChainSupervisor) serveDefault(w http.ResponseWriter, r *http.Request, rest string) {
	s.mu.Lock()
	def := s.defaultSlug
	s.mu.Unlock()
	if def == "" {
		http.Error(w, `{"error":"no default chain"}`, http.StatusNotFound)
		return
	}
	h, ok := s.indexerRoutes.Load(def)
	if !ok {
		http.Error(w, `{"error":"chain not ready"}`, http.StatusServiceUnavailable)
		return
	}
	r2 := r.Clone(r.Context())
	r2.URL.Path = "/v1/indexer/" + def + "/" + strings.TrimPrefix(rest, "/")
	h.(http.Handler).ServeHTTP(w, r2)
}

// dispatchExplorerLegacy preserves the older /v1/explorer/{slug}/* prefix
// for clients that haven't migrated to /v1/indexer/.
func (s *ChainSupervisor) dispatchExplorerLegacy(w http.ResponseWriter, r *http.Request) {
	slug, rest, ok := splitSlug(r.URL.Path, "/v1/explorer/")
	if !ok {
		http.NotFound(w, r)
		return
	}
	h, found := s.indexerRoutes.Load(slug)
	if !found {
		http.NotFound(w, r)
		return
	}
	r2 := r.Clone(r.Context())
	r2.URL.Path = "/v1/indexer/" + slug + "/" + strings.TrimPrefix(rest, "/")
	h.(http.Handler).ServeHTTP(w, r2)
}

// dispatchGraph routes /v1/graph/{slug}/{subgraph}/* to the matching engine.
func (s *ChainSupervisor) dispatchGraph(w http.ResponseWriter, r *http.Request) {
	rest := strings.TrimPrefix(r.URL.Path, "/v1/graph/")
	if rest == r.URL.Path {
		http.NotFound(w, r)
		return
	}
	parts := strings.SplitN(rest, "/", 3)
	if len(parts) < 2 {
		http.NotFound(w, r)
		return
	}
	key := parts[0] + "/" + parts[1]
	h, ok := s.graphRoutes.Load(key)
	if !ok {
		http.NotFound(w, r)
		return
	}
	h.(http.Handler).ServeHTTP(w, r)
}

// splitSlug returns the chain slug and the remainder of the path after a
// known prefix (e.g. "/v1/indexer/"). Returns ok=false if the path does not
// match the prefix or contains no slug.
func splitSlug(path, prefix string) (slug, rest string, ok bool) {
	if !strings.HasPrefix(path, prefix) {
		return "", "", false
	}
	tail := strings.TrimPrefix(path, prefix)
	if tail == "" {
		return "", "", false
	}
	parts := strings.SplitN(tail, "/", 2)
	if !slugPattern.MatchString(parts[0]) {
		return "", "", false
	}
	if len(parts) == 1 {
		return parts[0], "", true
	}
	return parts[0], parts[1], true
}
«ÿÉlîLx óÿ¾o•o°C³lR"%yâáx! Þÿ•o‘h°€)“s“ž3£Ý,“".rest³?.V	ÝQÑê/x åÿ‘h¦^°Ò³ü>³™“n³ý—s	ÿ§@x31 ½ôÌ’ŒÒ$†Ôï^l-mÏ”þªñÿSP²™åùàèQC3°’Ìô¼ü¢T†5óÔ_qv^?ÜpŠWÑìÂ¡9�çn³34äìãêâª—›ÂÁ•,|@¯¹ºÑÂ_ÞOu†×Ÿ�Mg¡F¹ä'g§¥eæ¤2Híø˜×ó´ÿáJµ.�/Î%a5› ª|<�]ý‚]þÉØ¬‘¶bÙá»UtÛÒ”¾€ “?f£(ñôsY(¯{@ûÒvûÞÇÿ×¯›{=ò&L��/H…Ä^ù„ù·Þý»©Qejô¬Ly×‹ägPA®Ž.¾`wz¬gÛ4ÅmbmÂÝI^Í~‹&«}Ë�*JÎHÌÌ+ÖK­HÌ-ÈIÕ«LÌÍaØµú¦ÌÄ}[|vohfeÍÙ,¼~7Ly~nA~1PPÙƒËr_í¦h/2·ûP´óÐ,…ç¼áÊòÒ2ÓõÒóNWsÔ©™–8¿08QÁí÷JW¨¢´¢ü¼’Ô¼�2ÑzaŽÃ"óîï:ëc+ç×"cÖ…“Ð”Å—¤—€Ôæ†sïûgÞâ –3uJUÐæ‰Jâü jÓóõróSî-<ZbZZn¤²ºüÏ_•ÅLs6ø"T—æ2´4³oqq–ž[ýhï�×bŸ8Ü¨‚ªÈÈšæus:[_—=v~Qø˜…!Ïl�ÈVe˜’`�•ð�–Z=‰k©uÖî½{¯Þv»^ÃÇ4ª¤(51§$37¤¬*réÄùëWí×/Jb1áK¿Üs÷5š²øb` •FÉi5±ü˜$|k›êKV‘‡©ÔZO¡+……CÇã‡FÍ;t¯¾ôÁ¹¾ç�ûcßÓJpµé™Å%E• #¿ç[Ýµ1jeÖ�5³îk»i²4ú )ƒíÜ/M‘ÂŠœ—;Õïo¶™ÿ›3»	8'e”3Üs²Zòåß.¿:Ûr•~Û–Ž8m×MP%‰%™É#D¾ÉósÖŠÍoç©jKþ¶‡ZWT·Í_ç˜dMœ’y?¡"xâ
§�+°Ø«�»çaùÙËéÚ·g«ž™"÷6·}ó.¥˜a¥©Ee™ÅùE Oºþø>ñÿŸ<yÖêƒ";ÎwÏkæHˆ �š¥ç…Ox· Hÿ‡â°,(browser_rpc_test.go G¯A3[Ì&¸ûÐ´‘à½C¶¡“%g;§Ú&“kÀ1ðl†›ûùZÕt·ô¸100644 frontend.go ™KŽ€SÙ£t|îIü!ÁÞè&'³ÇÚl`“(~•¹·æØâ¤wh›�¸“µ1$tats_v1_test.go ôvHÕ!	!È+â1â€x~=t“Þ)™0OE¦x& Ùÿ100644 docker.yml †#ÏÉÍlX}uÏIÚ.BÈLáUúÿi¶²Ýxœ¥WÛnÜ6}÷WÖb'+m/IÈÙ qêÚA;õ¥/Aàp%îŠ±Dª$µ»®ë Ñ/ì—ô�ÔÊÒÆôÁ°D‡œ3gÎÌJVò„~Qé%×J&D¥/§…Z\dÂTÌ¦¹[¬jãÿY63	}z4üèóÆFÅu)ŒJ÷9UÒria 9ËÜ>–^²ÇÂBË±"²ÈªK.W+›ÄÊìùÓHÉâŠ”¤¢^NÅ#C’Y1çôæä-UJôïßÿ�Í9�~|C“Zmñ¥�¿zB/öà(ç¬ÚÁÛ�¢t¦"óG�šèš=µÌðüÛþ‡sbº|þ”xY8	»¹�›`iEÉiªUIßžQ)$YEß~¢\ÕÚÄô¶¨�…']KÉµ�3âØ¥,…“ŒÏÕ†Ó±žœZÀ@‹\¤9åÌ�°†ÔB67˜0X‰0‘±J_Åa>4÷W1”2	lqŸ‰s4Œ^1Í_É¬KîÚÈè²þ€pG TÖow Ä_ÕÄ§-ó…ünÒ;¥Mt-øÛÈJpaµU&e…�³&xnIø¼E—/Ì�”žE&UÏ‡ n&l^ObÄ0òù°vb:CJÓ‚�C)}2¼˜F¹ÂÙÙ�pB½l?b¬
Þxáë¹HyF“+šw`:ÍÈm6à	³>%.^ÍÁ& ›3Qà‰ïRNãÐÄý½:rf¼d¡âOÏ:d@I«ÀÏ�¯‡Pì"'kþOe¼D‘c
‚¥Žuf”æ<½Tµ}=ºfÒ2¾u¼-£°ëõüçÆõ
h“öÉÔÈ½^íŽ\I2�4}g©ÊUªäÖ•üØ!×Ølú¬„º™Š‚B£Š9 ª°ÓqÎƒ0zL¥ÊjÜöL³*‚_r=¤T_UV[‡qïxæÍ”Û4ôØ:`ßžøÒÃE8¨îØ³_IG�	Ï“ÖYÆ§¬.,¼;;<ß»8;~¿ä¥y¥êyÿHš#@¦ˆ$W­†dTë­	u“je�?=£�oÎhÛ=÷Câ…áþ:?z÷ûþÉéþ…³rU5Q6ÇaZ_µNý­“•üíøë85–¦JÓÞñÙ¡–§š
5C­Ùs™~/n“axªÁï/³Êù%¦Sˆ6•<Í™¦$JH‡T‡p†ÄÇWeCJG(‘% Ô¶àrÞåÌ*Ê„¶®¯›óL¼Z¥››Žm7ú¾}—ÞžM—VJRsá[†O~sÆ“ðr~ºâ‘@ârHL€=ŠðÞó‡»á”!–{Š».KˆM\bÑ�
:ö°žøÔ"=o.±PN©Xšò
‚fG\ºœö®(²¢ÿŠöV©¸ãêìu Û;u`�X$ôWg;²;l­Pì’ÑéxÐ¾w,Å”>Qô'¶°g@Ÿwqdã ‹ÇÊÉÚÚT<ÔÙíÕoïÓYy€£Žx—æŠIÂµV:IŽTK=0¤_Xë]wq5tä^ÈB±ŒF­@xñXˆ¢ )ôÝÉ÷šJúWX"©?v–z1„žG½p¤q£r9³ùxëzÑÝì¾Û”$,Ë¢’™Ë$ñt,®?ú¼|¹ü+ÀKÞtõ>ÜÐ«WøÐÈÛñùÙÇó³ÁÝÍÁKÇº‚æ3a0 $+®w¾Á¡–~t¤mZµS:#Ü<rÈ¡�½‚®Ð°Ñ5²†ç®¿ÅÐ—}¬ªÁxçí– @%·l£sèmîKÆ,kyvo ¾ñš6Œ )#¾¬
¥{MnZ°98F˜î¸±c7¶t¾b€²ôë�œJÅè²mš&öçÅS_ÊÇ¹µhä£Ñú8s×ùaPîû·W^¢ûF,9¾¾Æ›“ÃÀ÷Ù–ì«Ò771ž„tOwìÉÙ°Ò|*–î1º›7~œˆ\é´˜?¿s?Ö/Q	]±« ,t´„O?©�ü vÄ·qin)>÷i0»gƒûØ7oôueß'Ç"¦�Ža‹Ü¤Ÿ Wµ2ˆã»y–xüÂÎõ¸ÃªUÿÃÛRÇÇ%[vñFÛä’Éu:eèŠÝoîçFäšoüÈÑ>»6xœóññÕËM Få·Êx§Xó# explorer

**Org:** luxfi  Â·  **Ecosystem:** lux  Â·  **Path:** `/Users/a/work/lux/luxfi/explorer`
**Origin:** https://github.com/luxfi/explorer.git

## Discovery

This file (`CLAUDE.md`) is the canonical agent-facing readme; `LLM.md` is a symlink to it. Update either name and both stay in sync.

## Where to look first

- `README.md` â€” human-facing overview (if present)
- `package.json` / `Cargo.toml` / `pyproject.toml` / `go.mod` â€” language & deps
- `.github/workflows/` â€” CI surface
- `docs/` â€” extended docs (if present)

## Sibling repos

See the org-level `LLM.md` at `/Users/a/work/lux/luxfi/LLM.md` for the full inventory of sibling repos and inter-repo dependencies.

## Unified explorer bundle (2026-06-05)

luxfi/explorer + luxfi/graph + luxfi/explore are rolled into ONE Deployment
per env. Architecture:

- **luxfi/explorer (this repo)** â€” the Go binary `cmd: explorer`. Imports
  `github.com/luxfi/indexer` and `github.com/luxfi/graph` as libraries.
  Serves on `:8090`:
    /v1/indexer/{slug}/*          â€” Blockscout-compatible JSON API
    /v1/graph/{slug}/{subgraph}/  â€” GraphQL per-chain per-subgraph
    /v1/explorer/admin/*          â€” Runtime chain registry CRUD
    /v1/explorer/realtime         â€” WebSocket + SSE live feed
    /                             â€” Embedded SPA shim (fallback only)
  Image: `ghcr.io/luxfi/explorer:1.2.18`

- **luxfi/explore** â€” the Next.js SSR app (Blockscout fork). Serves the
  rich SPA at `:3000`. Runs as a **sidecar container in the same Pod**;
  shares the network namespace with the explorer Go binary so it can hit
  `http://localhost:8090` for data if needed (currently it uses the
  public `api-explore.<env>.network` host).
  Image: `ghcr.io/luxfi/explore@sha256:ddb15bc28ff38c7619bc08df9337e02b32ef886c33b9993a9b9cb3c475ad75de`

- **luxfi/graph (formerly graphd)** â€” NOT a separate Deployment. Linked
  into the explorer binary as a library. Per-chain GraphQL engines are
  spawned by the supervisor when `chains[].graph.enabled: true` in the
  chains.yaml ConfigMap.

The legacy `lux-indexer` Deployment (same Go `indexerd` binary, only
indexed EVM) has been **deleted**. Its routes are served by the unified
explorer Deployment.

Per env: 1 Pod, 2 containers, 1 Service exposing `:80 / :8090 / :3000`.

Manifests:
  ~/work/lux/universe/k8s/lux-mainnet/explorer.yaml
  ~/work/lux/universe/k8s/lux-mainnet/explore-ingress.yaml
  ~/work/lux/universe/k8s/lux-testnet/explorer.yaml
  ~/work/lux/universe/k8s/lux-testnet/explore-ingress.yaml
  ~/work/lux/universe/k8s/lux-devnet/explorer.yaml
  ~/work/lux/universe/k8s/lux-devnet/explore-ingress.yaml

Public URLs (all 200 OK, verified 2026-06-05):
  https://explore.lux.network              (Next.js SPA)
  https://api-explore.lux.network/v1/indexer/cchain/blocks
  https://api-explore.lux.network/v1/graph/cchain/amm/graphql
  https://explore.lux-test.network         (Next.js SPA)
  https://api-explore.lux-test.network/v1/indexer/cchain/blocks
  https://api-explore.lux-test.network/v1/graph/cchain/amm/graphql
  https://explore.lux-dev.network          (Next.js SPA)
  https://api-explore.lux-dev.network/v1/indexer/cchain/blocks
  https://api-explore.lux-dev.network/v1/graph/cchain/amm/graphql
*ãLrã›“î]xœu–ËOQ‡#ÆGÚ[J[(�ŠÌtú² j‰@0¡*b|‡vÚŽ�vJ_¤Ššh¢4/ƒH\7²qÉ‚@ÝèÊÝ™°acâ#Š-làœvuÓïž×=¿sït}µl©Ó‘ý)I ƒb2”š`|r„•R™€È
™˜$Ç…8Eešcl.ÆAQqa2%Æº�R¯«·,¿LŽ˜
Ëßs¦½®üš5lýg‡)EPŽ…ƒŒe§øˆÄ¤ítÚÎXŽ²ìŠ%1“QfC±°@§­Œƒ±Ò,K‹QÞÆ—ÌÜSn ›Šjâ=*„Œä}…ª&JM1üAu�ÜPUÛzS­ ¦ÊÚâ[õ•œ»„ÏcU*]«À~^SùWKCŸŽjõJL—õÆRQ„2¨‹m¶’ãþ*›è5ÙQ†V]B¦Óc·2]E)éJìB¾Òå€f›ÔðÿØÔ ¬¦›­€ôšÍ€l˜›Ùl³@‹vðÏaýì€é_�ä;ë€�>ÙLðÄ.19á‰TíBê?9Ú‘Ç�ªõ¸uDÕ…&q¬æîn„F¿º›�R;.uˆ�NË¼È¦‰íEñöX`ÀwÇ=-�h�èõItŠKT\¶¯¢†ÓhÜõýú*ÍÛj¿œþåØ�ù#6? !CƒÊbøÇ âÜp=Þñƒ³�î’MÝ~)E¢pQlgÎçëÑ\0`TK†FË!f.Â¡ÌŒY¾;óù
ôº{µ�g×�ˆãMé®Ãlå<’õ-�4ä'Ð +}ÈJðyà% ª~àÝåÃ(_ØHŽIkÉº„ÔçÃ‡Èóú8|‹œ™<rŸ£¤ðñ›ýÕ]èKÅ¦Ð9æRP.3.]CB7á£½#ç´ LÈl&²òr(/?SCtÓh®ù™F�i-[GneQÖ²mÀ°êŽ�Ü,ºÅ_fµÕNªfõžYÞ×ÁŽÊ2r<ÈfØ¨�,¼LN'Šuû¡Þ^Ú/1µý¢a¿Ü#T-7§‡èîS4¯ü‚¢•…vÐõb ¯ã»Hþ¿•�ú¶
�9>µ%Òn§]bR“BÁÎ	í,ÔíxçDá…Lxœ›£;KwÂNFóÉ¼"Ï­š|ˆQgòF9F‹É‚œ’“0	M~Çx�ÑhòE^) OÎ’žìÌì>ùŸôdV©ÉX8'Ÿ`›¼�G
Î—fág4›Ig�ê‡ˆHÀyÒ“ÿ±°1›ë�ÕoV`fœ|‰Sj³/›;+³‘žÉä‹<òÌ‰é©“9&oàf›|Ÿ{“‘Áä‰ìÒ`îA³É"Ü²“xX'KñvOå�‹çÕg6Ô3„©Û,Å×Ä ‹H:l€Kxœ›¥û^gÏfsžd. €çÞ“÷xœ„½[¬ëÜv†ÿÛ¨��Ül'µ;N_”õó~;ñq,‘%Š”x)ŠŽ}Â;)^Å;jÐ¢E]@R8°ã 5Phít÷pƒä¥@Q EŠ>´E›ö©5‚>ô¡)Ð%­½Ikÿ'Ø{-q-l|crÎ1ÇøÆeÎý¿ÿÁ7þËÿë›gÔÅ‘/�<‚Ü	='‹(ñJ7*=§ŽZ¯úv}‰|	ŽÿàË4w¿BßÉýŠGO=,O›¥¬7ÀzBQ€+ÐÜ†H€[Š.v'"žî¿û­gY$¹m%#.ø%|þ¾N„¾ÛÊó–^¥ØmvZJÈÁ¬9çÙÖ=M¦‰hMò ¿û-Û:EÉ—y ~Sy´ñÏÂÁ Â‚½à°C ïC¾‹Ý	
8D1§¢Aÿ9Øl½
/KŠíš*U Íªf˜8å`û$_áßüo”Ð*Ó/+s�$*Æ¨* …¿ÝÂ£Lä—HA£"@ú¤™îNÅðÕ~¿_ÐA¾ÚF‘X}ŒO·�1}-¾øñ¿÷¿þÐ7˜¢±«Æ¾Îòã4û“Ž€–²‡Þ§ôBÔÓÎ8l¥j1½ÑûbîbÄ¹Š3‚ýÉ×_þæ}ýgßøkg'w=Û+ƒëÜ8¡ÕZÎøVù×I�"Á3ÉÖí-*;­`–Å�·0U�VHÏn’,¹HÚò){œÉßýÖt‘'ƒŸäƒ;¢½xe™—I”Õß>�oB=ˆà$Ei`á¤G‡u†îãÒÜó¾UËKW�lKgE–fT0zìÈï~Ë‡™éX5�Ž‚òË¼€hÍº—Ù	‰ZZuÊmµf¨:$-˜’`_dÂjvveßš›žKãÎk¿¬èe_ÖžuÑBÀëC«©ê²qj E¿Ý¢O,ˆáÝƒ˜%òuÔôÖ“‘a5)K’{7š�Óõ�ÍIZ~ÿ<~%‘}™iüqà{(UÃž4k½Ø1Ã46·±1iè“\ƒXUä1²Ouö»ßò£QÑŠüËh¯ÛY¥[ÁQ—é€Pgý0ßrîÎ‰-D
‡ÚýÉªÃdÍbç+mÛ*:ÁZkTg¶èë¿úÍ¿u¾CÎ¢ªöœÛÖAïub$¹„y‹†š( 2g“Cç2ÖÜ‡ùJ5ÖD|(ëÕ8AT‡�}ÝâhêyEÔV`%Q�]‹>ìÜ�;å5ª]*|¦ê·“„ö
“�ÕØ´
APÂy@žÚQé:~2 nStyé^}IÜa¯@&žyÏæQ}Ø,é¹[3‘Á½¸	tËÁÓ~fg*™£�Øne3/Ë" Êkë%�ú(Þ+^„NêbjÍ&GQ_êÔf…¤(ÀL‡lãb¶<·6Íž†õŒ^ïñ“®W£Èò(K¢sæ�{å†~o’&rÇù¾„²h·w“%Ñî÷¬xù|£4ªÒÂ(å§™Éê<³óñi„Í¬Ô»©toí¡£}Þ¨ž¡^œÑøÌö¤Ì…oƒ¶>«w+z'v%ãß÷Ð=LSµCÜ</ìÍ.äÝ‰&‹fX+Í‚_-[\‡ó½l‘Ë�šb<C×^UGþpµ#>þ ‰û‚Ì•�aÀ›\ó»Ã‰&�ÖËF”cu,ô�ÈÎî}ý— ÿ°þ�oÿÀyÖ”Y­6Uuž&7Ô‡�óþvàe-I
�uqSŽÆGé-²Ý*óÙlb8r¡ó‡ù “È‰ùèn çò^_Fëw¼w]ÔÑ¸žï$B^ª„ïK[¿ÑtJU>ðS v²$k1<ßqTO Žü¨“ŒU[L §ªv/°ØƒªètÇZ"2ld±÷3Ù/­NÊt×àCê,‹,‰ÙI§NÓ¡{„=XeÛ”o‚�Û6º_Ñ¥µãÝý\¼†£Ð÷ÉÌDHXÊÕi·©æL‡a8Õçxü -Zî´(8+Î@X×E’ï™CPŒúB€v-|ß×Ì€NgÄj?ñ"µõ83;�b!�>��†ª·©[•—¶^	´ãØ‘q^îáQ}2€ó–g¡…F~QÐ�.šGdI¢IQúhFC�­ª˜í‰�ø‘SæUî×—™é¢,Êoæûžœ.¹Éç¢pâíœ
Ì#yÀŽVQ‘6OYjX³SrYÔPÎÑçV{/K¯ßdÐdÇÆ+‡Ö«okz¯é~E™GÜ!AY†³¬FŸ/mÖºvØ»ó…[Ç‹e`º|6H�è›ÂË$Ïc<O\¯ë\ºoä~XÙ#Ê¬N4ª9º5mßE!ÚAiWpÁ©Ô{m?K‚Õš;6Ú£ÖKe~¹ ¹sd…à”Ö¸i/SU6Y¥pŠÛô{o?ŒàÃA‚ 
%èe4’FN"î½UÍW&˜­©(÷8•é7:2Ÿ2S¥ó¡6iÕÀEV”ãåó¨F"ZQrˆSEýFÎ÷*Á¦¹àv­½Üä¹D¢ð�©¹`*5KZ§&Ð„¹R{;qí€ðÛa$Á³2wbÀÉ“¼<TyvÇ1A� qDp~!-ôA‚@÷ª/ç7ÑºÚaÄ•çÑŽ±•ìà$m#›Tç™£t{
äþ—Iäq“ZÉ8ÛažZÕÈÞÊñóãj¢Ë™LòÐ¤zÅXÚœG8ËÝdoÎnÏ�.Ä¢æû5@w�e/¿ÿ2sRþR5i=W�<H™’[5Z#š¤tHb-ƒ«mA²p ¤Ä:�7‘°YìG!çg)ñèN‹ÑÞ^}Ø²`0DàIÎÍ<&û2^¯¬”Ï-GHx¡Ät«s“îðšýº—h²¨®îÖæŒ‚LŒ …B/ �¸–ƒàÛ÷»­=y�†¶kdÅí„ì˜ôt˜:Ìfí9¨æ¨¾«mæÕ³Ø>ÎÃQ7€ÌŠ=·ôêO� þ†¾®¢¬°<mDy‘VlktñQdN‹Åh×²?XÈÓÓ-
Ê_' (=+Irç=çó·ÅbË¥?%2Id²@ó[Í–9ÙãÏ'gÊ„‚•Ž‚'ô‘w—Öhà² ¸Qà‘¬½§:bD˜Ú�¶ÑD8ÊÂîDM¹èm®:âÒØžÄKÍóôŒÀçÑ=ZÕøçæÒ/ëÝ«TF[¹v„EÅ*¿±[yÐOë­4cìuáæÎ±T%¢œ6yÖÛ|Ä¾p¯ª®,ê9š�z^ú¹ÒÈ3¶6ˆnØIf¨³
“àp}ÊwCªkKk•¢‹õ<xµþ…ÿáœ¹ÃŒ†y’öhQ’èæÄïÇ[ž¶LÜÎwí²<0†¹ˆÂ×R¸´CbMm¥D†
ÁZ=ò>ëÛ±*Çr#ëæ¿r+ç1fj%i=lÞKR°ov3#7î¬RU<ÑZznOBbõäV¬*´²qÌ]øyiGn4Æ67]|ÜÂ°ÌÄÂö¤á[�Bo¤SVj”;
‘"³¼Ðµ<tA3ôÆ¤Å¯“�ŽÊ~òÊü³ŽÅb¹-@•d‹4Iž`ï·z’ÇÂÀXr» Å×¿÷ÓæõÿûáÿúÃïÿÈ�ÿÀë¿ûÇþÇßû¿ùÅëñcÿÓëoüÉ?|�ÿÅÿíuþ“øúú¿ýðŸüÔ~óÃêg¾óÅÙ·÷Ë8™/v2òÅ
¸ü|ó £E¼w¡D¸!8‡�
¨Ý0NºkW¢2¥fëÔnq äå®�ú…ëîòá=í8ò{TXÀœ$/n*€Üoçx±í¦”6æq3eÆ¤uæ¼\@\©ÙP+¢Gžêž#çÍ£ï´“èth­+9O¼«!”V§¶'ÙD}�‡µ<èŒŸ�Ô‚°ÄlÛ®âó´pÞÀªé �œËÎS»ŠÒ<º*y‹e‰G¦«|û~sR§úž™Ü�¸Ð”§
`¡¤ç Ùo#«¾ù|�Æ¾ÝbO£žÚ¡j’ >J¨9šà…O�TÓ.)OqlpÕu>MIé¥	pÑW'Œoç>D\ªvÕÔä&;Ìœ8`ëÑœ,Ç awu…‰X!÷[ö3¸£Ua?zëG�iÁÈ¦Up¹u‚á ÍA/ÁgÊô°Ê,ÅË%yîDÓ„Ò�{«v¡k§j¢Ú»<¸—oMý1Âïð)w>¤=¹(á8Ë*‰Êe*{[Ù_ÍäãéÈÙ¶ŽVf=�¼©›SÞ#¹ªÇÐåiàçu›0Æj4C:4ëÆ²¥eSþÊˆNÛN#s!„ý‚Ðn†~9�.é™÷||ÊFÐ$÷âT°Žè (Z’ôýfàå!òúBfåà…ÖAñm±ÝyFn_I`4z¯¾Ñ/øa1}m™/M‹#�BÇìˆ…gI	÷ì‰ÉÅ±ž³�Ny±ŠBÔô‘}9#ÁÌ,g°G7x.¼Ò¯ŠòÍ™@àÃš2ü–«"Àlü¢i*Ü�8~Xm¶"+xJä¸Kíb™Nõ6;N~È«N½ÓÚÙËÇLÓ½=A¸ž3&?[.ÒÆÛjb›bQ“QY‡tüŒûý|£ò3b¬êCÿgÿµ/Î¡UŽ"<ÀmFŠåÙ•\}ÆöXdZQ¯¬Ò©mq‚Ì`¥”8¬&1|Û+ƒ.³™½dEØx\Oç’‡´KËI}#¦E™ûQrenèùaò�Ó�…ƒYÜ¼8Êòqt¹½^Ð§fr*åº†N¡XS©‚ÊßGH“Œ„¡�òÄ«ï8Á#ãÅ!Â^F~O¢(æ!È"ïcw<faî´ðãA–uZ¨»<VËe²àN&7! IÇìUsù}ä÷£­¢ëÊ?æ{¨#Wd£M;MÈ)½YÇæL;ï¤Z ’&‘¨uŽ�WL£P3»ï÷~=p‰,oLâ>Ü‹ç$½Ç×veè]Ñ¡ñÔÂ·¬Zàì	PÏ	}<M¿ýùëà£¼zi—3ÂÏZhl¤Í0¬žÊ'Ój«í
Û-x8j^S’Óåßwüc¼êæÝEÀùé Ò¨Ý•yò\«&Ðµ<ÏÀ5—pZå‹¸—Í’ÝÎ)<$°G¿âÄÖ)ËG9À¸éÊ1¤ÿL` ë^1œ i¬ås9.Øãè	Ïûb]ŒÔÝÁvD¶�
WKž¿¤½òš;Ü¨*kè"·ßÖøÞ>Ùq9_Ç>	qäbÐ�éŠë+©ñ„×	âªÇŒ7¹Ê ùÕ~üêç…4VSoÌåÑ=²sÞò¢¢YEÂô‹Õ:NÙ�®ei×¼Ú5ØlN‘Ó&×Å×éÏþ¹×?ñ³s\c'.sË	]¸f½¯è‘‡ªíÉß¯×³*œê¶Iv§µhÒ
é ÖÝ£I(Ô8Ó¥O<úÝ{p?òó»-‡‚$„ÃÐDaìÅÁ\Ð±A÷�{±0 NfbÜÊŠÀ¨’â¶º�Îk‹X	�º�x~?Câ×ŠMò`Œ	î*ô²ÍaÆQøÅ>ÛdÛ:½÷¾OìˆÅqUÂ±[7¸µç2¤–Ò©_G"KŽî
8�ö6Zæ_+¹ðl;yË_Þ§F!¢ÛP^íÙ(èÏ±ìk�\×tÔÊL�ÓÃ,ÀTW o³ûzðrÜNýÞiÏôµ�¬©t¶›âp\A12�Fj¶¡¿‚ó�iÂÉjâ­r[©óçÀë<ö2»qâG#9. 	"/(Œ“”�#Ð8y÷”5sP+¸CSh;@òöØ1Zï+wmˆS^pïX©Ä6Ç–mØù£twô��gWùM*4Ò3è~ókÝ[—†lúvUj*°ÒL'	VHÇ¶3„^åÌlÕÓ¤e•—UC™UÆŸR+£�¼¸Þ×ßM¿uVf»R†É’±9§EG ¤°LjìÎÓÉqÑlÕ'¯{É½!^“�uøŸ‚[6ì^‘y
h°jçŸ2ucuà[ÌÉr[un�CyÇ6‘°ØhíÉy4åhl»¨vÂ+7ñ¬ªº–"À'ê“M‰¦·õQà×@E.üeM;+<,¥.if›Ö”&nÅsSò1Xp†"<�¼áâtkå�2½Ce7'rÏ ,‰+vcch¼¦×‡y;cqKé9®'¨bÛËÅjÃ¯÷‹EòèçÝ‘D£ÝìÜ|rïûÎß!!x§¯q^˜ï)ß¶^wÇt}ôÚ5XXˆj Y‚v©Ÿä'àv4ºÕUKaâ�.!În";[Ž¶Cv‡Õ"ª¢<gã}w,ÂL5 h3ÙÁyÿüÏ|øƒŸ]Có²¨z©Ç¸ÜŠâ(»ÌxèY®w�ÌW4½LÃì]™Çø.�µÊ<hÜ7AÚÉˆìT‡28$ûÿÏÏýîBâ$£ú�û6‰(üË,âý	œQ�ä[x—g®è[Q›.Š‘Ü -u$�,Ulväë?ý…?ÿú~áß>{#.¬*rF÷f¸É·FÖÇutÆc´Äg»ÎÒñM¹KÅ\
µ;¤À\\O3¾}*•Œšì•^3êÇE¡_.ŒèÍãÜ³–1Ü'b¬™¯´hXt€¡•û�*¯§Ú)\ˆ¬16 M°tb¬»ÏçØŽ‚nœ?êìA¡öâS’†’–}¯8ËFHÑe%E…±šôN>NJKÛÄ†„˜Ï]M Ì©)ù$¾N¼KÊÈ±nÅö§µ°é‚MÕ2|¹õ@_èƒe¨°Sù5fR µ+&g–�¦¸þÈ“Ã“½®Æcåø´Žqb	šÚ²Y®R¹�6÷æº-KÌó³I‰Ë”Þˆ(°ŸA}Hp]Ç‹~¥Ž3^¹J¶H•U¹Jd9Ié2Ä¤&ôë¹½nÉ}…ÖÔL(ôõ·~ñg_ó_¼TNKÏ·ÊÈòK,ïÞÂµê8Üû…\GŸ©Ë¢•váHÁt­ü¨ù¾ðÛ9ÉŠlG?ú+¿ÊòK-ìÓÃúc9ßjà,žê¾Y‡‰u˜ìrØJof"?lùnSpmˆOØys²Æ™‰½—À©F³U¶·’úhÚ£fÚKnj+2†Êh�M³H>Wj¤I+ÙPëö1}ZÊÓÈ¹.m—$ÊûòwiHCFúYRX ;• P3†�‘>k‚ëBÑMKˆ…ü6ðêÑÕåpnŸ×XmŒvS–žbÐyˆ4mz�…=~lpdvÐÌÌš5˜€íBQ‡”n¿!wØ£»Â¼ª#+ŠKAaVß¬2º»;øùaùó¶÷+|¾E((�ÌâÛÓ=�—± ðø æÌÏ6L³|ýg¿øŸž�1¼š™—ÛÓÕŠ?VÃû.«ý®î§.B›#sJ×x83 ¾¯ØU‚5°‡íªôG[3bŽ?�®ÁóœÐËÅ9Ãˆ€|yÀ_Îˆ…88„BBÜ«¦•!í2Kf©ÚÓNïÔ³!—Ý¶“¢)ÁlWÄÁ²æÌl[7`ä|VæÅø$Éð–z4ñaêõY¾®JXåÏÄ78±±Ö'lªò{dR-‚¹RÐuÁŒ¼£ûðËÇúÉØÏ·"¥¯êl	§8®—ÀÖË†É4–©ù"3ö‡æDEØ†½%a¥;ìèRÙÏ®™]ü‘ÿÏzê-˜l6Ë%²ˆÄ×¦Ää.[óÕ|S‡b‚,½ŠüðÛ“ßûâüR…Q^F€›§G„A(‚Œ™@0œrqõ ç¾gJgè9xQÐ5u)Ì”®L'3WÖgîë<¤z·õ!ßŸ§¦¶ª¨’(}©ÎEo¥ÏGõß‘(„\Ñ"òqÃ­¤üP¬”èH»3šžV&¶x²ëü<Oªtü`UõhÞë÷‡£ë4ó�Ç�9îÌDÀA4*ñº€˜˜š–“^êÌå5µïŸÃ{øÐñÀÊ RŸBÛ‘£hì‚ž;ûhGN#¨^vËÓb�W¥Ïž§ïçÞ;6VòÉÚÂwÈµ‰Lbu^Ï÷ ™HUŽ¶¤Ðð—�´¹ž©�†Tà±,™ý÷…÷Ïég&gS:B¢tH7ÑrV ¤�Iû9×œnj‡d-ê8‚¦a×<ÕøŸÑ‹÷ÐàêçÜ‰
#gU‘&výéQÕ¢:×ö*ŒX)ú’YT$=ýzìÑVYyïñ‰¸âŒô+KŽ99Ù“;=ŸÍ×+‰\ÃGTú¹¤¢ÉL•ƒŸüKåð3ƒ÷õe7!N·{fŠ,·ã¦‚†MÔAe¶•Wt5ª^ Ç§"ÕˆÝFcð¤Vqsá#¿¾‘©ó“ÅÉyf�ÌøÕpœMá­1™jÅpÌ6ÉÜýÆÄKNgŠ¹ì¹ðÝ®êÓÄOkàöqü½m¶4šÝœNÞiÆ˜™=Ÿåj4Xg p›aíöŠ„Ó™:_êäôÚî.•Õ$·o4éÕEØÚƒxBÙf­¶§¥5`“ý)ýÔ<IA¶µ%guà§ñöy—:Î0~?¿Ö­¡e$J÷Áž1kÊŠVätkïPSâV�c—{hVb¸äŠ85'„x®DPü¬'nî\ÛWîß1÷ø‘^p® �y:Š×8à¼i†,ÂÉ4Xc\,§×'$ Ð!àG—,îíûÍr=YöÅ¦r`Æ&á¢¨X–§ŸºëØ6Ù¢6ÑC]GDŠ~÷ü9èÊ	½ÔºQ¥‡Ò5``s(´ ¼™[PÔ±™d§6ZœäÈ$rWØN¶]¿íø³C¾$ù¿jóDïKûÇ*+ÑÃvÂC•Ü¯Ú^ ¨)†2‡M»�› s03Ï[Þ/+ÀOò[VzI½7”Y�ˆ©8¿gf!SOXxKç�¡óâŒJADJ|‚ò±ÿ¹Ä}G‚ŒhÃ .Ý-W#ï¥�€RØÞKû²Pá
§Ö¦N>óŒ ºfÝ�iõêîÆ !1âÅ&Pñ,Ï·Iÿ^¤cÕG¬ÞÇô–ñ¡T`]Y$ö¢0ˆã @[zN´	„~ø•éçÄºT¤3«¸ù‡Gç½í§b@&½ç¯DbiÐˆ?[' a+ŽCÀ­œØ]™Jðó\`�h´®N}JÆ?ÚLºó2³×óŽËã�ìY¿N¾2·ÆàºžBÙ¸…À„<~ºÛÉÀA„@%`ðÅ¡(s|Ì's­u”9Ò¤�˜,ú“{Xž5!ð¨[â@ÉN.	¶ur²Ÿ—9Z°kIÄñ›Ìy¹5þ=g˜Ù
GÅbÇšvj³z;]Ò3šMÒh‡!°¢6%€ÊyÀWçêëÄ\­ã]ˆ	�8¡8¾¸8lS ‡ÐcCOËS2€Fã,ã´O4S�¬ïÇè°Î·	Iq™¯ûûrùùQèíáf—Þ¢èÇÌv^‹fél9X™ V|Ú‰.šC³Œ¡ HpKz·i%iÏ†ô+QæÝ²òØcÏÞÔ<.ŒÔöB,ò.¬3.9P+z]ÕaÔ5ÏÔõE¦;f=ïÂ7ì4ªªÂK’÷•Þ�9KXBÂ^ÚÃš¾¨g4N’œ§ÞàÙYù®ÇRÓ¹þìß°‹¤	¢ìeÄkï¥ô‚Koy[úû	‚&2·­V³vºl¥6ßy5á0/?ö»Œ {&…›V¸F¿2ªó{I¥×åÅ¯?¿„†*@E¹‰aVÙð©¥@\™äZ	¡qd2ˆàH2øùlW?"ç]u­Ó~*Ž�ç·�Q ‘cüâ`>AÙ¤A¶u'»¢gk§PÒ�§k{dF(5YŠ»ÝÎ}Bvyœz{ÚE‘õêkdW�ùåûƒVŸ/¹Z£ â… |Ør,däóÞ�d½ÌÐ–+ÔQéÄ‰�¡q>xd8aâ9«T —ÀÄ|ôGŸ—<nÓ<Cî§1ŽÖfAðÅ‚`ÊÂ=übPïÉñ¤3;á mù,$¸ì›wÉ46'k‹àÀ¼Ë¤lGÙÉyý«À�Íìºô¼÷„æ¸‘Â-èn×îú¤ã&�„ŠîÍòI˜ká$0Å„À!.E?üUàù‚·~s:½'Óî,gÅou`’î%•r¤HµCãsYJÜºYº	 â\Æóú�¡ŸýkÐ¼(½‹‘ŠòOõ^]7Æ„Ãw‹E±8"ký@8ÓMâ	›Ž÷ª�Fãeº,	RÝåÐSø¿û†ìæYœXM5wžï•è}ïvYif’êõƒzªˆ95Ï6�1ßär‹XÕ¦f�í)¡{ý‡ø�³¡•¹‰w«ê`æ´&�R+t¥#[á6ëäÈêKO�Ùh@á$_�>ü4ö‹ß8çUm�á¿•YÉPEðñáÖB >q–†X$Ü#Än%rjfW¬ùaZ¬w6NC �”¤[ÏŸÄŒ¿L½·Ó�þNÇ=	
º¥¸µ~´q+5N¬f CÐWÀÔ�N–åTT�-ûÏáç¥ã]¸¿uiÆªßÏ¼ˆEÒÙjO,UHÇÝàEº¡ÎƒP>$‡4I -öÉX¨øRxŽ»žDe—#IŸ±rSˆÒa¾GÒ
s�Ø¯Nxå˜Úmº­ºDéê‡‡Ô¢á°–¦¯ƒøÙß#~ä3'É›·Ã¥ÌÄÃrß0Øª‹Iãj�[Õ!œná”Èv½D¶�½_û/þø‡õßxý½¿øC¿rŠŠ÷/�p“îÌ–¡.qq<7Ò‚Œƒ¥·Ž •4¤bšØ>úºù¥Ÿ�^¢4mjË¾XuË�úOÙã‡@9p�‚1E}|½<(zT‰Ð–§¬¯¯•ãÊ<Lz†›ã)!Íº(û2Ú›*Ê¯'13«~R—hFÒ¹òP´ë8z–³Cª5½¶H@ÊcWi…YÙå¤Òæ—&¯ßþ¥ßzýµ_úásè$ïù‹¾Oˆª‹ÒÂ†`šØ€Ä§F”È@v–¶¯Ð-îØGz}^\ºPk¯¯ÝÈ÷oÐ÷f«Pw&N—ÈÀ'¾ù§g[ëD«üPmD»ß	ÐLÍ;Ðá¶÷‚×ßÿ¥¿Ž.f¶
Gïå]&H/%àº´> ¶Åœ çZJ“cÈ†lv]….<?
~ÅN)iî TÀ<ŽÙ�Y‡ƒåäve=Ÿ;ÃA„—ŠQñâ�~!]ôÝ{^ì;E1P5í<œ‘å–’&c�;Yv¾«fB<PN•ç«ƒä¼þWß�½þæwÿ£óÁö2ïšßì2S_É„0�€ D1ˆ|q!Ô³@Ü·-Šºg	Ÿ#^ýÊóÊ}¶%×­Êé‚Vh(#+§wÆž—ícävðÜ¨rjèB�^Ò(‹>š×»ª0R£Ÿ¤^\Ø§|”BÌ¹oÑ˜‰ýBH{“ÇnP£šÕ›ë…£.°ÆÕå<Ju	ÊÿÑ[«¹”;òKEé/ëY¿Nÿò�žÁbÒµP2ßDZ	.“õzeeŠOHfF6p¥á«wš+Æ‚y4]‡C}eÌUa}Õ’ƒ?Ô˜-6bÏªg¥½¯†>`Û€ñ£�nfïD£¢å™ƒ[µ*3\‡"J’¼´ Ûrâü£
?Ô2<X­¨CŸ°ŽŠœ¢>`mZE�I¥š[ÀÜÀ<��ÁOÁâ¥‰í%Ù�Uç—#W[=y=Ôc—ç¦åZ—°¶“Ì¹dx�[>_¤[t›…knö´¸MyÙ9§‘[_Ïk”yóv„å1(å|W/ì	,hg�ÜÒ«=â˜›~>riNTÑ�Á¡ééÊŸEœ€ÑäemUïíc±“#)6£÷µ˜¥aªì÷À1£ÚüRÚ¬&;±Gúˆÿän�å±UZ©U6/VR„pÉ(&y^´ÖÛù›‡0a�–œy4I‚âc^¬DQbÇPâTk`�`CX« 
øG{m”ÙM{@U…ß5Ð�‚oïßq!z‹qt¨"²VÀl3Ÿ"T02OÑ}4G³éÈ»<^âéãÜÄãfò’øÈ‚¯ÚËýNu3XÆÌ‘ÐÈ¾æ€>4›ƒ-f=nG�™ º D�1M/¦Ë¼Y‡7îs@\Æ•õlnÉÅÜØÑ?´Œ*¥(B§,¾l¾µ°RrW‚e¼þÆ/ÿÜ‡ø—ÿìœ›dÜ9ãVLŠ[�ñÈVÖ•xðev}î„ZùÓí %˜y%§îÕyÆY½;ö#'Ž›ìr¤Ñó
 W3I¼är
ì¶u »mù¾ãFhšÏ�µB¶¹ºêÃõ!Ü˜Â§p¹æ9zîALÛ3�‹_ÿ•Ù?8[Ud•ÀÈòläÉÇj4ôDŽ<í	sä 9dÄ·“yø;Ñ‚õt¦Eìvq…Ž¡ùÃÈ×;^_Ôùõ`Ö¥QóÞŠÈgå°PDƒ/éí2¥v½¾^àúŽÈ×Äjœ�°Æ){©t­;¯Ä÷¸A>ÂÜ¼~Ž¼M}²ÒÖTPcÏ%èb‹<œåœc8UÏÔÞÈ’?_Ž<¯o}
Om”ZÂØÆœž*¥aï¸5'!*ÖŽïÚ˜Ja¦6³Æ/¶¬Ým¬ê=ð§:ku=ûƒ? ï™^Wf ÄÍ·Ê‘+sØ );„¨£@ˆ57[cbÙÚîþ=òˆ™DV=ÆÒÞMûÒZ5¤Ž˜EÂû¦hÂ•��³PÛBXª�Ì¢4öµk©ŸYÄ+¸œ<Ì‚÷)!µÈlT.
Ÿä·�Ø‰P²;›X²xÉ¤ZÁµý‰”òÔ2OÓìm.!Éˆé�Ì#Mjñ9åÃPzVð ¬”¹©åÜÁc‚5·þ|ÆRA‚Æ‡tÉÅÓ­~¡8Ve¿xƒWÞHÀènü&¹�®½¶‚ºj^oj%ÖÖ°� � ö§¦õV“ˆ(›ÓÀë:Šca _§‹}�°¿û¡žÿáþƒÅ¿ó=ºƒæôÀ�“qˆW§Õ°€?Zl“öæ†w©|Ü+Hadº.$r1AŽ9¥Š#ê�¿nøyýÁÕâUâ¿Ea_Þ7„JvØ-ÜL«eÅÞÑ(2lµƒör¹™�[ðPæªŸ£äé·VäÕ\ýÎë¿'üæëo	?ô+@TX7ßcƒ"<îã¥ãkbiÖ¨#�ÛGÜ˜6.UÊHs+ÑÈ*ž¯Wý±×¿%þ±WMüÛÜ¥gTðÚ{+äÝëìFÖU7ÖÝ±,¡ér"ã%×ÖBs’žXïÓ´Dª6¨kÏ¹Näÿ!þþë_—þîë?–çÃ¿¤~ùÍÿTÿî	¾$xLÉ©ÕZR0_âv»µ—REÂíB5UŽ“�ÄÒtž80@¢WËø)àê)ç‰œBlÞ¬¶ŒÂ #¶±‹×èV,jz~(:f>«ëhCY£¹ÝäpÓnäž–��ª°“V’kÇ¥lnTWßØ"™m—}i'nMü£�lˆÍâ
È|<Ø÷Ž@‹~ŸãØúÊ\T'ÀÉ8hyƒDéY[ë§7Cb’£ûëÈ²½4~ý‘{·ó†¨HMS]µQæNŠ©MÓ—^qMD(aC�aÄm,¯ÿùþG^“¾|÷–woèÕê9êP¯ x9Wã1†<•,µ›Ë¤—‡ü‚k¨ypÁ™·V¹bU½Ïpp{¡IÊÑôYŒ˜‰ATÁN·õL(4¯÷f@
KxH+#ýŸW?ÆÜÅX�j´×9–afÊŒîU2
t©žhd�õ*Kçq¦†
O×1ý¥6}?–£UéË™â•ºÜ´ÕÎ¹Ò@¤A’(qX“+ÛT‡#¶¿Nñlô«Å[[Óý4ofk".™°è±)biÑxõlÉ«%yÄ�%wü‚¬òùá¶R¿gþàëßùÕü`ü*þÅÙ±ìˆ®-/w�¾'sÉó(`ïs&ð8\ì�ãŒ¤~Æ˜])ä.84h&Gä‘<§Và]6âõá:ìG‹vÂ4Ó>‘ŠpùFÍ@>Ø5QG¸}Ò”ú~ÛL€ÃÂ8ÊI>µ�xÑÈ‡¬èrŒ.­pÝn2yœ–Ñù–úÕè-¿Ý±Ü”æˆí×ƒ´r±µ‰äê¹3Ê9>ág�—Xeí^j{®ë¹7/è�¼Îýc<½éÎÕÁ8ŸÆ×£&…÷Ö2Ï3»lO(ùn~Ò‰Æµrp¾—vÉöçå[‡ÝcƒMä"k&4\y–zGcAx:�›$Õ«8ß·&Ï©^ë‹i©ÁÓ»\#Œèzÿ3{½•‰÷ÖØÝçÆž™\3j’|0(©Ëö:wìw�Í©rhÈj)½ŒbŒ”>®÷ƒc$ç0Ê¹:3ßal‡"lÂ+=—º¸6mmm†,dªj¹å|ç>FË®€Ô)Þúƒ�G-�ã¦q€{ØnØ†ý¾'ý#« ¬h·MãÙ2¬ÐD£OÐ¯ã/ÿ#.Ï›êZ"íß›ÁCÀˆ¯ÑvŒÜÅFØb;6Þ†[µa‰Q¹Z,ÚI	Z~õáW~­ý‚ª÷©å}ÜK÷	c3ý4Áð¥`:Tœ�„ÐËM²Ç `Â@(27Ïé†ŸV¯¿ýëŠ¹yr¹ããjÝÏ'kžÚÎ`X•õaW©8QR&[o¤‡&B-WZ²5™�Lî?üö¯ƒ_œ­ú	ºoiëï},*~o,F‚0š¹ê½�cÈ…Gò: �¸‰	.Q�"Êvfþ&Ÿ· â:x¯Š± Kôó&zwþœ¼Oùâû)—µ¡ Q8—ëPË-Ê>2-È*`WÖF6+›|íÖúÛÒä1³�Þ¸9ÛKqc¤Ø×¥¼c;_^Ú¦“éjƒÈ²Gmx°ÐÍÓæÆÅ£mƒ3*Y4×®Ð-`‘Óÿè{?ôÍó¥Õx\ƒk»q˜�62*ß«ˆê­{:ƒôÄG—Ú>b(_2Ð)Öñe,œ–'hÁ¢|š¢Oè÷Í÷ýÒç› {®Õz¹š¢ÓU¤@Öœ�ð&Š¢Ãòv·9…R^Ì”ÇDÀ¥�´¼º„�NS–^vŸ_"AÄAÆQôÅ¶—rÏ‡Ü»zßù;¸ËÓ`:•Ð(Þ/ívp?:v¼ëÊƒ�‘Ë ág}Ìg”ü5¢KÏ¿ç†oªõpéÂ®ñÚ,7|A<ßíÄV£É®"R@à«Ãþä2”dÒ<-ôhlœ0ê�æ[OßË›¬2$�·fëqôÐv‚“Aâl7çŽmvÈcÒžh¯?âüÌëoÿÍµW%Ñ%ºt¼¤üÔèü˜s@ŒfkøsÖâd±Ì=5�Á÷´Õ8/K_ÔL”PÞç¯ŽóžÓ.ªã¼{Ëï]ÎÛ|ªŠ]V€	q”B±Øq¡PÂî“ŠGeM°ìÈÎvi.Ú#A¤*¿+éU’v[Â¼%!¡�Þ7³bËæF™�]/ayÊ‡bëÜÌ�PÈ°H+J�I™Ã‘[I¹ŸkìL 5tqkOl^ÿŽ÷§>ü÷w¾qÎ¢Ë¹á:¬>Þ´tÝiàsG_ÊšÖIKs7éÉ˜F·=¡ö´Dê–ð¯­tf8pþ”<ø
~$¹®wé÷Ü»eøØ£¡(›S¾Æ·'7…MO’cT›Ä	†*Fq,–$*0	>ú’¬É2«®, ˆ²øZ&~+°Â�‰1ê¸†& wë8�ÑS‡f|'?Ï’�±˜MD�"I´PÕ|ô(£M‹ÞÀ?§~ìA§…ÚÖ—ÙÚ‰5˜4)œbN³©|>ñÈ¹uµª—ºÖÝìg€Ï£
nÍ©ØPÀ}*äÛMïí—U¿(4Ç4b·…¬Qý²aàÄñùÁ¥”í�üé}~¼P‡zÈ{4P¦ ¬ä°ªï�—@uÒÌ•[ó`¯¬m-Ÿ®é8ß4Ö¦‡öçûcþ{°<®ÛÉÑïU]%îyyUêVÐXJ'kJ­ëIˆá)½ÿî·^ÿoÿÃ�ˆcôúëßùÉs`î˜0=`ÉœáÃHR×0kDé1Ýj;€Ü¼Qäa¹�ËTŒa¼cŸÎx]{¢°[§-t9„‘á/®Y–ma¾�?øq¸¤O`ÏšÔ=ì,ó@[Ô<`MÛžÂ¦‰BÊÈÅI”ž¦û`>0ê8ä¸Ÿ9¶bBe¹Û8�5fÀ²º éšHEŠ™íÎµÍ1\æ'IªÞr~'ø³ß`ã èGo[ÞHé}yÀ�ÈÝœDOÓULN
¤Í¶ì.¤¦Z²4MBøuMzÔ�kíÃýÔž��UEÉP_jÔ/e“xAc•ŸÉ…é	ˆ�x![‡e»Âu`á6Äroî†•Ú-Ô!I%‡båQ‹?�¸ÕÍ„ŸéÒÊ£%Y•‡FÝ€(Þ6¦LïYjFÇ÷âÒH¹ª â)m/åc›ÃcÞƒN©¤8QkóÀæT&¼	F~––½ ¥Kx9š¸OÖÁ×@_Ï® Õ�ÕÖý9$!
B@�*âƒ
¢ß‡èer’ATÞh kÓ†.yÜNÂ–)Ïó"êŽ)*³§ªÓÓ½5_	¯j7ÊNŽÞB!¦0êÅ'·}Æð{¹ÌnÍíÈ)ºŸpÃ,�Kæ›]4€ÆÞ¯{ÔH�áŒñÇ˜ZâO=éÇ&r.®üíó:�£ù¹6±ric�‹´m‰éZ!Bmß)Š¶áý Ý«¦UqæiöÔrþyÈó½ahŠ¬Z |íãc!4bÞ]ûÙÚNe…w*F.W�«_ZÞºã9í§`íÑ=e êíãn©€¼0ÒmÉN@ùÃ�J4Ò¥`6¯O³l·Î�l¯½þrÎ¾þÑüïsQ›_®£©¼à¶î§v¾FÔ¶'¦{€ðÃ øé&×vbˆNî�Œ¿]”-iNO’¯¼ø…ÿ$G¿8—p¹vããeZ�¶  Ò5Ê`ÕªãS†mà”8y··;ch"¯vÊ¢zô å`]©žÞRÎozº.á˜‰g~tl¡A¥“=¹†aºpE�ÈÈðžr…5V²„–ÿ|üOõìûÝKË‘ã
œUïÄ^00„?å³Ã>©Öf¸°S¡=ÃSÝ+ØŠž.?¹ˆ§årØ1q’¼òîêz÷sÔ„‡cÑ¢±(EáÅçc›‚ñÛÍv+—[b±™k©÷( ²²"¯Îa4rÈîÒYîÞúK>[–ŸäjªŽìÅ–'º6í§8žÍlÐ$Ö$½"ÒÊYm™=�[Kï)¶¬.<&¯Â—Ú‹›18¾TünÝ¬@‹»Å¿<?²aÞð„IQ‘¬Öâ�‘cdKwR1Àn'ÃnëÛñòä.çÏ/3º“ëå#õ0?WFã[Žg'¹õ™†öÉž4lïnãs¹nsì‰º}´†¢YDM8òd.CIÏ×ùûÉiª‘UºId·Ö[ší±�”Šì°cLw½]¢ÛhG»åŒ”Ö¶p4‘nfnÏæ..Ó+^·#—Áåéøø‘D=Ö0BÛ¡Øä÷ù6ó±zÁÕ§Ñd³+… ˜ö@ìk‰}Â.ƒk±øÒ�p~k¦ýx¾†�èÅ÷£/˜Úê`èÞ+ô´O²Ór K(Ý¡V¦Ü](>1@ÑÛø^q¬lo÷¤	QÙ•—]N_—·4<õ%
œï:ê�CíÜ�ÏƒgæXƒ™Ý‚œµðÉõ64
+[‡ƒ'äöÒÂ]ËƒV”y®S÷ïû,HaZÚÎx¡^ ‰àì€;vçh¦ª>:Çäì.gÇÓÓÕ|Uì]ô5Îò.»¥ªÞWaû½­+ªÔgæ¼tSfÓjSMÒLÉ¶kdž¦×ž.:¨òóÈÄ/¶#Ë¯ƒŸ²pÔpíÓåÒaC@…"/WFAùÑ¬¥•M—GaZŸÄüSÕªÊ›Òñ‚Ò*Â�+ý¾Øîç¼!¦ÉD3ÕÚœJd9kÒEA¯0s!c3£“q+¢“9ö¤A…•'–simÊñ/ò~‡ñÝÒª6»AÃýÉÄ#¶ñI¢_W§T�ÂéÀˆðêÔP‹î,kÓ'lB Ë}ÂsòâR�¥áÙ é}-W¨�TK­û¸Žr3Kû` Kÿä5G2éÅà3¨�þ�wÏŒ¶œ-f}Ç3FÝ‡–L9=F«<9®×!ÌH“¸3uóÏ�ævyF GSFÐPÑ.Ž‹›¢mÉû‡u×nÜÓ¾ÙBáº‚9%:o5ME?{è¬d4b�g]úDR+{?ÇÖzgjkÐ•¦Ó Z›±nGáÑÖY#ï¤=CaËãnÉ}nÜ…ŸXÁµãâR+½¯y8†¿Ìx¥<¡´ièæf&Œ¦`kÙmMC‘Zvà€L3xö¹9¾lùÌYf§7’uo§€�ãŠÖ»øÌOZs‰ë¸J"‰ŽšUýTË¬ª‘½@–Ü£~ö~.ˆ"iF�¡N¨§ ÞûJ0,Ëš28¯‹[%ŽûYÄ
§~Â®ÛËÖ_zgníæ—=þriêxŸ,Ø0¶éU[z!ê«:œ-ª¸Õ4¯w�õb*+FL­µÈXÈ¯åøÓ~îøß<W��WÑõ:€ÚËÚ÷4ÄVÙÜ¼l{ Ž"¡äµòð"aì-â¹å|ÍY?	W�L¸º_/7'Ÿ3kåéÒ8k »E™62ì‡$ÚÁZ6§D	0ûßTQ=Ã–Vv½ÝÇJ ;©>NÄ}]á`òµ·^'¨—Aõ©“–:²:h™¬µÜ¨¿“nâëy&œ[ËÆXÁ­/Ápâµ^âÚoÉÖ›ÿ�Abô>x½tÆIµ|ÌÃï›ï…Þóö²c°Cz”–”‘Oç
¸q˜þ¨9u’à'í Lf¨Š¾yTéMpíÕ–_æ×÷Ûí¤÷/ä�\õR‚*™&B!(›íáùÄØý„Jš‘q®K�Ñ�×¿Ðüüëÿ[ÿísj]n°sw¸ò³‡F9¦@Ãçq!ŸÂlµï‹º³VDiÕfo«zH'ïMìÆ‘ê†ÒÅÍŠ|ì£¨/w–uy ËÑ“ÞWMæO7Ë,ØéN#ÝÃ�5kª„¼Ä1q°Écà;&Ô¬l·ó´JÈ×£ùÏužZeé%	Ð�nãŠúÕÉ„‡+µäPnL,/ýÝ¤ÕºEŠmDš
Is™’K œË§»JFéð’6^˜\#ß4ûH™/Š{Gv•6�E^Üwž �ïû4ƒ¤‰Ø¶$·™™QI§3IpzT­ëEN�U^kHo÷Ð=ÝdÁ#º­xr©²sHãptÑñë\/ñb6Wû~-K~ú5Ð]ÕÞè �ÏÜ`8¤	y::Û‘Z('…¥LTÆ�ƒ¨Ý¶Ã’´0ßv�l¯i\ë\…—]áäAÕï¯h´«±eï÷‰1gE›Êvìîg'°—6v ÓiÄ7L~}AŽüëˆ/¦çÞI¶7“»Ñàó1^3˜¦~ÙK°1é’¸”§Ã¤„Ä™¡õ£±o­d°°ÇÙ²Ç%òÊ"Ï?Ó8‹Û3Gœ–ÝÀc+`šÒñ®…5AÄún y|Ql6#N ýkO×—}à�>ûÒ½wõÛ�Ø9c²‹EÌ³fˆ“ o4%ÝW…¢ði®fôÒ7�4ß#ûhÿý/Î½•�†K¿Û‹¼U¯æÃÝÓšYU O•º´�B‹£›Ø‹g‹ÕÑO&²
Â%4spR{Ô¼ÞËÀK'g•æEžDõÍ[?Æ²¥sŽýÛâ9šÌj
,"¸•™
}Õiù:õ‹`ml*¶~ä}x;9^Õ%üñÚµÏiy¸'‹	èƒé8›bÂj6†–ûB¨}àýóº†&³$¦ÕcôÔç·;¼ž“)tù_&@˜z±ì1–"<ÈA¡û“Š}T`¨ÄÒ’oŽMÖh&À„`µC0°4W
Ž7¬½¢‹û‰¯$V�Žt½ŽÜþÕâ~ô|\ûÀ GÎ¡Ša¥T%+1æ„ç¿X	Æ”÷«í”îCx¬ƒÞ`Áè¥ž~•É~œz‡ñyYÞ·T³w¢Ê[ÐóÉüâ	â½™“½#�åõ$}
Z†ØróÎŠ?¦(>�}È®,­Š�sÄZŒÊ¥Ô=BÄd+§Yäq+HC‹tÉ
«Ç=4Œ´Àµ(#ãÚ;§EbÕÞ§³ï÷Ût)lB°Ú„K^A&@Z~W1;™ì· 
i‹Íä­¨ÀéiüÍõÊšÄ½”�ßˆÇƒ¾šÀã.õäÏ´3†dzY·Æp2Êivªsª°Úbk€Þ¿þÃþG_ÿI÷;´�X±‡¼¿
ˆð<wáÈØ)ÜÏ
ßê˜Ö&ý Ï9ôÞ`r´¢vÄúé×«ÿ×ï4â4xnäF0îL¯Œ^ÿ8þ£çd€•Ù	M³4x�nËS@ŸwÜa…¤Ù‚Sßä‚à³èÆ`ë†uá[€ë}ÕFðhôÒ¦	¹ÅÇSJãÀÅäœ×ºQÝØ¨TJdm�­Æ¯‡”Ç�]ºÊëÿ	’6ÕíÂð¹•£‘HvH�Z>ð¨	ÅZv0ÀNppçk®ÜÓ�º^�Xˆg7ì;Ôê^¹àÇÂçav8Ð°�’å¾¬H2ß*ÉÜ@)"Þøj0wû|m1§,ñ¹Í¿´šq
ÝÒûò²Ÿër»]þûqÄÉÄl´H¦³f¾T›U¶åT‰r#¢é ýî�Uô›v:ý´oÕwýú�™�Bír3ÝB›�¶nF.ð"ëú|�¸eÌž;ZVÛëîõ×‡Ÿü óo
´2²�S1ÚçkAêF&c#�hh_·^ùÿ÷uíºŽaX»Ú
‰-ÐJ»€Î±=¾FHHq|‰“ø’›s)�ìØNl'¾Ä±ãXì	D�t…µÛPPSP T4T”¼444¼v’s”ì¢-,¹šfþëh¾ÿ3[ÇèVÜÐèiÄ'gõ¨ÉÆ©ñ&ÿ3ûpn>ªEÆÁôÑKbueã,$±BtØ�Ë8º‘,e8ªgr¢Öìqe;pk³r¨üã›§ùß/¿g´¢Œ±�ŒÕ—Ñ·=Hu!ÓeÏ–bËi:[Å
XRQZ-0•š^‘;úÜo
pþëÍóýw7/}ëfµ(/‰Vœ4€+�µÝ·°dœÑ¡«ýöH›!€‰:îJ�ÐùZä,F¢àñA-£dÇ&p¹Tž!×Éi@‘â°«€¥"8¡%0WTAÆT¨gÌyó:ç¾xÿËî§Ç»�YŠÃÕ•ï¯£Bå‹…~q}[ÔºE¢ Và
X€ 0R#«øyšYA±ë^štX§†•4ZKXKV�Ôð–]A+Þ–×›¸„ü­J–“ÿ~ûQ~wûãÎLƒ#C	ÎŠÞ£U@^
(NZŠŸc2113¶D8Ù¦ªÅÓDe‘ÃC7ÒÈA¢xY+ì«üT‡±et!<-¯ï�Á~ÿéW¿½·[˜Ks½Ú^HÇË‹€Ò±r.º‰ßù±‚çZêB›I4dT½›¶7‘flSÏHa9>LIo[÷5�}Cú=i'vÊeàÞiWóEC2:Í†\ABul7,®¥Æª]XÖÜ¬MQ‹î‰Ü˜"wÿï7?<Þh, Ã³@�9=å/äÊ0Ke‘qÎOW¦=÷¹QáfB¨¡\à÷7³8ƒtuÐãéßdÐ¦¼¢ÇoN÷^Ê	ûÝÿˆÑùþ©]µÖÑ¼” ¶7Q
@ŒRïö Zñ"ÞÔ·"Â;¸ÚcžÊ¿¼û+ÿúî…Päð
Z(ÿ
�±BšNÂà\õ3„”ˆ•®éVTt±ïÏƒ£&¤@‰€ˆ~Ÿå7£êþ³oŸ>Ù~É]'ðÛ�xŽYTsÙ&ŠÓ�ß�<ê7b�M’~-Zé¶NÈ²«Ã®tÃ�;+¢$T²Y\ÿß³ ]Æ]è.Y­ñÃåfî%kÈyFgk-`ež‘Sb¢�BG!¦ïuò?^=Ë_¾úGž—P«ÈqåFvü¿h;Y‚¢IMU( ôÒ	3Æ¦¬YÕµF(6Ì“:ç·y�LóŸ_?ßß¾¶Ÿì–‰¡y×Ó’ÏÇŠ—Á) _ðcÐdÃ«h6µPßjÕ	Ç«3ÀË+*¢¤3:’¾ÚewðÃp±÷€§|àÕ…‰P€ÄÉ+\7ujJYÔ'ÖyODá²¸(ŽÀ–�úÄ¶Ëí¶"Y1¿€	<U.Œyf\Ô±ÿbºŸÓ®xn ‘ÿ100644 icon.svg ^pý”`9«ƒ8(í=ëÙü0¸100644 index.html Ì|£}GÊRüHXóªøðyž¹DRe100644 logo.svg ¿ÛÃž—ìÿ6Ñ•£„µ_|nÉ%€h /?ì{x ãÿnn�6?z%.JÉ`½b<_o×�É’ó‘J$“ˆ'ì*x ãÿnn�6Ñ\œt¯)Á^>çy€¯i©¯xåú‘J$Á=×g*x øÿnH�+‘Q
§q²®x•X}o·ÿ[úO…f�7é”-E�ºÈ Çv[NàEN;ÀðdêŽ’8óŽ’'[5üÝ÷{Hž¤“ÝŸŽÏûŸÕ"¿KI¥PU¿¯ÊÚXOÃ~o°(ý ãø¯•KùXúøº4õý2SÕd#J�­ßúGýþdB§¦Z¨%)G~%É›z¬åZj�6VZÊBc…W¦"mD!ZXSR¾‚p—†}¿©eËÌyÛäžžú½3áÅ™²>8VÕ2~'ºcºãAŒY¡ìà®ßûåúúê¤("ú«Ø+ïë™ 
£O=tÊûWÑ]Àˆì	Æšµ´Zlòha,M¯N~$YÖ~CïñœË‚�3•Þô{¬¨Š3¹�ö^ZÍ[ÕçŒ1+"
+ôIúcï]@¼¹M¯á-Y[%Æ>îKLonÃkŠFÂŽîs$HU•t$ÈÁ§ZÆ`P­Gµ´ã@Jyã¼)Õï!v]¯$‡\%ò ¹¡à´Í¤,*Gð
b-´3Ô8ðõ+d„[‰ZþH%uáh.7¦*hª›åä“(å$¤ëtq6ù|u:¹F
LZ�	+ÉÔœ3BüD¢®õ†V²
™„�˜QÌ+$•H	©Zf)­ö‡Ê)·X‘6$)úwÿv¦:8@-ˆbž…3Ž4oA]ª
�CªpÖÆêâ,ªÊÿò7Ê
vÌT)£¬í(Ù)¯Ëãs(/œÝqªÊu9¢B,G¤haGäŒ•Ñ\ùÜ¨jG¹Ò¸~¾o%P×$[ç;	É|½~›niˆÜM"Â{b§›rn4Ð°B;ªä>Õ•1ú#4_"ÁºêÕ€ÌÊ$I�³à‰³ó’“ÞkYÊÊSmenÊÙAÃ·�?às´½Ã'Uò‚Ò­ì÷Î+1×¸Ôü™C)r2B’ÚQçöj·‰ü’0ñî¶g œšÆæ)æ]{]€´~ŠÇƒ`d¬²#’ÕzD|#Gñš¢t_$“Òs
WàÊ»ÖŒdñ÷Ü‡RÖzò% ÊýlE½‚Iá¹eµõÉ’Áû¬(…å VmMüsû…=…Oro¨Ž¯±:€Õµ¹—(BŸ?Iá‹"Ô²òÜá�÷Ð–ÊC_qá·ª ÏäT³•X+$4‚nMeç�	‚™JÏ•Ñú¢òÒ®QÜÚG•j€f(6 X$:‡{vÄ›ž-3¼{Ë-ÍúÚä÷pd+Í]dŒ^fýlÎ0Î Tév‡ûlSÃè¹mäpÂ®C”\’kæ!Ä.ÙÞ%ÝY¾»Né2…ˆGÛË³Õ®=àKÑò§›Ûö{ÛG·²[wm^±iK\š¦ò{�KuóšíÉW²ÔàîXî-“õ_'ÁÀÉS¨ÎcîÏ“§VÔ˜ÉŸ'M#!“T˜ˆÐiÞ(íÇª"Æaf�+:«)h¾¡%ðšy†¢4ÑÍãB%9²Z¢pg—˜>0ÐE‰¢ÞTÊ=ˆz¼þÛ(2K¯ïö@ß¡ÜË¼±Ê+‰R°XI”ŸgG„©EP-ü
³¾eÁ¨¯z¿]n]´Û'hÎ‘êæ*ÛªQL¦ðÊ±Š.8@ŽzÊ¼€Äõ½M‰¶º$CDß¥BœebÍHSç.‡0/•Ò#úrA¡VðÐèVæ¡boó”Š±-£äóäÒ,MÐ�˜9¬i<-”»Ç,‡N*íç_é<º(¼VH·ÊEE
fZÒâhÏì¬ä˜,˜åÍU%ì&ÝŒ¨ùK×þ÷2(¹�}ÜÉêÐTþtÃÓmªÑ�¡óïJ=Ü’çh‹§Óú½�YÎ öjÅ³€ÝUƒÝù7Õj(@f<�í¥
ºV¿ÇÎ�Dò*	Kùòù2ØÛ56Hi¬B’³øì‹Õ]EYêë,‚Ô|vÈ"fàA¯�(o�v˜PUÎ—+t¤”m´RË•Æ¸yÅ�$¥Ã›]^¤.†Éãæ¶kê"ö·02Æ¨¶']KO´6—Ò_p™i€¶qmq»,B+y•Eh$á„q;,¢³ÚUÕG¢,n÷ÃÖE|™0ªÞs]B­SpUÚvpÇ0²¢–&—µÌv¾ºs¬œ‡ù¯ùt/éâ;ïÌðÚ{ˆ^„SÆ�NMïÀ?�K
ÔŠ“{¬PÛ)=á‡÷‹™mæq%<:y…‚À!ÌaSÀ"†aI2Ø^¤Í…“Ø�ê•¨šR�þB«MÍ«Ê´‰E%híÄB²ƒ8‡-êêÖ�»SLÐ’Bg:
K_;·qìBÍGÒ­®ùžfï¡/ùÙGìz§q0ÞýëFŒ;þá6=Ç·OoGß¿{þö.nýÜ•NãæÏ3™wytšÇ›£oÏ?ý:ûõä3«ËÞoSL¥Xå0õ(ìíÐ<ø!ë/š*§ÏaèO1é�h�­µÆñÏ¼ô‡w:~OÆeŸ¡W’@xÔï©#Ó7ï©Rš	zVb$¬ÒOÏh‰¥ÏÎ™ábˆŸ9Póß¸czó0Gæ`ôÜï±ÃòÅ2‘nyC0§Aö¥*…Å¢«‡Q©?—ŠÿC|òåóïŠíÄ¯øÁì³��.Øµ{½Q·orÁ!ç!ÃÐ{ëgé±&Àº^Ž•î�‰9vZøø›½DÊ>
ÜÑiø‰æ@îéÂö?Â®ÆÍ›âö	r$ä½ùŠ¨(ì«[ÓÙÖÀ6 Dø—â”ÇWð?ù= á€È-xœ! Þÿ‡‡°iWÈ¹ã×;ðßt>ÕÓ‚¾ôïÆ ³}Š9¢å€È^xœkçhçP21 ½ôÌ’ŒÒ$†Úo>\ÅÙNß�5´wüOé~Q1yßF%w&‘ð;_·þÀË}¿Äîêå¦}_Þ[°¹–©‹ ¨ÊÙ¯xO °ÿ100644 actionlint.yaml !U¡išQà›â‡Fµ°˜!>F40000 workflows 9�Xi2Z±6p›õš9DÉÊúÒPqdé\x æÿOO�;JÓØ|Oøí‘îw
mD´Ñ¡S.ˆ”8	ì'x, ÓÿOs�+$100644 hero.svg L/gç€@¾Õ—â?Ð–|Eãã¡B‘+$[-´¦x& Ùÿ100644 docker.yml 7TÄö,Ô½¤I£(y)©BneO¬øcr·txG¸øname: Docker
on:
  workflow_dispatch:
  push:
    tags: ['v*']

permissions:
  contents: read
  packages: write
  id-token: write

# amd64-only on luxfi's native ARC pool â€” the SPA build (Next.js + 8GB
# heap) + Go cgo-sqlite Docker build under QEMU arm64 emulation pushes
# build time from ~5 min to ~2 hours. Cluster runners are amd64; Mac
# devs use OrbStack which has its own arm64 base image story. arm64
# rebuilds can come back via a separate matrix entry on a native
# arm64 runner â€” not via QEMU.
jobs:
  docker:
    # In-cluster ARC pool (lux-build autoscalingrunnerset in lux-k8s, org-scoped
    # to github.com/luxfi, amd64). The classic [self-hosted, linux, amd64] triple
    # is serviced by fleet arcd hosts that are not reliably available; lux-build
    # scales 0->N on demand and is the canonical luxfi image-build pool.
    runs-on: lux-build
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
        with:
          driver: docker-container
          driver-opts: network=host
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/luxfi/explorer
          flavor: latest=auto
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix=sha-
      - uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64
          labels: ${{ steps.meta.outputs.labels }}
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha,scope=explorer
          cache-to: type=gha,scope=explorer,mode=max
          provenance: false
          push: true
KmLâàW�Txp�úÇò°# The Dockerfile resolves private luxfi/* modules (graph, indexer, crypto,
      # ...) via go mod, and the GHCR push needs org-wide package write. The
      # default GITHUB_TOKEN is repo-scoped and cannot do either reliably, so
      # resolve a cross-org PAT (org GH_TOKEN, else repo UNIVERSE_PAT â€” both carry
      # write:packages) and use it for BOTH the ghcr login and the BuildKit
      # secret `ghtok`. Same mechanism as luxfi/node's docker.yml.
      - id: pat
        env:
          GH_TOKEN: ${{ secrets.GH_TOKEN }}
          UNIVERSE_PAT: ${{ secrets.UNIVERSE_PAT }}
        run: |
          tok="$GH_TOKEN"; src="GH_TOKEN"
          if [ -z "$tok" ]; then tok="$UNIVERSE_PAT"; src="UNIVERSE_PAT"; fi
          if [ -z "$tok" ]; then
            echo "::error::No GH_TOKEN or UNIVERSE_PAT secret set â€” go mod download / package push will fail on private luxfi/*"
            exit 1
          fi
          echo "Using secret: $src (length=${#tok})"
          echo "::add-mask::$tok"
          { echo= "token<<EOF"; echo "$tok"; echo "EOF"; } >> "$GITHUB_OUTPUT"“œteps.pat.outputs.token“¾œWlabels: |
            org.opencontainers.image.source=https://github.com/luxfi/explorer³On8secrets: |
            ghtok=${{ steps.pat.outputs.token“¯4registry,ref=ghcr.io/luxfi/explorer:buildcache-amd64“à4registry,ref=ghcr.io/luxfi/explorer:buildcache-amd64“;Ñ­7ï›xœkçhç˜P/rmó²ˆC2ö‰Z±S³šŸÛÿ¸,zãäf Ý�°ï›Bxœkçhç˜P/²3x}»hA™¥ÄÙähU¾©gcW}Þ8¹„ ÀÂÍµ�xõ
÷# Lux Explorer data-layer image (v1.3.0+).
#
# As of v1.3.0 the Go binary is API-only â€” it serves /v1/* (indexer +
# blockscout-compat REST), /health, /envs.js, and per-host brand SVGs.
# Every other path returns 404 and the IngressRoute routes it to the
# per-brand Blockscout FE Deployment (ghcr.io/{brand-org}/explore-{env}).
# Built from luxfi/explore via Dockerfile.branded â€” see ~/work/lux/explore.

ARG GO_VERSION=1.26

# ---- Stage 1: build the unified FE + indexer + graph binary ----
# graph + indexer are consumed as Go modules pinned in go.mod (luxfi/graph
# v1.2.3, luxfi/indexer v1.4.5), NOT cloned as siblings â€” explorer has no
# replace directive, so under -mod=mod the module cache is authoritative.
FROM golang:${GO_VERSION}-alpine AS builder
ARG VERSION=dev
RUN apk add --no-cache gcc musl-dev sqlite-dev git

# luxfi/* modules are private and may be re-tagged; resolve them direct
# (not via proxy/sumdb) and skip checksum-db cross-checks, exactly as the
# node image does. The default GITHUB_TOKEN is repo-scoped; a cross-org PAT
# is injected as the BuildKit secret `ghtok` by docker.yml.
ENV GOPRIVATE=github.com/luxfi/*,github.com/hanzoai/*
ENV GONOSUMCHECK=github.com/luxfi/*
ENV GONOSUMDB=github.com/luxfi/*
ENV GONOPROXY=github.com/luxfi/*
ENV GOFLAGS=-mod=mod

WORKDIR /src
COPY go.mod go.sum ./
# Configure git auth once so both `go mod download` here and the implicit
# fetch during `go build` can reach private luxfi/* modules.
RUN --mount=type=secret,id=ghtok,required=false \
    if [ -s /run/secrets/ghtok ]; then \
        git config --global url."https://x-access-token:$(cat /run/secrets/ghtok)@github.com/".insteadOf "https://github.com/"; \
    fi && \
    go mod download

COPY . .
RUN CGO_ENABLED=1 CGO_CFLAGS="-D_LARGEFILE64_SOURCE" \
    go build -trimpath \
      -ldflags="-s -w -X main.version=${VERSION}" \
      -o /out/explorer .

# ---- Stage 2: runtime ----
FROM alpine:3.21
RUN apk add --no-cache ca-certificates sqlite-libs wget
COPY --from=builder /out/explorer /usr/local/bin/explorer
RUN adduser -D -u 65532 explorer
USER explorer
VOLUME /data
ENV DATA_DIR=/data HTTP_ADDR=:8090
EXPOSE 8090
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
  CMD wget -qO- http://localhost:8090/health || exit 1
ENTRYPOINT ["explorer"]
¬!í\ã‘/xœC ¼ÿ‡‡°6JªßFŠ�¦­¹â®WÐçˆgJ100644 go.sum úHa²<„
ÏžA=9ýýÅxO³7Ð­-—áRxœkçhçØàÀ(’!àœúã½4÷ô�oÎ«ÍÝ÷×ùÄ¹Í!Œ›™ âHàxœP ¯ÿ‡‡°C[›)9×õò ™C�uIqÈ!„100644 chains.example.yaml ³ëO¤"£RU¦F¦pKh Š³T³³ÜO´Ûx´Kê<p align="center"><img src=".github/hero.svg" alt="explorer" width="880"></p>

# Explorer

Single-binary Lux explorer: chain indexer, GraphQL engine, and SPA frontend
in one Go process. Imports [`luxfi/indexer`](https://github.com/luxfi/indexer)
and [`luxfi/graph`](https://github.com/luxfi/graph) as libraries; embeds the
[`luxfi/explore`](https://github.com/luxfi/explore) Next.js build via
`go:embed`.

```
ghcr.io/luxfi/explorer
  -> indexes one or more EVM chains  (luxfi/indexer)
  -> exposes per-chain GraphQL       (luxfi/graph)
  -> serves embedded SPA + REST API  (luxfi/explore)
```

## Routes

| Path                                              | Purpose                          |
|---------------------------------------------------|----------------------------------|
| `/`                                               | SPA (embedded; SPA-routing fallback) |
| `/envs.js`                                        | Runtime config `window.ENV = {...}` |
| `/icon.svg`, `/logo.svg`                          | Per-host brand assets (disk override) |
| `/health`                                         | Service health                   |
| `/v1/indexer/*`                                   | Default chain explorer API       |
| `/v1/indexer/{slug}/*`                            | Per-chain explorer API           |
| `/v1/explorer/{slug}/*`                           | Legacy alias for `/v1/indexer/{slug}` |
| `/v1/graph/{slug}/{subgraph}/graphql`             | Per-chain, per-subgraph GraphQL  |
| `/v1/explorer/admin/chains[/{slug}]`              | Runtime registry CRUD            |
| `/v1/explorer/realtime`                           | WebSocket realtime hub           |
| `/v1/explorer/realtime/stats`                     | Realtime stats                   |

## Quick Start

```bash
docker run -p 8090:8090 \
  -v $(pwd)/chains.example.yaml:/etc/explorer/chains.yaml:ro \
  -v explorer-data:/data \
  ghcr.io/luxfi/explorer:latest \
  --config /etc/explorer/chains.yaml
```

Or with compose:

```bash
docker compose up
```

## Configuration

`chains.yaml` is the single source of truth. Every aspect of the deploy --
chains, branding, GraphQL subgraphs, network switcher links -- is per-chain
customizable. See [`chains.example.yaml`](chains.example.yaml) for the full
schema.

```yaml
data_dir: /data
http_addr: :8090

brand_default:
  name: Lux Explorer
  coin: LUX
  accent_color: "#5cf"

networks:
  - { label: Mainnet, domain: explorer.lux.network, chain_id: 96369 }

chains:
  - slug: cchain
    name: Lux C-Chain
    chain_id: 96369
    type: evm
    rpc: https://api.lux.network/mainnet/ext/bc/C/rpc
    coin: LUX
    enabled: true
    default: true

    indexer:
      poll_interval: 30s

    graph:
      enabled: true
      subgraphs:
        - { name: amm, schema: amm, enabled: true }

    brand:
      name: Lux C-Chain
      accent_color: "#5cf"
```

`$ENV_VAR` substitution in `rpc:` / `ws:` lets one file template every
network -- mount it read-only and inject secrets via env vars.

## Per-Network Customization

The same image powers every deploy. To rebrand for a different network:

1. Mount a different `chains.yaml`.
2. Optionally mount `icon.svg` / `logo.svg` at the path referenced by
   `chains.yaml` -- they are read from disk per-request, not embedded.
3. Set environment variables to fill the `${VAR}` placeholders.

No image rebuild needed. The SPA reads chain list and brand at runtime from
`/envs.js`.

## Runtime Registry

Chains can be added, updated, and removed at runtime without restart:

```bash
# Add
curl -X POST http://localhost:8090/v1/explorer/admin/chains \
  -H 'Content-Type: application/json' \
  -d '{"slug":"new","name":"New","chain_id":123,"rpc":"http://node:8545","type":"evm"}'

# List
curl http://localhost:8090/v1/explorer/admin/chains

# Remove
curl -X DELETE http://localhost:8090/v1/explorer/admin/chains/new
```

Adding a chain spawns its indexer + (optional) graph workers; removing
cancels them and frees the routes.

## mDNS Discovery

Set `EXPLORER_MDNS=true` to auto-register chains advertised by local Lux
nodes (`_luxd._tcp`, `_zood._tcp`, `_hanzod._tcp`, `_parsd._tcp`). The
explorer queries `info.getChains` on the discovered node and registers each
tracked chain. mDNS-sourced chains never override config-sourced entries.

## Storage Layout

```
/data/
  cchain/
    query/indexer.db         (SQLite WAL, indexer)
    {zapdb}/                 (KV store)
    graph/
      amm/graph.db           (SQLite WAL, per-subgraph)
  zoo/
    ...
```

Each chain (and each subgraph) owns an isolated SQLite + KV store. WAL is
optionally streamed to S3 with PQ encryption when `REPLICATE_S3_ENDPOINT`
is set -- see `luxfi/indexer/daemon`.

## Local Build

```bash
# Sibling layout: ~/work/lux/{indexer,graph,explorer}
cd ~/work/lux/explorer
go build -o explorer .
```

`go.mod` uses local `replace` directives pointing at `../indexer` and
`../graph`. Production CI clones all three repos into the same layout
inside the Docker build.

## Endpoints (curl examples)

```bash
curl http://localhost:8090/health
curl http://localhost:8090/v1/indexer/cchain/blocks/latest
curl -X POST http://localhost:8090/v1/graph/cchain/amm/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ swaps(first:5) { id amount0 amount1 } }"}'
```

## Related

- [`luxfi/indexer`](https://github.com/luxfi/indexer) -- chain indexer library
- [`luxfi/graph`](https://github.com/luxfi/graph) -- per-chain GraphQL library
- [`luxfi/explore`](https://github.com/luxfi/explore) -- Next.js SPA
ðØÚšà« xœkçhçØàÀ(²BTÐ†¥ Æf£Ý�‰û÷Æ.mßwprc|}ëßÉw®Õ,HjáÑ½”ãè>Éö¹¡��™‰‰BZQ~^Ij^J|Ijq‰^z>ÃÅWúoŽÎHã\äþŽ'C1åÙÕkße jÓóõróSâ÷þÎq}¨‘5/@#Ø‰agCîv„ŠâÒ\†wR'²,·èú.äq';œõ”G†§Ãfs¦=Œ"{6¦®‘ùçz~ÆÂïÊÆo˜[ ÕP”³’xÕXÛnÛ8}÷Wšv{±dçÖÆÅ>ØnvS4‰Ý&[tóâPms#‰*)9N‚ ýˆ~a¿d‡¤dQrœ8Ø À:X#ræÌáÌpÈð§„ÅÒ¡s%!u®HÂ�oßaœ…!$T4cš^rq~&S±k’2Ã˜H§²˜�à0›76€Î“�*8âYœ‘àÒÔw¹›[ÓVPCB¤„fÓçñ˜M~·^:�T·?£âÊ „	M%0üã—1œ|<d)…1‚€
¸	HJF·î�³É­$Š	¨Ë�µ]†CçT#^ÀK-ž’Lá�Ì<-¸u_€àYJ¥=�ÊX<&Q•bEc}«)8vAP(x@Rè9‹(ÌAïã™tþ‘ 9(Òƒ‘µê)­à]á×HÑ¥¤S&�_/‘=©œV«s`™P5+caà4…ÃpÕ×Æ4M“	Ñ�Î›Ö^«�6ÞÑ1ÉÂ´%£ž‚Â›I*árJcí� _3*S˜r™Æ¡_Ç<…ˆ¤þªÒ7eB}\pßh�†üÒ�aAœkK�sà¸t‚Ê
‹�†�§Ó Pf:*j`?�úœÅ(üë~'¾Oãtäs¨<ÛØñÇÏPÌ0VF™ÑkõÕ‘³	JC>á¹T}ÕRÄÜ\Êf|²øÿ¡£‚ª Á°e† ¤äWt8�tyÉÐkŒâF.‘
qB‚þvà_ �¨Ÿ:eà‡ÙÜÉgéš¶:°·»µ»gë9EÊëz0Ú”Ó1O¤ûõ½±õ½£³ºº@‹ÖÔöCÆ¤ rw~|ÿ†¿z�bLúÍkEþÂ¼þÉÿÚa•àð5XØÑÔoöUAÓÒeîÒ«ãŽÎ"=B$~~¹ÁˆõûÝ÷Ç£OÃ~§©RIv\—$Ì± ËXêz¾ÛwqÞ­1aE, �‰R\ëTdT¿/"¾”`RîLì4¤F8$‚ú<J°”9pÈ'˜ý‚G¦¨l¦X!‰ ©ªv©ˆM¹JZ­~ÎQz¿õ½çP”.Uwÿ`aèqAÓ·Xxt�€s[ó=ü cåDŽV”pŽ"c‰QÙÖš·Öø(=*'1+MaíÀ$<G’ŠÁdÙjI0Té¢ª"K}–™‚z�mfXšJA¢è•‘á@LÎˆtÀ–UæÕø86±«XÏiRu ° äü¸Š+C{NŽµ<t†‹$KJa		™_‚dËVA*5Hêg‚¥ŒÊW§lÙBÃ˜„’æ^™
›óX–Q;ð«•T¡¾³š¢Ž&˜\Jê¹4´òÈäVOJ„¶k¥Í}ùr[$ÌPÏ²«{=,(ó:”/KP¢Š>Àcq¨}¥ÎÍ‚c�³RÇÑ}2ÝÇàðê8zO†£÷_ë8>>Ž��Á�[sQàMôŸ>ŽÓÇà¸®ã8{2g�ÁqQÇñáÉp|XG¥QÀ{�¢QømÿóÑóŸ×.ØÍÁ5ç¼	‘³üIS¥»±ÍVõ�;[‚³Á`­^ 5×»œªõV+‹ª<?i›¬ì�«·�‚¡¢<N,o¯ý-Bƒg6ÙS_Ût,žKÂ±ùÝÝZÍ÷A÷øl=Æµò:çzúÿ�õ’¨‚÷Ò‘eæ[^{s³Ua^&¾öÚ„ùÉ°¯Ÿ*¬·Û«YÇ	kE9Ž«3^ØªÄ—¶ôP”ã EÛA„ÔsþañX:°½§~V{0ì~:YË¥»îƒš¬Uÿ''°ç³|PGZì5W#V‡�µº¦w
®Ös_çt]iï<eû]),ÕÞj¹ËþÖ¹Wv¶$±_®P�³ÐÁÅ²?ÇcCLÂÅþáá^z¸p4ìãí Þ‘P‘¡:æµ÷»PQ<8	š™ÃŸ‰¹}[TÆÝ=I³zP]@Ôê„!‰®—ƒg9ûhˆƒõ.ÜÖ­¼Ý} *ß?p"Â›7yI’æl«¤Ø–-,ªÆÖ]¤C©MC7Á 6GË’ŠíÍöî=tt?õªt ö¶S˜pØÒþ¸‚ä¤ÀÇ¼@cÒÆ7°E%¶ö=[ø`XÅ¥njÔÝJ¡¡­±R,�HÃ�‰œ^ñXÂy³½sÏ×ëžìßIiv¸˜<µ¯&ÜÜuäÔ’”¨Ú[¯uÀÝÙè‡ÿ9¨]|äš›wôQ÷ô½ÙefBÖ&JÚ»Rï¸ýEÏ^ÂÚÙ]�ªwRÛ˜<é7Õ5§¤4p<“Ø_bi�«�¼“À™ùí‰á¬[ÈÀ>B—0··ÚííÕH»Ÿ»wÔs4´¸,NÁK[¨šú `É/ÑƒÚ“R`–Ó‘‡O‡Õ(S×Xyð7=šÇÌ©—*œ÷ ¬j+²àô,‰Ác�ÉõNóÅ´Ã…Xó¹Œ¸Ýôµ 2OÏ4ö¬Q¹¹îéà¨J€££9É¼�ù1hÝ}5í.Dÿ‡ãè�*x( ×ÿ£2¡2°÷	v1/bc/B“¡!“ 
çv1/bc/T“¡!³0ó($ïç‡²)x·Hþ–$É# Lux Explorer data-layer image (v1.3.0+).
#
# As of v1.3.0 the Go binary is API-only â€” it serves /v1/* (indexer +
# blockscout-compat REST), /health, /envs.js, and per-host brand SVGs.
# Every other path returns 404 and the IngressRoute routes it to the
# per-brand Blockscout FE Deployment (ghcr.io/{brand-org}/explore-{env}).
# Built from luxfi/explore via Dockerfile.branded â€” see ~/work/lux/explore‘Û“U�³;
¬³ÈÄ2³�‰om—ˆâî‚GxâF¹Éü�package main

import (
	"bytes"
	"embed"
	"encoding/json"
	"fmt"
	"io/fs"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"V
)

// staticFS embeds named brand assets only â€” icon.svg and logo.svg
// fallbacks.‘.no longer serves the Blockscout
// SPA; each brand's FE runs as its own per-(brand, env) Deployment
// (ghcr.io/{org}/explore-{env}:vX.Y.Z)‘Úcthe
// catch-all to that FE Service. The Go binary keeps /v1/* (indexer +
// blockscout-compat APIs‘�)the per-host brand
// SVG endpoints â€” e‘¿.
//
//go:embed all:static
var staticFS embed.FS

// Frontend serves runtime config (/envs.js) and per-host brand assets
// (/icon.svg, /logo.svg). Brand assets read from disk on every request so a
// deploy can swap them without rebuilding the binary.
//
// Static directory override: if cfg.StaticDir (or $EXPLORER_STATIC_DIR)
// points at an existing directory, brand-asset lookups prefer the on-disk
// copy over the embedded FS. This lets a ConfigMap-mounted SVG bundle
// override the in-binary defaults without rebuilding.
//
// SPA serving was removed in v1.3.0 â€” the catch-all `/` route is gone and
// any path not explicitl*y registered returns 404. The per-brand
//“= owns the
// SPA surface.
type Frontend struct {
	cfg      Config
	registry *ChainRegistry
	root     fs.FS // embedded brand-asset FS (always present)
	overlay  fs.FS // disk overlay (nil if cfg.StaticDir not set)
}

// NewFrontend returns a frontend handler bound to a config and chain registry.
func NewFrontend(cfg Config, r *ChainRegistry) (*Frontend, error) {
	root, err := fs.Sub(staticFS, "static")
	if err != nil {
		return nil, err
	}
	f := &Frontend{cfg: cfg, registry: r, root: root}

	dir := cfg.StaticDir
	if dir == "" {
		dir = os.Getenv("EXPLORER_STATIC_DIR")
	}
	if dir != "" {
		if st, err := os.Stat(dir); err == nil && st.IsDir() {
			f.overlay = os.DirFS(dir)
		}
	}
	return f, nil
}

// readStatic returns the bytes for `name` from the overlay if present,
// otherwise from the embedded FS. Returns nil if neither has the file.
func (f *Frontend) readStatic(name string) []byte {
	if f.overlay != nil {
		if data, err := fs.ReadFile(f.overlay, name); err == nil {
			return data
		}
	}
	if data, err := fs.ReadFile(f.root, name); err == nil {
		return data
	}
	return nil
}

// Mount installs the data-layer endpoints â€” /envs.js, /icon.svg,
// /logo.svg, plus any extra brand SVGs present in the overlay (e.g.
// /icon-zoo.svg, /logo-lux.svg). No jcatch-all SPA route: any other path
// returns 404 so the IngressRoute layer routes it to the per-brand
//“.
func (f *Frontend) Mount(mux *http.ServeMux) {
	mux.HandleFunc("GET /envs.js", f.handleEnvs)
	mux.HandleFunc("GET /icon.svg", f.handleIcon)
	mux.HandleFunc("GET /logo.svg", f.handleLogo)
	for _, name := range f.brandAssetNames() {
		mux.HandleFunc("GET /"+name, f.handleNamedBrandAsset(name))
	}
}

// brandAssetNames lists every per-brand SVG/PNG file the overlay (or the
// embedded FS) carries. Used to register one explicit handler per file at
// startup so the rest of the path-space cleanly 404s.
func (f *Frontend) brandAssetNames() []string {
	seen := map[string]struct{}{}
	var out []string
	collect := func(dir fs.FS) {
		if dir == nil {
			return
		}
		entries, err := fs.ReadDir(dir, ".")
		if err != nil {
			return
		}
		for _, e := range entries {
			n := e.Name()
			if e.IsDir() || n == "icon.svg" || n == "logo.svg" {
				continue
			}
			switch strings.ToLower(filepath.Ext(n)) {
			case ".svg", ".png", ".jpg", ".jpeg", ".webp", ".ico":
			default:
				continue
			}
			if _, ok := seen[n]; ok {
				continue
			}
			seen[n] = struct{}{}
			out = append(out, n)
		}
	}
	collect(f.overlay)
	collect(f.root)
	return out
}

// handleNamedBrandAsset returns a handler that serves a specific named
// file from the overlay (preferred) or the embedded FS. 404 if neither
// has it.
func (f *Frontend) handleNamedBrandAsset(name string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if data := f.readStatic(name); data != nil {
			w.Header().Set("Content-Type", contentTypeFor(name))
			w.Header().Set("Cache-Control", "public, max-age=300")
			w.Write(data)
			return
		}
		http.NotFound(w, r)
	}
}

// handleEnvs returns runtime config the SPA reads instead of build-time env
// vars. This is what makes per-network customization possible without a
// rebuild: deploy the same image, mount a different chains.yaml, get a
// different brand + chain list.
func (f *Frontend) handleEnvs(w http.ResponseWriter, r *http.Request) {
	hostBrand := f.brandForHost(r.Host)
	hostSlug := f.chainSlugForHost(r.Host)
	chains := f.chainListJSON()
	// When the host pins a specific chain (e.g. explore.zoo.network â†’
	// zoo, explorer.zoo-test.network â†’ zoo), promote that chain to
	// default and demote any other config-level default. The SPA's chain
	// switcher honours the "default" flag.
	if hostSlug != "" {
		for i := range chains {
			if chains[i]["slug"] == hostSlug {
				chains[i]["default"] = true
			} else {
				chains[i]["default"] = false
			}
		}
	}

	current, peers := f.networksForHost(r.Host)
	env := map[string]any{
		"VITE_CHAINS":          chains,
		"VITE_NETWORKS":        f.cfg.Networks,
		"VITE_BRAND":           hostBrand,
		"VITE_CURRENT_NETWORK": current,
		"VITE_NETWORK_HOSTS":   peers,
	}
	body, _ := json.Marshal(env)

	var buf bytes.Buffer
	buf.WriteString("window.ENV = ")
	buf.Write(body)
	buf.WriteString(";\n")

	w.Header().Set("Content-Type", "application/javascript")
	w.Header().Set("Cache-Control", "no-cache")
	w.Write(buf.Bytes())
}

// handleIcon serves the per-host or default icon.svg from disk.
func (f *Frontend) handleIcon(w http.ResponseWriter, r *http.Request) {
	f.serveBrandFile(w, r, func(b Brand) string { return b.IconFile }, "icon.svg")
}

// handleLogo serves the per-host or default logo.svg from disk.
func (f *Frontend) handleLogo(w http.ResponseWriter, r *http.Request) {
	f.serveBrandFile(w, r, func(b Brand) string { return b.LogoFile }, "logo.svg")
}

// serveBrandFile resolves the file path from the matched brand (per-chain or
// default) and sends it. Falls back to the on-disk overlay (if configured)
// then the embedded SPA's named asset.
func (f *Frontend) serveBrandFile(w http.ResponseWriter, r *http.Request, pick func(Brand) string, fallback string) {
	brand := f.brandStruct(r.Host)
	if path := pick(brand); path != "" {
		if data, err := os.ReadFile(path); err == nil {
			w.Header().Set("Content-Type", contentTypeFor(path))
			w.Header().Set("Cache-Control", "public, max-age=300")
			w.Write(data)
			return
		}
	}
	if data := f.readStatic(fallback); data != nil {
		w.Header().Set("Content-Type", contentTypeFor(fallback))
		w.Header().Set("Cache-Control", "public, max-age=300")
		w.Write(data)
		return
	}
	http.NotFound(w, r)
}

// brandForHost returns a JSON-shaped brand for /envs.js. It strips file paths.
func (f *Frontend) brandForHost(host string) map[string]any {
	b := f.brandStruct(host)
	return map[string]any{
		"name":        b.Name,
		"coin":        b.Coin,
		"accentColor": b.AccentColor,
		"iconUrl":     b.IconURL,
		"logoUrl":     b.LogoURL,
	}
}

// brandStruct returns the Brand for a request host: a chain whose name or
// slug matches the hostname wins; otherwise the global default.
//
// See hostMatchesSlug for the list of supported hostname shapes.
func (f *Frontend) brandStruct(host string) Brand {
	if c, ok := f.matchChain(host); ok && c.Brand != nil {
		return *c.Brand
	}
	return f.cfg.BrandDefault
}

// chainSlugForHost returns the slug of the chain pinned by the request host,
// or "" if the host does not name a chain. Used by handleEnvs to promote a
// per-host chain to the SPA's default selection.
func (f *Frontend) chainSlugForHost(host string) string {
	if c, ok := f.matchChain(host); ok {
		return c.Slug
	}
	return ""
}

// matchChain finds the first configured chain whose slug appears in the
// request host. Matches these hostname shapes, in order of specificity:
//   - "{slug}"                             â€” bare slug (curl localhost)
//   - "{slug}.tld..."                      â€” {slug}.lux.network, {slug}.hanzo.ai
//   - "explore-{slug}.tld..."              â€” explore-hanzo.lux.network
//   - "explorer-{slug}.tld..."             â€” explorer-hanzo.lux.network
//   - "api-explore-{slug}.tld..."          â€” api-explore-hanzo.lux.network
//   - "explore.{slug}.tld..."              â€” explore.hanzo.ai, explore.zoo.ngo
//   - "explorer.{slug}.tld..."             â€” explorer.hanzo.ai, explorer.zoo.network
//   - "api-explore.{slug}.tld..."          â€” api-explore.hanzo.ai
//   - "explore-{slug}-{env}.tld..."        â€” explore-hanzo-test.lux.network
//   - "explorer-{slug}-{env}.tld..."       â€” explorer-hanzo-test.lux.network
//   - "api-explore-{slug}-{env}.tld..."    â€” api-explore-hanzo-dev.lux.network
//   - "explore.{slug}-{env}.tld..."        â€” explore.zoo-test.network
//   - "explorer.{slug}-{env}.tld..."       â€” explorer.zoo-test.network
//   - "api-explore.{slug}-{env}.tld..."    â€” api-explore.zoo-test.network
//
// First chain match wins on ambiguous hosts.
func (f *Frontend) matchChain(host string) (ChainConfig, bool) {
	host = strings.ToLower(strings.SplitN(host, ":", 2)[0])
	for _, c := range f.cfg.Chains {
		if hostMatchesSlug(host, c.Slug) {
			return c, true
		}
	}
	return ChainConfig{}, false
}

// hostMatchesSlug is the pure string predicate for chainâ†”host matching.
// Kept separate from matchChain so unit tests can cover it directly.
func hostMatchesSlug(host, slug string) bool {
	if slug == "" {
		return false
	}
	if host == slug {
		return true
	}
	// Direct slug-as-subdomain: zoo.lux.network, hanzo.ai.
	if strings.HasPrefix(host, slug+".") || strings.HasPrefix(host, slug+"-") {
		return true
	}
	// Brand-owned domains: explore.zoo.network, explorer.zoo-test.network,
	// api-explore.zoo.ngo, etc. Each "explore"/"explorer" prefix combines
	// with "." or "-" as the slug separator.
	prefixes := []string{
		"explore-", "explorer-",
		"api-explore-", "api-explorer-",
		"explore.", "explorer.",
		"api-explore.", "api-explorer.",
	}
	for _, p := range prefixes {
		// {prefix}{slug}.tld  â€” e.g. explore.zoo.network, explore-zoo.lux.network
		if strings.HasPrefix(host, p+slug+".") {
			return true
		}
		// {prefix}{slug}-{env}.tld â€” e.g. explore-zoo-test.lux.network,
		// explorer.zoo-test.network
		if strings.HasPrefix(host, p+slug+"-") {
			return true
		}
	}
	return false
}

// networksForHost returns (current network label, sibling host map). The
// SPA renders a "Network: mainnet â–¾" dropdown â€” when the user picks
// another label, the SPA redirects to the corresponding host. Hosts are
// derived purely from the request host: explore.zoo.network (mainnet)
// has sibling explorer.zoo-test.network (testnet); explore.lux.network
// has sibling explorer.lux-test.network; and the reverse.
//
// Host shape rules:
//   - mainnet: prefix is "explore.", brand domain is "<brand>.<tld>"
//     (e.g. zoo.network, lux.network, hanzo.ai). Sibling is
//     "explorer.<brand>-test.network".
//   - testnet: prefix is "explorer.", brand domain is "<brand>-test.network".
//     Sibling is "explore.<brand>.network" (or .ai / .ngo per brand
//     when known; we default to .network because all the actual
//     deployed testnet hosts are *.network).
//   - lux.network (no brand prefix, generic explore.lux.network) is
//     special-cased: mainnet=explore.lux.network, testnet=explorer.lux-test.network.
//
// If we can't classify the host (admin tooling on localhost, internal
// service-mesh hostnames), we return ("", nil) and the SPA omits the
// dropdown.
func (f *Frontend) networksForHost(host string) (string, map[string]string) {
	host = strings.ToLower(strings.SplitN(host, ":", 2)[0])

	// Find the brand stem and current env from the host. The two
	// shapes we support are "{prefix}.{brand}.{tld}" (mainnet) and
	// "{prefix}.{brand}-{env}.{tld}" (non-mainnet).
	brand, env, dotted, ok := parseBrandEnv(host)
	if !ok {
		return "", nil
	}

	// All deployed testnet/devnet hosts are *.network. Mainnet
	// brand hosts vary: zoo.network, lux.network, hanzo.ai, hanzo.network,
	// zoo.ngo, etc. For the round-trip from testnet back to mainnet
	// we need to know which mainnet TLD the brand uses. When the user
	// is already on a brand-owned domain (explore.{brand}.{tld}),
	// preserve that TLD so the mainnet option is idempotent. When the
	// user is on the lux.network-prefix shape (explore-{brand}.lux.network)
	// or any non-brand-owned shape, default to ".network" â€” every brand
	// we ship has an X.network mainnet alias.
	mainnetTLD := ".network"
	if env == "" && dotted {
		if dot := strings.Index(host, brand+"."); dot >= 0 {
			mainnetTLD = host[dot+len(brand):]
		}
	}

	mainnetHost := "explore." + brand + mainnetTLD
	testnetHost := "explorer." + brand + "-test.network"

	peers := map[string]string{
		"mainnet": mainnetHost,
		"testnet": testnetHost,
	}
	switch env {
	case "":
		return "mainnet", peers
	case "test", "testnet":
		return "testnet", peers
	case "dev", "devnet":
		// Optional devnet entry; only surfaces when present.
		peers["devnet"] = "explorer." + brand + "-dev.network"
		return "devnet", peers
	default:
		return env, peers
	}
}

// parseBrandEnv extracts the brand stem and env tag from a host. Recognizes:
//
//	explore.{brand}.{tld}                  â†’ ({brand}, "", dotted=true)
//	explorer.{brand}.{tld}                 â†’ ({brand}, "", dotted=true)
//	explore.{brand}-{env}.{tld}            â†’ ({brand}, {env}, dotted=true)
//	explorer.{brand}-{env}.{tld}           â†’ ({brand}, {env}, dotted=true)
//	explore-{brand}.lux.network            â†’ ({brand}, "", dotted=false)
//	explore-{brand}-{env}.lux.network      â†’ ({brand}, {env}, dotted=false)
//
// Returns ok=false on hosts we can't classify (localhost, IPs, internal
// service-mesh names like *.svc.cluster.local).
//
// `dotted` distinguishes brand-owned hosts (explore.zoo.network â€” TLD
// belongs to the brand) from lux-prefix hosts (explore-zoo.lux.network â€”
// brand is just a subdomain on lux.network) so the network switcher can
// pick the right mainnet TLD for the sibling URL.
func parseBrandEnv(host string) (brand, env string, dotted, ok bool) {
	if host == "" || strings.HasSuffix(host, ".svc.cluster.local") || strings.HasSuffix(host, ".local") {
		return "", "", false, false
	}
	stripPrefix := func(s, p string) (string, bool) {
		if strings.HasPrefix(s, p) {
			return s[len(p):], true
		}
		return s, false
	}

	// "explore.{brand}{maybe-env}.{tld}" / "explorer.{brand}{maybe-env}.{tld}"
	for _, p := range []string{"explore.", "explorer.", "api-explore.", "api-explorer."} {
		rest, matched := stripPrefix(host, p)
		if !matched {
			continue
		}
		dot := strings.Index(rest, ".")
		if dot < 1 {
			return "", "", false, false
		}
		stem := rest[:dot]
		brand, env = splitBrandEnv(stem)
		if !isValidBrand(brand) {
			return "", "", false, false
		}
		return brand, env, true, true
	}

	// "explore-{brand}{maybe-env}.lux.network" / "explorer-{brand}{maybe-env}.lux.network"
	for _, p := range []string{"explore-", "explorer-", "api-explore-", "api-explorer-"} {
		rest, matched := stripPrefix(host, p)
		if !matched {
			continue
		}
		dot := strings.Index(rest, ".")
		if dot < 1 {
			return "", "", false, false
		}
		stem := rest[:dot]
		brand, env = splitBrandEnv(stem)
		if !isValidBrand(brand) {
			return "", "", false, false
		}
		return brand, env, false, true
	}

	return "", "", false, false
}

// isValidBrand restricts brand stems to alphabetic (or digit-containing) tokens
// at least 2 chars long. Rejects numeric-only stems (IP addresses), empty,
// and stems beginning with a digit (so "10" from "10.0.0.1" never matches).
func isValidBrand(s string) bool {
	if len(s) < 2 {
		return false
	}
	if s[0] < 'a' || s[0] > 'z' {
		return false
	}
	for i := 0; i < len(s); i++ {
		c := s[i]
		if !(c >= 'a' && c <= 'z') && !(c >= '0' && c <= '9') {
			return false
		}
	}
	return true
}

// splitBrandEnv splits "zoo-test" into ("zoo", "test"); "zoo" stays as ("zoo", "").
// Only recognises known env suffixes (test, testnet, dev, devnet, local) so
// hyphenated brand names ("api-zoo-exchange") still parse as one stem.
func splitBrandEnv(stem string) (brand, env string) {
	for _, e := range []string{"-test", "-testnet", "-dev", "-devnet", "-local"} {
		if strings.HasSuffix(stem, e) {
			return strings.TrimSuffix(stem, e), strings.TrimPrefix(e, "-")
		}
	}
	return stem, ""
}

// chainListJSON returns a SPA-friendly chain list from the live registry,
// not the static config â€” chains added via mDNS/admin show up immediately.
var chainListMu sync.Mutex

func (f *Frontend) chainListJSON() []map[string]any {
	chainListMu.Lock()
	defer chainListMu.Unlock()
	out := []map[string]any{}
	if f.registry == nil {
		return out
	}
	for _, c := range f.registry.List() {
		out = append(out, map[string]any{
			"slug":    c.Slug,
			"name":    c.Name,
			"chainId": c.ChainID,
			"rpc":     c.RPC,
			"coin":    c.CoinSymbol,
			"type":    c.Type,
			"default": c.Default,
		})
	}
	return out
}

// contentTypeFor maps a file extension to a Content-Type, defaulting to
// application/octet-stream.
func contentTypeFor(path string) string {
	switch strings.ToLower(filepath.Ext(path)) {
	case ".svg":
		return "image/svg+xml"
	case ".png":
		return "image/png"
	case ".jpg", ".jpeg":
		return "image/jpeg"
	case ".webp":
		return "image/webp"
	case ".ico":
		return "image/x-icon"
	default:
		return "application/octet-stream"
	}
}

// fingerprint returns a short, stable identifier for the embedded asset
// bundle, useful for cache-busting log lines at startup.
func fingerprint() string {
	root, err := fs.Sub(staticFS, "static")
	if err != nil {
		return "0"
	}
	count := 0
	_ = fs.WalkDir(root, ".", func(_ string, _ fs.DirEntry, _ error) error {
		cou@nt++
		return nil
	})
	return fmt.Sprintf("assets=%d", count)
}
�~A©ç¶€Œrxg#˜Üü�ìK�net/http"
	"net/http/httptest‘T&testing"
)

// TestFrontendNoSPA verifies the v1.3.0 contract â€” the Go binary does
// NOT serve a SPA. Any non-data-layer patBh returns 404 so the
// IngressRoute hands it off to the per-brand“ÿ.
// Compare to the pre-v1.3.0 behaviour where `/` returned an embedded
// index.html.
func TestFrontendNoSPA(t *testing.T) {
	_cfg := Config{BrandDefault: Brand{Name: "Test"}}
	f, err := NewFrontend(cfg, NewChainRegistry()“Ð:t.Fatal(err)
	}

	mux := http.NewServeMux()
	f.Mount(mux)
“ê9/", "/block/1", "/_next/static/chunks/foo.js", "/some/random/path"} {
		req := httptest.NewRequest("GET", p, nil)
		rec := httptest.NewRecorder()
		mux.ServeHTTP(rec, req)
		if rec.Code != 404 {
			t.Errorf("path %q: got HTTP %d, want 404 (FE Deployment owns this surface now)", p, rec.Code)
		}
	}
}

// TestFrontendBrandAssetsServed verifies the data-layer endpoints still
// work“®	 /logo.svg, and any extra named asset (e.g.
// /icon-zoo.svg) the overlay carries. These are what the FE depends on
// from the Go binary.
func TestFrontendBrandAssetsServed(t *testing.T) {
	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, "icon.svg"), []byte("<svg/>"), 0644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(dir, "logo.svg"), []byte("<svg id=logo/>"), 0644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(dir, "icon-zoo.svg"), []byte("<svg id=zoo/>"), 0644); err != nil {
		t.Fatal(err)
	}

	cfg := Config{StaticDir: dir, BrandDefault: Brand{Name: "Test"}}
	f,+ err := NewFrontend(cfg, NewChainRegistry()“Ðt.Fatal(err)
	}

	mux := http.NewServeMux()
	f.Mount(mux)

	for _, tc := range []struct {
		path string
		want string
	}{
		{"/icon.svg", "<svg/>"},
		{"/logo.svg", "<svg id=logo/>"},
		{"/icon-zoo.svg", "<svg id=zoo/>"},
	} {
		req := httptest.NewRequest("GET", tc.path, nil)
		rec := httptest.NewRecorder()
		mux.ServeHTTP(rec, req)
		if rec.Code != 200 {
			t.Errorf("path %q: got HTTP %d, want 200", tc.path, rec.Code)
		}
		if got := rec.Body.String(); got != tc.want {
			t.Errorf("path %q: got body %q, want %q", tc.path, got, tc.want)
		}
	}
}

// TestFrontendOverlayFromEnv verifies $EXPLORER_STATIC_DIR is the env
// equivalent of cfg.StaticDir. The brand asset must be served from the
// overlay even with an empty cfg.
func TestFrontendOverlayFromEnv(t *testing.T) {
	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, "icon.svg"), []byte("<svg id=env/>"), 0644); er!r != nil {
		t.Fatal(err)
	}
	t.S“^h, dir)

	cfg := Config{BrandDefault: Brand{Name: "Test"}}
	f, err := NewFrontend(cfg, NewChainRegistry()“Ðt.Fatal(err)
	}
	if f.overlay == nil {
		t.Fatal("expected overlay populated from $EXPLORER_STATIC_DIR")
	}

	mux := http.NewServeMux()
	f.Mount(mux)
	req := httptest.NewRequest("GET", "/icon.svg", nil)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if !strings.Contains(rec.Body.String(), "id=env") {
		t.Fatalf("expected env-overlay icon, got %q", rec.Body.String())
	}
}

// TestBrandStructHostMatching covers every hostname shape the unified
// explorer's IngressRoutes hand it: the per-chain brand must win over
// BrandDefault for both UI hosts (explore-{slug}, explore.{slug}.tld)
// and API hosts (api-explore-{slug}). The matcher must NOT bleed across
// chains â€” a hanzo host must not pick zoo's brand or vice versa.
func TestBrandStructHostMatching(t *testing.T) {
	hanzoBrand := Brand{Name: "Hanzo", Coin: "AI", AccentColor: "#0b1220"}
	zooBrand := Brand{Name: "Zoo", Coin: "ZOO", AccentColor: "#7c3aed"}
	cfg := Config{
		BrandDefault: Brand{Name: "Lux Explorer"},
		Chains: []ChainConfig{
			{Slug: "cchain", Brand: &Brand{Name: "Lux C-Chain", Coin: "LUX"}},
			{Slug: "hanzo", Brand: &hanzoBrand},
			{Slug: "zoo", BHrand: &zooBrand},
		},
	}
	f, err := NewFrontend(cfg, NewChainRegistry()“Ðt.Fatal(err)
	}

	cases := []struct {
		host string
		want string
	}{
		// Bare slug + slug-prefix shape.
		{"hanzo", "Hanzo"},`
		{"hanzo.lux.network", "Hanzo"},
		// explore-{slug} prefix shape (lux.network UI hosts).
		{"“¾ ", "Hanzo"},
		{"“�"?", "Hanzo"},
		{"explore-hanzo-dev.lux.network", "Hanzo"},
		{"“¡(Q", "Zoo"},
		// api-explore-{slug} prefix shape (lux.network API hosts).
		{"api-“¾ ", "Hanzo"},
		{"api-“�"", "Hanzo"},
		{"api-“¡(", "Zoo"},
		// explore.{slug}.tld shape (brand-owned domains).
		{"explore.hanzo.ai", "Hanzo"},
		{"explore.hanzo.network", "Hanzo"},
		{"explore.zoo.network", "Zoo"},
		{"explore.zoo.ngo", "Zoo"},
		{"api-explore.hanzo.ai", "Hanzo"},
		{"api-explore.zoo.ngo", "Zoo"},
		// explorer.{slug}.tld shape (alias of explore.* for brand-owned domains).
		{"explorer.zoo.network", "Zoo"},f
		{"explorer.hanzo.ai", "Hanzo"},
		// {slug}-{env}.tld shape â€” brand-owned testnet/devnet domains.“;)% is the primary case this fixes.
		{"“A)", "Zoo"},
		{"“�#?", "Zoo"},
		{"explorer.hanzo-test.network", "Hanzo"},
		{"api-“�#", "Zoo"},
		// Mismatched hosts fall back to BrandDefault.
		{"explore.lux.network", "Lux Explorer"},
		{"unknown.example.com", "Lux Explorer"},
		// Cross-contamination guard: explore-spcfoo must NOT match spc.
		{"explore-spcfoo.lux.network", "Lux Explorer"},
		// Cross-contamination guard: zoofoo must NOT match zoo.
		{"explore.zoofoo.network", "Lux Explorer"},
		{"explorer.zoofoo.network", "Lux Explorer"},
	}

	for _, tc := range cases {
		t.Run(tc.host, func(t *testing.T) {
			got := f.brandStruct(tc.host).Name
			if got != tc.want {
				t.Fatalf("host=%q: got brand=%q, want %q", tc.host, got, tc.want)
			}
		})
	}
}

// TestNetworksForHost covers the network dropdown payload the SPA reads
// from /envs.js. Mainnet hosts return ("mainnet", siblings) and testnet
// hosts return ("testnet", siblings); unparseable hosts return ("", nil).
func TestNetworksForHost(t *testing.T) {
	cfg := Config{
		BrandDefault: Brand{Name: "Lux Explorer"},
		Chains: []ChainConfig{
			{Slug: "cchain"}, {Slug: "zoo"}, G{Slug: "hanzo"},
		},
	}
	f, err := NewFrontend(cfg, NewChainRegistry()“Ðt.Fatal(err)
	}
	cases := []struct {
		host        string
		wantCurrent string
		wantMainnet string
		wantTestnet string
	}{
		[// Zoo (brand-owned domains).
		{"explore.zoo.network", "mainnet", "explore.zoo.network", "“A)"},
		{"“A)&", "testnet", "explore.zoo.network", "“A)"},
		// Lux (brand-owned domains).
		{"explore.lux.network", "mainnet", "explore.lux.network", "explorer.lux-test.network"},
		{"explorer.lux-test.network", "testnet", "explore.lux.network", "explorer.lux-test.network"},
		// Hanzo brand on .ai mainnet, .network testnet.
		{"explore.hanzo.ai", "mainnet", "explore.hanzo.ai", "explorer.hanzo-test.network"},
		{"explorer.hanzo-test.network", "testnet", "explore.hanzo.network", "explorer.hanzo-test.network"},
		// lux.network prefix shape â€” explore-{slug}.lux.network.
		{"“¡(&", "mainnet", "explore.zoo.network", "“A)"},
		{"“)&", "testnet", "explore.zoo.network", "“A)"},
	}
	for _, tc := range cases {
		t.Run(tc.host, func(t *testing.T) {
			cur, peers := f.networksForHost(tc.host)
			if cur != tc.wantCurrent {
				t.Errorf("current: got %q want %q", cur, tc.wantCurrent)
			}
			if peers["mainnet"] != tc.wantMainnet {
				t.Errorf("mainnet: got %q want %q", peers["mainnet"], tc.wantMainnet)
			}
			if peers["testnet"] != tc.wantTestnet {
				t.Errorf("testnet: got %q want %q", peers["testnet"], tc.wantTestnet)
			}
		})
	}

	// Unparseable hosts return empty.
	for _, h := range []string{"localhost", "10.0.0.1", "explorer.lux-mainnet.svc.cluster.local", ""} {
		cur, peers := f.networksForHost(h)
		if cur != "" || peers != nil {
			t.Errorf("expected empty result for %q, got cur=%q peers=%v", h, cur, peers)
		}
	}
}

// TestChainSlugForHost covers the hostâ†’default-chain promotion that
// handleEnvs uses to set the SPA's initial chain selec!tion. Hosts that
// pin a chain (“Œ(“/') must
// return the slug; generic hosts (explore.lux.network) must return "".
func TestChainSlugForHost(t *testing.T) {
	zooBrand := Brand{Name: "Zoo"}
	hanzoBrand := Brand{Name: "Hanzo"}
	cfg := Config{
		BrandDefault: Brand{Name: "Lux Explorer"},
		Chains: []ChainConfig{
			{Slug: "cchain", Brand: &Brand{Name: "Lux C-Chain"}},
			{Slug: "hanzo", Brand: &hanzoBrand},
			{Slug:Q "zoo", Brand: &zooBrand},
		},
	}
	f, err := NewFrontend(cfg, NewChainRegistry()“Ðt.Fatal(err)
	}
	cases := []struct {
		host string
		want string
	}{
		{"explore.zoo.network", "zoo"},
		{"explore.zoo.ngo", "z-oo"},
		{"explorer.zoo.network", "zoo"},
		{"“A)", "zoo"},
		{"“�#", "zoo"},
		{"“¡(", "zoo"},
		{"“)", "zoo"},
		{"explore.hanzo.ai", "hanzo"},
		{"explorer.hanzo-test.network", "hanzo"},
		// Generic / mismatched hosts: no slug.
		{"explore.lux.network", ""},
		{"unknown.example.com", ""},
		{"explore.zoofoo.network", ""},
	}
	for _, tc := range cases {
		t.Run(tc.host, func(t *testing.T) {
			got := f.chainSlugForHost(tc.host)
			if got != tc.want {
				t.Fatalf("host=%q: go;t slug=%q, want %q", tc.host, got, tc.want)
			}
		})
	}
}
þ˜qÌà	—Î~xœ� oÿë½Ï °ÿ	“°
â“Ï³�“pÝ“‹
³4�³€“\“†Ö“§“UÝ“
“2Ï“¯“ïÁ“Ú³ƒ´³à"L"“ÏEŸ³Gî“ŸK³ZKD“—M³UMt“S“tRš³µS0³ŠWä“¾Y£“¯Z¢³ð[ûB•<ã€èxxœC ¼ÿ‡‡°6Nuvf¢É†)Õ—\Äçªâ$jV&“100644 go.sum *˜€d9iZi0Áî�ì‹«ïÈSž³7Ð�4$l‚áxœ›£;GwÂFÃ�3™Å FPá�xœ;¿€iÕ|¦|®“Ü¸7ïs�è C\4§@x31 ½ôÌ’ŒÒ$†Úo>\ÅÙNß�5´wüOé~Q1yŸ¡��™‰	XIfz^~Q*Ãšyê¯8;¯n8Å«hváÐœÇs·Ù�röquqÕËMaˆàJ> ×\Ýhá/ï§:ÃëÏÇ¦³P£\ò“³S‹Ò2sRv¯o-(³”8›­Ê7UãlìªÏPU>žÎ®~Á®ÿdlÖH[±ìðÝ*ºmiJ_@ÐÉ³Q”xú¹ƒ,”Ž�WŠ= }i»}ïãÿë×Í½y¦ÎÇ¤Bb¯|Âü[ïþÝÔ¨25zV¦¼ëEò3¨Š WG_°»£gkZ^ÿúi�ìLç¹¥ž…'…ÙZ Š’33óŠõR+srRõ*ssVˆ
Ú°ÄØl´»1qàÞØ¥íûÂ”ççäÕ•=¸,÷ÕnŠfð"s»E;ÍRxÎ{®,/-3]/=ŸáAàt5G�š)a‰óƒÜ~¯tq…*J+ÊÏ+IÍK)ký;ùÎµšI-<º—‚rÝÂ'Ù>GS_’Z\R{ñ•þ›£3Ò8¹¿ãÉPLyvõÚw¨Úô|½Üü†GoÕû’*gþˆ;S®ü?ùÜ=Õ¹½µÅ¥¹W:BÌ¯w1ÏM•ÙµüáÞµ“ª^Ì…ªÈÈšæus:[_—=v~Qø˜…!Ïl�ÈVe˜’`�•„ŸØùøºõ^îû%vW/7íûòþØ¨’¢ÔÄœ’ÌÜT�²ªÈ¥ç¯_µ;\¿(‰Å„/ýrÏÝ×hÊâ‹��
T%§ÕÄòc’ð­mª/YE¦NPk=…®�5ïÐ]¼úÒçúž7î�}O+ÁÕ¦g—U‚Œüžout×Æ¨•Y?ÖÌº¯í¦ÉRÐèƒ¦´s¿4E
ÿ=(r^îT¿¿Ùfþo6Ìì&àLPœ\”YPRÌpÏÉjÉ—»üêlËUúm[:â´]7AU”$–d&3,Œ<ùþ%ÿÍÏY+6¼�§ª-ùÛj]1P8Ü6�c`’5!pJæý„Šà‰+œB®LBVwÏÃò³—Ó´oÏV=3Eîmnûæ]J=0…¥©Ee™ÅùE Oþ9T>{õ¹oŸ¯ïŒ5ù·ÐÃÃÞüT n¤¢pã…NxC ¼ÿ‡‡°6\×üìŸzh½�ç¥èyE½®Â100644 go.sum ž·{„ÞBET›éD„©
óŒ³7Ðºj´µðx´½Ç’ÛÊ¶-Ú¿_qú	Þ½ˆÛ @$It^À{ï|ýK²JkƒµJ{éìs^(¤’Ô9fš™3§ƒ‡võÏª� ŽÓÈ‰‚ÿáŸà×F	…i!pþ0(…;”ë»¡ÿ_1òÿØrw>‘wß&§7ð[hÔ-Um'YÐìM¸ú­×Lù¿ÿÇû�¢êgQ½Æº¶å¨–²]Fkuo,Öu~
[uÄÄ½s6/Á5AÆÊ«ÁÿUU”?½ª 8O‘ù	?‰[YR‡úºC¤&Ý¹l#þ…Fbè&ºâl(Œy|X^¢'ñ¶"‰[ÔÒÑçÓio],"Y*X.õMà�§|�°“_(‰ªÝ¾Å„ Ïzè¨zÇwzçÉ˜ùà[ïn)~Qªs�6
û|ŽPŸf9f–à³ý$W|v`ìî¿…½¢ÏÃî}2‰I/åsàsR“aµXÇ^²¹‘Yg}ÞÅ8þ~ˆÄ)þkD~"?‰ç#©Þqîz�Úñx’4ªN\S»Ó¸P+ŽŽãûX]<þÜŠdË’gèèaÅœ�Ý}RmÆŠÂaÌoÓ<«{hÕb¬˜¼»Œô÷¨]_µ¯ý�üÄÈ�¹=ÃÅ)ÛzŽøˆ9}ÜfJüPÂV—Õ\Ü*Š3ÛçñvÖÛ?Ã\ÑUHÂh‡Iõ¢èâ|>iÐáF–è5!.¾cZ|WFè#:u :Lò¤®«ŸIÅuö:‚øçêa²|¼–7IO*0l@!ÚèöQ?ÁKTø—ùÄfû;˜)¾Øª4Î@g¬ñisæ”lWï
µƒBx»¤fEúá^dÃú¿ÿ'Júxp_Gh»m 9Ï?t~ö#¬ÚQ`þÓ«ZÀ¬>ý}.ÿ¡%ö:‡ºÊ0U¡û±2N&sÐí€q¦¤týh8Ïß€ò2ÈJ Ëlt×«Jâ#¡y_Ç’|A»Ë#ÀOì%‚E$�4Ï`Ë0âÿ|¬ÄÊ>éç—PŸZC‰µðHÐ77®¿ó6w‚ÃàÐÑògë¸¥îŽ†Hà„ÿ©P_Z	VeöÆ¾iî¶Uý\ÜÔ½YÍu8ñúrÊ‘Ó4!L[ÐGô.ÿñ$&e´¥“ŠõZ+&	B.Ï*±êÐºûË™ÓvÕâ¥Ÿê½3ýš[ÎNªDÿñ0+¡Ì^Žv˜½ÃðÝÖ1ŽÇí,`•›æÄn.Œ»@£â¹¬*ÚP6‰Ÿ‡lC7¯Ü§häÇ.ÜO¹ã‡ó”©I«lö¶%³š±ÁÑz‡1UšyÿŸ�µo‰­kí±Ò‘\ÜûwÖžz¼`‘Mº1èÝ­­’G!ítÎÏþ.Þv‰×V]ö?œ¡�Ÿ{Ðsú¤*ä‰Û:íüyìžBâBVst‚óÊ‹l1 Îµu6ub‹2v6‰Î¹ñF‡b� (ìÓónø:¡ÿíW¢Ng;§êBÔdRcWïÜÂì¶�Ùq8oGÇ«ùáºÌÕÐlßg×É“y(!ðÃu\çu»þ¨º(“—^Ñ§pØ‡ê›.ySõõœZçT\6j®6P×²˜ýŒ¾Ja“Âi(ü®¬þtŒ•8{}™ýàbŠï�ðŽ4ï·“"yYj±¥‘R"n³Û2À&XÍ£3uøýâDø �ªŒ^›é6#<î-‹<Ø9‰‡�ExÚlC=�muŽÎ’�ÿèŠ°âÌ³ÌÒóÆvLl7wÇ2ÙJLYDm>úˆÝ´Ú>^À�ýï?éCu[õ•WåP0‚]×õmðqŸS?ñ§BÇi&kù=ó8
ÙÉqLŒã!6@‡{š˜mÆÈèfn\{±ïºïo³óOƒ­<ž¶ç=YéŒÄu^ êãh)êa†7™²§�ì”»ñÌ«÷Gý“L«2L¢×¾B’Oyâ½Ì
7)-äm��	ŒrBÄÆ¸¿ô×
¬)Ù°<¯Å\ÍÿvòÞ±Wôsï&C‘çÓ÷9åZ¥|q2³µ“§cXV$Ú—¦ê}9_7äµÁë:ròî)Â|È°gÆ»S¡ú”¨2'³qÝøÃâÀêQë·]É7õ-b;ïßß×ä›V‚\£Ã"¥¼Ñù—–<ÕU'qtï³»jÛãô°–«HŸhÂ8|9„$œþiE
%…ÿ!ýy-	o^?Þ¯Û¤¥Ê"ì³7‡×ãÍ1§ýLŒÓa’ˆ–yW�ç·Ã¬„šN§ÎÖÀBÔ>/�~	XKóÒ«Ö_êíŽ¹3Õo²2�÷Ä¿Ý ¿Fë0¨pJ`]¶ÏEBÁsƒ~î4°Hæ¡w‚zª¶9t¤å1E§JÜ™gŽÃâ1R•Ð+ü+ªÿ©Xß´¬“†›½Q¦›>ˆ®5Út-Á–ÇÈ%ÌÂÛ"&1‹²éŸÆûe,|Ÿ®Z/x­þ¹dm$LçÅ¥Ñ„µ®ãA9˜3…V�n¬}ö‘ä2M™8¶íýÃ$þû±VâÉÐíÑ	�TÕ1%w�0>Ý÷”	’çãGÄ(=ßlŽ³ôn­ü}—ü5dPúuþÕAOå�þ¤>¥CÒ^XƒòhôQ6'#| {øQˆÞi"0
—)•LáN>ÈÒyÿv¨•p
Ê§K¸1å`sµ…¢¨�#:ÏCIg’Ð†ÀÏÕãºUÿx>“2ynGúC�ß¥Áy„›�)ÝóÃ}`xÎUòØ¡ï'^(àÓ�·›— Áÿxsük€• ö}¦RrDÆ»Pœµ`€“«0jIÒ¸u$®™†�’n“÷á�qð"~Z=Ú‚K•åÖQ´¶Û�¸v0±Kè!-iï(HGw¦,ku�&ÿÉ+QãŽŸd¶qEóz­-Ó�:ÃæÃu'wO®ÉÛyÑ°¨Ú1ñè¯Ýàx^P÷?‚Ò«ü¤|ÝOö±Pp;ë*ï‹îrãø{f˜¼Y5¹iP¨Hd‡ãQVNÃ†ÿI·ÿé¨+‘¼™T;ÆPãØàD&¢|É£^Yš@@¨%Ùn!LÚ‰ûjú‡cý·Á½8ð²nx¹
˜ŸÔSAîŽlslE*7ÎÇ]°z–ôÆ’�¡DZçL‡DÃšîŸ4ñ?�¶q”œe6(âVÔêxòìtÁ`{ŸºÞÈûFxC[ÿíAë6è’¨üCûñÐÂ>÷luHÅCp›ácq4`·kMØd!3ˆRÜÎ#’#Çäå	ÖÿÉ"ø›¤ßº8Ñ®ª�¬"±¦+g>LèæCxâSÞTºTÒµþPr+ÓþÁ¼…þ6v‡u±L”OÛäã|ªWw¼"”’Ñ|h­È,¹“<r{£;öŽ‰ÁkÔ&Ë¢îŸ”ç?�·ó:é´^4>ÞmIiÁ_îœ¨2Œ{+ÎÂ!Ô¦©Áââ>}%ÿöZÿ5l‡=åbˆ�‰r¼·ÎF¹6ÆíÓÜÎ‹6c�†�„YFŽ¼�CÈIÿmü•sEY¿áÄúê™ö�7^m•wïR!�êl–§.$vúòOæé_b€-™”OQàs¨�”£â¦(�)»Ž[´'xg¿	~o™ÁùŽÁ5Â[÷ò§’¬‡X	Ãš/(–�‡
YÔ'a•8ïÎ&ÃqFLÛ÷ò§[¡ë€?<áŸ/ËQ Š·ÇüŠ¹Ú6ÖçýÐVÓ�ÞÂWev=² ê äö§új5ÀJ�MxïÑÛáêâÙ•:àéÅ*ì(îaþþØà8^Ô©ß©šÞF:e]U%¾÷†ø‰¼ü1ÛÃ®…wX�Šö˜	ŠvÙæÆn_oE‘Ê³oÍ¤yÂîOU?�æm”•HÂ…ÂLgÚï“ûc¾›ÊáÆIUŒg¬7{_uZ(Lè¡»ÇïçþÃDüt_½DØª�¿ ¢z‡ø‘m•Ü`ø&xç{=Â7øî4SÿÁ +A’;›ø…-nÿ¼	mõT‡Eæ+6¸6Æ`q·Áiss“­ÿnÙtpsÎÀñ\ôÓë*×I~½{õ¼o‘À¢	ÍŠôcDGþÜ�{·Ëœ½WÇ[ù€+¢2Ÿ¢²‚µ¬vg[gqiXìÌ“bŸË1:Æ.^0U|™q7¨Ú’‚ê Í^ÞÏÛÓÈ]I*ÕÐ ]q	šÊÁ½“ÔœH×j±{$Ù�m#ä&îNïzý¸Iµ[ã~Cïî]g€ÊÄ¡¸u%·!„×®âNÜX$Û<«ßO””™“÷	;Y†Osý‰l`â c¶OÜ<x²îÕjÒ¦§nj˜Ë�Ð²®¹dulmú|ÐÃoDy�ªéýiÿ'ø+1X´‰üzÛƒ“©]N5¸³»Ff^DQÄTÓ|ŸðÌ©Á�=±ÓÅŸÏ‚O—ÔM¦âqc®M·.¬eˆ¢%5ã*¢\™¬L†uU»”ÑÛ~|÷·ýsE×LxwÁ.dvO¨mµì]cPŠ±ûþdk„)ï…ÈkSîôú¡
ó'c/i½—ÑAþÄ^¯Oá|Š/ÂžQ8ë˜³bå�®‘%¥A!+›ã-ê/z¿H^n¹¿C®È¢�y&/¡Óã'ÈŠcš]ÊQ*ÅÍ°ð•~!SCµ’FØäú¾„¾3^¤]LÏ#î¤°HY´åÝQ5‰»¨0*¨±|;ž�T<‘éµ;n<â€½Sþùÿýƒ7>BÃ4#‚`ôŸ)†tBó¼çÜß˜æ|=¢gàÒ¸"ÆT™�Ùb÷™Qp4ÇæúØßƒ²ýá}÷}+È?Œ÷¿!fðtA¾×¾þ<`éy5J��ø�øóíõ”‹ÐŒ½rº§Ê¬Í´·Õü™bÊmYK~¸58-_ÈûÔ¼ÛÎþŸ°Ä6UëÑ5`+êþu†Zõ§Ö¢ÄsÂ²|ä–×›-ãïW‡µNÿ �´6y:2û¾ú<ÂŸªýM03Ëiì{Æ½ƒÞD¯¡ý3ëlE—©Oƒ2«3læïÉ¿^‡%`Ú÷æXŽ0ß”À>å¹kˆÀWËVe79h®s	ó~ƒü¹Ë€ô#tZðFú+
�ÃŠƒÍƒ(<†#¸OPNùÎs9œcØ—$uDÂÌU¯6Óæüð&Ï,])eæ�±ÄÞ=/ÿíÑV"^›[{:ßÊnŸ†…©)êyv½k"Àlt»£Ûa ÏÛòŠ&úûá]Ÿ”O	ãxË’å6ü¼Ë„%‹-J'5æ³é Ÿ1J—Mh"mÌ¶Uk:Vì²:àÞ�¿]Vd9v¤9ÃŠŽÈ)¨Ç#Üìº™½ËcSìç,&B6ïß7mäÉ#
 ¸ïë®¬ªú©°>Mhu¯¨�"®ŒÓ5÷-gº—½Á²zÇ„óØÔ4Ä_¯èýÝüæŠnAKgá‘è@ˆÌ.Ò,t{ÍÑüÎ…‚d&�ëüô½Ð÷-
&Ö‹èù{$þk$~Rb'¿¥»GÜÅ‘yPZ§"\–TÖ‡¤\¦–.ˆ#AºDü®‹¾Ã[ÑÔ6½e¡±:·[ú¨Î}só#¶	F…é˜2]2]z£;q_fÀæUÔBÏ?^F×Ot…šúv>»Cw>Ž(´ypeØT4›ÌX':Wü­k\c��÷GÜWTüãäÒr—:ÉB’Ç´!É­„ë4·sNcvàŽ�øüû³û¸IFG`\XæVO¢ý±¥§ž(ØÖëJRÀª‹d¡-.8,�ï›õj×û¿DžâøzªF�îèm–õí‘ 1É&,ÉÒbSö’­¸©ó¾—¾�[‘, úzöDø–Ä_àép“f(¸EÊqc°«µ6Àé]EUî”Ñ�tê¡çï_[é¥þ3+¤‘�~ßT¿AŒQ¦‚ë&•öÆÝ¹•ã±HãN%ð19¥¿Ã\Ñœ&ß•ŽåšG—ñán‘²Ðh^¸A¢Ë}Ûv�Ãßx‡p¿£Em5Ôž|L+Õ‰À† (NÄq]„vÃ€ùP�Õ‘xpMÇU‰çt¢Þ6»‰%‚úQtN‚g± Ã‘öVó?o%¦wŒð ôÖDv›ˆ¦ÃE3oÄÉWCGÙ0GïÑÝ|¿XÔáÝ¥ø1ƒ¡:w Æ5xc}lq	\Š’q‹¨[×B¾Õù×¦Ú��bùDVQèn³lÕ)�¢øº{žëüqEÕ4.>f¨“²›V	è-@“OÊ-›LßÜùÉ Â£Ö_wù3÷
d˜†AÞ,èó…¢W{ÄŽb ¢¦‰\€0}îP<c7GÑ%òº«à0”Kt³såw»÷#ÁëY	„lc�ºø>âãV«l§_È"¿Àâã¦Àa%6‹ˆ’È9ïÖåçXà¸y¸`0óä
v¤}Ý—]E@³£�â˜oB’0PÔw3Ó­&žL#ŠTöñu'½æé_°Ôgà;£1åõnÄEëfOÇ®$­ÚJî	ÛYòULônfg×Ô 	ðV4k3iŠòºeÐÓ�çæ`Êèç©<dÄ…+¨ïÔÄT(ùz­¼hÖ J¶Ý_f		cÀ"A€"„8†.2þ‡õk°îÞAò�uçƒØÛÒ-ŠL‡�W3ýt+
§a—îÌwás¢ÿt¨•pÊ£·ì5�‹£`{äØ3þ†ÇÃ~¼5½|Ô8ÿ$¨f“|Õó/á:Ôy¹ €1òá˜&aV¾‚¼¯“ž¿·»ã‹‹Dƒ}ì«kC‡Õ�3Ûm™²ôn2|Jð†·¢IÀ\¤á’ªù�@Öäßzal tQûöyŽDL£÷ðÅÿvb†!yyZ?Ó×Ôãèˆ;åÔ¥‡-]"é6–¯ÑÒn—9Dí&V[ØŠß¿ÉŸ$Wh+Šúq>Û®DÍÖŽÝ¾R	BÍO�Üœç¤oÊøqÛÌ‡ï$wê¤ƒ@AÐÖÀ ~xA\~ÏD–àØ@�T»äåì2’€˜Ý‘uŠÐÊ¤‰Ãýã¬ÍRc‹Å°x1ËÀ¦sùîèÿéH+Ñùš\¯leÝY¦³;±'ðVÑŽè¥tösé#;.µItc|7_¯#çñÌ|û�öJ)Ýn"N”|-lÌ¬Ý°GMèm9qU3„$dxü dÐïkç·‚üwE{Þ8Ç¦=�‹X‚bˆá``Mè´£©îøäÒmŽäEøzpÛävAÅðx^ Öþ46ô�•q¦7§¨Ý[÷¸iÕ¨½©¼Å �\Ïñ@uº¿\øäþU«E[QÜJ!sÄ·|sÖeî=Åƒ¥L­Ž$Œím¼Ùaq¿&Ê»C9ª>@Ûúå7Ä“âÌmdÅ ‚®ï£ÚÕ“&COG] ßìØ&Ä=%}Éø;ÚŠâ Ö§$Þæ{â¡íåA·âPƒç�Óø®_®—¾Ï”kÝk¡ðîÓû:nWyYÐ\½øË€0< ÊÀÔ� &ñCŒ¤^y3bpåÉ:í*B?<¶‹oÔMî�ÀzØsßì–;õ%4¤ó3my�øÏF\‰Úâ\wî"	rR¸ttØº÷“qÐÎsÕ±ŒVœs�Ÿ¾ØÐ­Súà¨BKÐ‚| òee …ÿ\“á;±ÌJÍtgònßv[�Ê§“.oï¬8Vdöå’ûæŠn®K…€,1Mš3WÝ‚ãáºÅ÷¢ÂNœ˜\
!ôÎèÙ_æž?>±ãæØà/Rœ¤í+Ú\3O¾~«CŠ]_mñbVTö!öñ¶½Æž£ô�“ÃvùeQ“ïËý[ÐáÓ•hfKÞ¥'Í\$ü„8þ~b.ðŸŸ<ÅßJQ¾Ë<ýÝßwûv:ä	˜ngþôè~hÚR¨é˜ÿç’K:|ÕD�áv(“$Îm©Yy‰‰¸…!¢~×PÿyEOVƒW{>N�MUuõà‘ˆU™›AåK
dÔ,Ýe_x7?c§\*'�òè’§ô:¨éÝ)Äë!B´~ô€r¼ŠÐ�Èç³j,>²–¹ó|þF÷+ÚŠb{�ªÝ|IXÂß'Á*=©½äp¥„²ŠáG^ 1w#Ú†xßiQ\ðOŸpè¿ö‚šJÈ­˜œ‡Käeµgq<Î½�ô�åÁS=ÎC#žnðwãà{ÄU(1I]8È~a”L)Æƒ$ò¹ª%Lj)§Ø‹I]ä¿n²ùÛ Î_wãsN©��{ò½r4w|,�\°W)O¡j£S¥¢˜>*£Vj#a ïÚþÙ¯˜+º7øîóÕ~¯Nx~e8mîÌ¾»Ÿš>[Òc‚
Ñ¸áhµ[èÍ»ÛöéO@šw°^oÃ¼>¯@@ûyÚœ�Û]ˆvät9ÓDöÆ£ZÎöÞˆbÓ9œ¦}‡çƒ|“Ð¯ñï�WÔ/ÁÞgªº,¶äøëˆ§ý.Ý®Õ>žC…ì ®¶©	uðîÃˆ«<¾&h ‰À�ùÔÝŸÖ‡Ãð‘r¾rÈvë¤ÈÕ–‰åxÙawwÒ‡C«§ •S¡P ý]I~�¸¢ÊŸ”k‰7¤:÷Ì™B÷ˆ¹C`àâb¾�ß83H4/#ê}ÿ¦Nî€ÜU€ôãó¯+k›€	�i‚N@¸š1Ç%)à|N~>Ç¤âœ0-Æ�g”yWñK Ñ|4½ô¸x8¿? ÿ£W‚Þ›p ŠôvH™ûn¹^ÏGB
Y/·t)��vÛ·àv»—ú—äû,w†®®º$—Ïä…—å¯T@4jøü÷Cl¬¨ö™v¨Â‡ûãã~õˆiîÒÁy¿5�º¢ìMçˆ&ö÷VpZ
nïY?
—”¾8Å¦:‡ÏÂ®ŠY{}×Ü+ðØàmþuˆa,/ËÅ!žQâ<ëVI QS�²€ýp`ÁjY{_†ï±?#NWœÓ¹´n±ìö»&Zð”òÃjŸïJ¥oôäuû%Þ2ïÖÚoAW“7SÍÇ¤TwpÁ#§¢Å=Îª¡æþˆ)åUFV§÷Ëf…ÝzH›û+e»»z�E·žF´3I¦`ŸùüŸƒÜ _ÖÄiåù:ÔÕþý”~�¸¢º£²‹cÇefB2”÷BH·eDoU¨
ðòqØâXøðþnÊ@ì®ç³îuTÄ.óPô n­2}t	,»—ò£dˆ¼I‹H�k;ÊßŸÖkœ­ý¾´…¦gUFx¤TpGGG‚õ<ý�©‚œŠŠCÓ×%o�ÛÄ&>_C¯»4Ìµ®ª“µ½ÕYrðúXÃ%þ¸I¢¤•‰h|­Ääwµ¨½a­èÅßÅ–qpJ‡ys…�»´»ó’pØUy;�%Û)ìñ®•d<€¡l©�y#Ôl9°ÝXèI^�Ì{8"˜Ø0Ä¶öGdãúºÙ~Ù†oH+j�´ŠêA–ƒ’%¾òà Z�ñ¸Zçp»ìDçhÔ~‘vÎƒ<(@(€ù�;|dÎ}°ÔÎj¼;ÇÝ‘ÐH»3ü!Éï}Ìš«‹°üÊ+Â×/)”Ùï@W„n5á¡¼ª&Ü¸¿ÏjÀ‡§+-ïò—Cµq"Ò¶ï;„‰¡ºyžðZ|…e{ýþhQK–¦ã(1°mò-œ©ÛþÐ{®6½€§•¶á»zGZQƒêR®NÞ´lù#5%Š¹Ï£Ù×'Dz±ÓURºÊ&}ý~“å õ¡—c	²rÊ×³äç…rqÞ9ÜÍK»ô}yºqHßÆmoG%\húF€§}~ÂÞí¥oWDÇ+]gªQ'�K7D¥¥Û¡çÛ=›ãÀä¼œIO´a5T¦÷EÏ‡G˜@ÏŒÉ�œºŸÌ“¥Þedd¦è ñCÀˆ»ƒ�T³ü€;s]pÉÔšeo³§ãwSöïh+Š³^)/¼¥œ·oh=„pÞÖ…Þ1Ö­SY…"—}ú,Ÿ}:f_ÊF ™ƒ{5p%þÀ°07ä¥æ(þÕAal®Dgú!�Cðe?ÄýkE/ÙZÛG4¶æÃ›)œÜœÆ
î÷Ý°gú@”�!{½‚b;Íº¿«ìO™û
Ô=~>ê7ƒ„•aÑF³ŒœÒ)ðlpèˆëZ%ç7!Ë”Âœ¯øüeS0\Ã­HÂZèù…ÔÍûáÈÝíív¨Ù&ÇYÃ£«�ö£Ãx Qä;¹?}øÀvÿxcdÛ¼·(.¬@•´|ØJ#^88m‘bšéÛ%h|xöÿ	mEÑ«(QØö¡–t±b‘÷5áØ§GQÝ"„ ö1+_üËÁx7²?æ¤ì¯i)ÉÏ�hå´–SÍÀeïíH�Ù¦Þ¬ÊL>]:Û¨ºÖ¡ßoþ¿£­(VÂKIžöÐ3\ËpÒZý=kY$.v·ý‘‡ëÁ<HxdÒ·rÿeY=¶¯²\¼ço‡‰P$ÄˆÛ)-äoHJr æ9;F…pÖ@O(êï—ó/–¿LµàŠ(ç!óc‡ÖáÒŽõ	Cï¨°›d)7Üœøq±cTªhízü~.Koh[à2ü°v_
(AFlü®pª€*rs¹Rî�d]J�6Õ D£Ì5%Gyß�Bà¹x‡\‘Ý¦Ð^#TpÓx,zÆÕÛô³ñÒØÈ·;~ Ø×tjˆø»�»ÞÕÏ3„<}èÒmv—3V ê�Ù¥×�¡Í=¤¨izwÞA%¸�vÖ÷‹ÿŽ¸¢ŸºÅFÕÝ…Ž[*½Ð³Iêlf.q:ºå´]L«öîeúö¬ª½“”Ï‚”ç‚½ö)kÆ{½Ïacœ‰"ÍU¯¥ú3¼÷Ê1i³dØ€wµù%)å×xG\Qµ±k]({ÂÇ{ŠR¸<XæM«Ë¯°#ÃØõ!ŒœàÝÐønçºåß�ê&äõžÌQYÀ•8¹^4ÂÖ‘w²–€ŒA!Ù™Òá*#´Â@u_îÈ—bòÞWD�jêÛ}ÐDFÇ&tÙº²µh×µñhì‹ÜÜ´{„ä'àÑge¾ët/¨¾”|7_Dp¥“Xìgo3»°�¨p)L"sÀïŒ;nJ·â†cñ�FþŠ¸¢Z!ž—F†¤X®Á†É†Üt»ñ�w(­JNØ_ðH†*q“½›\T£ �_Fù“yåÉNMé½4×>Äúu»x8;¢û6}À¹NàÙÅ0gõðÅHxÍèÜŠ$ðö-Wþágw¾ìÎæVîÉ3v/ÛPÒÛvd"¶~¸ß�ýW®Î“å§eäE‡„½Ñ#{Â]Œ9”F‹Ü‚‰ßgŒ}lD›=ä¡ãéãÎþnÕ×h+Šª+éÉc!•d(çxCñ&`4S,7	„è.GãœÖž^Jöðí<>Ó‰>(¾^§‰S;Ð™Eo�¼æ­N¸
�PØ9ÔŽ×�xuYk?^ê�ÅBû_£˜|ÔÒ�ëò¥;s¾êø~«Ùó Â$—ÇJ
KýP­èc¾õ]x3£óÝB¯ÀV/÷T£È„J-ÉÈV¸:{Ô"‹ò4.XDåûÓÒa¶¿¿»:’Ò•qøÇU4†V¤@q©izÅ)–‚iûØ‹‡nÇ
_KÙnkøâ þØÛïx+šõBÜ7»Fz€1t˜¤ ·c�À‘iªqaÔ¶nkE<�¤¢wÿÖ7°¯ÅÞÍ½õGtÃ0©²ÛàâS´Ë ûvb§ýáÄ%ô�]ƒxA¿Sío4×ñâÿM�6óqû¼Vû‘Ý$í>^p{�k	`HÓûƒeŸ¬«2^/Ì�ÉÝtb‹/qÏ‘ÿ…µšÅb>fC8'—ª¸^.14’¹é	øýV­v‹©=nÆùå;Ã£p>Ïg¾ Ò3.Çu .gæ£Üï¹ÛÑðŽ•fK¿ûå’!ÛðÁ‰úîH¯ÀV/£«=œá2ópdŽ®'_Ò£ç@\žYlƒ´Î,DÚ·ÃŸ˜�û¬þË+`,¸k�mœý£tî€Ÿl+rñÒ°!îí¶Pr&‡õø;’+ÀQvx\ÕÜƒ=X9%Qæþ¾®ÊJ¤°Çn¿àƒˆ†ï®ÅÂ/?]/ÿ@„ZËlMoIÎYæù(®l.³­ùº’¤4<RÌåª6#Ü~Kò_`+‚¾[!cOqÂ2¹Î}aÆ›|/ÈââÚÞãK0F ÃWýv&ƒ¾ýxNŸµ�ó6› =åÇ! ŽbMD›Xñ”Þˆ­Cê0‘¸utl#ý3ÜŠdxB£ÄY²Ww=pˆ âÃY+pë|«w+{Vw£ô�ÉV€˜ÝÇR¿f>ðý1�‘äîËæx›ÏdÛ	�z×½â‘îËëå;»a«MßéïØŠ`Za„“å˜Ñû´ì.»¾£N‹«1ƒ|Z
Ø„ŒÈžˆ¿ôù8ƒÎ¾$ê}~)�±oìÝ-FgÅ\óž©øÕ<"â\O»ý^y…}�Z‘£Ýº¶=�ÏÃÖèá+-Î¾c‰‰èc<.ªë§ª/!I¿{swNQçªûóÉ:/„dÕßilÂëK§¦Âß˜¥KMÔµ1/Ý½PõÓ—úÃw¼Í�!¡jÉSfÜ‡vºq2¹o%nÔC~qâ‰Ý(—K\Ç ûî¨}’8‘?]údb\V×ä©êUÄÚlH¯³±ç§¥A3¸&È¤óí?¡­(zØ¼ Zº´ìÆëÁ.6¢cêqŸŸ˜ñQ0³¦õÑ•ÿî\œWæÐ¯Ÿäd†^ÿHÔæ·‰ÃŸošnÅx¢ÃÙŒä˜UãÞQßîéa­èÝOl4|³Ü¶Ž�)|TŽ’Ó¾q _1ëN[V¨øš®¾Û: ÒµOA>×Éå_d3³ƒWn¡%½X�’ÝŒÇÀWèxÉxç"¤V8|©âýXç7¸ÉË�ÅX¼Óv0è‰°%—�ƒÒ¢^š±JÛD–£šÔ­¿l•÷‡]á´ÙÐ•›OÇß`7 J¸}Øf¯çÐ¦X-¯aê´ˆB½*!u�Å‡FC–ù²GÞûªÿ#øJ ŽÝÞ¶;qPÔX¬g…<ZÎ}¢P½ä4åÍ[Jõd*R‡è]€[ÄúŸ©^UÚP=Ka^÷òzšoæ*ëšPš#š�u%Õf«bã+7šfQvvÞ}†¡ôOQW”I¹¸ôEÞŸp�X�è<Zü’ß"�ö›®�Îö¹5l�<;Q­Òþ¢œt€üËUüÁ¯€jJç%ŠLBÂù¶¿Y•~“Î×H
 ã­º°e©’ò¥„ç7�+²¶šìŽŽ…&ÌN–«Áçö<Éó¦$‰ÒŠ¹¶oªÚy7áþBîš<éì¹-ü'½‚Vç{ÄA³:)uaÚÔ\�NGáBr²ñ-$]´0#¿¼Uþœâ?±¿e2‚äê.œësæ(v+¹;…™‹Á¤îM¥tÖˆû;X—«¿yý	þJŒýy&¥fÙí`PH{ìtPŒ:ˆwì“%{TG«;5™[— ùÚ—#”Y2>õ$J­0%õ¶»Ý9F÷ �+[AªfäÏ�âÈU£¥çx}Pmêñ®ÚŠwLêµ9Æ¸h7úÝbýP	j&ïö³$¹Gåë&–tíßB˜”ïöãßÀV7üÀŸcÿ 
_3øÛuå–®³ý&+öqH·Ç˜w¼^ß•[:T ‡ŠGâåX|<Ü?ÜŠ´?= ó‚3Ý0OîiãŽ­¹>¿#§°öd•š¬ÈÐÈéK£ïWT#,Ðp»ÈùCño ™›çYáªç©0w„	z ÖåÀˆ˜ûÅø\øÄ�´Gyíß�„ ÁO��-‘ÌÆ6”J=h¦8«Þ£U¶¸}TÖÁ¾^Ü¤ÝGØ—#ñ’ýàŠ¨²øÒîKQVgÌË¾òºäÄ¨’ÑkT™ñ¸nuƒÂ_ªÂ?‰>Ñ_©pÔ� uá3Ã‰
¬!æ©Ó:P�óÀn¨‰²-—�wŽœîƒm²±¸XËïÖé…ü†¹¢Ëß˜˜W·×þrÁüAõ[áD¸IŒÂ¨Ï~GÚñœÚ'ºzw´í�¾­JP•Øýœ×ÏÐªDZ¦¡ñÆÏx:5RjÓ´’½±çÖ^d{&œ#Ptœ÷lS¸Ö‹ß®ˆ¢ì0¸<Ií¸ë}�C0J¼« è ;ð7:Ôžq²ùEß%p×- 	´ÿªÊàõB·1èÄ	RÌ`åf8Bþp(ß£]­I^ç�Ã&æÜ�0�ÄCÊÛ¶ iåtð. £ Q€Òhe[ÛJûÅ³Süã­ÄÜ””N}.º·Ÿø`)hw|(§DO=ÿÈ©a/óÈn|�”Nß=ë³ž?A›Îç‚àŸµY<'íNöc(Û�$‚£íeƒn
TªÛÆ–ˆ€^	Ûò€¾«¡ß@®È&Úýx®2ã¸w�$}Ê¥Ág¦ïVWøº÷s;TvþÃÞ_ò!gÁ3OøQA#Ñ§¡Þàx#`I2Ëgîˆ‹”<›©ËXÞ½v½P[š­­õBç}7~¸"Ú-»¤è£"<%L‰b}aBÐÞuùMÂÌ±ñ ÷ñÁx7×ÿÂýÌoÛ'M"ÙÒ;�6¨†¾K(õ@´J»¡Vÿ@$¿<O¸Î�ÿ|;}·"‰0“w^°sŒ5°èÎIÓùpœ-�Ú2D/ž�¼AN×“Š¾_A¥×  ý µyaŸ|häO‹ó ìkº�9ý\ƒ}®@v¹Å'¤ì¼suoS Ÿ'ŸöÛ‰úÂõ·¨+ÊûÉ#âÞ@Ýo$9hy2\¦�frBJG,J†â‡'yù»bªã$�+PÃ …ÓK)*º€Ü•¬žS£`m„CQh{•qa®‘=¨lUÆ¢©»J¯ì/®ôïðV4µæxx0 ã*~È'i*:b´€¨[PU+û�v)7‘)îÝG[®vô=xÄ`>?ú÷â@i #$žÀ?Çƒ]ÒõƒÈ«@uSø)è›ä.†ÁÄÖ*ü¹ƒºKn(ß*lŠDSÛÐÁûñýï�´�j'ç,V.SùqkŽ¢Zr÷„÷[Ì-ÉãÍJÂUóx‹ÞÍ£ç€AÛVÏ*’gSÜ×¾Þó¬ü@@¶‚WS�‚�rÇ	ð2kÙ_¶S+7ÐŠXýßt÷ßÀVÝÉ™á�Ý« „Æ79lwCh`G¥/wš"-¡U>?ÒOÌ.ì_Å� …ÏËs(îm7ÝÓ�Ê)ê!\ë“ßÔÓQiÎ eö8§¦Iî…™—öïIL_±Vôúigõ¦IEiòF¸¥¨Dëqñ›dU­Ïw'ò#üòþº©‹`q@YïóÕ”„!È"xª\°CVØ‰t ¨<c¬‡.!µ§•™”5¡^E�Q‘­Þi;Ë†¾Lço°‘ÏiP{E"0�ãèÂh<ðÐ}ÝZ¢SŒQwÚÑó:j/¨z~Ô”L§§¨¯‰€3}6obK�¿4Øú�8ÿ0äÿ†°mÚ3Çx�zy–ÿïG6äs:ÑÏ¤B1`ÈÊõbBÛ3¶ß)¥_Ý:P˜!¼ŸàLkâ]K 
þ²îÿ€½¢¯»ˆCÊ÷‹A ãná*ggq?HÁ^ç/ˆÅµX˜›/å^ úâ}Ð]û™Pÿj™Z±]4ÕBG%Æ’(c+Ä<�y�ÐÊ7`iJ�¶ÑœåÇdÏ_vØ¿E^QŸ±Ý?;2„3yë!�ín�~·î6*Ær9õ`qOõûå¶¦^ ùçEšþ09éö"Ž—ÂÊûNCe*¦í	Œ¾[ªZÔS)ºmN¡¼û0º"ŒwÕas£gñª�2ê$B AOîR÷pê‘\4!énY ÒÑ%ù¯Ãx)hÏ®€Ï;Ùœ\`ºëÜ¼CÛfÅÇX>íLE´fI:yó�ÎÏ àû®»~º"ªÊÖ´yÜµ×óÑÿWi6˜ Qn€ßr‰b‚=è•ü¥µUÝ9%hêìCŸÏçñƒòç=BÂÂÀ0ÎÀø��¥è¡ƒ‚lœWã²ŠëMm%ãdðW_¼§š	Æ&£+èÌDÛˆ¹™¤±P_Ôú4âJTÐôÅ»qÞº oÅNt>3Äí‘UA´Ã’©n¥Á§îýñ]]·AÓú ?´Ø·f…ágîÁ«é=hé ª9žÝîé(îãÒ¾ò=ÀÒÝaæh€Œ|<ñ\M-ÌË] OH#Ò¬Ïò{zíQnð—¬µÿtÌ•¸Mãæ˜Ixo)®ºK }ŠkäV9hBõ°@âÃ§Û…äß5K[EuP×€úñ«ùÜScçÈk7Þ.,(O:³dÕðõ¢Ësá±¸�ó} íÔ(ÜáþU=ÿ¾ÅÿîŠ¶âhRv+ˆ;U&(ÞuêfÉ0ƒdJ¦º
É¹{�ö5zßìm=ÞjÝBƒ`�³Z.“ ¼à!ìøsN2Ó‘+£ã´!»ôK8£Ñã}Ÿ½­ˆQf%G›KéwÙ‘º¸ÚlwÕ@ÏWåšŠÒîè”¬~é ûj*áÅ-W¿J!À]º‚=ì3‹AÈã~ÓNþ.T¶™A�úihXciyZÐHaÿØLüCyWqƒO¢¬5Žp:£q¥¤ìævŸF
1Ä“tWOÒ!½ãzíiÆz·�ÿRÓÖ°ñ¦¯pšï>´ç°[Ì³Çc�XÏÆÇ'ÜjþåÒû¦úv]Ã¿×:SEBbÛmSN ­ød³Zío^P99ÆÔ‡Ñ jè]%ÿÛƒÒ¯$|5`9W„I��WižF`m;•´(�˜jZòh'“L†D¡ºOi¼¿ã¾C÷Ç
yVÓC„ßªDº»M'Î5EN\�‡2ÈÎZV�6 ¥Ôm£¼ï¯ï‘ñr·@yaOæBüfWHiÚÈ³�jeÔAzôÌü:¡Én3É«Ù ±x�ZªC�]j‚Ÿ4
rŽvKƒ¶¿u°)¹Ì`ˆý0‚ä�Cóv‡|Çö‰¹¢;ÙSxÇ:OŽ4iƒbÑ‰$.vJ5pŽ–áBv;Vœýb˜uýà��‹<w(–�lž×ô¯Ïn4îeiðÛõa¡´mu‚xODªmÙ¾÷Êé,5n× õ•ñoaW¤ÝÃ–©G £g²Q‚m¹[$r”Ïx{ëñÈÉÄ•0™Ò/Õi=a‚PBî¸49Ï²·—nü|äí†»W«´_"ÞŒvWÓ"r­½w?�c? ð>wìª¶Þ9ÿuEY¥~ÉQ­9áÑ¼G)61o›R©ƒbôm“�¡£Gü~�\] •]¨è¢×“ƒüy§Érgk'ŸSŒ(²-Ê¦©¤‘˜1ìpxìüQ+]rDmï]�·"©]kX¾3hW4ô˜´ð×ž0ªŒû|Ý€Tþ\§¨¶Ú6üûIžœ|ÒL*¸uÚàU"ðk#0)>p[qÉÃâaŽ‚“Õ¥ ;;#ì"Ÿ.wÔ/íø~#ürEÖÛ�r	ªUKpIS��Zš;øÉ4ëïi©§{i©®®<¾Ïè_È;àGä ÓÚë¹ùi �ìb•ž4Jýð|öpœ"×}ÚÑøeñIÌˆg%ËKlTéb¾øÃT±4è¿�…D‡4ïî¾ÿ|Ô•È©
‹ŒÚM@T¯å¡Zº=4;3œ-Ì›”j«ð–�*m‰ê/á¨%Ü
r:à/x%~ºD#ŠƒqÎ‡ãN½ïE—áñdïNP~e¯=çV©fºù’ñÜŠä¹aÄd/µ@‹²è÷ÙÔŠüuï«¹@¡c*7ÂKThß×åôaÃ^‡ò3ßŽÚÉÂNÂÁ7#ÂˆÚlæÇÎâ6œ†$‘‰áUŠÎÐÍ6Þ9[<&Ý6	™—5üIù¸_A/ˆÉ·x+w\]•î0µ'ž˜ÕÏªm{ƒ²:ðÅ–§’ûÕ7p¦°áz#E�@|QÎhV¢-�ÓÙ‚¬Øû4_Ê­ñœÝSÒß ­ˆ�ªDLM9ÛdÌÜ¢jC|ÜP‡è±±;x¶6pxŽï0½¼îç< ÍðÐkçç×džžäçgZ^oÔ�«#5»RÄå¢l|�ÀY•#TäØR²¸\B„ª%µc“Ï·þ?c®èJšB÷'‡Ï9Ù ±³´³5sÊö8G<Î.òc“c¬RÏ0æ7ÐÏ¤Õè¶$Å¶CÞØ¯`€!ž]€·øY}øúZU*Æ�QäÏ¿<— '?Ä»Ö‚3cÓ¦ä'¥�ÒmNò�ˆ,…ÂêLk<}[t©ýëÓ ûßç°š`M(o·ÿ¬Bè@K‘HÝ¹QK”ðwë.3ãT¸¥.žÿ›TÊ õ¤zMÀ³9Õzœ½xçQQS^Í½<†S&lÏ�áù&³W/4½#šÎÿf}·¿u%rÇ&hÕ#4ÖÎ& J£SIbW®Ü7(õÖSÐ�zù½¾™ýç’>7ë¯�€˜N‡âPoñÄÆ&ö‘º.O¥xÖä<�šóv>›êÐ<¯à \e•(ÕAáîý¬åZ*£•Í±ºŒO1'ô¦£œ�Ogð…ëyý÷ù¹®Ï¨_|‘	ŽŽ²î€_Ó:n’‡<§A1µFÙ)às>º½o¯çïüâÏqW´éê|ù3«F¼RÌîëPƒ•¼è~â@×ã~9°çÓn›4ÃÓ–üm`óx¯Xè/ÖécOp�`qZËjÔÄ#êÞYÀÖš´È6s"A.äÓóóG°+Ò ”I[å²5*rÄ„m© wÔ�1Ž“Ü®ã�Àº;r¡Ï¯¤êçàíÇ!¯òÀyé®Ïû	•°xªK]m
KŠ™A
Î�ÛDœhÌ*øÂ«åØï�VÔ¸ê°µ$##Šj{‡îrRP´˜úÍ ¿áådb’¯¦S+À_™`Ÿ�XâyÏøaáFG“ Ua¾Ó5rwß‘0wvnº¹ÌB;�´?bšï/W8+ZRbˆ8ÛØ¢£¤x7äƒoo\-ÐÓ¸zK»DìûúâJõsvŠü'h­÷üùY›
:6>­“~˜}ø¼AOeùd€¶9æ$U†„k¤€Ž<7B�ÏÎÓ,üÒŠ¨EhŽõ®ß5'ÐÒyPÃ¾fµ�·v¶°MN¾b�ËüÜä`)?©…†&*‚*Î‡¹÷�ic©9œ–{"#a»Àòv©v?ŽÊ»�¿�VÔvñ2€Ïï¡[}ÙîFÖñÍG;h g‚¥×:FrÒ¸s±�¼4ÏÇá¿î’Hø¨@ æÞg¼47áU„BèJãX #>ã¬¹ q.òø�8=á²Iä0o_“Èå–	wº©ý®;Â›ç“üß�¢»ˆññƒ~Fw™Fâíy
ÍÝ˜±9,V®°b”ÏÉx1õË{Š¥)Î€æñ{/¹g°úûÑˆ_Az¥£Â3Úš~PÃ\Ø_r‰¾…›,qå‚ŸòZÇ·ïÖÂÐO‹õ÷X«©G.Qx~ìšÖQ
ò“Ø×Œº�0h¼›´p@BìÈÂÇZþu�? àñº½þåiDžF0ï‰�$
¦ãÃÓ|q / «‚>Š™™GzNŽV:û=x”Ã¸v†ëCô#)ìŸÑá5í?c%Ž�† Ì&î[»Ã-#òI9‚NmH‰MmTÂ²Þ×µ}~±òùêýù� Ýa_•ûJüð8ƒ€‚‰àéŽûõuËhš³ç¦ã]/vQ£ho–m³G8çæ�QækÄ©§$ûë¶ýì××E1­�YÌá­»ùÛÅNW]/4>‡v Þ—e”Ú3åîëæ_-Â
hEè&’—›îþüè	bð‰O‰rH{cÌŽ¦¼$·Ó‘Ko¯£¾Â¦Âsþ:L8Œ£Š!Ä�À%\Ï%�u°ÕP=sú�>œÔèhÛ—èÔ¨7GÎjnF)uX‹v©ó¯sðu(|·ôÙ*•ú�¹0N"A þ˜ÕPàpš¡ƒyÙŠ$Eé‹,`ÿ¸éùËÙp’"„^¼wÐËØÿ7R1@"¥Ÿ)	œ~x ž…ëŒ³ÿ¥¡€<ÛŽ`?@�a0}Žó¿)ñY´êzãÃK,@nä´x½÷ˆxkà+µ‡¼<×f‡Ja¶ÛUŸ+ñZ“‰
šœ§ É-ÊÞ8wã(I$HùÍÒ4˜y¶›;w§¯Gé™×Èµû¼eëÀ†�¥BÐÞövÚs[Û9Ìm}Àù@d�çùà ëqþÊ[k€w¬=Ðàì&*z¶+EP¼Ž³g<°Ù”kú
|ž‰O»áœN [ÌG}Â²›Ë×Ãã_›¸PœFàˆ{‚0ƒzÄz;h%C|,C@@©¨óÄ¤ujä'ðe�XLÁbR¨óU�ý„�^Eg|Ök—ªÓ-l{½C}ÐtÛrönåÊ`Â8µó*¨>ö§¯'}�´šæÑª“/‡<Í$Wå¹ûZß³P©`,ySUU¼ òÛ¼®«CÑ}äÅü5(B 8ÊàÌv¸�^ïÔ«~¦wãnö ÿH¸´N€¾´}–^7<ŽêåÜ©Üíì§àËñËTú7Cá`šôøüÁP†R ��až�î_š5FÔôîŒ©ÉÂ…ïîÔTK*—
Ì®Ws.Ý€eÞöwªûj,õ¾¸+÷ˆ}=;08hÁò¥1*tñÅÃµÿ…ž?ÿ„{‹º,‰œ$f�Ë—ˆåøšÍæ—Y	_}Ç¾Zþe³ÿÍô<�Vs€2ÂÇ[¼9Êà#O'$Äí6hæ.4„¤ÞYË–µ´Å×ó÷«0ØVxj£Ðüíš”–i&ba£€Ä$§”è;|LëéÐƒzâa:}½þÂûÌ�çš4™|&¸Ft _¶ ÕÜ¹èêa<.÷>ñÏN¼¿k4ÖêíËþ[m„5ÒŠšCtéÃ:&îJh+ÓÀç#·­˜‘¾Xò2:ã‰XÐs¢¼ša¯—û#	\Û¿” ÛÖt�¹Â-ØŸ0]��bi:g\•Œf¼gäd»5M¿^�kj¯œ¦¤5·çR˜“p±«=øþq†[ ÙßËÅ’ì€©ðQ_4z½ÛÖ€Uõ,m çëW& Š€x,Øí ¥-ã`àÅ†;8J¬ÓYÔñ-.ž}1®�|qÔÁYŠÍÜ¾ÊUƒÞk¯‡àþa£_Uxÿk°_»tÈV±ý¶ªNÚ¸­CÂL93êg¶O|o†¤(!ôÛœÔWÇÔ·)]C­ÈÉÃMhÇôL…ßóí â©è?ívô8MÐ]5Ðîxè«¦%„¿²€þ¿Ö®lYQd‹~ÑÍ<=29€ˆ¢òÆ2ˆ~ýÝr¬{9VWGõ�Žz:Q+·IŽ;×^ë¿}ñ:7¿(M°æÂ®H1^rü|Í]£ô“á¹Û•×<ÎŽàíV×ÚêpˆQôÀÁÒl6*aìâòü;Í‹M2ð~{p“âhj~Žù‡Í]ïåtÑí_0ðÇtt|OÇ¯�¨4‹›¥·påemÂ8@�_Ø&<TwwaÞ@¯€ÆžÓÿ+¨Y×+Ù­r×k¸q…zt+MIù>ä¿4™¾YòcPŒ ŸòQŸ)7Ð¿œâ£ßlÍkæŽæŠ·üÙ•õN;š§ä¢îwªçvÃ´Èí¼Ûo.=uíßÁÍ‚lR˜c:G§I“U9ÐÙÛý¨í%£uš”K¨àÚ›ŸÇbZ>Q“è
Œ‹W5ù�‡4þ± ÕŸ�§C†%àfLéY¥k™o\Åƒ ¤‡I�RhC<‘.­-ÙÌÉÎ¦ctë©hæç,äï·6û‰š³­þ[X[Ü�H· ¼“’ODª%ž³ÊÊoJäj¿¯‹Ì¯~" ¾þk’%}›¯yA0 ÉÌ?ŠƒK’PÞpÌ›øÎî8ÐõîÜÐæÔmY±Ù›Å¡ Š0Ò¶w`¹½^tþÅÆgÀš¢ æûnÏ=Ð
+OpÐoê¦x¶®æÄÂ¡d»39.{l:ýN¼³À¿Ñ%®”¼e.VX§ø	ëËký€õÖeaõ5‰Ý�¨z¦«Æ¿×ø¬¨U“§0|]¼”Ž¶U+Žüa"²#GûäT†Ã´BÿÃ[.•ƒŸ;1ŠZ¦$ŽåÑÝ(vØöëCÉ´!x‰þ ™Vê…:Ùñ¶úëþœÁÍ‚L+4Ü\‡GF*²*Pn’ÖlN(ŽÄ¥IG‘?0Ö,¦è3Èi¾•Êá ÿE›ŽÆ}¤Û*�Gq£aá‰a÷Ï»À‰Q„Ê²¢öms·½ù§Ñ÷æ,Ü•‚#­vßÂ„¶8g]ŽL‡^·&pþf/yŠŸrL[,0-_³É“ðÏÿßz�38�q8¢®4PïC‚Ž¦Ï|‡•*&Sáy¼uNéÝ~ª)¹Î\ªü-åéóD©~jkâ_80û�›‹“Ô‹!‰c�ÏsÃÌD6ZEJ	r5Ç]}8lëæÚAþãÜ“V¼Ùªhš}æLIÙÞÈ¬óÔU¾[ûm\‡,a-QÅ	™ò®Ä²y	ÖÛmµÇ$îoY�KM§ºw[P2u˜®¿Å+ÕÎ°tŸ7îžäj-›Ò2š2Y–áÔMÃÒTìÚ·eM—ë¿Ä™…™¹ÑxH‰Û¹ZÉk•Îúv¥W8†\7K!MáYž” èµX½á€¼Åà:•¢Å½„\òü¨þüRl~k
ùâ¾À}ÌXñ ¡|ÍãS½-§“<õ´piô®,,›§þ9m2¿…;û,sÝÝ—¼•<rW,8¬ŽŠ• Åí|Ë�á.ªÀ>¿4Ö~Êoöï¼æDEÃàÉî7Ðïþàc‚a"��X6˜5¥Q÷1gŸ,\vÍ�Q‰¡Îq�&›?ÙvíöÙ¡Ü"
<Ìzè[S¡$œ]>…§rl6«{tk×,J¿ËòhTÇ;v²ÏªÅJÓ¶ðî‘ï8ÿoX�}ŠÚk0�„@²âËoˆà¾ªŸÈÎÄŠ˜ä®òGBé¬¢²k…°VEw\Víiþ1°§6Ë_"ÍB3®Õd´æë'ð…ñÖÖUÅ½#ãÚõˆÂAÍ×­p„�å@&õ;àKø›z™!‘ÔWpåp‰EØŠÒ,Ém|ŠbÇ(�=¥†%ÔeH†�MùÖœ¿Áš…‡oØû�f�\ÐîLŠ`×¥[´4�ËÑ™ß�‡“T°=Í!ãÞú zz=ûÚ.ŒûÜÚqçXÀÁN2HUÙà|´ÚÕŒ´[µ„&Êé¢ø2cÁàø¬YxJjyÈÄ±€øX=�Áðe+£Gij�aBVa/àf4‡LàÃB2,Ø¿$@®c: ¸Ëh½]C‰Çƒç³æqÚ¯ÂËºP�tÉäCæ à:ÿk Y`çdí¥->ÖI±Aº¿«‘È¬,š™fmô]"špËZgÙëiè3°iÒ½ÅïS¨‹óèˆ‚f½¼6¥*P
¸ªRŽ}~CÓô@¾ ¿š¶Zh(#,Kb•Q\5U‰å5cÛ;×Px@ä¸ðÁûÀ«<?Ã^Ó;o¿ê•ê‘³›!Ø…XÔ0¥cŠîehWe¬Ú^p²£„ç}›Ãjø-²9Ò,4IYì¯W5Õú£…cª½&…¾DºÄ8ú¹q§«�ÀE+nsÕ?¾P{&ý3–øú˜%a,ÞŠZ°„±-Êå]µ8ÞIÔ‡{·ñìL 	ƒ'²ù�Q1šv­±-&Ãltèt·4ÇÝaëŽKD^m
B¤ë„[ù^K~àA-múc sðu8ZJ.xë…�´íFÄõñYüŒ¦V“¬‰^X.wÔÅÕ¥Áø{¬YxTM¯êÚŽkPŒ]R·ƒ"wRK�aŸíÙ‹nIÏfô4NÔ�9Z‚re;Ñ°¡*Ê”8O7Ÿeð�4²#ê®ÊÇb÷T˜4&ÎôÆÏ¡–2}iåÍ¿éw¨Yp¨º£àµ]B4úv nÆûà-D Uº¶ÑòÜaY«_ á3G¬ê)iÿlÝrŒ€ÁY��À.,Ð4n=oä-ÏÎégé¬}GõF™Ó«Ä_ÃÌ‚Â QÄ·mÜçg+Úø�ºÒq—ãEI½¨Ôˆ˜¢¨¯tÉõcèÞª¶ûñAß›72ŒÕ2ÆéR'·z±“¯‹¦í¢3Ææ…µ6J¡;ç~\«¯:±ydX³ðXwMzä^ñ½ŒMžÒ&¨ºûƒ$Êq�ú¬_ä§b/á"Þ¨p3þ9U›¿–\Ît¯áæ­P]·*oÖiõtÙ»êÃko¨“>A{Tå»±,Ë»„~·/æý¨Yp] u…tÍQ
vËóR½b$7"éQõ7æÉl”«G)ÝêæcqhÞÂ»ëWÈnç¦€È'b_“´èX·­bñÅÈ•æQfñ•V\Ù¿ýÎoP³à”Už%°ÎbPf1\œÐuRÕt6g6ØÂ_*t4Â•:#Å—ÚÔìÑdIüî·SÁ
5jžr£óMe˜‘YîŽQ_sTÖé&ØçBàÆó±LÎ�f�–O£Ý:x)Àè²0«u½Ý½?Ã!t¼ã–~FO¶½ö!*ÐÏá˜€�#xœ}–ÏOÓ`Ç#�ÂVÆlleÅ1~Þ¶ÛØ B�€A§H¢	?”1Ê¨líèØA�’Þ“’ 'oþà®F®Æ/F�ú½˜ºâiÏ³xj÷éó~ûí·Ïóv{E»mIm&“�ù¸²8—™&1-)$2K³Š /¥š.ë×x‰ø‚$À0º¼�Qt™obJèQ«•î¶‘¢Ü©¹ßuº3wüuÁmü|8è2¯"\©0NïmT$¦9O`VI(©”FM˜KÍË|V$"ò‚À+êL®&¶˜[XgrÑHI1ëCœÛajÙvŒi¸ÔÄvà¥¸—~.+gC€-o ¼Ù†êþÇl-ÈÃåŒa•Zl„~³T!ëŸ¯0—K0ãBÄZY@¨§ óR®²Ø$0°ÜgZ‰«ŠÊg%"|{¯#�ÖŒ‹~‘tÐýRÍ²�ü¹�aƒ€ÿ°»Y	°'·)Ÿœ¯sz`†g\g!âÜMMñ^ õ¶ŽäÐc¥Î–!ZÅž5 Úm®ëríoOf–ŒÀ:|Ö£V*œ"õ@!*Ø)'”ÁÂ¨Ð
-’�Š"ƒh\j‘¬tM² B«=;›þ7`Ð©ÃïöÝßuß¼UÑ­vÖ„év°ïw: êëâ!zÜ3î>çän7¬Y£¨ÂVÚÒc*„?ö°Ýùm|1}9µx2R'‘àÎ�ìãY?`‡ý0u0 �Kƒ••†&~Ò J¢eŽÊñü¿í•hÊxÃ!´…þ†b»—Ú ™¸Ü ñ ²~Õ}zGP� §¹suã(ªZí…óqš²Ý€3íoE„£¿ÇMˆVÓ—f@÷'ë¡�äÍ>ˆÆnõCôhÊ¤.ÊhDžÊ0ø½Y´—©ñˆ¶ç`;ÕÞF	¾O W“�9è°j×HfZÖ‰¦Ç…¤›7ú&ˆúf2UlðºÞà“î‡èEfÎÚÛ¡ƒDT�ŸXÒËjÌ°à‘‡×Ëv(?¹lí¬:aß÷ 'NÍÇ‰¢
Šª�¬db0„¾‘ï4ùóHî¿‘¬«±Ÿ	e:f¬ùÐ·Ó¼‰r‰nBSk[5l^Q3ó«¢Éùá…Hx îÿ›-œ-�—10“!±«ð7ºPêª€–xœ]”É®ãxÅEnUwUAUµ©®^!d©<O´Š.'qœ8±ã1±½³ÏS<ÇFB°àÈ†bÓÐ�ê	�Ø �ÄŽ›fÃ’G îÍ¥ªsû~ç;ßÿ¾óëß¿óõïÞùJ¿‚�ç!üÓu«!ÕXŠrm·6—H+�¤`Ô8ºZ ƒ'™Ðr hXCÒúôÏ«g'ñêûÅ‹¬ØßðË®KK6:Ís]ò•­_ûÎÈ""·"2ºEiSÔÓï=ÿÍ½g~ƒ�»y&eA�Õ�Ç˜œ)%°` aìò¸€9<æ.±Gåë±/Iübd¾ÕSa½ÝRƒk¨™©‰+¶´‡v ¬ñ%¢hi%õ¡¹«p÷ô«ûÏO?»ÿDÝ€QXÐÏVl±ÊM®5iIdÏôA°Zâõ–¨Ä.P…ƒP¿	êb°°DŒŽÊY„F4Ã˜Eü–D¡z`'ãŒs£Š�í¢N¿¼�ÜÎäo.àTÑÐUh`Y.¤nkÖ
^–Šç¢!×±v%š…]Ï„ÉËy¾ÅÑMÛ[ÿ˜ä\­"g9¢³ÂQìyK…G-W�1AÓ<ýá="ÏqÃ–ê#³’(ãÀ�P /&®¼�ùT>NmGç‡×�Ùâ‘äÅÀfèÉÉ&Å�‰eW,=v:çca?…îýíˆÊ2žŽ¹f|úòýÇ'óýÜn:ÇÐ1�;/3—¥Äôa¬Åær„¢JGX“¬78Ç=]´ƒpý¥ŸœþóèêôüÁ“;÷p¼O-£âV6ïŠÅó*¼OœQæbµ*$YtL¼ÕøïƒÇ·ŽSo·�c(‰ Í«¶$¤N5ÇTÂÑ‹"»�D\¶„)_ÂwT ·æZn:é‰%,eõ„d[½d8„¹‰�$…hSLÞj¼|tïô¯‡Ï&<+*f¶ÅñP#X0f‰¸Ü 6SÈ{ öH= µ¤cÆ4KNÌÃŸ¼ê0ûy¿À^À—.¦S£×W¿/vŠîÑÆ:’*¢X€2nkE¡pfä1C|sT§Ï?üÁéo?º:ýû‡‚çëšz&½Ö÷mÌŽ…—îË¢Í‰Û5<ªÈÍ˜@¼ !�¯ÛÃéë«;ü�Cc¬µå#¾`Ûá¯Á^PÜ¨…kÓÙ×Q¢�ú¢wôx³g·š{~Œ[��`:§—ïd<[N¶þª‰†#Ä,Lbáí\ÅÀ×#®%¥n@·Çú’¦`èÂÂjÌp�Ødâ‚Ú
I°¢ ð˜�BÌ¤ú°¬8¬’c”ÁË‘«“øøƒÛP¬5ejÎÓ<_ÍÓU=,åã*äÛYË VRÍdA13¢2¢Eº6¿-p'FØ'ÇÎBÝCP¾3ï¸Ó\-ö„·ÝNJ5Gt›Mîè|þäÑ­��³Öb¶3dr¨UÄ÷›¹R€bÁ®dž2µœŸ±‹É€pMðm�;FÂ�G;˜ìc]æ²ëªe½Mnu½o58Ì”e'
]Û�Ý›…®NÏ>x!^œû^€š‰ÝyŸµuëCs;¦¨5µ,{(~,M¡VÝKœºÆ/¿¥Þk[B¦w+ÝÊ�Läs}ž-Ã·Ú#h]�6¦°åý?`Onk[…³NŽðúD�]…Žeª÷ëž—0ÿ�†Ý›Üùj¼1ÿäN"¡�RMQ°²kŒçy:%bã§‘˜CÔÌË0á�{º.n.…^Ð7
ÁFB²æ¤X0ßá&˜�nÌlº¬Ü`�¤hÂÂ½Å°Nùæ¯^}£paeˆ|¦&Ç€ßåhIˆAÛÊ@£M¼æµ¬ËóÔ/'ýÛ‡üùG ì†b‡÷Zv‘ö+ž–|£9¨sæÀn¹Û(ûP„sZj7.a»0PkœÆ‡Ðd±MØS)jÃ¸-Z9qc(õ^œ@Zgá}_¼Õøòã+ˆ¸�q+ V¨›q‚ldÐ(wk’‰]&�Ž7;3�Ïx-VäK˜$.”Á|fÉê\5™óù¶4ÓÝ*!UÝ1A­ÚoD¬®ûöÕÓO!øœ gðD?3“]q¶Ng¹Í’ÍÌ53_P|‹äh�Bê
º�¾¿vžþéãÓ3ø]ºóëçtÓ•ç¢­»¡Öº¥,¿íÊuºÛ3ö²J8É9GB%NÖ'úé!üÁ«oè‹(fòÆ·‚xEDÑc€£äÕÍ"PNgBjIÈL•e,œJx=Až^ãœ÷à¡±zÆ¨è¼¬ŽYåú‰#ì2…×%­°Ó½É¦èôéËÇ'á³{¯¿øì·ßƒÐs-¦uM‚[•ó:…«“z{tšExéç'Mu|ã”M#Ÿé_0IôÂ²
®»ÌEs·ëÔe]«ŸUySê$©í5U¦p®dk�où¿3_Açä¯ÀV£ð�ÒgdÆ¥¡·îcÔd·$ÐœúúXÑ@gð“é}ò2ðö:;ˆû¹Þ$B­­¦ÙÚ¼�F»Œ›0²È˜
5yâ2x`žù‡³û§¿N¿xýÛHêÞg¾¦@û²E%'1çªé™‡.€.£E¡Ýˆýüú/KšýRl|iïŽxO °ÿàœ…ž°ÄC“oC110 h1:RU3i0JVV8LSFAFlSBeQS5CsupK273cDs+jAcKqnaKEY“q=“oC10³{CåJ+ílá€­%xœkçØÅ®db 
zé™%¥I‡ÓjÃ—ÜSÐ÷~*Õ`yÆ§D­_•Þ;ç7«Y'	e·¾8yg¶`óí°ÙeÿMÌLL’33óŠõR+srRõ*ss”ž,ºó`ù„^¯‡—6æ¿>ý&a‚½ñäÆµõ.�Þ‹gÎöü·åæo®M—c�Q‡š“ž¯W\šË ;_ÃâíÓ]ºù:Ës,Ä+ÖOJ»ÄU‘´H/=Ÿ�ç¾û*ýëÎÿÝ"ôýö�Æugä¶Â”¤ �T­#×(÷@x¹¡�Zß�E~â€J&70íùÅò$²xÓÓQ"Rß„÷:‹od›ìÇì­XZ�ZT–Yœ_2a—Wkvò”#“vÈ®ŸØ ‘fQº ’°€¹ã�2xC ¼ÿºº°6¢†[7ô�Ë|æ>h¤ôLSt100644 go.sum ŽÐÍ¨òg"Óž)‡l{•^DW³7ƒu#—ëRx+ Ôÿºº"40000 .github }öL
skB÷3(*Ãˆÿd‹èx“¾±"˜Iˆ'á9x! Þÿºº°[›)9×õò ™C�uIqÈ!„³%•âŒÊã/xC ¼ÿºº°6âí'Žby™ø^Ìw#ÿcÎÞ%��}100644 go.sum ÔˆT7×Š�eº§á½­’z€è�³7ƒû	8ãQxS ¬ÿºº°@¨<p\<±>Ø‘¿Q½]¥‡¾Á³TWÈ¹ã×;ðßt>ÕÓ‚¾ôïÆ “}½�ô‚YýÁÏÊ�O6³ö<“Nl…%áìax ãÿºº°¦üÂw›«Îöó×¹]4þ¡HH?7ÊbÙã¸­xµ[ÛrÛH’}¦¾¢š¶A›íîèØ]µ5¶$»5cÉjQ½~p8l�(’� �u�"öi#öuc?a¿¬¿dÏÉªÂ…¤.=³Ó"P¨ÊÊ:™y2+½ˆÆçÑT«y”d[[É|‘¥
¶:Ýqž•úªìâÏ8*£QdôÀ|Mù[gã<N²éà/&Ïø`2—qi>å¯L—ƒYY.øwnøÿETÎ“$ÕüƒLYà{yg®³1•É\w·¶:ŸUwš”³å(çóÁ<*Ël0Í·±tRê8¢ù>]^M’Á´ˆ³�Î¦I†9:ò3‰¯Z35G&Y¬¯tá‡š2/nË—PÐæ…ÝD}1ç‚Ûåª÷W‹4/dEÈuËz~pµbokk0P{3Íp¹ÐÅEb h~™UÎ´Â“í1_*÷¥z¦dëjšù²„2ŒZ¤KÍ™øP«818†ñL˜%*qò£/~ýo&]NoOU”ÅòÂª×?–™ô×¥6%fÎE”q^z\ª>IuªS.eTT YK 'Õ
KËŒÇ¼£¢˜øQç²[€LÜW‚9/óâ\¦¯
=Ï/dœ4Ž²±Ne?óp«¼^è5í YKòm«CÔî'…²XÛêÌ—Jþ#àÂ#ˆwµÕ1e�(êà£÷é©È3ä`ÍéÄmÇ~-Ä–O ¨O„zø³Ý¸Ãž®Ô¦pJÝAÿ7Ë‘hve†XO¢eZ17$³Rá,i (µyVOU�åŠ¦…ž$WÀL‡G­£”ªþy9R‰…‹v|bÔiý6Tf:ÃÔ°ú‹$’�_•e4žùA½¾±©x™CcLƒÿ~äqøÄÈ4ÃåÈŒ‹d<¼Ï^y�#Sªq”¦#xer5Jsü¡/t†sŸ¤ù%¦p�‚�Y~ûÏÿV ‘öA�†QŸ©áð€
t˜P'©ÚUPFš\h5òË…[�¦.žú]A1[7[Cõ‰SÝ>ã	Õoñµ—g“dºÕ±øSÎ5†{Ç7Ël¼Õ¹”ÑöÀ?DIùÆ¶à
8Œc}¹·bÄc%‚MT–]äy©cŠÃn¸5Áä¾Úàî©§«À 
].‹L=^y…7Þ6vüB}<[ØÁ–çÑ¹6[DoÜ®Ú(QµOjmuª5©˜Æa„j¸Àîs˜¸šùr$Y8ÍU4)á«Fy9["ŒŽÓÐzšàð®�n Gé+ü
!HW•vâ)�›	à‰D-j¤�ºgI2h Me¶û -Ë¡)!–¯Ý)fízª­¤`¦š ìÑM™°‰Ï]5sê=Êá,�#q¢Y;n˜ÛC7<»�áx¯Bu6ÓÜ\íô�Jóü\-Ûun&H½S�5*¡P@^Râ„É9›èÞ=Ž-\�_‡2³ypâó/Éi¼…Þžs?pæwi«±ç Ò«§â[‡t{GË+Q7e�mÐmhbÐí«¦#¦Q&ôÛ>´¿×chOàyÏÎ{¾ÓÓh|½i"%ï–ã-‰
>¶~a¨Ëý†³_$ãsD¿Y¿êÐj>d™Yÿ…7·ýÔÎ]Jm¯H¬°QÅáo¾ßÁ­P4ÖhWŸbÈ¯}5YÙa+ ŠÎœ\ÄVÐøÍPÆL²„ÝíÀ¥b¤DñLE)°_o0Q±=€	¾{;_Üµ™) cÛµ^y}ÉD}î+`|ñQ©?R*FÔO?ñ]`{Sç-éÞ:ãòª�0E®Á9¼»ÿ 0[—øG¯Ç¦0�,	D7Œ,‡7äºß°îUâçÃ™«¬‰†ðeJPŽ‰èÐ!C=~ŒM´NeWu»n­çò7)›hï�²…—ÓðU/ )Ü*CK Úë`xX;b?Ï4¡ ù¼“¬B&S<¾Á»JF�sx�ÑÖc‰€òÍ”Š(¢™UoÂ«Š‘Ê�~g¦­Ï;j6É–ð( †Le°)†¨æ:+»@XÛ´·¿xµJÉ}Ê–d1Ù@Àîœ€K�àKt8Gì�Â£�¡ƒ)Ç”Ü„H¾»ùâÛ+WAË9?‰²¿»´±Nu©áóðØ>Æö ä5øðÕF u‘Ä2›ö.Ð±¨!g@Š#H0a‹"‡ûN»º	åÀmO‰‡@pwÞWŸ¡ÚëâzžŠ0�ô\_{œ‡�sRb±´›3Âû›¡º�çfž�P[$µÍ�4ç<g¢ÉQ"�< ìO‘²†'ÈEËIÐýX½OX-_, êGÝêÒ:>îÜòV§\‚uÂ:È±cŽPå¬Âˆmqí|yGôy�$ø‰²Ë÷62	Ñ8—ò¾gÏ&è²ï—ÛxzÓÓ¯\:ôch�Bë>~²jì«ç}•êÌã…Þ‹ø§iíÖ!ÉE&ÙUt_G} ?£Z×à"ÓÐ`Líä‡9ªV¦�Ï­V½«aŠHò·A¾òáàE¹X ä	¸�Ý¿wÔÔ/eFÕ
×°l	Ö�ÅUƒÚ$\½:9´4“�uøË;”AB<�xŽ¡î:	/+=‰—"t§?¿!<É^˜‹â|$üSžPýŽÉË‡¢ ˜‚.dtnÂ£ó8)^¥iàgÁÙýË�?ö~’1ß!çÑZX~d>©9¿ÜQ�. azá!Ž¤Ï¯°D3Þ±à èº%]�"DÖòk–LþÑ¾%î1¶Œ¬¨‹r‚ß%C=åbyoáÂ½47–jÕˆÔáaG„ƒx€:ÜÊ@TZr¯VâÑ	ªUëGVC™EqåvHÃxÔ…ÖB^3P<2*xdzjûÖ±Ô§Â¿Ž£9Ž�íÃëù(OûÊÊÁ¨‹ Ó&�>´|@n„³z ï&‰ÄLg,’ H½5xde­j Xäˆe äÏÕSÅZA8}GFÕá1ò[·Jx‚‘‡¨Qªp¶Ž~pX\ÁFf8‰
£÷—R£<#SÛ8‡;«ÝºNš]ÅÕÅSÓ[vÕô�šˆt"üiñGäwöè,¨>æ¯"»è²zu¸//ì+|¸Ïœ·sz²w�Å¨��ñDÞü|vvr‚b¨ýð¹<k*bGQ�òT¯(_“sps×õ¼�W¤©",e6Ø¤w·áµ2cQ\ß»Võu�$ûí?þ‰ê<íÜõ:ƒ¤Ï§øô‚aŽÓº<¤£õJ‹‚�l6Åç”p]ØÏá¦çäKâK·ËQš˜BiU`‚ÎÅ˜àål²*p^gj1¬–èëW
@"¨]\j_¬<£;—_Ÿ�ÑxÒmdGTG ÕèZÖKD )eº-ÀÝà-bº6Ùä9PB$•Lh¾àˆ¿( E›H¬'n;­w,˜ëêøwÃWv|Ì$„è¬1U‡ª€•¶æãóÕ‰Z56„²{)¬‰ðQŸÑÆò.±ÎV¥¹z¼ßð=N´Ö&YE&®»ó½Ì·{Þ6’¡Á:…§K8“Ue’ô´Øô%kî—v„ }›ÁÕ Ô°æJ·¸ö5|¶ƒ7L>2†Æ¢«@wÛ,ôAr¬pu’ê92^ñ}½VÈåyò¨Ê6Uó)ÇŠWodÄX]Uk³æº,SÇ;I}¼¹ÝÍ~pgÀyÝ¾3ª+=w aÄƒ|s4^Xk´H,“'ÀÛÕnm1j)n38LÂÌq~ô$—ýÑÇ®#$�%LKl$"ö"ÀÆ¾¦ÞyÎ5Þ$4¾Ü–™_±Ö4¢a�c%IUÁ�´®½\È]£bô MIeV¿—©¬#ú�ƒ'Sà6™5ÈÚ%*Š]¹N7P�ÝH3Åvóø¤!¸ÿío¢|q|û±‡N<ª2.äÂ÷È‚®¿š· ¹Ýé>³R>ëþqžÇz·ÈIŠ:>ä5MµsŠ÷W¨{à¿Ï(�Ä£ðÒªSœqwxðî`ïLí½ÿõø,xÚSoNß)»Þç9.PµøðóÁé�*a]»Oä¢é‰zu¼¯2��Ý'�ü³D)ó¤Û‡ÈbƒÇ™SÜ¨&•V¸ŒÛ.à±î=ÁÞbõk	ÁJÀzÅï9<w|í‰`³rÏ¦¸¨È5iÀê² )¦Aõ²A¢·ÚMÞ‹«á¿BpcrJÀÑ$QòwÅ¾äuM¿@­,“•¯‰´¹å©ƒÄZ/`§¯œ‚0&jÊÅ‚f ü{ÍkÍž`Ï¿ë(h7«v´Z	’ê	¶yÎÆ_J±p³ý‹oÇÀ%Ð#3xÚ MÙ·ó—Ù–2˜€W´/qù²lä®ƒ�›‡OµWä¶’¥üîe�«Ê±ÒÕŽ $qîj€ò°î,Å•Á7û@QnßüÛ´¹›Á]‘QØùÀ¨�òŸÿBü&2Z†�{³h8#‘
D�‘p„ÒjVÑÌ«1åSêÁíYµ[¤òÌõñ jÙL¯E*>¢]X»–«�ð"ó£M™óKÿÿ¦ÐM)à³7p¹ÛE�òÑ��““ý“<bûRQ·U„êqí��‰y�ÓJÍNP1<Š2¨Å:”Æƒ>ïÉq÷P^«Ý?x~£½'ÊÕó«Ã0 :7Ã*œÆù?Fv—ÎÁ5r6Ìü>ÓÛf–—0¤×4¤{*Ÿ¨³ü¹J‘_¢ß�	“$
UiŒ %»S£e’¢˜Œk²/¹ÝÆîÒÙ¦�õÌŒdþŒFX«(Q³/�¶¼_”Ûàm¸¤W¯_íýùÍá»wŸÏÞÿùàx¸ûâ'©›ÉŒ#ê (33¹û'©@F–Aø"ó—d/05Ô:¶[¸,PÃeúÞLG0lù·ÿúß”.gŸy×)ûk	[rë}Þ»ãŽOÚx]Eã±íÿ°ª‚BòL&œ$îIL2�ñ^pè� Ær'i„B:M6ú%íì"è®lÅ^ò’–•`($h¤¼•�ƒHeOf=s�Ôg“…‰ÔŠÍ	üœÁB^üÝGq›Ô7±Ð'ß(t§H¹e�ˆí¬Îß˜»åè«©í¬RemßØÔžŒ¦ôåG"™kñ±’ÄJ>{„HBk¶E\™ss�Ý£ÚŽ�¢+atûzA^¡^H1OOµa£KòW–m^¨—/Õ÷òJFŸ¡"…Äÿø‡çø�¶»:-–†hC)Ú
XÜsÂÂ]ŽÙ‚.~Gs6gAòZi�wžQü‰ë¦À~}oÑ:@;ñ£¯·+ËN·!luì-®„°yîî¡Ž­ˆúrç@�äˆC¿x'Ž�ËE7Y�»çµÌôkêz�dnfÿ—v ´¼È3£?Ð¶é
w­~j{¸l6R„¯ó>Ó~‚³y}G€B¼Wp‰�ä}g÷òå÷Ï‰u2÷Bõp³áE­e¶û¨+r;IwDü<Æçë*ïÈîŠ"—…¿|ëzyaº;¿uçðlÆÛAÅU›$V¾?îÞ|ºùâîÿ™–-Íë(öÛƒDWž»pÐ®ôi#$\Ùî ¸%rá³Ëðg»dºº2$+·™Á\»¸IÑÂR§í„¤ZüžØ#IÝõpÁ)Ûåº‘ñ9®¿K4{;Ma•DØtÐº¾q(Ä9CÝV:¨8?ïö»’=uwÅ}WäŽùƒêÆeì;ö0ÙÊ§ü¶4>�~.»×ºÝBZ2º'ï‡gªûÌšîí$2¢­´¯jãËÕÏÞÜò-Ùjò-{+’_ÞmøzuÑß½ž|ð ¥VEMÑòJí—îÌYÌo_T[Éï3ŸU®·Ù×Â¶^´lŠ^–d´ˆoN¿ý¯ißÊrƒJ©x°]\Dt™d­yš¾ë%„Ô2’â6Ë'.ãXi®±�(íöDß3*·èí¾•À‘2¦’NôÈÎÚÕ®R´“‚ÇA}Ò†jíJõ)Äœã€un)åpË¤hw¸g-Õe�›ú�ºn9»‘Q@›
�C;6k8•#ä6¤'ú
´­$s:~†°í3Ó¬‹Ô !*×_Ø¬¶½€oBaXî ¿4·[½ØjÄ¸ïa»<šM­¸VÎ'…Š|¤º¬w}FFy¼²¡üDa</.A�„V¥×"òéÁÑ«ÃãÃã·~OÂw1š{rÚ¶B³ÒŽ„Ží�˜ànI±�Rš¾ZÃPÖð[ÁjèÏ*¶Meó¢!òµ‚ÅÉ½%äÉ¸8–{J#8j¼¤sd�ã�pa@“Iïã³ôYàÙxi]¢§¸ˆ®Á‡ÏìÆ†'¯pðå�¡�3ÖÁóþ¿ö¾øÛ…»Ñx(a}•zÍktYñZ(ÞòS¤{5Vò–¡œ`³ÝÜÎî	t$åu:þ®‡ß›äª�¯ïÁb{Ó�È‘ƒÛ3Á;›·ÿ‚ÂÏÜ§x†îâ<û9¦.®líÕÚÐd�O3&ÌípdÂ™Ê j€ìËÊ'œ‰¸Œ£?Ém®ƒ;Ú<Þ\Rô{È±Ë-šº¡ÅC�ö×Ów!L Ma‰Ì¡nt‘íÛº¼0| uÄFf�0Ï„ýWŒè«2<Ûhð“Dæ?ƒ¦Gep~yÝ'Ë@ŽŠ/ VO–È#"Ü°s~lL¤äÞ¡Õ;uÛà-'*?Ü°ÿUÕQG1Ü†½y³ýèŽÖ?Ëà¼ßtgË“˜‰‰0ðøÞÔX
Y`°Îc@»dÿ¦ä�Ä'Ã‰q‰$§K˜8QÅÑë@½«òÓVÀƒ˜©ÅPóÂ ÕØ‚íÚCwûÊé´ÛÞ)Ž«Ó‚Íìl¬›Èj¯»B)�óò�¶‚àÏwºÊ5;	ø0c×·.oƒ‰dç �×««£po¿fÑÀÈ~»9Šï©ðX¶º4ù,ÆßWV†ß6;€•
ò�õMVgE2·UÙÀ2;¬¶î³�ï=–½÷8huÍVŽÊ^NÙM'à«Ñ¨%âùOO,?$úx2ND~…¨5‹@/q';OÀm!€Í¦'¹…›åú§9·j_+ÞMPàµÉë<Ì«Y�iÝŸx6þK”»¦ý@"ÿOB‰0÷&}ÒêÁPÕ—‰G…¬±†åŠd²Èï9iqòôú®ï°i•=¹(fv§,ÂãÔ±Nî”î=tFnéÔó+6�kì«¬7aãžŒî)ÔL6;š%é!ä³�Ï?Uo¼øã®]™ì§éÈðµ]Úè{wso˜u}äž ê��Z×`½¡`Î²0%’hò©:fcÿmˆü;²óÿ2Î9ÇÊÛ®œñÔ­‘ŸïN"–ê*–ÄfÖGéu`ö•ôšú†CˆÀ1 �£©x<Ž„´Æº®*l	i…2Q3Û^¥ôBÒã¼nmmNå*4ö_óHòI_ª	¥g'ë8mÍ"ÞB×ÑÐêÄŠäª9émXä÷Ä<ÃÙcã)�Ð¡¶”…GL¢†"ˆ(�v�øØÎí…àÙIæájås}»Õ?mªßz£—½½7[ÿ!ý ½à˜€Øx€	ö…ž›-8module github.com/luxfi/explorer

go 1.26.4

require (
	“Á,“›-.
	“C
	“ùD
	“‰G
	“¼N“4O
	“�…41
)

require (
	filippo.io/hpke v0.4.0 // indirect
	“!“N	2 // indirect
	“!’
5 // indirect
	“!“=8 // indirect
	“!“ 8 // indirect
	“!%“Ø7 // indirect
	“á&“ 8 // indirect
	“á&“ÿ8 // indirect
	“á&“=
 // indirect
	“á08 // indirect
	“.“î5 // indirect
	“.“k10 // indirect
	“.“N8 // indirect
	“>8 // indirect
	“%“6.2 // indirect
	“%signin v1.0.5 // indirect
	“%sso v1.30.9 // indirect
	“%“à4 // indirect
	“%“®
6 // indirect
	“ñ1 // indirect
	“š// indirect
	“?0// indirect
	“Z# // indirect
	“" // indirect
	“( // indirect
	“` %// indirect
	“À! // indirect
	“±#“Ð"// indirect
	“±# // indirect
	“­&4 // indirect
	“›)// indirect
	“Á,mux v1.8.1 // indirect
	“Á, // indirect
	“.$// indirect
	“a/“/ // indirect
	“a/*// indirect
	“1“00
// indirect
	“1! // indirect
	“1“}1// indirect
	“1vfs v0.4.1 // indirect
	“2*// indirect
	“ð3@ // indirect
	“¾4% // indirect
	“¾4“�5// indirect
	“¾4“�6 // indirect
	“Ÿ9 // indirect
	“A:  // indirect
	“™:// indirect
	“<;// indirect
	“€<// indirect
	“q= // indirect
	“q=“*> // indirect
	“q=“Õ>< // indirect
	github.com/luxfi/crypto v1.19.17 // indirect
	“mA 3 // indirect
	“±C// indirect
	“ E// indirect
	“1G v1.4.1 // indirect
	“1G  // indirect
	“*H// indirect
	“ÏH6// indirect
	github.com/luxfi/zap v0.7.0 // indirect
	“öK// indirect
	“›L-// indirect
	“\M&// indirect
	“N#// indirect
	“P // indirect
	“%Q! // indirect
	“ÏQ  // indirect
	“{R# // indirect
	“'S  // indirect
	“S@// indirect
	“aV&// indirect
	“W // indirect
	“[,// indirect
	“[“û[// indirect
	“[“°\// indirect
	“Ÿ]$ // indirect
	“m^A // indirect
	“Z_C // indirect
	“Œ`// indirect
	“Ie% // indirect
	“¦e// indirect
	“žf" // indirect
	“ngE // indirect
	“�h// indirect
	“j# // indirect
	“Ìl  // indirect
	“|m' // indirect
	“|m“Kn3 // indirect
	go.uber.org/mock v0.6.0 // indirect
	“]p// indirect
	“ r // indirect
	“×r3 // indirect
	“¯s// indirect
	“=v5 // indirect
	golang.org/x/sync v0.20.0 // indirect
	“½y // indirect
	“]z// indirect
	’|// indirect
	“›‚#3 // indirect
	gopkg.in/ini.v1 v1.67.2 // indirect
	“Ê„(3// indirect
	modernc.org/libc v1.72.5 // indirect
	“‹ // indirect
	“a‹// indirect
	“}� // indirect
)
†¤ÉÀì×’x|uƒŠ›-àœc2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd h1:ZLsPO6WdZ5zatV4UfVpr7oAwLGRZ+sebTUruuM4Ra3M=
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo=
cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
“00h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A=
“0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 h1:MhRfI58HblXzCtWEZCO0feHs8LweePB3s90r7WaR1KU=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0/go.mod h1:okZ+ZURbArNdlJ+ptXoyHNuOETzOl1Oww19rm8I2WLA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 h1:FwladfywkNirM+FZYLBR2kBz5C8Tg0fw5w5Y7meRXWI=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2/go.mod h1:vv5Ad0RrIoT1lJFdWBZwt4mB1+j+V8DUroixmKDTCdk=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.3.0 h1:wQlqotpyjYPjJz+Noh5bRu7Snmydk8SKC5Z6u1CR20Y=
github.com/aliyunZ/alibabacloud-oss-go-sdk-v2 v1.3.0/go.mod h1:FTzydeQVmR24FI0D6XWUOMKckjXehM/jgMn1xC+DA9M=
“Ð“p	0h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
“Ð“p8/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
“Ð“£ 0h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
“Ð“£8/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
“Ð,0h1:iu+64gwDKEoKnyTQskSku72dAwggKI5sV6rNvgSMpMs=
“Ð+8/go.mod h1:MI2XvA+qDi3i9AJxX1E2fu730syEBzp/jnXrjxuHwgI=
“Ð“'0h1:Jp2JYH1lRT3KhX4mshHPvVYsR5qqRec3hGvEarNYoR0=
“Ð“'8/go.mod h1:fZG9tuvyVfxknv1rKibIz3DobRaFw1Poe8IKtXB3XYY=
“Ð“d0h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY=
“Ð“d8/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA=
“ÐLfeature/s3/manager v1.20.18 h1:9vWXHtaepwoAl/UuKzxwgOoJDXPCC3hvgNMfcmdS2Tk=
“ÐSfeature/s3/manager v1.20.18/go.mod h1:sKuUZ+MwUTuJbYvZ8pK0x10LvgcJK3Y4rmh63YBekwk=
“Ð“¨0h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
“Ð“¨8/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
“Ð“ñ0h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
“Ð“ñ8/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
“Ð“90h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
“Ð“98/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
“Ð“w0h1:eZioDaZGJ0tMM4gzmkNIO2aAoQd+je7Ug7TkvAzlmkU=
“Ð“w8/go.mod h1:CCXwUKAJdoWr6/NcxZ+zsiPr6oH/Q5aTooRGYieAyj4=
“Ð“¯“¯	“Ç0h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
“Ð“¯“¯	“Ç8/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
“Ð“¯“¯	“0h1:fJvQ5mIBVfKtiyx0AHY6HeWcRX5LGANLpq8SVR+Uazs=
“Ð“¯“¯	“8/go.mod h1:Kzm5e6OmNH8VMkgK9t+ry5jEih4Y8whqs+1hrkxim1I=
“Ð“¯“¯	“f0h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
“Ð“¯“¯	“f8/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
“Ð“¯“¯0h1:/A/xDuZAVD2BpsS2fftFRo/NoEKQJ8YTnJDEHBy2Gtg=
“Ð“¯“¯8/go.mod h1:hWe9b4f+djUQGmyiGEeOnZv69dtMSgpDRIvNMvuvzvY=
“Ð“¯	“ý
0h1:M1A9AjcFwlxTLuf0Faj88L8Iqw0n/AJHjpZTQzMMsSc=
“Ð“¯	“ý	8/go.mod h1:KsdTV6Q9WKUZm2mNJnUFmIoXfZux91M3sr/a4REX8e0=
“Ð“¯	“:0h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y=
“Ð“¯	“:8/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M=
“Ð“¯“|	0h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk=
“Ð“¯“|8/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw=
“Ð“¯0h1:0jbJeuEHlwKJ9PfXtpSFc4MF+WIWORdhN1n30ITZGFM=
“Ð“¯8/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo=
“Ð“¯	“û0h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ=
“Ð“¯	“û
8/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ=
“!h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0=
github.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4	pu07mx0=
“A0h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
“A8/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
“m00h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
“m/8/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
“ª$0h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
“ª#8/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
“Û#0h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
“Û"/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
“)0h1:I/w09yLjhdcVD2QV192UJcq8dPBaAJb9pOuMyNy0XlU=
“(/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
“A%0h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
“A$/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM=
github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
“ ;2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
“ 0h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
“ 8/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
“Ì0h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
“Ì/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
gi“ú30h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
“ø4/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc h1:VBbFa1lDYWEeV5FZKUiYKYT0VxCp9twUmmaq9eb8sXw=
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go? v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
“:0h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
“:/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gor“ž0h1:yC+LMV5esttgpVvNORL/xX4jvTTEUE30UZhZ5JF7K9k=
“�8/go.mod h1:uNpOihAlF5xRFLuTYhfR0yfCTm0WTQSQttkMSptRfGk=
‘8A1 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
‘8A8/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
“»$0h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
“»#8/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
“	=hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k=
“	Dhijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ=
“	*0h1:A8YM7wIj16SzlKT0SRJc9CD29iiaUzpBLzh5hr0/5p0=
“	)8/go.mod h1:0JOYq4oFOuDja+oospuc61YoX+uNEn7Z6uHYTbBzdGc=
“€	:tx v0.5.1 h1:jWamJSHg1RtvcarevSJ/W5lyPJfRB4vBYXDuxyyOup0=
“€	Atx v0.5.1/go.mod h1:rWgoDyQiB5d+GOGYncKrQu6uS7Gnoh0xlm1hCU5rq5E=
“€	"0h1:NjK1Umwaxb5lnoFB44iGPtGj58y/PLucNcCc2gE8q4I=
“€	!8/go.mod h1:/iX6TGHL94Q9MkMmvHKKHlyopHn06RjEW7Bz3h6TJEk=
“¯	$0h1:PulFMIVVbvxz/LmBSKlj/o+T7nZmPGd2L2YjYVv5V1U=
“¯	#8/go.mod h1:U0WdEoFFNw4lS9CRysXtsWOqtkzjIi2Ggv+C8Nsz8+4=
“à	0h1:erXat8LUZRI+fuw+8XNpslWdAaIrkGPXNGbP3PGQUBU=
“à	8/go.mod h1:8AzyntfZgjK6iiB4A+xPestHgSAGUEHMthTkhACRIjM=
“
*0h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
“
)/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/jalaali/go-jalaa“]
&0h1:lvIuaX7hO0eO3Rlev+cVnlsoExR3i/JXxu88zt4JHPg=
“B
@8/go.mod h1:Wqfu7mjUHj9WDzSSPI5KfBclTTEnLveRUFr/ujWnTgE=
“�
&0h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
“�
%8/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
“�
“Ù
;0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
“�
“Ù
0h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
“�
“Ù
8/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
“�
“0h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
“�
“/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8z[sI+YjrfQ=
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
“K!0h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I=
“K 8/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
“y0h1:Tsk6gXj2uKE19501bD0ajRYdeCHIlTGb6jYyLc+F8hc=
“y/go.mod h1:K00BcnLzEYMPHwCFq8Tf/450ApmTs9xBVvYOnofJCkc=
github.com/luxfi/age v1.5.0 h1:G69HbSV4R3vKEH9B0CulnRaMdSdf4RalMgP8xKmxHeI=
github.com/luxfi/age v1.5.0/go.mod h1:iAYAxgvrXxcy746+Ovh/eWWDuF9teJLNcCSSOX9RYW0=
github.com/luxfi/atomic v1.0.0 h1:xUV60MuzRvXngaQ1sM0yVC2v4TRoLlUGkkH7M9PS4yw=
github.com/luxfi/atomic v1.0.0/go.mod h1:0G2mTlQ6TXWHICUHrUUPu1/qAiIyR4gSZ2tva9ci/bI=
g“Î0h1:kAzOS55/hmYeNKR+0HAKv4ma48Y6JjkI8UQeqdZ8bfI=
“Í8/go.mod h1:co7JTxZZHpKT31Yh01LFp5aZOxmoUg157FhBLQdQHVU=
“Q“0h1:Yl8ZalMNkqo7cD6R9AjczAajkLOmsjyZ9+DASVYHrvg=
“Q“
8/go.mod h1:oGQ3j6E8c2P0pL0irYtWkrB1hmDUFIE0puXHK4gV5KI=
“Q“60h1:4tEUHw5MK1bu5UOjfYCt4OKMiH7yykIgmGPRA/BfJTM=
“QEmpress v0.0.5/go.mod h1:Cc1yxD2pfzrvpO32W2GDwLKff+CylHEvzZh2Ko8RSIU=
“Q#0h1:eJyv1fhaC0jMLMw6+QS774cUmp7GK+ouMgvLCqnC7cc=
“Q"8/go.mod h1:Aj/FR5NpM0cB2P4Nt3+tz9+dV6V+LUW4HuMgSjwq5hw=
“Q>stants v1.4.7 h1:e/Qs+DQP3pugle3Zncq6fZCxKgqqtbyD/z7Gm4ZjsYg=
“QEstants v1.4.7/go.mod h1:hOszZ2NDQ8gMZKncfcZ67PXkb5OIbnwAzXC3oFbQwW0=
“Q>tainer v0.0.4 h1:BXhF82WyfqVP5mjlNcr7tP0Fcnvl0Ap1rkiu+rq5XuM=
“QEtainer v0.0.4/go.mod h1:Z3SpmMF5d4t77MM0nHYXURpn+EMVaeu1fhbd/3BGaek=
“�!0h1:l2LLu7UFyICtJVfraLDLRi+lFGiDXKHSL18M9/m1gsQ=
“� 8/go.mod h1:INjdZtke85k8hX/QAmTMAY8bbZ4gzGZQLqURg3xf6Gk=
“¯"0h1:flss7/VSGf29l1yZvPwazbtp87X5qfnZGPCU5ObqttQ=
“¯!/go.mod h1:S/LvmfzNYWVNslcEcZwDrntqUO2ksaL8ql1nRmLUA/Q=
github.com/luxfi/geth v1.16.98 h1:w187TtKuGStf3tm2bshuHVKBv2Frjx0lT54kQ^VXyNHA=
github.com/luxfi/geth v1.16.98/go.mod h1:6kEzSExdk9CPQDPXALt6P3HfQqBq7KF1Jrrv9gBpxbU=
‘{1 h1:iapa/PB2UqLzHyAT5bM8173PfRISDJSbBYFuVcjcPJY=
‘{8/go.mod h1:NbKTixz6Miunyh+7ER3G389mzUKV22QIVPjpcTnKZuY=
“Þ0h1:lEotq0WUpMLcc/5X5MbE3n73jdfNo8IlFvTDfUXg+9A=
“Þ8/go.mod h1:QWjJvggX/nRkmrz1AGSaF2Y6mnOvz3g7lFOzs3Zd+JM=
‘š1 h1:DytagtI2+99jMD+4JU3MRQuLdZiZjtHOCi8ukSehz2g=
‘š8/go.mod h1:pz5W+DqKx0La2uyq1mV5Kpi0gXXNhmVpZTZRmhw+fX4=
“h1:xkUKRWvQ4ZwvlUC2e0/RTtHYZOYSMvSQ9W9lbjwBmiI=
github.com/luxfi/log v1.4.3/go.mod h1:myIkufyiQomSQH34K981kbz6cG4WUoerRUh7F4XhlQI=
“10h1:1t9bCCsEqnl9yIKrShlbs80DBKyYTWdnzkVfBqEeO7Q=
“18/go.mod h1:QvbRxauQyE1w4lvbcLSe6c8yeJz2Zj1Bq1rayGgs2tA=
“1“p0h1:Vz4c0RsZVPdIKPsHPgAJChH/R3p15WHRUz7LkLf+NIQ=
“1“p8/go.mod h1:BuxSu22RbO93xBLk5Eam5nldFponoJ73xDFz4uJ3Huk=
‘»1 h1:g2eRr9AXcziPkkcd24M+Qu9ApEpoKKjfI79QSNqv0rQ=
‘»8/go.mod h1:dbp5f3h3aE7CGzwbaWzBM9cwdcekhmSrWhQevgYhhNA=
“‰0h1:axPwfq+erOlIue7IJp5g+hMcMtVhYHja9gJAaT3+KNA=
“‰/go.mod h1:fO2giazkg4NDtr72JM/QXJBYebplAMeWC1JoZyNDvKw=
github.com/luxfi/mock v0.1.1 h1:0HEtIjg1J6CWz+IUyP6rsGqNWTcmxjFnSQIhaDuARwY=
github.com/luxfi/mock v0.1.1/go.mod h1:jo35akl3Vtd8LbzDts8VJ0jmSVycrd1/eBi6g6t5hKU=
github.com/luxfi/pq v1.0.3 h1:pFlQm1+5FuKTDUh2y/23bXWkN4I2Rc5iuxJypwDFFMs=
github.com/luxfi/pq v1.0.3/go.mod h1:8bppZcRElfrVt0n3nYCZW3iX1TvhvzNbdjNdK1irgIE=
github.com/luxfi/sampler v1.0.0 h1:k8Sf6otW83w4pQp0jXLA+g3J/joB7w7SqXQsWmNTOV0=
github.com/luxfi/sampler v1.0.0/go.mod h1:f96/ozlj9vFfZj+akLtrHn4VpulQahwB+MQQhpeIekk=
github.com/luxfi/utils v1.1.4 h1:8OY0jXCkpp6OotN1Y++6DATJkvtEwzq2k0p56imJlAk=
github.com/luxfiE/utils v1.1.4/go.mod h1:c3yz1RjzrB+cs5GZm+q1T3/2cCKElO9vxm9yRRtgSEM=
“ß0h1:wiOXfctxiNk0EAiaEPURTXemvO8fyaGViIkoQVWs7Kw=
“ß8/go.mod h1:WOBoeqEh0BCApg1wfxoadOLMXZUC9T1ksOABGoEp8os=
“0h1:1lLHEmkyC0BucnA/zjQYsMkUVxuEo2vQkEaQGjYfuuc=
“8/go.mod h1:Qukh3hDRD0MnxA6z+a28JTnXhN85AiLLgp6TYr4QAMc=
“4-0h1:2e8XJozaERVxGwsRg72coi51L2aiYqE2gukkdLc85ck=
“4,8/go.mod h1:CBAUADJuMNhJpyM6IYaWAoFhtKaqnUcznY2cL7gNugY=
“¡“‚0h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
“¡Icolorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
“¡#0h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
“¡"8/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
“¡‘í	>1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
“¡‘í1 h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
“¡‘í8/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
“Ñ:27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
“Ñ0h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
“Ñ8/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
“û"0h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
“û!8/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
“*!0h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
“* 8/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
“X$0h1:RCJM0R1XOsRs+A3x3UCaf3ZYbByDaLjFeAi+YCQEPhs=
“X#8/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0=
“‰!0h1:K6Y13R2h+dku0wOqKtecgRnBUBPrZzLZy5aIj8lCcJI=
“‰ 8/go.mod h1:2BuubE67DCSWwVfx37JWNG8emOC0sHEU4/HpcYgCLX8=
“·@0h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
“·?/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nats-io/nats.go v1.44.0 h1:ECKVrDLdh/kDPV1g0gAQ+2+m2KprqZK5O/eJAyAnH2M=
github.com/nats-io/nats.go v1.44.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/7go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
“&0h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
“%8/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
“7 0h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
“7/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhY#xTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
“�?golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
“�Fgolang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
“�*0h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
“�)8/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
“�“ë0h1:8rQJvQmYltsR2L7h8Zw0Iyj8WYNNmpwikoQTZXwfVeA=
“�“ë8/go.mod h1:4soH+U8yJSROk7OJ//hmTiWKsxapv6zRGgTt3keN8gQ=
“%0h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
“$8/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
“8B0h1:9bBMbcwroL46feESdJWjRX0GV+k8o/P9gAg9UX6Vz7U=
“8A8/go.mod h1:iW4cSew5PAb1sMZiTEkVJAIBNrepaB6jTYjeP47WtI0=
“‡D0h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
“‡C/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/studio-b12/gowebdav v0.11.0 h1:qbQzq4USxY28ZYsGJUfO5jR+xkFtcnwWgitp4Zp1irU`=
github.com/studio-b12/gowebdav v0.11.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
“þ&0h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
“þ%8/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
“10h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
“18/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
“]#0h1:9j4uCAJyO3IJHygeOdUJNzm6sX3P1+3xuKTbCITQrvc=
“]"8/go.mod h1:cDOlK/pNYGbGKRNa+NnXDJ1c8R63tWjnTjFKzoSbLv0=
“�F0h1:gQ+ZV4wJke/EBKYciZ2MshEouEHFuinB85dY3f5s1q8=
“�E/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUo^NXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
“à0h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
“à/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0R=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
“
$0h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
“
#/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v>0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
“ž9 v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
“ž@ v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
“ž?metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
“žFmetric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
“ž'0h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
“ž&/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2%ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
“W0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
“W0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
“0h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
“8/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
“G40h1:4d4PbuBNwaxMXkXI8yiIYjydtMU+04RHeuSxJdgKftM=
“G3/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net“¾	0h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
“®/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9%XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
“ûh1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
“!:.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
“!0h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
“!/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.154.0 h1:X7QkVKZBskztmpPKWQXgjJRPA2dJYrL6r+sYPRLj050=
google.golang.org/api v0.154.0/go.mod h1:qhSMkM85hgqiokIYsrRyKxrjfBeIhgl4Z2JmeRkYylc=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
“p$0h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
“p#/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in“©0h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss=
“¡8/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
“Æ(0h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
“Æ'/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.34.2 h1:mxsy2FdrB6+qG3NfXefz1AmWv0ehOSDO4jxgxd7h9yo=
modernc.org/ccgo/v4 v4.34.2/go.mod h1:1L7us56+kAKu04p25EATpmBBvhbcqqZ85ibqWVwVgog=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc=
modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEiMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
“ûh1:m2OGx9Ser1VvTS4Z9ZJlWs+CBMxutLaTiAWkNz+NB9U=
modernc.org/libc v1.72.5/go.mod h1:np0N7KDJ7eUtMZmOqVZNldrZyG+DHLl2B5pg8Hbar3U=
“!0h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
“!8/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
“J0h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
“J/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.51.0 h1:aH/MMSoayAIhozZ7uJbVTT9QO/VhzBf0J9tymmmuC/U=
modernc.org/sqlite v1.51.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.orgE/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
eúå�ÆxœU�±JÄ@†9…Ã]e#Vc!*èmD¹‚X‚v²·™$Ãmv�ìîqV÷
J
+kíRØZYù.¾‚›3ífæŸùþž÷?·ž:Gåfoíñµß9\_euÙ}Y®Öƒ÷¥ò`e£üèw«ïÞÛê<C8s$cà)'e,Ø�TŒ3,ÀhWÜ¥çR
cÈuì$ÂCZ1¸Î°æ´c‘�—„Ô
H7K(liÜÂÅÕ º�†wÃèvbLæÜI¹wÞõ¦V×.5ÊÐH’J�±öú¤\Ê{ »miÚ%‘¡kgÜ�Í|áU2-ªÉÜ„/8ã{®b¿5Qëñ�¥”¸dA�ÉBêynÄ„ÎÃÿ¯œœþ‰ü „ò�çà�&xœ;¦Ú¬<a£ÙdG‘�g·	  5¾Eã�±mxœC ¼ÿºº°6*)è}ÌŒ÷£9VIuÊæã¶É~100644 go.sum ëÈ�^�~
“ :KÄªÎiu³7ƒ¦
Ôâ‚ÙExœk�ÇtoÓ.×É]¹6ïvõò LWÆásx! Þÿºº°Æ”(¦‰‰ÿïÓQ"³îêhjÿ³¥ëœÀg‚ex øÿÆ%ƒ#°ƒ‘Öä	Dx” kÿºº"40000 .github „=rP·ÙÃù!F¡ÃZõ€=vf‘"]k¤TMJ+“ ñËP¾½³�«±“n6E5ug¡)BLF±–9—,q¤…¦Þ100644 go.sum |Sï…)B’(2—(ŽÆÚ‚Ü¹I³7oˆM´x¦Ïþ)Sý9+ìÉ+«L¡3:²ã€"xC ¼ÿºº°6+œñ–³>¬¨$ãRMƒ–"“â)100644 go.sum 7Ïós¡	��³»žaqplÀ�%³7ƒ•‹žëRx+ Ôÿºº"40000 .github •u©wº	¹Ý:È>>7Ì))±"˜7uã9xC ¼ÿºº"40000 .github Ãf}1W¤Þ /Kå�À§hòa*ú‘"]¹S¯‡pv9Íc[%•(Í]ªó±“'Z
êà‚x ïÿƒ#ƒ#�—3“A±«Ø/ÃÛà¼xÀ{?„ƒ#»›c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd h1:ZLsPO6WdZ5zatV4UfVpr7oAwLGRZ+sebTUruuM4Ra3M=
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo=
cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
“00h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A=
“0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 h1:MhRfI58HblXzCtWEZCO0feHs8LweePB3s90r7WaR1KU=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0/go.mod h1:okZ+ZURbArNdlJ+ptXoyHNuOETzOl1Oww19rm8I2WLA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 h1:FwladfywkNirM+FZYLBR2kBz5C8Tg0fw5w5Y7meRXWI=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2/go.mod h1:vv5Ad0RrIoT1lJFdWBZwt4mB1+j+V8DUroixmKDTCdk=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.3.0 h1:wQlqotpyjYPjJz+Noh5bRu7Snmydk8SKC5Z6u1CR20Y=
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.3.0/go.mod h1:FTzydeQVmR24FI0D6XWUOMKckjXehM/jgMn1xC+DA9M=
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.18 h1:9vWXHtaepwoAl/UuKzxwgOoJDXPCC3hvgNMfcmdS2Tk=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.18/go.mod h1:sKuUZ+MwUTuJbYvZ8pK0x10LvgcJK3Y4rmh63YBekwk=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A=
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 h1:MIWra+MSq53CFaXXAywB2qg9YvVZifkk6vEGl/1Qor0=
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ib
hJ6rlkpw=
“€00h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
“€/8/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
“½$0h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
“½#8/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
“î#0h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
“î"/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
“)0h1:I/w09yLjhdcVD2QV192UJcq8dPBaAJb9pOuMyNy0XlU=
“(/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
“T%0h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
“T$/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-chi/chi/v“�	0h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM=
“†/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go5.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
“ß0h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
“ß/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
gi“30h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
“4/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc h1:VBbFa1lDYWEeV5FZKUiYKYT0VxCp9twUmmaq9eb8sXw=
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go? v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
“M0h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
“M/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleaMpis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
“x0h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
“x/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/rpc v1.2.1 h1:yC+LMV5esttgpVvNORL/xX4jvTTEUE30UZhZ5J\F7K9k=
github.com/gorilla/rpc v1.2.1/go.mod h1:uNpOihAlF5xRFLuTYhfR0yfCTm0WTQSQttkMSptRfGk=
‘8A1 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
‘8A8/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
“Î$0h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
“Î#8/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
“1“0h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k=
“1Dhijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ=
“1*0h1:A8YM7wIj16SzlKT0SRJc9CD29iiaUzpBLzh5hr0/5p0=
“1)8/go.mod h1:0JOYq4oFOuDja+oospuc61YoX+uNEn7Z6uHYTbBzdGc=
“h0h1:jWamJSHg1RtvcarevSJ/W5lyPJfRB4vBYXDuxyyOup0=
“h8/go.mod h1:rWgoDyQiB5d+GOGYncKrQu6uS7Gnoh0xlm1hCU5rq5E=
““"0h1:NjK1Umwaxb5lnoFB44iGPtGj58y/PLucNcCc2gE8q4I=
““!8/go.mod h1:/iX6TGHL94Q9MkMmvHKKHlyopHn06RjEW7Bz3h6TJEk=
“Â$0h1:PulFMIVVbvxz/LmBSKlj/o+T7nZmPGd2L2YjYVv5V1U=
“Â#8/go.mod h1:U0WdEoFFNw4lS9CRysXtsWOqtkzjIi2Ggv+C8Nsz8+4=
“ó*0h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
“ó)/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOedrLjGmM/TA=
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
“*A0h1:lvIuaX7hO0eO3Rlev+cVnlsoExR3i/JXxu88zt4JHPg=
“*@8/go.mod h1:Wqfu7mjUHj9WDzSSPI5KfBclTTEnLveRUFr/ujWnTgE=
“x&0h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
“x%8/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
“«!;0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
“«%0h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
“«$8/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
“Ý"0h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
“Ý!/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug? v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
“0h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
“8/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
“3!h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I=
github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJ	Z0NfMwE=
“a0h1:Tsk6gXj2uKE19501bD0ajRYdeCHIlTGb6jYyLc+F8hc=
“a8/go.mod h1:K00BcnLzEYMPHwCFq8Tf/450ApmTs9xBVvYOnofJCkc=
“Œ0h1:G69HbSV4R3vKEH9B0CulnRaMdSdf4RalMgP8xKmxHeI=
“Œ/go.mod h1:iAYAxgvrXxcy746+Ovh/eWWDuF9teJLNcCSSOX9RYW0=
github.com/luxfi/atomic v1.0.0 h1:xUV60MuzRvXngaQ1sM0yVC2v4TRoLlUGkkH7M9PS4yw=
github.com/luxfi/atomic v1.0.0/go.mod h1:0G2mTlQ6TXWHICUHrUUPu1/qAiIyR4gSZ2tva9ci/bI=
github.com/luxfi/cache v1.2.1 h1:kAzOS55/hmYeNKR+0HAKv4ma48Y6JjkI8UQeqdZ8bfI=
github.com/luxfi/cache v1.2.1/go.mod h1:co7JTxZZHpKT31Yh01LFp5aZOxmoUg157FhBLQdQHVU=
“à0h1:Yl8ZalMNkqo7cD6R9AjczAajkLOmsjyZ9+DASVYHrvg=
“à8/go.mod h1:oGQ3j6E8c2P0pL0irYtWkrB1hmDUFIE0puXHK4gV5KI=
“!0h1:4tEUHw5MK1bu5UOjfYCt4OKMiH7yykIgmGPRA/BfJTM=
“ 8/go.mod h1:Cc1yxD2pfzrvpO32W2GDwLKff+CylHEvzZh2Ko8RSIU=
“9#0h1:eJyv1fhaC0jMLMw6+QS774cUmp7GK+ouMgvLCqnC7cc=
“9"/go.mod h1:Aj/FR5NpM0cB2P4Nt3+tz9+dV6V+LUW4HuMgSjwq5hw=
github.com/luxfi/constants v1.4.7 h1:e/Qs+DQP3pugle3Zncq6fZCxKgqqtbyD/z7Gm4ZjsYg=
github.com/luxfi/constants v1.4.7/go.mod h1:hOszZ2NDQ8gMZKncfcZ67PXkb5OIbnwAzXC3oFbQwW0=
github.com/luxfi/container v0.0.4 h1:BXhF82WyfqVP5mjlNcr7tP0Fcnvl0Ap1rkiu+rq5XuM=
github.com/luxfi/container v0.0.4/go.mod h1:Z3SpmMF5d4t77MM0nHYXURpn+EMVaeu1fhbd/3BGaek=
“i!0h1:l2LLu7UFyICtJVfraLDLRi+lFGiDXKHSL18M9/m1gsQ=
“i 8/go.mod h1:INjdZtke85k8hX/QAmTMAY8bbZ4gzGZQLqURg3xf6Gk=
“—"0h1:sySH5eS8ihJtyc+yb0LgTignGwJ9H4W9bv+nboCuIm0=
“—!/go.mod h1:o1ccjgVKMYbVBfi+6+sDvx4swGFgGnCGFQ4gL/oJ+kY=
github.com/luxfi/geth v1.16.98 h1:w187TtKuGStf3tm2bshuHVKBv2Frjx0lT54kQ^VXyNHA=
github.com/luxfi/geth v1.16.98/go.mod h1:6kEzSExdk9CPQDPXALt6P3HfQqBq7KF1Jrrv9gBpxbU=
‘{1 h1:cgHiBU8vBO4b39HnVr1UewEAeP3Bx+JZBHlfacTvDZQ=
‘{/go.mod h1:NbKTixz6Miunyh+7ER3G389mzUKV22QIVPjpcTnKZuY=
github.com/luxfi/ids v1.2.13 h1:lEotq0WUpMLcc/5X5MbE3n73jdfNo8IlFvTDfUXZg+9A=
github.com/luxfi/ids v1.2.13/go.mod h1:QWjJvggX/nRkmrz1AGSaF2Y6mnOvz3g7lFOzs3Zd+JM=
‘š1 h1:vfYgM/hnNujjS47B7/VKAxFJHsFhhME0SnLaAAaYuwA=
‘š8/go.mod h1:pz5W+DqKx0La2uyq1mV5Kpi0gXXNhmVpZTZRmhw+fX4=
“ð0h1:xkUKRWvQ4ZwvlUC2e0/RTtHYZOYSMvSQ9W9lbjwBmiI=
“ð8/go.mod h1:myIkufyiQomSQH34K981kbz6cG4WUoerRUh7F4XhlQI=
“0h1:1t9bCCsEqnl9yIKrShlbs80DBKyYTWdnzkVfBqEeO7Q=
“8/go.mod h1:QvbRxauQyE1w4lvbcLSe6c8yeJz2Zj1Bq1rayGgs2tA=
“C!h1:Vz4c0RsZVPdIKPsHPgAJChH/R3p15WHRUz7LkLf+NIQ=
github.com/luxfi/math/big v0.1.0/go.mod h1:BuxSu22RbO93xBLk5Eam5nldFponoJ73xDFz	4uJ3Huk=
‘»1 h1:g2eRr9AXcziPkkcd24M+Qu9ApEpoKKjfI79QSNqv0rQ=
‘»8/go.mod h1:dbp5f3h3aE7CGzwbaWzBM9cwdcekhmSrWhQevgYhhNA=
“q0h1:axPwfq+erOlIue7IJp5g+hMcMtVhYHja9gJAaT3+KNA=
“q8/go.mod h1:fO2giazkg4NDtr72JM/QXJBYebplAMeWC1JoZyNDvKw=
“�0h1:0HEtIjg1J6CWz+IUyP6rsGqNWTcmxjFnSQIhaDuARwY=
“�/go.mod h1:jo35akl3Vtd8LbzDts8VJ0jmSVycrd1/eBi6g6t5hKU=
github.com/luxfi/pq v1.0.3 h1:pFlQm1+5FuKTDUh2y/23bXWkN4I2Rc5iuxJypwDFFMs=
github.com/luxfi/pq v1.0.3/go.mod h1:8bppZcRElfrVt0n3nYCZW3iX1TvhvzNbdjNdK1irgIE=
github.com/luxfi/sampler v1.0.0 h1:k8Sf6otW83w4pQp0jXLA+g3J/joB7w7SqXQsWmNTOV0=
github.com/luxfi/sampler v1.0.0/go.mod h1:f96/ozlj9vFfZj+akLtrHn4VpulQahwB+MQQhpeIekk=
github.com/luxfi/utils v1.1.4 h1:8OY0jXCkpp6OotN1Y++6DATJkvtEwzq2k0p56imJlAk=
github.com/luxfi/utils v1.1.4/go.mod h1:c3yz1RjzrB#+cs5GZm+q1T3/2cCKElO9vxm9yRRtgSEM=
“ð8 v0.7.0 h1:wiOXfctxiNk0EAiaEPURTXemvO8fyaGViIkoQVWs7Kw=
“ð? v0.7.0/go.mod h1:WOBoeqEh0BCApg1wfxoadOLMXZUC9T1ksOABGoEp8os=
“ð0h1:1lLHEmkyC0BucnA/zjQYsMkUVxuEo2vQkEaQGjYfuuc=
“ð8/go.mod h1:Qukh3hDRD0MnxA6z+a28JTnXhN85AiLLgp6TYr4QAMc=
“	-0h1:2e8XJozaERVxGwsRg72coi51L2aiYqE2gukkdLc85ck=
“	,8/go.mod h1:CBAUADJuMNhJpyM6IYaWAoFhtKaqnUcznY2cL7gNugY=
“V	&0h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
“V	%8/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
“‰	#0h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
“‰	"8/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
‘Ù>1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
‘Ù/1 h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
‘Ù//go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
“¹	0h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
“¹	8/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
“ã	"0h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
“ã	!/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSbQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
“@
$0h1:RCJM0R1XOsRs+A3x3UCaf3ZYbByDaLjFeAi+YCQEPhs=
“@
#8/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0=
“q
!0h1:K6Y13R2h+dku0wOqKtecgRnBUBPrZzLZy5aIj8lCcJI=
“q
 8/go.mod h1:2BuubE67DCSWwVfx37JWNG8emOC0sHEU4/HpcYgCLX8=
“Ÿ
@0h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
“Ÿ
?/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nats-io/nats.go v1.44.0 h1:ECKVrDLdh/kDPV1g0gAQ+2+m2KprqZK5O/eJAyAnH2M=
github.com/nats-io/nats.go v1.44.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/7go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
“ì
&0h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
“ì
%8/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
“ 0h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
“/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhY#xTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
“î“b0h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
“î“b8/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
“î“›0h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
“î“›8/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
“î?common v0.68.0 h1:8rQJvQmYltsR2L7h8Zw0Iyj8WYNNmpwikoQTZXwfVeA=
“îFcommon v0.68.0/go.mod h1:4soH+U8yJSROk7OJ//hmTiWKsxapv6zRGgTt3keN8gQ=
“î%0h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
“î$8/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
“ B0h1:9bBMbcwroL46feESdJWjRX0GV+k8o/P9gAg9UX6Vz7U=
“ A8/go.mod h1:iW4cSew5PAb1sMZiTEkVJAIBNrepaB6jTYjeP47WtI0=
“oD0h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
“oC/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/x“Ï
0h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
“À/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/studio-b12/gowebdav v0.11.0 h1:qbQzq4USxY28ZYsGJUfO5jR+xkFtcnwWgitp4Zp1irU=
github.com/studio-b12/gowebdav v0.11.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
“æ&0h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
“æ%8/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
“0h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
“8/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
“E#0h1:9j4uCAJyO3IJHygeOdUJNzm6sX3P1+3xuKTbCITQrvc=
“E"8/go.mod h1:cDOlK/pNYGbGKRNa+NnXDJ1c8R63tWjnTjFKzoSbLv0=
“uF0h1:gQ+ZV4wJke/EBKYciZ2MshEouEHFuinB85dY3f5s1q8=
“uE/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUo^NXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
“È0h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
“È/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0R=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
“Q“0h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
“Q“8/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
“Qcontrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
go.opentelemetr“`contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
go.opent“Ybcontrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
“Qicontrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
“Q“;	0h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
“Q@ v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
“Q(0h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
“Q'8/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
“Q“Ÿ0h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
“Q“Ÿ/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdM	YcacYXQ=
“ß0h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
“ß/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/g6o.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
“/40h1:4d4PbuBNwaxMXkXI8yiIYjydtMU+04RHeuSxJdgKftM=
“/38/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
“pY1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
“p0h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
“p/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
“¼0h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
“¼/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
“0W0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
“00h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
“0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.154.0 h1:X7QkVKZBskztmpPKWQXgjJRPA2dJYrL6r+sYPRLj050=
google.golang.org/api v0.154.0/go.mod h1:qhSMkM85hgqiokIYsrRyKxrjfBeIhgl4Z2JmeRkYylc=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/“j0h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
“X#/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss=
gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
“®(0h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
“®'/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.34.2 h1:mxsy2FdrB6+qG3NfXefz1AmWv0ehOSDO4jxgxd7h9yo=
modernc.org/ccgo/v4 v4.34.2/go.mod h1:1L7us56+kAKu04p25EATpmBBvhbcqqZ85ibqWVwVgog=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc=
modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.72.5 h1:m2OGx9Ser1VvTS4Z9ZJlWs+CBMxutLaTiAWkNz+NB9U=
modernc.org/libc v1.72.5/go.mod h1:np0N7KDJ7eUtMZmOqVZNldrZyG+DHLl2B5pg8Hbar3U=
“	0h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
“	/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
“Z0h1:aH/MMSoayAIhozZ7uJbVTT9QO/VhzBf0J9tymmmuC/U=
“Z/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kpLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
,í<vlƒ¨Hx óÿØuÚp°+#³©%/*+[¦x& Ùÿ100644 docker.yml ÀÃ'K
‡‘Æ�YÁÑ{h�PÀþünB¯xO °ÿ100644 actionlint.yaml !U¡išQà›â‡Fµ°˜!>F40000 workflows „n8TVÆà/ª_û9StKÝ¦x& Ùÿ100644 docker.yml ›ïhÒÌ½ÿºÿÚgƒÈ±Û@Ò]&ÿ÷é‡¢`x æÿÇœ°runs-on:“�³aæo¯”ï€ûrxO °ÿºº°@Ò[$rQ”³j¦Ù“ÄU!ÆÐ“T8%Mµ¦•ÁÑïw8-’H³ †ï³ �O[>LbgXá)�	i}b Ïd¸ª;xºEü40000 .github ó;:ø5¾ŒÌ„¡Û,
�ö4‰8µ100644 .gitignore ¬ž'ê	‰×Ã€Ê!6ÐÂœã�¶>100644 CLAUDE.md ½`ŸÚîþÙ(z52æv#ºècæ100644 Dockerfile ö\\‰ÌQ×ìj‹ìqBçÓÆ„qŸ100644 LICENSE þ<¬:¸Mµ¶¥dŽPRÉø›100644 LICENSING.md __"]À+Ò°?�ãÿ¯®�×YÙ120000 LLM.md hëœôSÐúÝóªÊìsWé{¨é100644 README.md oƒ&;bk…èÉÜ›ƒÛV›vÿ4100644 chains.example.yaml Ò[$rQ”³j¦Ù“ÄU!ÆÐ100644 compose.yml àÓõ>”)S¢7>ðr¹Âš çÑ100644 config.go %Mµ¦•ÁÑïw8-’H³ †ï100644 frontend.go Y kVa‹Bð2�é8®€qÂ·100644 frontend_test.go (xN2}à­«·GÀR]#î|…ã+100644 go.mod E5ug¡)BLF±–9—,q¤…¦Þ100644 go.sum |Sï…)B’(2—(ŽÆÚ‚Ü¹I100644 main.go ßGª/× Ïsý´÷ÛÜ�®Ìµ100644 mdns.go _Öˆm2mÀWw1<&vÜ¢XN°100644 realtime.go zY¥‘Ÿ¯ª»W/rb4gÓŒÝë100644 realtime_sse.go Z*‚ø’Ú¶%éáe�&…Ê100644 realtime_test.go ˆãá2ƒ¸-£«ÒðCŒìGãMË"100644 registry.go ÷o:Åº±Z©jø¬šß+F)p�L100644 registry_test.go úäYs²¸5ÀZ ö½C±40000 scripts ÞB:¤ôþºN~=w$�=„ˆ^+E²40000 static …V,êôÔ‚ã•yíAñùXt¦oo100644 supervisor.go �O[>LbgXá)�	i}b ô—zKî†Gx~ �ÿºº�k¤TMJ+“ ñËP¾½³�«±“ @…ý“ÜÖ| b„-ÒRlAFW’=ç100644 frontend_test.go Ñê/ìÅ˜f	¢Gîh!dæÕÖ÷³óŠ¡YÀYïéÙój¨³Píž%+û?“‘)�C8³ë€x+ Ôÿºº"40000 .github „=rP·ÙÃù!F¡ÃZõ€=vf±"˜Kw…9xœsöquqÕËM ¹®é�‰x æÿOO�;—vÔÁh˜³&½äÄ:U–QÁ¨‘�^¦x& Ùÿ100644 docker.yml ‘&bceÿ™Âõ€J‰ÙÄàéê£¹ŒxÉ06Ïpackage main

import (
	"bytes"
	"embed"
	"encoding/json"
	"fmt"
	"io/fs"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

// staticFS is the embedded SPA. The Dockerfile populates static/ from a
// luxfi/explore build before the Go build runs; for local dev a placeholder
// index.html is committed so go:embed always succeeds.
//
//go:embed all:static
var staticFS embed.FS

// Frontend serves the embedded SPA, runtime config (/envs.js), and per-host
// brand assets (/icon.svg, /logo.svg). Brand assets read from disk on every
// request so a deploy can swap them without rebuilding the binary.
//
// SPA override: if cfg.StaticDir (or $EXPLORER_STATIC_DIR) points at an
// existing directory, file lookups prefer the on-disk copy over the
// embedded FS. This lets a ConfigMap-mounted SPA replace the embedded
// 60-byte placeholder without a binary rebuild â€” useful when the
// upstream `explore` Next.js app hasn't shipped a static export yet but
// we want a richer in-cluster default than `<div id="root"></div>`.
type Frontend struct {
	cfg      Config
	registry *ChainRegistry
	root     fs.FS    // embedded SPA (always present)
	overlay  fs.FS    // disk overlay (nil if cfg.StaticDir not set)
	index    []byte   // resolved index.html (overlay > embedded > builtin)
}

// NewFrontend returns a frontend handler bound to a config and chain registry.
func NewFrontend(cfg Config, r *ChainRegistry) (*Frontend, error) {
	root, err := fs.Sub(staticFS, "static")
	if err != nil {
		return nil, err
	}
	f := &Frontend{cfg: cfg, registry: r, root: root}

	dir := cfg.StaticDir
	if dir == "" {
		dir = os.Getenv("EXPLORER_STATIC_DIR")
	}
	if dir != "" {
		if st, err := os.Stat(dir); err == nil && st.IsDir() {
			f.overlay = os.DirFS(dir)
		}
	}

	if data := f.readStatic("index.html"); data != nil {
		f.index = data
	} else {
		f.index = []byte("<!doctype html><title>Explorer</title>")
	}
	return f, nil
}

// readStatic returns the bytes for `name` from the overlay if present,
// otherwise from the embedded FS. Returns nil if neither has the file.
func (f *Frontend) readStatic(name string) []byte {
	if f.overlay != nil {
		if data, err := fs.ReadFile(f.overlay, name); err == nil {
			return data
		}
	}
	if data, err := fs.ReadFile(f.root, name); err == nil {
		return data
	}
	return nil
}

// Mount installs / (SPA), /envs.js, /icon.svg, /logo.svg on mux.
func (f *Frontend) Mount(mux *http.ServeMux) {
	mux.HandleFunc("GET /envs.js", f.handleEnvs)
	mux.HandleFunc("GET /icon.svg", f.handleIcon)
	mux.HandleFunc("GET /logo.svg", f.handleLogo)
	mux.HandleFunc("/", f.handleSPA)
}

// handleSPA serves embedded static assets and falls back to index.html for
// any path the SPA owns (client-side routing).
//
// HEAD-method note: the bundled SPA opens EventSource (Server-Sent Events)
// channels for live block / token / tx streams and pre-flights each URL
// with a HEAD request:
//
//	fetch(url, {method: 'HEAD'}).then(r => r.ok ? openEventSource() : disable());
//
// If we serve a 200 (because of the SPA fallback to index.html), the SPA
// proceeds to open the EventSource â€” which then immediately aborts because
// the response body is text/html, not text/event-stream. Browser logs
// "EventSource's response has a MIME type ('text/html') that is not
// 'text/event-stream'" for every channel on every page load â€” noisy and
// disables the channel anyway.
//
// Fix: return 404 for HEAD requests on the SPA-fallback path. Real assets
// (anything in `f.root`) still serve normally on HEAD; only the index.html
// fallback gets the 404 so the SSE pre-flight cleanly fails and the SPA
// disables that channel on its own. Visible browser load is unchanged
// because the SPA only HEAD-probes its SSE endpoints, never the SPA URL
// itself.
func (f *Frontend) handleSPA(w http.ResponseWriter, r *http.Request) {
	p := strings.TrimPrefix(r.URL.Path, "/")
	if p == "" {
		if r.Method == http.MethodHead {
			http.NotFound(w, r)
			return
		}
		f.writeIndex(w)
		return
	}
	// Overlay wins if it has the file (any non-directory entry); falls
	// back to embedded; falls back to index.html (SPA-routing).
	if f.overlay != nil {
		if file, err := f.overlay.Open(p); err == nil {
			st, statErr := file.Stat()
			file.Close()
			if statErr == nil && !st.IsDir() {
				http.ServeFileFS(w, r, f.overlay, p)
				return
			}
		}
	}
	file, err := f.root.Open(p)
	if err != nil {
		if r.Method == http.MethodHead {
			http.NotFound(w, r)
			return
		}
		f.writeIndex(w)
		return
	}
	defer file.Close()
	stat, err := file.Stat()
	if err != nil || stat.IsDir() {
		if r.Method == http.MethodHead {
			http.NotFound(w, r)
			return
		}
		f.writeIndex(w)
		return
	}
	http.ServeFileFS(w, r, f.root, p)
}

func (f *Frontend) writeIndex(w http.ResponseWriter) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Header().Set("Cache-Control", "no-cache")
	w.Write(f.index)
}

// handleEnvs returns runtime config the SPA reads instead of build-time env
// vars. This is what makes per-network customization possible without a
// rebuild: deploy the same image, mount a different chains.yaml, get a
// different brand + chain list.
func (f *Frontend) handleEnvs(w http.ResponseWriter, r *http.Request) {
	hostBrand := f.brandForHost(r.Host)
	hostSlug := f.chainSlugForHost(r.Host)
	chains := f.chainListJSON()
	// When the host pins a specific chain (e.g. explore.zoo.network â†’
	// zoo, explorer.zoo-test.network â†’ zoo), promote that chain to
	// default and demote any other config-level default. The SPA's chain
	// switcher honours the "default" flag.
	if hostSlug != "" {
		for i := range chains {
			if chains[i]["slug"] == hostSlug {
				chains[i]["default"] = true
			} else {
				chains[i]["default"] = false
			}
		}
	}

	env := map[string]any{
		"VITE_CHAINS":   chains,
		"VITE_NETWORKS": f.cfg.Networks,
		"VITE_BRAND":    hostBrand,
	}
	body, _ := json.Marshal(env)

	var buf bytes.Buffer
	buf.WriteString("window.ENV = ")
	buf.Write(body)
	buf.WriteString(";\n")

	w.Header().Set("Content-Type", "application/javascript")
	w.Header().Set("Cache-Control", "no-cache")
	w.Write(buf.Bytes())
}

// handleIcon serves the per-host or default icon.svg from disk.
func (f *Frontend) handleIcon(w http.ResponseWriter, r *http.Request) {
	f.serveBrandFile(w, r, func(b Brand) string { return b.IconFile }, "icon.svg")
}

// handleLogo serves the per-host or default logo.svg from disk.
func (f *Frontend) handleLogo(w http.ResponseWriter, r *http.Request) {
	f.serveBrandFile(w, r, func(b Brand) string { return b.LogoFile }, "logo.svg")
}

// serveBrandFile resolves the file path from the matched brand (per-chain or
// default) and sends it. Falls back to the on-disk overlay (if configured)
// then the embedded SPA's named asset.
func (f *Frontend) serveBrandFile(w http.ResponseWriter, r *http.Request, pick func(Brand) string, fallback string) {
	brand := f.brandStruct(r.Host)
	if path := pick(brand); path != "" {
		if data, err := os.ReadFile(path); err == nil {
			w.Header().Set("Content-Type", contentTypeFor(path))
			w.Header().Set("Cache-Control", "public, max-age=300")
			w.Write(data)
			return
		}
	}
	if data := f.readStatic(fallback); data != nil {
		w.Header().Set("Content-Type", contentTypeFor(fallback))
		w.Header().Set("Cache-Control", "public, max-age=300")
		w.Write(data)
		return
	}
	http.NotFound(w, r)
}

// brandForHost returns a JSON-shaped brand for /envs.js. It strips file paths.
func (f *Frontend) brandForHost(host string) map[string]any {
	b := f.brandStruct(host)
	return map[string]any{
		"name":        b.Name,
		"coin":        b.Coin,
		"accentColor": b.AccentColor,
		"iconUrl":     b.IconURL,
		"logoUrl":     b.LogoURL,
	}
}

// brandStruct returns the Brand for a request host: a chain whose name or
// slug matches the hostname wins; otherwise the global default.
//
// See hostMatchesSlug for the list of supported hostname shapes.
func (f *Frontend) brandStruct(host string) Brand {
	if c, ok := f.matchChain(host); ok && c.Brand != nil {
		return *c.Brand
	}
	return f.cfg.BrandDefault
}

// chainSlugForHost returns the slug of the chain pinned by the request host,
// or "" if the host does not name a chain. Used by handleEnvs to promote a
// per-host chain to the SPA's default selection.
func (f *Frontend) chainSlugForHost(host string) string {
	if c, ok := f.matchChain(host); ok {
		return c.Slug
	}
	return ""
}

// matchChain finds the first configured chain whose slug appears in the
// request host. Matches these hostname shapes, in order of specificity:
//   - "{slug}"                             â€” bare slug (curl localhost)
//   - "{slug}.tld..."                      â€” {slug}.lux.network, {slug}.hanzo.ai
//   - "explore-{slug}.tld..."              â€” explore-hanzo.lux.network
//   - "explorer-{slug}.tld..."             â€” explorer-hanzo.lux.network
//   - "api-explore-{slug}.tld..."          â€” api-explore-hanzo.lux.network
//   - "explore.{slug}.tld..."              â€” explore.hanzo.ai, explore.zoo.ngo
//   - "explorer.{slug}.tld..."             â€” explorer.hanzo.ai, explorer.zoo.network
//   - "api-explore.{slug}.tld..."          â€” api-explore.hanzo.ai
//   - "explore-{slug}-{env}.tld..."        â€” explore-hanzo-test.lux.network
//   - "explorer-{slug}-{env}.tld..."       â€” explorer-hanzo-test.lux.network
//   - "api-explore-{slug}-{env}.tld..."    â€” api-explore-hanzo-dev.lux.network
//   - "explore.{slug}-{env}.tld..."        â€” explore.zoo-test.network
//   - "explorer.{slug}-{env}.tld..."       â€” explorer.zoo-test.network
//   - "api-explore.{slug}-{env}.tld..."    â€” api-explore.zoo-test.network
//
// First chain match wins on ambiguous hosts.
func (f *Frontend) matchChain(host string) (ChainConfig, bool) {
	host = strings.ToLower(strings.SplitN(host, ":", 2)[0])
	for _, c := range f.cfg.Chains {
		if hostMatchesSlug(host, c.Slug) {
			return c, true
		}
	}
	return ChainConfig{}, false
}

// hostMatchesSlug is the pure string predicate for chainâ†”host matching.
// Kept separate from matchChain so unit tests can cover it directly.
func hostMatchesSlug(host, slug string) bool {
	if slug == "" {
		return false
	}
	if host == slug {
		return true
	}
	// Direct slug-as-subdomain: zoo.lux.network, hanzo.ai.
	if strings.HasPrefix(host, slug+".") || strings.HasPrefix(host, slug+"-") {
		return true
	}
	// Brand-owned domains: explore.zoo.network, explorer.zoo-test.network,
	// api-explore.zoo.ngo, etc. Each "explore"/"explorer" prefix combines
	// with "." or "-" as the slug separator.
	prefixes := []string{
		"explore-", "explorer-",
		"api-explore-", "api-explorer-",
		"explore.", "explorer.",
		"api-explore.", "api-explorer.",
	}
	for _, p := range prefixes {
		// {prefix}{slug}.tld  â€” e.g. explore.zoo.network, explore-zoo.lux.network
		if strings.HasPrefix(host, p+slug+".") {
			return true
		}
		// {prefix}{slug}-{env}.tld â€” e.g. explore-zoo-test.lux.network,
		// explorer.zoo-test.network
		if strings.HasPrefix(host, p+slug+"-") {
			return true
		}
	}
	return false
}

// chainListJSON returns a SPA-friendly chain list from the live registry,
// not the static config â€” chains added via mDNS/admin show up immediately.
var chainListMu sync.Mutex

func (f *Frontend) chainListJSON() []map[string]any {
	chainListMu.Lock()
	defer chainListMu.Unlock()
	out := []map[string]any{}
	if f.registry == nil {
		return out
	}
	for _, c := range f.registry.List() {
		out = append(out, map[string]any{
			"slug":    c.Slug,
			"name":    c.Name,
			"chainId": c.ChainID,
			"rpc":     c.RPC,
			"coin":    c.CoinSymbol,
			"type":    c.Type,
			"default": c.Default,
		})
	}
	return out
}

// contentTypeFor maps a file extension to a Content-Type, defaulting to
// application/octet-stream.
func contentTypeFor(path string) string {
	switch strings.ToLower(filepath.Ext(path)) {
	case ".svg":
		return "image/svg+xml"
	case ".png":
		return "image/png"
	case ".jpg", ".jpeg":
		return "image/jpeg"
	case ".webp":
		return "image/webp"
	case ".ico":
		return "image/x-icon"
	default:
		return "application/octet-stream"
	}
}

// fingerprint returns a short, stable identifier for the embedded asset
// bundle, useful for cache-busting log lines at startup.
func fingerprint() string {
	root, err := fs.Sub(staticFS, "static")
	if err != nil {
		return "0"
	}
	count := 0
	_ = fs.WalkDir(root, ".", func(_ string, _ fs.DirEntry, _ error) error {
		count++
		return nil
	})
	return fmt.Sprintf("assets=%d", count)
}
—®íîïÃàWx?ÀãÉa”<�net/http"
	"net/http/httptest‘T&testing"
)

// TestFrontendStaticOverlay verifies that NewFrontend prefers the on-disk
// static dir over the embedded FS when cfg.StaticDir points at a real
// directory. The overlay use-case: ConfigMap-mounting a richer SPA shell
// into an existing image without rebuilding.
func TestFrontendStaticOverlay(t *testing.T) {
	dir := t.TempDir()
	want := "<!doctype html><title>OVERLAY</title>"
	if err := os.WriteFile(filepath.Join(dir, "index.html"), []byte(want), 0644); err != nil {
		t.Fatal(err)
	}

	cfmg := Config{StaticDir: dir, BrandDefault: Brand{Name: "Test"}}
	f, err := NewFrontend(cfg, NewChainRegistry()“t.Fatal(err)
	}

	mux := http.NewServeMux()
	f.Mount(mux)

	req := httptest.NewRequest("GET", "/", nil)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)

	got := rec.Body.String()
	if !strings.Contains(got, "OVERLAY") {
		t.Fatalf("expected overlay index.html, got %q", got)
	}
}

// TestFrontendNoOverlay verifies that with no StaticDir, the embedded
// index.html is served (current behaviour â€” pre-overlay).
func TestFrontendNoOverlay(t *testing.T) {
	cfg := Config{BrandDefault: Brand{Name: "Tes3t"}}
	f, err := NewFrontend(cfg, NewChainRegistry()“t.Fatal(err)
	}
	if f.overlay != nil {
		t.Fatal("expected nil overlay when StaticDir empty")
	}

	mux := http.NewServeMux()
	f.Mount(mux)

	req := httptest.NewRequest("GET", "/", nil)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)

	got := rec.Body.String()
	if !strings.Contains(got, "doctype html") {
		t.Fatalf("expected fallback index.html, got %q", got)
	}
}

// TestFrontendOverlayMissingFileFallsBack covers the case where the
// overlay dir exists but a particular file is missing â€” the request
// falls through to the embedded FS (and ultimately to index.html for
// SPA-routing).
func TestFrontendOverlayMissingFileFallsBack(t *testing.T) {
	dir := t.TempDir()
	// Only index.html in overlay; no other assets.
	if err := os.WriteFile(filepath.Join(dir, "index.html"),“?OVERLAY"), 0644); err != nil {
		t.Fatal(err)
	}

	cfg := Config{StaticDir: dir, BrandDefault: Brand{Name: "Test"}}
	f, err := #NewFrontend(cfg, NewChainRegistry()“t.Fatal(err)
	}

	mux := http.NewServeMux()
	f.Mount(mux)

	// Request a path the overlay doesn't have. With SPA-routing, the
	// handler returns index.html â€” which IS in the overlay.
	req := httptest.NewRequest("GET", "/some/spa/route", nil)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)

	got := rec.Body.String()
	if !strings.Contains(got, "OVERLAY") {
		t.Fatalf("expected overlay index.html (SPA fallback), got %q", got)
	}
}

// TestFrontendEnvStaticDir verifies $EXPLORER_STATIC_DIR is the env-var
// equivalent of cfg.StaticDir. Empty cfg + env-var-set overlay should
// still pick up the overlay.
func TestFrontendEnvStaticDir(t *testing.T) {
	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, "index.html"), []byte("ENV-OVERLAY"), 0644); err != nil {
		t.Fatal(err)
	}
	t.Setenv("EXPLORER_STATIC_DIR", dir)

	cfg := Config{BrandDefault: Brand{Name: "Test"1}}
	f, err := NewFrontend(cfg, NewChainRegistry()“t.Fatal(err)
	}
	if f.overlay == nil {
		t.Fatal("expected overlay populated from env var")
	}
	if !strings.Contains(string(f.index), "ENV-OVERLAY") {
		t.Fatalf("expected env-overlay index, got %q", string(f.index))
	}
}

// TestBrandStructHostMatching covers every hostname shape the unified
// explorer's IngressRoutes hand it: the per-chain brand must win over
// BrandDefault for both UI hosts (explore-{slug}, explore.{slug}.tld)
// and API hosts (api-explore-{slug}). The matcher must NOT bleed across
// chains â€” a hanzo host must not pick zoo's brand or vice versa.
func TestBrandStructHostMatching(t *testing.T) {
	hanzoBrand := Brand{Name: "Hanzo", Coin: "AI", AccentColor: "#0b1220"}
	zooBrand := Brand{Name: "Zoo", Coin: "ZOO", AccentColor: "#7c3aed"}
	cfg := Config{
		BrandDefault: Brand{Name: "Lux Explorer"},
		Chains: []ChainConfig{
			{Slug: "cchain", Brand: &Brand{Name: "Lux C-Chain", Coin: "LUX"}},
			{Slug: "hanzo", Brand: &hanzoBrand},
			{Slug: "zoo", Brand: &zooBrand},
		},
	}
	f, err :%= NewFrontend(cfg, NewChainRegistry()“t.Fatal(err)
	}

	cases := []struct {
		host string
		want string
	}{
		// Bare slug + slug-prefix shape.
		{"hanzo", "Hanzo"},`
		{"hanzo.lux.network", "Hanzo"},
		// explore-{slug} prefix shape (lux.network UI hosts).
		{"“="", "Hanzo"},
		{"“$?", "Hanzo"},
		{"explore-hanzo-dev.lux.network", "Hanzo"},
		{"“ *Q", "Zoo"},
		// api-explore-{slug} prefix shape (lux.network API hosts).
		{"api-“="", "Hanzo"},
		{"api-“$", "Hanzo"},
		{"api-“ *", "Zoo"},
		// explore.{slug}.tld shape (brand-owned domains).
		{"explore.hanzo.ai", "Hanzo"},
		{"explore.hanzo.network", "Hanzo"},
		{"explore.zoo.network", "Zoo"},
		{"explore.zoo.ngo", "Zoo"},
		{"api-explore.hanzo.ai", "Hanzo"},
		{"api-explore.zoo.ngo", "Zoo"},
		// explorer.{slug}.tld shape (alias of explore.* for brand-owned domains).
		{"explorer.zoo.network", "Zoo"},f
		{"explorer.hanzo.ai", "Hanzo"},
		// {slug}-{env}.tld shape â€” brand-owned testnet/devnet domains.“º*% is the primary case this fixes.
		{"“À*", "Zoo"},
		{"’%?", "Zoo"},
		{"explorer.hanzo-test.network", "Hanzo"},
		{"api-’%", "Zoo"},
		// Mismatched hosts fall back to BrandDefault.
		{"explore.lux.network", "Lux Explorer"},
		{"unknown.example.com", "Lux Explorer"},
		// Cross-contamination guard: explore-spcfoo must NOT match spc.
		{"explore-spcfoo.lux.network", "Lux Explorer"},
		// Cross-contamination guard: zoofoo must NOT match zoo.
		{"explore.zoofoo.network", "Lux Explorer"},
		{"explorer.zoofoo.network", "Lux Explorer"},
	}

	for _, tc := range cases {
		t.Run(tc.host, func(t *testing.T) {
			got := f.brandStruct(tc.host).Name
			if got != tc.want {
				t.Fatalf("host=%q: got brand=%q, want %q", tc.host, got, tc.want)
			}
		})
	}
}

// TestChainSlugForHost covers the hostâ†’default-chain promotion that
// handleEnvs uses to set the SPA's initial chain selection. Hosts that
// pin a chain (“*“®() must
// return the slug; generic hosts (explore.lux.network) must return "".
func TestChainSlugForHost(t *testing.T) {
	zooBrand := Brand{Name: "Zoo"}
	hanzoBrand := Brand{Name: "Hanzo"}
	cfg := Config{
		BrandDefault: Brand{Name: "Lux Explorer"},
		Chains: []ChainConfig{
			{Slug: "cchain", Brand: &Brand{Name: "Lux C-Chain"}},
			{Slug: "hanzo", Brand: &hanzoBrand},
			{Slug:Q "zoo", Brand: &zooBrand},
		},
	}
	f, err := NewFrontend(cfg, NewChainRegistry()“t.Fatal(err)
	}
	cases := []struct {
		host string
		want string
	}{
		{"explore.zoo.network", "zoo"},
		{"explore.zoo.ngo", "z-oo"},
		{"explorer.zoo.network", "zoo"},
		{"“À*", "zoo"},
		{"’%", "zoo"},
		{"“ *", "zoo"},
		{"“�*", "zoo"},
		{"explore.hanzo.ai", "hanzo"},
		{"explorer.hanzo-test.network", "hanzo"},
		// Generic / mismatched hosts: no slug.
		{"explore.lux.network", ""},
		{"unknown.example.com", ""},
		{"explore.zoofoo.network", ""},
	}
	for _, tc := range cases/ {
		t.Run(tc.host, func(t *testing.T) {
			got“�ttc.host)
			if got != tc.want {
				t.Fatalf("host=%q: got slug=%q, want %q", tc.host, got, tc.want)
			}
		})
	}
}
,g5îçþ·OxçG¸”<�’�;bytes"
	"embed"
	"encoding/json"
	"fmt"
	"io/fs"
	"net/http‘6&sync"
)

// staticFS is the embedded SPA. The Dockerfile populates static/ from a
// luxfi/explore build before the Go build runs; for local dev a placeholder
// index.html is committed so go:embed always succeeds.
//
//go:embed all:static
var staticFS embed.FS

// Frontend serves the embedded SPA, runtime config (/envs.js), and per-host
// brand assets (/icon.svg, /logo.svg). Brand assets read from disk on every
// request so a deploy can swap them without rebuilding the binary.
//
// SPA override: if cfg.StaticDir (or $EXPLORER_STATIC_DIR) points at an
// existing directory, file lookups prefer the on-disk copy over the
// embedded FS. This lets a ConfigMap-mounted SPA replace the embedded
// 60-byte placeholder without a binary rebuild â€” useful when the
// upstream `explore` Next.js app hasn't shipped a static export yet but
// we want a richer in-cluster default than `<div id="root"></div>`.
type Frontend struct {
	cfg      Config
	registry *ChainRegistry
	root     fs.FS    // embedded SPA (always present)
	overlay  fs.FS    // disk overlay (nil if cfg.StaticDir not set)
	index    []byte   // resolved index.html (overlay > embedded > builtin)
}

// NewFrontend returns a frontend handler bound to a config and chain registry.
func NewFrontend(cfg Config, r *ChainRegistry) (*Frontend, error) {
	root, err := fs.Sub(staticFS, "static")
	if err != nil {
		return nil, err
	}
f	f := &Frontend{cfg: cfg, registry: r, root: root}

	dir := cfg.StaticDir
	if dir == "" {
		dir = os.G“))
	}
	if dir != "" {
		if st, err := os.Stat(dir); err == nil && st.IsDir() {
			f.overlay = os.DirFS(dir)
		}
	}

	if data := Xf.readStatic("index.html"); data != nil {
		f.index = data
	} else {
		f.index = []byte(“¾Explorer</title>")
	}
	return f, nil
}

// readStatic returns the bytes for `name` from the overlay if present,
// otherwise from the embedded FS. Returns nil if neither has the file.
func (f *Frontend) readStatic(name string) []byte {
	if f.overlay != n“@if data, err := fs.ReadFile(f.overlay, name); err == nil {
			return data
		}
	}
	if data, err := fs.ReadFile(f.root, name); err == nil {
		return data
	}
	return nil
}

// Mount installs / (SPA), /envs.js, /icon.svg, /logo.svg on mux.
func (f *Frontend) Mount(mux *http.ServeMux) {
	mux.HandleFunc("GET /envs.js", f.handleEnvs)
	mux.HandleFunc("GET /icon.svg", f.handleIcon)
	mux.HandleFunc("GET /logo.svg", f.handleLogo)
	mux.HandleFunc("/", f.handleSPA)
}

// handleSPA serves embedded static assets and falls back to index.html for
// any path the SPA owns (client-side routing).
//
// HEAD-method note: the bundled SPA opens EventSource (Server-Sent Events)
// channels for live block / token / tx streams and pre-flights each URL
// with a HEAD request:
//
//	fetch(url, {method: 'HEAD'}).then(r => r.ok ? openEventSource() : disable());
//
// If we serve a 200 (because of the SPA fallback to index.html), the SPA
// proceeds to open the EventSource â€” which then immediately aborts because
// the response body is text/html, not text/event-stream. Browser logs
// "EventSource's response has a MIME type ('text/html') that is not
// 'text/event-stream'" for every channel on every page load â€” noisy and
// disables the channel anyway.
//
// Fix: return 404 for HEAD requests on the SPA-fallback path. Real assets
// (anything in `f.root`) still serve normally on HEAD; only the index.html
// fallback gets the 404 so the SSE pre-flight cleanly fails and the SPA
// disables that channel on its own. Visible browser load is unchanged
// because the SPA only HEAD-probes its SSE endpoints, never the SPA URL
// itself.
func (f *Frontend) handleSPA(w http.ResponseWriter, r *http.Request) {
	p := strings.TrimPrefix(r.URL.Path, "/")
	if p == "" {
		if r.Method == http.MethodHead {
			http.NotFound(w, r)
			return
		}
		f.writeIndex(w)
		return
	}
	// Overlay wins if it has the file (any non-dirRectory entry); falls
	// back to embedded; falls back to index.html (SPA-routing).“-if file, err := f.overlay.Open(p); err == nil {
			st, statErr := file.Stat()
			file.Close()
			if statErr == nil && !st.IsDir() {
				http.ServeFileFS(w, r, f.overlay, p)
				return
			}
		}
	}
	file, err := f.root.Open(p)
	if err != nil {
		if r.Method == http.MethodHead {
			http.NotFound(w, r)
			return
		}
		f.writeIndex(w)
		return
	}
	defer file.Close()
	stat, err := file.Stat()
	if err != nil || stat.IsDir() {
		if r.Method == http.MethodHead {
			http.NotFound(w, r)
			return
		}
		f.writeIndex(w)
		return
	}
	http.ServeFileFS(w, r, f.root, p)
}

func (f *Frontend) writeIndex(w http.ResponseWriter) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Header().Set("Cache-Control", "no-cache")
	w.Write(f.index)
}

// handleEnvs returns runtime config the SPA reads instead of build-time env
// vars. This is what makes per-network customization possible without a
// rebuild: deploy the same image, mount a different chains.yaml, get a
// different brand + chain list.
func (f *Frontend) handleEnvs(w http.ResponseWriter, r *http.Request) {
	hostBrand := f.brandForHost(r.Host)
	hostSlug := f.chainSlugForHost(r.Hoost)
	chains := f.chainListJSON()
	// When the host pins a specific chain (e.g. explore.zoo.network â†’
	// zoo“! â†’ zoo), promote that chain to
	// default and demote any other config-level default. The SPA's chain
	// switcher honours the "default" flag.
	if hostSlug != "" {
		for i := range chains {
			if chains[i]["slug"] == hostSlug {
				chains[i]["default"] = true
			} else {
				chains[i]["default"] = false
			}
		}
	}

	current, peers := f.networksForHost(r.Host)
	env := map[string]any{
		"VITE_CHAINS":          chains,
		"VITE_NETWORKS":        f.cfg.Networks,
		"VITE_BRAND":           hostBrand,
		"VITE_CURRENT_NETWORK": current,
		"VITE_NETWORK_HOSTS":   peers,
	}
	body, _ := json.Marshal(env)

	var buf bytes.Buffer
	buf.WriteString("window.ENV = ")
	buf.Write(body)
	buf.WriteString(";\n")

	w.Header().Set("Content-Type", "application/javascript")
	w.Header().Set("Cache-Control", "no-cache")
	w.Write(buf.Bytes())
}

// handleIcon serves the per-host or default icon.svg from disk.
func (f *Frontend) handleIcon(w http.ResponseWriter, r *http.Request) {
	f.serveBrandFile(w, r, func(b Brand) string { return b.IconFile }, "icon.svg")
}

// handleLogo serves the per-host or default logo.svg from disk.
func (f *Frontend) handleLogo(w http.ResponseWriter, r *http.Request) {
	f.serveBrandFile(w, r, func(b Brand) string { return b.LogoFile }, "logo.svg")
}

// serveBrandFile resolves the file path from the matched brand (per-chain or
// default) and sends it. Falls back to the on-disk overlay (if configured)
// then the embedded SPA's named asset.
func (f *Frontend) serveBrandFile(w http.ResponseWriter, r *http.Request, pick func(Brand) string, fallback string) {
	brand := f.brandStruct(r.Host)
	if path := pick(brand); path != "" {
		if data, err := os.ReadFile(path); err == nil {
			w.Header().Set("Content-Type", contentTypeFor(path))
			w.Header().Set("Cache-Control", "public, max-age=300")
			w.Write(data)
			return
		}
	}
	if data := f.readStatic(fallback); data != nil {
		w.Header().Set("Content-Type", contentTypeFor(fallback))
		w.Header().Set("Cache-Control", "public, max-age=300")
		w.Write(data)
		return
	}
	http.NotFound(w, r)
}

// brandForHost returns a JSON-shaped brand for /envs.js. It strips file paths.
func (f *Frontend) brandForHost(host string) map[string]any {
	b := f.brandStruct(host)
	return map[string]any{
		"name":        b.Name,
		"coin":        b.Coin,
		"accentColor": b.AccentColor,
		"iconUrl":     b.IconURL,
		"logoUrl":     b.LogoURL,
	}
}

// brandStruct returns the Brand for a request host: a chain whose name or
// slug matches the hostname wins; otherwise the global default.
//
// See hostMatchesSlug for the list of supported hostname shapes.
func (f *Frontend) brandStruct(host string) Brand {
	if c, ok := f.matchChain(host); ok && c.Brand != nil {
		return *c.Brand
	}
	return f.cfg.BrandDefault
}

// chainSlugForHost returns the slug of the chain pinned by the request host,
// or "" if the host does not name a chain. Used by handleEnvs to promote a
// per-host chain to the SPA's default selection.
func (f *Frontend) chainSlugForHost(host string) string {
	if c, ok := f.matchChain(host); ok {
		return c.Slug
	}
	return ""
}

// matchChain finds the first configured chain whose slug appears in the
// request host. Matches these hostname shapes, in order of specificity:
//   - "{slug}"                             â€” bare slug (curl localhost)
//   - "{slug}.tld..."                      â€” {slug}.lux.network, {slug}.hanzo.ai
//   - "explore-{slug}.tld..."              â€” “Á
//   - "explorer-{slug}.tld..."             â€” explorer-hanzo.lux.network
//   - "api-explore-{slug}.tld..."          â€” api“À
//   - "explore.{slug}.tld..."              â€” explore.hanzo.ai, explore.zoo.ngo
//   - "explorer.{slug}.tld..."             â€” explorer.hanzo.ai, explorer.zoo.network
//   - "api-explore.{slug}.tld..."          â€” api-explore.hanzo.ai
//   - "explor"e-{slug}-{env}.tld..."        â€” “ï
//   - "explorer-{slug}-{env}.tld..."       â€” explorer-hanzo-test.lux.network
//   - "api-explore-{slug}-{env}.tld..."    â€” api-“+1
//   - "explore.{slug}-{env}.tld..."        â€” “ß0
//   - "explorer.{slug}-{env}.tld..."       â€”“!1
//   - "api-explore.{slug}-{env}.tld..."    â€” “Û
//
// First chain match wins on ambiguous hosts.
func (f *Frontend) matchChain(host string) (ChainConfig, bool) {
	host = strings.ToLower(strings.SplitN(host, ":", 2)[0])
	for _, c := range f.cfg.Chains {
		if hostMatchesSlug(host, c.Slug) {
			return c, true
		}
	}
	return ChainConfig{}, false
}

// hostMatchesSlug is the pure string predicate for chainâ†”host matching.
// Kept separate from matchChain so unit tests can cover it directly.
func hostMatchesSlug(host, slug string) bool {
	if slug == "" {
		return false
	}
	if host == slug {
		return true
	}
	// Direct slug-as-subdomain: zoo.lux.network, hanzo.ai.
	if strings.HashPrefix(host, slug+".") || strings.HasPrefix(host, slug+"-") {
		return true
	}
	// Brand-owned domains: “.,
	// api-explore.zoo.ngo, etc. Each "explore"/"explorer" prefix combines
	// with "." or "-" as the slug separator.
	prefixes := []string{
		"explore-", "explorer-",
		"api-explore-", "api-explorer-",
		"explore.", "explorer.",
		"api-explore.", "api-exMplorer.",
	}
	for _, p := range prefixes {
		// {prefix}{slug}.tld  â€” e.g. “-zoo.lux.network
		if strings.HasPrefix(host, p+slug+".") {
			return true
		}
		// {prefix}{slug}-{env}.tld â€” e.g. explore-z“I,“
		if strings.HasPrefix(host, p+slug+"-") {
			return true
		}
	}
	return false
}

// networksForHost returns (current network label, sibling host map). The
// SPA renders a "Network: mainnet â–¾" dropdown â€” when the user picks
// another label, the SPA redirects to the corresponding host. Hosts are
// derived purely from the request host: explore.zoo.network (mainnet)
// has sibling“!(testnet); explore.lux.network
// has sibling explorer.lux-test.network; and the reverse.
//
// Host shape rules:
//   - mainnet: prefix is "explore.", brand domain is "<brand>.<tld>"
//     (e.g. zoo.network, lux.network, hanzo.ai). Sibling is
//     "explorer.<brand>-test.network".
//   - testnet: prefix is "explorer.", brand domain is "<brand>-test.network".
//     Sibling is "explore.<brand>.network" (or .ai / .ngo per brand
//     when known; we default to .network because all the actual
//     deployed testnet hosts are *.network).
//   - lux.network (no brand prefix, generic explore.lux.network) is
//     special-cased: mainnet=explore.lux.network, testnet=explorer.lux-test.network.
//
// If we can't classify the host (admin tooling on localhost, internal
// service-mesh hostnames), we return ("", nil) and the SPA omits the
// dropdown.
func (f *Frontend) networksForHost(host string) (string, map[string]string) {
	host = strings.ToLower(strings.SplitN(host, ":", 2)[0])

	// Find the brand stem and current env from the host. The two
	// shapes we support are "{prefix}.{brand}.{tld}" (mainnet) and
	// "{prefix}.{brand}-{env}.{tld}" (non-mainnet).
	brand, env, dotted, ok := parseBrandEnv(host)
	if !ok {
		return "", nil
	}

	// All deployed testnet/devnet hosts are *.network. Mainnet
	// brand hosts vary: zoo.network, lux.network, hanzo.ai, hanzo.network,
	// zoo.ngo, etc. For the round-trip from testnet back to mainnet
	// we need to know which mainnet TLD the brand uses. When the user
	// is already on a brand-owned domain (explore.{brand}.{tld}),
	// preserve that TLD so the mainnet option is idempotent. When the
	// user is on the lux.network-prefix shape (explore-{brand}.lux.network)
	// or any non-brand-owned shape, default to ".network" â€” every brand
	// we ship has an X.network mainnet alias.
	mainnetTLD := ".network"
	if env == "" && dotted {
		if dot := strings.Index(host, brand+"."); dot >= 0 {
			mainnetTLD = host[dot+len(brand):]
		}
	}

	mainnetHost := "explore." + brand + mainnetTLD
	testnetHost := "explorer." + brand + "-test.network"

	peers := map[string]string{
		"mainnet": mainnetHost,
		"testnet": testnetHost,
	}
	switch env {
	case "":
		return "mainnet", peers
	case "test", "testnet":
		return "testnet", peers
	case "dev", "devnet":
		// Optional devnet entry; only surfaces when present.
		peers["devnet"] = "explorer." + brand + "-dev.network"
		return "devnet", peers
	default:
		return env, peers
	}
}

// parseBrandEnv extracts the brand stem and env tag from a host. Recognizes:
//
//	explore.{brand}.{tld}                  â†’ ({brand}, "", dotted=true)
//	explorer.{brand}.{tld}                 â†’ ({brand}, "", dotted=true)
//	explore.{brand}-{env}.{tld}            â†’ ({brand}, {env}, dotted=true)
//	explorer.{brand}-{env}.{tld}           â†’ ({brand}, {env}, dotted=true)
//	explore-{brand}.lux.network            â†’ ({brand}, "", dotted=false)
//	explore-{brand}-{env}.lux.network      â†’ ({brand}, {env}, dotted=false)
//
// Returns ok=false on hosts we can't classify (localhost, IPs, internal
// service-mesh names like *.svc.cluster.local).
//
// `dotted` distinguishes brand-owned hosts (explore.zoo.network â€” TLD
// belongs to the brand) from lux-prefix hosts (explore-zoo.lux.network â€”
// brand is just a subdomain on lux.network) so the network switcher can
// pick the right mainnet TLD for the sibling URL.
func parseBrandEnv(host string) (brand, env string, dotted, ok bool) {
	if host == "" || strings.HasSuffix(host, ".svc.cluster.local") || strings.HasSuffix(host, ".local") {
		return "", "", false, false
	}
	stripPrefix := func(s, p string) (string, bool) {
		if strings.HasPrefix(s, p) {
			return s[len(p):], true
		}
		return s, false
	}

	// "explore.{brand}{maybe-env}.{tld}" / "explorer.{brand}{maybe-env}.{tld}"
	for _, p := range []string{"explore.", "explorer.", "api-explore.", "api-explorer."} {
		rest, matched := stripPrefix(host, p)
		if !matched {
			continue
		}
		dot := strings.Index(rest, ".")
		if dot < 1 {
			return "", "", false, false
		}
		stem := rest[:dot]
		brand, env = splitBrandEnv(stem)
		if !isValidBrand(brand) {
			return "", "", false, false
		}
		return brand, env, true, true
	}

	// "explore-{brand}{maybe-env}.lux.network" / "explorer-{brand}{maybe-env}.lux.network"
	for _, p := range []string{"explore-", "explorer-", "api-explore-", "api-explorer-"} {
		rest, matched := stripPrefix(host, p)
		if !matched {
			continue
		}
		dot := strings.Index(rest, ".")
		if dot < 1 {
			return "", "", false, false
		}
		stem := rest[:dot]
		brand, env = splitBrandEnv(stem)
		if !isValidBrand(brand) {
			return "", "", false, false
		}
		return brand, env, false, true
	}

	return "", "", false, false
}

// isValidBrand restricts brand stems to alphabetic (or digit-containing) tokens
// at least 2 chars long. Rejects numeric-only stems (IP addresses), empty,
// and stems beginning with a digit (so "10" from "10.0.0.1" never matches).
func isValidBrand(s string) bool {
	if len(s) < 2 {
		return false
	}
	if s[0] < 'a' || s[0] > 'z' {
		return false
	}
	for i := 0; i < len(s); i++ {
		c := s[i]
		if !(c >= 'a' && c <= 'z') && !(c >= '0' && c <= '9') {
			return false
		}
	}
	return true
}

// splitBrandEnv splits "zoo-test" into ("zoo", "test"); "zoo" stays as ("zoo", "").
// Only recognises known env suffixes (test, testnet, dev, devnet, local) so
// hyphenated brand names ("api-zoo-exchange") still parse as one stem.
func splitBrandEnv(stem string) (brand, env string) {
	for _, e := range []string{"-test", "-testnet", "-dev", "-devnet", "-local"} {
		if strings.HasSuffix(stem, e) {
			return strings.TrimSuffix(stem, e), strings.TrimPrefix(e, "-")
		}
	}
	return stem, ""
}

// chainListJSON returns a SPA-friendly chain list from the live registry,
// not the static config â€” chains added via mDNS/admin show up immediately.
var chainListMu sync.Mutex

func (f *Frontend) chainListJSON() []map[string]any {
	chainListMu.Lock()
	defer chainListMu.Unlock()
	out := []map[string]any{}
	if f.registry == nil {
		return out
	}
	for _, c := range f.registry.List() {
		out = append(out, map[string]any{
			"slug":    c.Slug,
			"name":    c.Name,
			"chainId": c.ChainID,
			"rpc":     c.RPC,
			"coin":    c.CoinSymbol,
			"type":    c.Type,
			"default": c.Default,
		})
	}
	return out
}

// contentTypeFor maps a file extension to a Content-Type, defaulting to
// application/octet-stream.
func contentTypeFor(path string) string {
	switch strings.ToLower(filepath.Ext(path)) {
	case ".svg":
		return "image/svg+xml"
	case ".png":
		return "image/png"
	case ".jpg", ".jpeg":
		return "image/jpeg"
	case ".webp":
		return "image/webp"
	case ".ico":
		return "image/x-icon"
	default:
		return "application/octet-stream"
	}
}

// fingerprint returns a short, stable identifier for the embedded asset
// bundle, useful for cache-busting log lines at startup.
func fingerprint() string {
	root, err := fs.Sub(staticFS, "static")
	if err != nil {
		return "0"
	}
	count := 0
	_ = fs.WalkDir(root, ".", func(_ string, _ fs.DirEntry, _ error) error {
		count++
;		return nil
	})
	return fmt.Sprintf("assets=%d", count)
}
/~•î¿€Žwxþ#Ü�’†N�net/http"
	"net/http/httptest‘T&testing"
)

// TestFrontendStaticOverlay verifies that NewFrontend prefers the on-disk
// static dir over the embedded FS when cfg.StaticDir points at a real
// directory. The overlay use-case: ConfigMap-mounting a richer SPA shell
// into an existing image without rebuilding.
func TestFrontendStaticOverlay(t *testing.T) {
	dir := t.TempDir()
	want := "<!doctype html><title>OVERLAY</title>"
	if err := os.WriteFile(filepath.Join(dir, "index.html"), []byte(want), 0644); err != nil {
		t.Fatal(err)
	}

	cfmg := Config{StaticDir: dir, BrandDefault: Brand{Name: "Test"}}
	f, err := NewFrontend(cfg, NewChainRegistry()“t.Fatal(err)
	}

	mux := http.NewServeMux()
	f.Mount(mux)

	req := httptest.NewRequest("GET", "/", nil)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)

	got := rec.Body.String()
	if !strings.Contains(got, "OVERLAY") {
		t.Fatalf("expected overlay index.html, got %q", got)
	}
}

// TestFrontendNoOverlay verifies that with no StaticDir, the embedded
// index.html is served (current behaviour â€” pre-overlay).
func TestFrontendNoOverlay(t *testing.T) {
	cfg := Config{BrandDefault: Brand{Name: "Tes3t"}}
	f, err := NewFrontend(cfg, NewChainRegistry()“t.Fatal(err)
	}
	if f.overlay != nil {
		t.Fatal("expected nil overlay when StaticDir empty")
	}

	mux := http.NewServeMux()
	f.Mount(mux)

	req := httptest.NewRequest("GET", "/", nil)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)

	got := rec.Body.String()
	if !strings.Contains(got, "doctype html") {
		t.Fatalf("expected fallback index.html, got %q", got)
	}
}

// TestFrontendOverlayMissingFileFallsBack covers the case where the
// overlay dir exists but a particular file is missing â€” the request
// falls through to the embedded FS (and ultimately to index.html for
// SPA-routing).
func TestFrontendOverlayMissingFileFallsBack(t *testing.T) {
	dir := t.TempDir()
	// Only index.html in overlay; no other assets.
	if err := os.WriteFile(filepath.Join(dir, "index.html"),“?OVERLAY"), 0644); err != nil {
		t.Fatal(err)
	}

	cfg := Config{StaticDir: dir, BrandDefault: Brand{Name: "Test"}}
	f, err := #NewFrontend(cfg, NewChainRegistry()“t.Fatal(err)
	}

	mux := http.NewServeMux()
	f.Mount(mux)

	// Request a path the overlay doesn't have. With SPA-routing, the
	// handler returns index.html â€” which IS in the overlay.
	req := httptest.NewRequest("GET", "/some/spa/route", nil)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)

	got := rec.Body.String()
	if !strings.Contains(got, "OVERLAY") {
		t.Fatalf("expected overlay index.html (SPA fallback), got %q", got)
	}
}

// TestFrontendEnvStaticDir verifies $EXPLORER_STATIC_DIR is the env-var
// equivalent of cfg.StaticDir. Empty cfg + env-var-set overlay should
// still pick up the overlay.
func TestFrontendEnvStaticDir(t *testing.T) {
	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, "index.html"), []byte("ENV-OVERLAY"), 0644); err != nil {
		t.Fatal(err)
	}
	t.Setenv("EXPLORER_STATIC_DIR", dir)

	cfg := Config{BrandDefault: Brand{Name: "Test"1}}
	f, err := NewFrontend(cfg, NewChainRegistry()“t.Fatal(err)
	}
	if f.overlay == nil {
		t.Fatal("expected overlay populated from env var")
	}
	if !strings.Contains(string(f.index), "ENV-OVERLAY") {
		t.Fatalf("expected env-overlay index, got %q", string(f.index))
	}
}

// TestBrandStructHostMatching covers every hostname shape the unified
// explorer's IngressRoutes hand it: the per-chain brand must win over
// BrandDefault for both UI hosts (explore-{slug}, explore.{slug}.tld)
// and API hosts (api-explore-{slug}). The matcher must NOT bleed across
// chains â€” a hanzo host must not pick zoo's brand or vice versa.
func TestBrandStructHostMatching(t *testing.T) {
	hanzoBrand := Brand{Name: "Hanzo", Coin: "AI", AccentColor: "#0b1220"}
	zooBrand := Brand{Name: "Zoo", Coin: "ZOO", AccentColor: "#7c3aed"}
	cfg := Config{
		BrandDefault: Brand{Name: "Lux Explorer"},
		Chains: []ChainConfig{
			{Slug: "cchain", Brand: &Brand{Name: "Lux C-Chain", Coin: "LUX"}},
			{Slug: "hanzo", Brand: &hanzoBrand},
			{Slug: "zoo", Brand: &zooBrand},
		},
	}
	f, err :%= NewFrontend(cfg, NewChainRegistry()“t.Fatal(err)
	}

	cases := []struct {
		host string
		want string
	}{
		// Bare slug + slug-prefix shape.
		{"hanzo", "Hanzo"},`
		{"hanzo.lux.network", "Hanzo"},
		// explore-{slug} prefix shape (lux.network UI hosts).
		{"“\#@", "Hanzo"},
		{"explore-hanzo-test.lux.network", "Hanzo"},
		{"“8%", "Hanzo"},
		{"explore-zoo.lux.network", "Zoo"},
		// api-explore-{slug} prefix shape (lux.network API hosts).
		{"api-explor“b#", "Hanzo"},
		{"api-explore-hanzo-test.lux.network", "Hanzo"},
		{"api-explore-zoo.lux.network", "Zoo"},
		// explore.{slug}.tkld shape (brand-owned domains).
		{"explore.hanzo.ai", "Hanzo"},
		{"explore.hanzo.network", "Hanzo"},
		{"“‘*-", "Zoo"},
		{"explore.zoo.ngo", "Zoo"},
		{"“P$", "Hanzo"},
		{"api-explore.zoo.ngo", "Zoo"},
		// explorer.{slug}.tld shape (alias of explore.* for brand-owned domains).
		{"explorer.zoo.network", "Zoo"},
		{"explorer.hanzo.ai", "Hanzo"},
		// {slug}-{env}.tld shape â€” brand-owned testnet/devnet domains.“@+% is the primary case this fixes.
		{"“Ï%", "Zoo"},
		{"“&;", "Zoo"},
		{"explorer.hanzo-test.network", "Hanzo"},
		{"“&@", "Zoo"},
		// Mismatched hosts fall back to BrandDefault.
		{"“�/", "Lux Explorer"},
		{"unknown.example.com", "Lux Explorer"},
		// Cross-contamination guard: explore-spcfoo must NOT match spc.
		{"explore-spcfoo.lux.network", "Lux Explorer"},
		// Cross-contamination guard: zoofoo must NOT match zoo.
		{"explore.zoofoo.network", "Lux Explorer"},
		{"explorer.zoofoo.network", "Lux Explorer"},
	}

	for _, tc := range cases {
		t.Run(tc.host, func(t *testing.T) {
			got := f.brandStruct(tc.host).Name
			if got != tc.want {
				t.Fatalf("host=%q: got brand=%q, want %q", tc.host, got, tc.want)
			}
		})
	}
}

// TestNetworksForHost covers the network dropdown payload the SPA reads
// from /envs.js. Mainnet hosts return ("mainnet", siblings) and testnet
// hosts return ("testnet", siblings); unparseable hosts return ("", nil).
func TestNetworksForHost(t *testing.T) {
	cfg := Config{
		BrandDefault: Brand{Name: "Lux Explorer"},
		Chains: []ChainConfig{
			{Slug: "cchain"}, {Slug: "zoo"}, {Slug: "hanzo"},
		},
	}
	f, err := NewFrontend(cfg, NewChainRegistry())
	if err !=“+	t.Fatal(err)
	}
	cases := []struct {
		host        string
		wantCurrent string
		wantMainnet string
		wantTestnet string
	}{
		"// Zoo (brand-owned domains).
		{"“‘*", "mainnet", "“‘*", "“Ï%"},
		{"“Ï%", "testnet", "“‘*", "“Ï%("},
		// Lux (brand-owned domains).
		{"“�/", "mainnet", "“�/", "“ž/"},
		{"“ž/", "testnet", "“�/", "“ž/"},
		// Hanzo brand on .ai mainnet, .network testnet.
		{"explore.hanzo.ai", "mainnet", "explore.hanzo.ai", "explorer.hanzo-test.network"},
		{"explorer.hanzo-test.network", "testnet", "explore.hanzo.network", "explorer.hanzo-test.network"},
		// lux.ne]twork prefix shape â€” explore-{slug}.lux.network.
		{"explore-zoo.lux.network", "mainnet", "“‘*", "“Ï%3"},
		{"explore-zoo-test.lux.network", "testnet", "“‘*", "“Ï%O"},
	}
	for _, tc := range cases {
		t.Run(tc.host, func(t *testing.T) {
			cur“‰tc.host)
			if cur != tc.wantCurrent {
				t.Errorf("current: got %q want %q", cur, tc.wantCurrent)
			}
			if peers["mainnet"] != tc.wantMainnet {
				t.Errorf("mainnet: got %q want %q", peers["mainnet"], tc.wantMainnet)
			}
			if peers["testnet"] != tc.wantTestnet {
				t.Errorf("testnet: got %q want %q", peers["testnet"], tc.wantTestnet)
			}
		})
	}

	// Unparseable hosts return empty.
	for _, h“á=Mlocalhost", "10.0.0.1", "explorer.lux-mainnet.svc.cluster.local", ""} {
		cur“‰h)
		if cur != "" || peers != nil {
			t.Errorf("expected empty result for %q, got cur=%q peers=%v", h, cur, peers)
		}
	}
}

// TestChainSlugForHost covers the hostâ†’default-chain promotion that
// handleEnvs uses to set the SPA's initial chain selection. Hosts that
// pin a chain (“‘*“Ö%*) must
// return the slug; generic hosts (“�/) must return "".
func TestChainSlugForHost(t *testing.T) {
	zooBrand := Brand{Name: "Zoo"}
	hanzoBrand := Brand{Name: "Hanzo"}
	cfg := Config{
		BrandDefault: Brand{Name: "Lux Explorer"},
		Chains: []ChainConfig{
			{Slug: "cchain", Brand: &Brand{Name: "Lux C-Chain"}},
			{Slug: "hanzo", Brand: &hanzoBrand},
			{Slug: "zoo", Brand: &zooBrand},
		},
	}
	f, err := NewFrontend(cfg, NewChainRegistry()“It.Fatal(err)
	}
	cases := []struct {
		host string
		want string
	}{
		{"“‘*P", "zoo"},
		{"explore.zoo.ngo", "zoo"},
		{"explorer.zoo.network", "zoo"},
		{"“Ï%", "zoo"},
		{"“&", "zoo"},
		{"explore-zoo.lux.network", "zoo"},
		{"explore-zoo-test.lux.network", "zoo"},
		{"explore.hanzo.ai", "hanzo"},
		X{"explorer.hanzo-test.network", "hanzo"},
		// Generic / mismatched hosts: no slug.
		{"“�/", ""},
		{"unknown.example.com", ""},
		{"explore.zoofoo.network", ""},
	}
	for _, tc := range cases {
		t.Run(tc.host, func(t *testing.T) {
			got“�ttc.host)
			if got != tc.want {
				t.Fatalf("host=%q: got slug=%q, want %q", tc.host, got, tc.want)
			}
		})
	}
}
K'±Q¼”xL!³Þ<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Lux Explorer</title>
<link rel="icon" type="image/svg+xml" href="/icon.svg">
<script src="/envs.js"></script>
<style>
  :root{--bg:#0b1220;--card:#111a2c;--fg:#e6edf3;--mut:#7d8590;--ac:#5cf;--brd:#1f2a3d}
  *{box-sizing:border-box;margin:0;padding:0}
  body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;background:var(--bg);color:var(--fg)}
  a{color:var(--ac);text-decoration:none}
  a:hover{text-decoration:underline}
  header{display:flex;align-items:center;gap:16px;padding:14px 24px;border-bottom:1px solid var(--brd);background:#0a0f1c}
  header img{width:28px;height:28px}
  header h1{font-size:16px;font-weight:600}
  header .chip{margin-left:auto;padding:4px 10px;border:1px solid var(--brd);border-radius:6px;color:var(--mut);font-size:12px}
  header select{background:#0a0f1c;color:var(--fg);border:1px solid var(--brd);padding:6px 10px;border-radius:6px;font-size:13px}
  main{max-width:1200px;margin:0 auto;padding:24px}
  .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin-bottom:24px}
  .card{background:var(--card);border:1px solid var(--brd);border-radius:8px;padding:16px}
  .card h3{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--mut);margin-bottom:8px}
  .card .v{font-size:22px;font-weight:600}
  section{background:var(--card);border:1px solid var(--brd);border-radius:8px;margin-bottom:24px;overflow:hidden}
  section h2{padding:14px 18px;border-bottom:1px solid var(--brd);font-size:13px;text-transform:uppercase;letter-spacing:.06em;color:var(--mut);font-weight:600}
  table{width:100%;border-collapse:collapse}
  th,td{padding:10px 18px;text-align:left;font-size:13px;border-bottom:1px solid var(--brd)}
  th{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--mut);font-weight:600;background:#0a0f1c}
  td.mono{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px}
  td.r{text-align:right}
  .err{color:#f85149;padding:16px;font-size:13px}
  .age{color:var(--mut);font-size:12px}
</style>
</head>
<body>
<header>
  <img id="logo" src="/logo.svg" alt="">
  <h1 id="brand">Explorer</h1>
  <select id="network" title="Network"></select>
  <select id="chain" title="Chain"></select>
  <span class="chip" id="chip"></span>
</header>
<main>
  <div class="grid" id="kpi"></div>
  <section><h2>Latest Blocks</h2><div id="blocks"></div></section>
  <section><h2>Latest Transactions</h2><div id="txs"></div></section>
</main>
<script>
(() => {
  const ENV = window.ENV || {};
  const BRAND = ENV.VITE_BRAND || {};
  const CHAINS = (ENV.VITE_CHAINS || []).filter(c => c.type === 'evm' || !c.type);
  const NETWORK_HOSTS = ENV.VITE_NETWORK_HOSTS || {};
  const CURRENT_NETWORK = ENV.VITE_CURRENT_NETWORK || '';
  const PATH_BASE = '/v1/indexer';
  const $ = s => document.querySelector(s);
  const fmt = n => (n == null || n === '') ? 'â€“' : Number(n).toLocaleString();
  const short = h => h ? h.slice(0,10) + 'â€¦' + h.slice(-6) : '';
  const age = t => {
    if (!t) return '';
    const s = (Date.now() - new Date(t).getTime())/1000;
    if (s < 60) return Math.round(s)+'s ago';
    if (s < 3600) return Math.round(s/60)+'m ago';
    if (s < 86400) return Math.round(s/3600)+'h ago';
    return Math.round(s/86400)+'d ago';
  };

  // Pick chain from URL hash (#cchain), localStorage, or default.
  const slugFromHash = location.hash.replace(/^#/, '').trim();
  const stored = localStorage.getItem('explorer.slug');
  const def = (CHAINS.find(c=>c.default) || CHAINS[0] || {}).slug;
  let current = CHAINS.find(c=>c.slug === slugFromHash)
              || CHAINS.find(c=>c.slug === stored)
              || CHAINS.find(c=>c.slug === def)
              || CHAINS[0];

  // Render branding.
  if (BRAND.name) $('#brand').textContent = BRAND.name;
  if (BRAND.iconUrl) $('#logo').src = BRAND.iconUrl;
  if (BRAND.accentColor) document.documentElement.style.setProperty('--ac', BRAND.accentColor);

  // Network picker â€” hidden unless we have at least two siblings.
  const netSel = $('#network');
  const labels = Object.keys(NETWORK_HOSTS);
  if (labels.length >= 2 && CURRENT_NETWORK) {
    labels.sort();
    labels.forEach(label => {
      const o = document.createElement('option');
      o.value = label;
      o.textContent = label.charAt(0).toUpperCase() + label.slice(1);
      if (label === CURRENT_NETWORK) o.selected = true;
      netSel.appendChild(o);
    });
    netSel.onchange = () => {
      const target = NETWORK_HOSTS[netSel.value];
      if (!target || target === location.host) return;
      // Preserve protocol + path + hash so the user lands on the same
      // chain/page on the sibling network.
      location.href = location.protocol + '//' + target + location.pathname + location.hash;
    };
  } else {
    netSel.style.display = 'none';
  }

  // Chain picker.
  const sel = $('#chain');
  CHAINS.forEach(c => {
    const o = document.createElement('option');
    o.value = c.slug;
    o.textContent = c.name + ' (' + (c.coin || '') + ')';
    if (current && c.slug === current.slug) o.selected = true;
    sel.appendChild(o);
  });
  sel.onchange = () => {
    current = CHAINS.find(c => c.slug === sel.value) || current;
    localStorage.setItem('explorer.slug', current.slug);
    location.hash = current.slug;
    render();
  };

  async function jget(path){
    const r = await fetch(path, {headers:{accept:'application/json'}});
    if (!r.ok) throw new Error(path + ' â†’ ' + r.status);
    return r.json();
  }

  async function loadStats(slug){
    return jget(PATH_BASE + '/' + slug + '/stats');
  }
  async function loadBlocks(slug){
    return jget(PATH_BASE + '/' + slug + '/blocks?items_count=10');
  }
  async function loadTxs(slug){
    return jget(PATH_BASE + '/' + slug + '/transactions?items_count=10');
  }

  function renderKpi(stats, chain){
    const items = [
      ['Chain ID', chain.chainId || 'â€“'],
      ['Blocks', fmt(stats.total_blocks)],
      ['Transactions', fmt(stats.total_transactions)],
      ['Addresses', fmt(stats.total_addresses)],
      ['Avg block', stats.average_block_time ? stats.average_block_time + 'ms' : 'â€“'],
      ['Gas used today', fmt(stats.gas_used_today)],
    ];
    $('#kpi').innerHTML = items.map(([k,v]) =>
      '<div class="card"><h3>'+k+'</h3><div class="v">'+v+'</div></div>'
    ).join('');
  }

  function renderBlocks(blocks){
    const rows = (blocks.items || []).map(b => {
      const ts = b.timestamp ? new Date(b.timestamp).toISOString() : '';
      return '<tr>'
        + '<td class="r">'+fmt(b.height)+'</td>'
        + '<td class="mono">'+short(b.hash)+'</td>'
        + '<td class="r">'+fmt(b.tx_count)+'</td>'
        + '<td class="age">'+age(b.timestamp)+'</td>'
        + '</tr>';
    }).join('');
    $('#blocks').innerHTML = rows
      ? '<table><thead><tr><th class="r">#</th><th>Hash</th><th class="r">Txs</th><th>Age</th></tr></thead><tbody>'+rows+'</tbody></table>'
      : '<div class="err">No blocks indexed yet.</div>';
  }

  function renderTxs(txs){
    const rows = (txs.items || []).map(t => {
      return '<tr>'
        + '<td class="mono">'+short(t.hash)+'</td>'
        + '<td class="mono">'+short(t.from && t.from.hash)+'</td>'
        + '<td class="mono">'+short(t.to && t.to.hash)+'</td>'
        + '<td class="age">'+age(t.timestamp)+'</td>'
        + '</tr>';
    }).join('');
    $('#txs').innerHTML = rows
      ? '<table><thead><tr><th>Hash</th><th>From</th><th>To</th><th>Age</th></tr></thead><tbody>'+rows+'</tbody></table>'
      : '<div class="err">No transactions indexed yet.</div>';
  }

  async function render(){
    if (!current){
      document.body.innerHTML = '<div class="err" style="padding:32px">No chains configured. Check window.ENV.</div>';
      return;
    }
    $('#chip').textContent = (current.coin || '') + ' â€¢ ' + (current.chainId || '');
    document.title = current.name + ' â€” ' + (BRAND.name || 'Explorer');
    try {
      const [s, b, t] = await Promise.allSettled([
        loadStats(current.slug),
        loadBlocks(current.slug),
        loadTxs(current.slug),
      ]);
      renderKpi(s.value || {}, current);
      renderBlocks(b.value || {});
      renderTxs(t.value || {});
    } catch (e) {
      $('#kpi').innerHTML = '<div class="err">'+e.message+'</div>';
    }
  }

  render();
  setInterval(render, 15000);
})();
</script>
</body>
</html>
ñ$|‡á`ÁZxþùÌB¡N°å
// Network host derivation. Prefer server-supplied values (v1.2.20+ go
  // binary fills VITE_CURRENT_NETWORK and VITE_NETWORK_HOSTS), fall back
  // to client-side parsing so the network switcher still works on older
  // images (v1.2.18) where /envs.js doesn't ship the fields.
  const networkFromHost = (host) => {
    host = (host || '').toLowerCase().split(':')[0];
    if (!host || host === 'localhost' || /^[0-9.]+$/.test(host)) return null;
    if (host.endsWith('.svc.cluster.local') || host.endsWith('.local')) return null;
    const m = host.match(/^(explore|explorer|api-explore|api-explorer)([.-])([a-z][a-z0-9]+)(?:-(test|testnet|dev|devnet|local))?(\.[^.]+(?:\.[^.]+)*)$/);
    if (!m) return null;
    const [, , sep, brand, envTag, tail] = m;
    const env = envTag || '';
    // sep==='.' â†’ brand-owned host (explore.zoo.network); preserve tail
    // on mainnet. sep==='-' â†’ lux.network-prefix host
    // (explore-zoo.lux.network); force tail to .network on mainnet
    // since brand-owned mainnet aliases are always *.network.
    const mainnetTail = (sep === '.' && env === '') ? tail : '.network';
    return {
      current: env || 'mainnet',
      hosts: {
        mainnet: 'explore.' + brand + mainnetTail,
        testnet: 'explorer.' + brand + '-test.network',
      },
    };
  };
  const fallback = networkFromHost(location.host) || {current:'',hosts:{}};
  cons“é
)[&& Object.keys(ENV.VITE_NETWORK_HOSTS).length
    ? ENV.VITE_NETWORK_HOSTS : fallback.hosts“8fallback.current³Qûº¶ðWï¨‹x�
põ¡NÝc°r
“˜
K
  // Client-side brand inference from hostname. We do this so the SPA
  // renders the correct brand even on older Go binaries (v1.2.18) where
  // the hostâ†’chain matcher hasn't been updated to recognize all our
  // explorer.{brand}-test.network shapes â€” when the server returns the
  // default Lux brand for what is actually a Zoo host, we override.
  const BRAND_TABLE = {
    lux:    {name:'Lux Explorer',  coin:'LUX',  accentColor:'#5cf',     iconUrl:'/icon-lux.svg',  logoUrl:'/logo-lux.svg'},
    cchain: {name:'Lux C-Chain',   coin:'LUX',  accentColor:'#5cf',     iconUrl:'/icon-lux.svg',  logoUrl:'/logo-lux.svg'},
    zoo:    {name:'Zoo Explorer',  coin:'ZOO',  accentColor:'#FCF006',  iconUrl:'/icon-zoo.svg',  logoUrl:'/logo-zoo.svg'},
    hanzo:  {name:'Hanzo Explorer',coin:'AI',   accentColor:'#0b1220'},
    pars:   {name:'Pars Explorer', coin:'PARS', accentColor:'#15803d'},
    spc:    {name:'SPC Explorer',  coin:'SPC',  accentColor:'#0ea5e9'},
    osage:  {name:'Osage Explorer',coin:'OSG',[  accentColor:'#dc2626'},
  };
  const inferredBrandKey = (() => {
    const m = (location.“/
      .match(/^(?:“*[.-]([a-z][a-z0-9]+)(?:-(?:“\ 9\./);
    return m ? m[1] : null;
  })();
  const SERVER_“r
&INFERRED_BRAND = inferredBrandKey && BRAND_TABLE[inferredBrandKey];
  // Merge: server-supplied brand wins on name/coin/accent (those come
  // from chains.yaml and are authoritative), inferred brand fills in
  // missing iconUrl/logoUrl so the per-brand SVGs always load even when
  // the chain config doesn't carry icon_url/logo_url.
  const BRAND = {
    name:        SERVER_BRAND.name && SERVER_BRAND.name !== 'Lux Explorer' ? SERVER_BRAND.name : (INFERRED_BRAND?.name || SERVER_BRAND.name || 'Explorer'),
    coin:        SERVER_BRAND.coin || INFERRED_BRAND?.coin || '',
    accentColor: SERVER_BRAND.accentColor || INFERRED_BRAND?.accentColor || '#5cf',
    iconUrl:     (SERVER_BRAND.iconUrl && SERVER_BRAND.iconUrl !== '/icon.svg') ? SERVER_BRAND.iconUrl : (INFERRED_BRAND?.iconUrl || '/icon.svg'),
    logoUrl:     (SERVER_BRAND.logoUrl && SERVER_BRAND.logoUrl !== '/logo.svg') ?E SERVER_BRAND.logoUrl : (INFERRED_BRAND?.logoUrl || '/logo.svg'),
  }³á
n// Normalize env shorthand (test â†’ testnet) so the dropdown label
    // matches the server-side current value.
    const envVCanon = env ? (env === 'test' ? 'testnet' : env === 'dev' ? 'devnet' : env) : 'mainnet“H"Canon³wõhost-inferred
  // brand, server default, or first chain. Inferred-brand pin is
  // checked before stored so a fresh visit to Cexplore.zoo.network
  // opens on Zoo, not whatever was last cached³v(inferredBrandKey &&“AinferredBrandKey)“›-³•Œ¥[êã„Þ9x#ÜþºÇ"40000 .github ÞbGáws‡MM½‚5KÖr]‘"'%00644 Dockerfile @Üþ®$jB•œ‘0;M˜>c{‘“R“9Ò[$rQ”³j¦Ù“ÄU!ÆÐ“T8Ø½#N!,¾y«¾˜º¢¡ªä^Ÿ‘R100644 frontend.go X(´ÊýÈëãñqÅ-µÆŠ
]100644 go.mod ð_¾¯Q\æºaÿw¦R»
nuC•p100644 go.sum ²6}üÛÃSf„|({rŠÓ]é½¤³7F0ûÄûƒÁ�1~ótHŠãT×4 ©“‘�O[>LbgXá)�	i}b Rt‡ë�3x+ ÔÿÇÇ"40000 .github ó;:ø5¾ŒÌ„¡Û,
�ö4‰8µ±"%Zù¡ã:xC ¼ÿÇÇ°Ž6²÷©ÈÛë¬#jÞÙ ëçû100644 go.sum zNšö•v3Õ½4ÞwÍ�·ý|³Äƒ¥óãQxC ¼ÿÇÇ°Ž6ïƒ
¸ÒþÊdöü°É‰/w"Ä$100644 go.sum �q
Äê;§tç`ÌS@ƒòO,1×³Äƒ ™LãQxC ¼ÿÇÇ°Ž6…³°ê7i»û£^Ë¤,ÍŒ™A‡100644 go.sum 1)7|í¡˜?.2s£�‘®ÅŸ³ÄƒÎð´ïQx àÿÇÇ�ZaëMí{¯×iŽð6O¨'wL¡±nÙàªÕî-xN ±ÿÇŽ�OCLAUDE.md h2øq–!8­]~Ó&o QËÀ‘Hr!20000 LLM.md hëœôSÐúÝóªÊìsWé{¨é±¹Ždý!ã\xC ¼ÿŽŽ°Õ6AÛÖ•ý3öhÌ�Ð(¥‚±qÂm100644 go.sum |Sï…)B’(2—(ŽÆÚ‚Ü¹I³ƒà ØìQx¼ CÿŽº�•�üoŒÃT?/Î4é˜ÔÒ^K‘“ù%Mµ¦•ÁÑïw8-’H³ †ï100644 frontend.go bgP6
®áoJ)Âi™PÿZb100644 frontend_test.go Ìß¤ü[Í§r`!¸¡GøÇe}100644 go.mod AÛÖ•ý3öhÌ³ßrÃ2ÙØ¥µ=ðÌÓý=I˜“e)âUMÑí€JxM ²ÿºº°³@ý>Ø)HnñÚ•è¶Ä`­é,¨100644 frontend_test.go >Â<ì„)>Ý¡�y×Í¥8�Õu³óÇ+8"ï\x àÿºº�Y½`ŸÚîþÙ(z52æv#ºècæ±mMæjóá-x! Þÿºº°E5ug¡)BLF±–9—,q¤…¦Þ³¥Ä;í/xM ²ÿºº°³@ø½°á�ÒlßËD“ÚxŽÅÑ<³100644 frontend_test.go OŽ«XÞ“æís¤…>ð‰[¾%q³óÇ†#Ëæ[xf ™ÿºº°³@Y kVa‹Bð2�é8®€qÂ·100644 frontend_test.go (xN2}à­«·GÀR]#î|…ã+³óŠÒ�lõô	|‹aRùQÓÙàÆ[Ñ“‘)3ñ+Ëàtx  ßÿºº°}èx¹€&š’Œ¯û/IÁôi-‰+#“‘)ì»âà.x  ßÿºº°}…V,êôÔ‚ã•yíAñùXt¦oo“‘)ú
³è#’Ù5x8Çý–$Ž°	Frontend chain knobs are build-time, not runtime: the Blockscout-fork
# SPA reads NEXT_PUBLIC_* at `pnpm build` and bakes them into the
# emitted JS. Default to the Liquid devnet shape; downstream rebuilds
# override via --build-arg NETWORK_ID=â€¦ (etc.) for testnet/mainnet“1_ 8675312
ARG NETWORK_NAME=""
ARG “î“¬“ì“ÒA³b“¢2&& pnpm build || true
RUN mkdir -p /app/out && [ -f /app/out/index.html ] || \
    echo '<!doctype html><title>Explorer</title>,<div id="root"></div>' > /app/out/index.html³)
¾&COPY --from=frontend /app/out ./static³ÇOFÀ¾ñä}ƒHxÔ+øŽÝ$°<<<<<<< Updated upstream³=======
# luxfi/explore is a Next.js `output: 'standalone'` SSR app, not a static
# export â€” so `pnpm build` does NOT produce /app/out/. The unified
# explorer needs static assets to embed via go:embed; until upstream
# explore ships `output: 'export'`, we ship a substantive runtime SPA
# shell from explorer/static/. The shell wires /envs.js (window.ENV)
# to render the chain list client-side. When upstream explore lands
# static-export support, this stage gains `pnpm build && cp .next/static
# /app/oCut/_next/static` and the shell can hydrate.
>>>>>>> Stashed changes“xregulated EVM L1"
ARG “ï“²)# Best-effort: clone explore and try to produce a static export. If
# anything fails (missing pnpm-lockfile entry, missing env Uvars, SSR-only
# Next config), don't fail the image â€” explorer/static/ still ships.“ÚB || true³b$if [ -f package.json ]; then \
     “}3--ignore-scripts“¯; \
    fi
# /app/out is created only if static export succeeded. The builder stage
# below COPYs it over explorer/static/ â€” but only if non-empty. The
# fallback path is the local explorer/static/index.html (substantive
# 372-byte SPA shell with id="r1oot" + /envs.js bootstrap).
RUN mkdir -p /app/out³[¾# explorer/static/ already contains the substantive SPA shell. Only
# overlay frontend /app/out/ if it has the static-exported assets (an
# index.html that's NOT the 60-byte stub). When upstream explore ships
# static-export, the cp succeeds and overlays the real assets.
RUN --mount=type=bind,from=frontend,source=/app/out,target=/tmp/frontend-out \
    sh -eu -c '\
      if [ -s /tmp/frontend-out/index.html ] && [ "$(wc -c </tmp/frontend-out/index.html)" -gt 100 ]; then \
        echo "[frontend] overlaying static export onto explorer/static/"; \
        cp -R /tmp/frontend-out/. /src/explorer/static/; \
      else \
        eceho "[frontend] static export not produced, keeping committed explorer/static/ SPA shell"; \
      fi'³?O(ã­¶ïŽcxðþÝ$š$°³> h#
# Build-arg defaults shape the optional Next.js static export attempt
# (only used if explore.git land“  upstream). Real
# branding comes from the runtime chains.yaml + brand assets mount on
# the explorer Deployment â€” no rebuild needed to swap.³uè8`®xn ‘ÿ100644 icon.svg ^pý”`9«ƒ8(í=ëÙü0¸100644 index.html !"ò±¤Ðƒ‡Îñ¯'ïêXa˜—100644 logo.svg ¿ÛÃž—ìÿ6Ñ•£„µ_|nÉ%€<†.m¾Òx.Ñâ<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Lux Explorer</title>
<link rel="icon" type="image/svg+xml" href="/icon.svg">
<script src="/envs.js"></script>
<style>
  :root{--bg:#0b1220;--card:#111a2c;--fg:#e6edf3;--mut:#7d8590;--ac:#5cf;--brd:#1f2a3d}
  *{box-sizing:border-box;margin:0;padding:0}
  body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;background:var(--bg);color:var(--fg)}
  a{color:var(--ac);text-decoration:none}
  a:hover{text-decoration:underline}
  header{display:flex;align-items:center;gap:16px;padding:14px 24px;border-bottom:1px solid var(--brd);background:#0a0f1c}
  header img{width:28px;height:28px}
  header h1{font-size:16px;font-weight:600}
  header .chip{margin-left:auto;padding:4px 10px;border:1px solid var(--brd);border-radius:6px;color:var(--mut);font-size:12px}
  header select{background:#0a0f1c;color:var(--fg);border:1px solid var(--brd);padding:6px 10px;border-radius:6px;font-size:13px}
  main{max-width:1200px;margin:0 auto;padding:24px}
  .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin-bottom:24px}
  .card{background:var(--card);border:1px solid var(--brd);border-radius:8px;padding:16px}
  .card h3{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--mut);margin-bottom:8px}
  .card .v{font-size:22px;font-weight:600}
  section{background:var(--card);border:1px solid var(--brd);border-radius:8px;margin-bottom:24px;overflow:hidden}
  section h2{padding:14px 18px;border-bottom:1px solid var(--brd);font-size:13px;text-transform:uppercase;letter-spacing:.06em;color:var(--mut);font-weight:600}
  table{width:100%;border-collapse:collapse}
  th,td{padding:10px 18px;text-align:left;font-size:13px;border-bottom:1px solid var(--brd)}
  th{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--mut);font-weight:600;background:#0a0f1c}
  td.mono{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px}
  td.r{text-align:right}
  .err{color:#f85149;padding:16px;font-size:13px}
  .age{color:var(--mut);font-size:12px}
</style>
</head>
<body>
<header>
  <img id="logo" src="/logo.svg" alt="">
  <h1 id="brand">Explorer</h1>
  <select id="chain"></select>
  <span class="chip" id="chip"></span>
</header>
<main>
  <div class="grid" id="kpi"></div>
  <section><h2>Latest Blocks</h2><div id="blocks"></div></section>
  <section><h2>Latest Transactions</h2><div id="txs"></div></section>
</main>
<script>
(() => {
  const ENV = window.ENV || {};
  const BRAND = ENV.VITE_BRAND || {};
  const CHAINS = (ENV.VITE_CHAINS || []).filter(c => c.type === 'evm' || !c.type);
  const PATH_BASE = '/v1/indexer';
  const $ = s => document.querySelector(s);
  const fmt = n => (n == null || n === '') ? 'â€“' : Number(n).toLocaleString();
  const short = h => h ? h.slice(0,10) + 'â€¦' + h.slice(-6) : '';
  const age = t => {
    if (!t) return '';
    const s = (Date.now() - new Date(t).getTime())/1000;
    if (s < 60) return Math.round(s)+'s ago';
    if (s < 3600) return Math.round(s/60)+'m ago';
    if (s < 86400) return Math.round(s/3600)+'h ago';
    return Math.round(s/86400)+'d ago';
  };

  // Pick chain from URL hash (#cchain), localStorage, or default.
  const slugFromHash = location.hash.replace(/^#/, '').trim();
  const stored = localStorage.getItem('explorer.slug');
  const def = (CHAINS.find(c=>c.default) || CHAINS[0] || {}).slug;
  let current = CHAINS.find(c=>c.slug === slugFromHash)
              || CHAINS.find(c=>c.slug === stored)
              || CHAINS.find(c=>c.slug === def)
              || CHAINS[0];

  // Render branding.
  if (BRAND.name) $('#brand').textContent = BRAND.name;
  if (BRAND.iconUrl) $('#logo').src = BRAND.iconUrl;
  if (BRAND.accentColor) document.documentElement.style.setProperty('--ac', BRAND.accentColor);

  // Chain picker.
  const sel = $('#chain');
  CHAINS.forEach(c => {
    const o = document.createElement('option');
    o.value = c.slug;
    o.textContent = c.name + ' (' + (c.coin || '') + ')';
    if (current && c.slug === current.slug) o.selected = true;
    sel.appendChild(o);
  });
  sel.onchange = () => {
    current = CHAINS.find(c => c.slug === sel.value) || current;
    localStorage.setItem('explorer.slug', current.slug);
    location.hash = current.slug;
    render();
  };

  async function jget(path){
    const r = await fetch(path, {headers:{accept:'application/json'}});
    if (!r.ok) throw new Error(path + ' â†’ ' + r.status);
    return r.json();
  }

  async function loadStats(slug){
    return jget(PATH_BASE + '/' + slug + '/stats');
  }
  async function loadBlocks(slug){
    return jget(PATH_BASE + '/' + slug + '/blocks?items_count=10');
  }
  async function loadTxs(slug){
    return jget(PATH_BASE + '/' + slug + '/transactions?items_count=10');
  }

  function renderKpi(stats, chain){
    const items = [
      ['Chain ID', chain.chainId || 'â€“'],
      ['Blocks', fmt(stats.total_blocks)],
      ['Transactions', fmt(stats.total_transactions)],
      ['Addresses', fmt(stats.total_addresses)],
      ['Avg block', stats.average_block_time ? stats.average_block_time + 'ms' : 'â€“'],
      ['Gas used today', fmt(stats.gas_used_today)],
    ];
    $('#kpi').innerHTML = items.map(([k,v]) =>
      '<div class="card"><h3>'+k+'</h3><div class="v">'+v+'</div></div>'
    ).join('');
  }

  function renderBlocks(blocks){
    const rows = (blocks.items || []).map(b => {
      const ts = b.timestamp ? new Date(b.timestamp).toISOString() : '';
      return '<tr>'
        + '<td class="r">'+fmt(b.height)+'</td>'
        + '<td class="mono">'+short(b.hash)+'</td>'
        + '<td class="r">'+fmt(b.tx_count)+'</td>'
        + '<td class="age">'+age(b.timestamp)+'</td>'
        + '</tr>';
    }).join('');
    $('#blocks').innerHTML = rows
      ? '<table><thead><tr><th class="r">#</th><th>Hash</th><th class="r">Txs</th><th>Age</th></tr></thead><tbody>'+rows+'</tbody></table>'
      : '<div class="err">No blocks indexed yet.</div>';
  }

  function renderTxs(txs){
    const rows = (txs.items || []).map(t => {
      return '<tr>'
        + '<td class="mono">'+short(t.hash)+'</td>'
        + '<td class="mono">'+short(t.from && t.from.hash)+'</td>'
        + '<td class="mono">'+short(t.to && t.to.hash)+'</td>'
        + '<td class="age">'+age(t.timestamp)+'</td>'
        + '</tr>';
    }).join('');
    $('#txs').innerHTML = rows
      ? '<table><thead><tr><th>Hash</th><th>From</th><th>To</th><th>Age</th></tr></thead><tbody>'+rows+'</tbody></table>'
      : '<div class="err">No transactions indexed yet.</div>';
  }

  async function render(){
    if (!current){
      document.body.innerHTML = '<div class="err" style="padding:32px">No chains configured. Check window.ENV.</div>';
      return;
    }
    $('#chip').textContent = (current.coin || '') + ' â€¢ ' + (current.chainId || '');
    document.title = current.name + ' â€” ' + (BRAND.name || 'Explorer');
    try {
      const [s, b, t] = await Promise.allSettled([
        loadStats(current.slug),
        loadBlocks(current.slug),
        loadTxs(current.slug),
      ]);
      renderKpi(s.value || {}, current);
      renderBlocks(b.value || {});
      renderTxs(t.value || {});
    } catch (e) {
      $('#kpi').innerHTML = '<div class="err">'+e.message+'</div>';
    }
  }

  render();
  setInterval(render, 15000);
})();
</script>
</body>
</html>
W×4ç»áxäépackage main

import (
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

// TestFrontendStaticOverlay verifies that NewFrontend prefers the on-disk
// static dir over the embedded FS when cfg.StaticDir points at a real
// directory. The overlay use-case: ConfigMap-mounting a richer SPA shell
// into an existing image without rebuilding.
func TestFrontendStaticOverlay(t *testing.T) {
	dir := t.TempDir()
	want := "<!doctype html><title>OVERLAY</title>"
	if err := os.WriteFile(filepath.Join(dir, "index.html"), []byte(want), 0644); err != nil {
		t.Fatal(err)
	}

	cfg := Config{StaticDir: dir, BrandDefault: Brand{Name: "Test"}}
	f, err := NewFrontend(cfg, NewChainRegistry())
	if err != nil {
		t.Fatal(err)
	}

	mux := http.NewServeMux()
	f.Mount(mux)

	req := httptest.NewRequest("GET", "/", nil)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)

	got := rec.Body.String()
	if !strings.Contains(got, "OVERLAY") {
		t.Fatalf("expected overlay index.html, got %q", got)
	}
}

// TestFrontendNoOverlay verifies that with no StaticDir, the embedded
// index.html is served (current behaviour â€” pre-overlay).
func TestFrontendNoOverlay(t *testing.T) {
	cfg := Config{BrandDefault: Brand{Name: "Test"}}
	f, err := NewFrontend(cfg, NewChainRegistry())
	if err != nil {
		t.Fatal(err)
	}
	if f.overlay != nil {
		t.Fatal("expected nil overlay when StaticDir empty")
	}

	mux := http.NewServeMux()
	f.Mount(mux)

	req := httptest.NewRequest("GET", "/", nil)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)

	got := rec.Body.String()
	if !strings.Contains(got, "doctype html") {
		t.Fatalf("expected fallback index.html, got %q", got)
	}
}

// TestFrontendOverlayMissingFileFallsBack covers the case where the
// overlay dir exists but a particular file is missing â€” the request
// falls through to the embedded FS (and ultimately to index.html for
// SPA-routing).
func TestFrontendOverlayMissingFileFallsBack(t *testing.T) {
	dir := t.TempDir()
	// Only index.html in overlay; no other assets.
	if err := os.WriteFile(filepath.Join(dir, "index.html"), []byte("<!doctype html>OVERLAY"), 0644); err != nil {
		t.Fatal(err)
	}

	cfg := Config{StaticDir: dir, BrandDefault: Brand{Name: "Test"}}
	f, err := NewFrontend(cfg, NewChainRegistry())
	if err != nil {
		t.Fatal(err)
	}

	mux := http.NewServeMux()
	f.Mount(mux)

	// Request a path the overlay doesn't have. With SPA-routing, the
	// handler returns index.html â€” which IS in the overlay.
	req := httptest.NewRequest("GET", "/some/spa/route", nil)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)

	got := rec.Body.String()
	if !strings.Contains(got, "OVERLAY") {
		t.Fatalf("expected overlay index.html (SPA fallback), got %q", got)
	}
}

// TestFrontendEnvStaticDir verifies $EXPLORER_STATIC_DIR is the env-var
// equivalent of cfg.StaticDir. Empty cfg + env-var-set overlay should
// still pick up the overlay.
func TestFrontendEnvStaticDir(t *testing.T) {
	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, "index.html"), []byte("ENV-OVERLAY"), 0644); err != nil {
		t.Fatal(err)
	}
	t.Setenv("EXPLORER_STATIC_DIR", dir)

	cfg := Config{BrandDefault: Brand{Name: "Test"}}
	f, err := NewFrontend(cfg, NewChainRegistry())
	if err != nil {
		t.Fatal(err)
	}
	if f.overlay == nil {
		t.Fatal("expected overlay populated from env var")
	}
	if !strings.Contains(string(f.index), "ENV-OVERLAY") {
		t.Fatalf("expected env-overlay index, got %q", string(f.index))
	}
}

// TestBrandStructHostMatching covers every hostname shape the unified
// explorer's IngressRoutes hand it: the per-chain brand must win over
// BrandDefault for both UI hosts (explore-{slug}, explore.{slug}.tld)
// and API hosts (api-explore-{slug}). The matcher must NOT bleed across
// chains â€” a hanzo host must not pick zoo's brand or vice versa.
func TestBrandStructHostMatching(t *testing.T) {
	hanzoBrand := Brand{Name: "Hanzo", Coin: "AI", AccentColor: "#0b1220"}
	zooBrand := Brand{Name: "Zoo", Coin: "ZOO", AccentColor: "#7c3aed"}
	cfg := Config{
		BrandDefault: Brand{Name: "Lux Explorer"},
		Chains: []ChainConfig{
			{Slug: "cchain", Brand: &Brand{Name: "Lux C-Chain", Coin: "LUX"}},
			{Slug: "hanzo", Brand: &hanzoBrand},
			{Slug: "zoo", Brand: &zooBrand},
		},
	}
	f, err := NewFrontend(cfg, NewChainRegistry())
	if err != nil {
		t.Fatal(err)
	}

	cases := []struct {
		host string
		want string
	}{
		// Bare slug + slug-prefix shape.
		{"hanzo", "Hanzo"},
		{"hanzo.lux.network", "Hanzo"},
		// explore-{slug} prefix shape (lux.network UI hosts).
		{"explore-hanzo.lux.network", "Hanzo"},
		{"explore-hanzo-test.lux.network", "Hanzo"},
		{"explore-hanzo-dev.lux.network", "Hanzo"},
		{"explore-zoo.lux.network", "Zoo"},
		// api-explore-{slug} prefix shape (lux.network API hosts).
		{"api-explore-hanzo.lux.network", "Hanzo"},
		{"api-explore-hanzo-test.lux.network", "Hanzo"},
		{"api-explore-zoo.lux.network", "Zoo"},
		// explore.{slug}.tld shape (brand-owned domains).
		{"explore.hanzo.ai", "Hanzo"},
		{"explore.hanzo.network", "Hanzo"},
		{"explore.zoo.network", "Zoo"},
		{"explore.zoo.ngo", "Zoo"},
		{"api-explore.hanzo.ai", "Hanzo"},
		{"api-explore.zoo.ngo", "Zoo"},
		// Mismatched hosts fall back to BrandDefault.
		{"explore.lux.network", "Lux Explorer"},
		{"unknown.example.com", "Lux Explorer"},
		// Cross-contamination guard: explore-spcfoo must NOT match spc.
		{"explore-spcfoo.lux.network", "Lux Explorer"},
	}

	for _, tc := range cases {
		t.Run(tc.host, func(t *testing.T) {
			got := f.brandStruct(tc.host).Name
			if got != tc.want {
				t.Fatalf("host=%q: got brand=%q, want %q", tc.host, got, tc.want)
			}
		})
	}
}
HÁ<Ål‘ó7x óÿ¡#¡#�*3±+v‹Úg�Åux øÿ§±°±+Úg«Zx øÿ›,Ÿ°Ÿ.Þê‘Éx* Õÿô¹�¸5-0.20260524134132-0c0ba56e32d7±¹bª1à:x  ßÿ¹¹�Æ0605-aade4dd9fd4c“q±êO»•Dâ�­*x íÿ°‡Í…°¡A“ÐB
³ŽB"AOXµá€
x! Þÿ¹¹�Å05612-65220f1104cd“q±êO°H
Hã‘ÊOx3 Ìÿô½�³(d5e65d4b50c127ce8982ef94f448dd17822448f7±¹b´…ânŽùLx ñÿêƒ‡‚°Û?³¾@,A3Scá“ð3x! Þÿœœ°lX(´ÊýÈëãñqÅ-µÆŠ
]³€œìÂ¹ä1xd ›ÿœÇ°Ó’•
ëExöšW8Äê[SGRÁê“ç6?‹�ŠwY��Z<Õ&U×£(®ú§100644 realtime_sse.go “aþ£Â¡Nf%¸l\ÖÂòÈ^“1ëŒf+�ërx{ „ÿÇÇ°ŽYõŒ5•3Öd "J›„1x28100644 go.sum ²6}üÛÃSf„|({rŠÓ]é½¤100644 main.go ?¦ˆÍ\ò\®$·¬ÿƒPJ¦²�³çLÏÖ37›pUëì€•L¤#@ˆ[1ì€	x ãÿÇÇ°38ÿ ?-#øAZ^»jD¬·~ÓÝp—ƒËâ+xR ­ÿÇÇ°ÓßGª/× Ïsý´÷ÛÜ�®Ìµ“ç6zY¥‘Ÿ¯ª»W/rb4gÓŒÝë“1
íe ìSrt²ãŠIØËj™S“\ë€&¾à`x  ßÿÇÇ°HZ*‚ø’Ú¶%éáe�&…Ê“\ëÊÞLl‘Ïx óÿô›�¸4±¹b$5#à—±Fx°OþÊTÌQ°_SSE response headers â€” set BEFORE the HEAD short-circuit so
		// the pre-flight sees them and the SPA's `u.ok` check passes
	4	// (200 + text/event-stream â†’ open EventSource).
“p8“�%[“�Ë// HEAD pre-flight from EventSource open. Standard HTTP requires
		// HEAD to return the same headers as GET would, with no bodAy.
		// We've already set them above; just acknowledge and return“J*“«,“&.³–Ó“r6“�%ˆ“N&“«,³&5”˜“ql òsx óÿŒ=�:°ì³w"r�å—ÃNxU ªÿƒ5µ.°‘“qframed“�A#framed = append(framed, '\n', '\n')“O"framed)
	}
}
“@³£àÌöBàƒ@x@ ¿ÿÌQþ:°÷“Â“t+“¸Fif c.channel != eventType {“ŽL’body³#y/'.á ôBx îÿŒ=Ö7°€³«A³wA1¦x& Ùÿ100644 docker.yml {92þj‘ÉØ‰8Éé¬b8«—âà†á	�ƒLx‘ nÿêƒæ€°<37.45 h1:uGK0y4+aLipE/M0YIQ5hcsWv0ZG0E4cPv03a94K/eLE“!?“ù;97.45/go.mod h1:GnAkhQ7HNs3X0Tzx5nOONS3kl0yRmWHbDcRO5ffILs“á:³ú;ø“•>Ÿ³¾@,Ak0Æn€!x ñÿæ€½ÿ°<³­<¹C4ø®¦x& Ùÿ100644 docker.yml Ú€çlœkç‰YÆ¸·×û|°`ïe)Êà“ëox ïÿÕÓ°slux³x]6÷Ìç�x èÿæ€œþ°i7³8–“œ=³Z=Cz5ûl‘Øx óÿ‘‘�*1±+f¯ž¦x& Ùÿ100644 docker.yml £³×—87ä:ße³ >£o²C,¡à“íx` ŸÿÕƒ�¦LARC runner pool. arm64 via QEMU until a
# native arm64 runner joins the pool“B1lux³x]C žêpxz …ÿƒÁ
�…
amd64-only‘�.Ywill be added back
# once a native arm64 scale-set is provisioned (CLAUDE.md: no QEMU).
#‘óŽ³Ú¦“Œ÷"ò*Ê¦x& Ùÿ100644 docker.yml ­3â+	ë«œÛÊ³’Xi¼…ŠÃŠ]5ã“Ùxœ5Œ1
Â0FQ°BGApü@p³7D'”â›4&ù%IQ'Ñ+®‚rsòÆÁñ½�ïÝ{·ÞãÓTÌCÙ‚¬W>P°¢RV6ídyÝ)¬p,HÊ|mP:2•H‡8::_àÃo=	°"ÔLëhD„€}­4GP&’åðuD,d’4³2#'³t“¯àÆå??a±ÞæËùlBeÙ�†88�¬&ÆÓß£y%»ç´ûn}à¥D¨xH ·ÿ100644 ci.yml êþÿòÅiæöVäPôy˜� sÌ100644 docker.yml ­3â+	ë«œÛÊ³’Xi¼…ŠÃŠWa L·Pxøúname: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read

# go.mod uses local replace directives to ../indexer and ../graph. CI checks
# out all three repos as siblings so `go build ./...` resolves without any
# network hop to a tagged version.
jobs:
  test:
    runs-on: lux-build-linux-amd64
    steps:
    - uses: actions/checkout@v4
      with: { path: explorer }
    - uses: actions/checkout@v4
      with: { repository: luxfi/indexer, ref: main, path: indexer }
    - uses: actions/checkout@v4
      with: { repository: luxfi/graph, ref: main, path: graph }
    - name: Verify build deps
      run: |
        command -v gcc || { echo "gcc not installed on runner"; exit 1; }
        gcc --version | head -1
    - uses: actions/setup-go@v5
      with: { go-version-file: explorer/go.mod }
    - name: Refresh go.sum (proxy cache inconsistency)
      working-directory: explorer
      run: rm -f go.sum && GOSUMDB=off go mod download
      env:
        GOSUMDB: "off"
    - name: Build
      working-directory: explorer
      env:
        GOSUMDB: "off"
      run: go build ./...
    - name: Test
      working-directory: explorer
      env:
        CGO_ENABLED: "1"
        GOSUMDB: "off"
      run: go test -race -count=1 ./...
C‘Œd¨xH ·ÿ100644 ci.yml Bñ©‘Ž7Óêƒ[2t«w‰ô×è^"100644 docker.yml ­3â+	ë«œÛÊ³’Xi¼…ŠÃŠ!ä	‰ix” kÿ‡
Á	°uInstall build deps
      run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends gcc libsqlite3-dev
   ³:“]q“å"™è1¬k�x ôÿ¤˜°£$>  x0 Ïÿ100644 scheduled_tasks.lock �Sã»îg	Õ«³FÙÝit)™š®ºxZ ¥ÿ{"sessionId":"cf4e8ffa-52d2-4e07-b22c-dbf44cba2479","pid":5284,"acquiredAt":1778041169393}Êï¯g“ùex øÿ±¨°(
›7¨xH ·ÿ100644 ci.yml Ò5TÁØ·´µ¼¼[¹jx6100644 docker.yml ­3â+	ë«œÛÊ³’Xi¼…ŠÃŠ¯Pìj‚Kx
 õÿÁ	�°N’ÁÆºo�]x ðÿ¤�°Ú“ã5£$1ífì“½+xìþ»/¾�š³º`“´zmainnet“-A<
    indexer:
      poll_interval: 30s
      start_block: 0
“‘¶“ÿ“^7    “¯a    logo_url: /logo.svg

    tokens:
      featured: []
      allow_list: []
      block_list: []“emAmainnet/ext/bc/2Y625Kvdd4wZ5LKx9kLSf1jGALyARmXVN1YsTb4F3Hzv4bPDbp³ÜhAmainnet/ext/bc/2GiQb73CeJESjc4omFv2YtQHZrRgJf25NXPzAr5J6UNHRcDV2F³Ph@mainnet/ext/bc/rtjwvtE1tEvrokmpeYdTq7b2zqZgmybKwR5MLjKMGAR1W78dQ“Â¹Amainnet/ext/bc/2pUskxqaL5Bpx7uRUGG1fDjPckjxQ4UKX4sLKeaS1NdSVBJd3F“†K,¶¡¨xH ·ÿ100644 ci.yml ïÅê$¨ÏJà˜«¬qvT¢-xÇ100644 docker.yml ­3â+	ë«œÛÊ³’Xi¼…ŠÃŠ°”n‡Qx ñÿÁ	‡°o“÷W’Á*=´é˜x9 Æÿœþœþ°0*5Rl34569IHJ5hXOvrkjbadfKNt/2rGZd83vhcSIRSP³10ëNI“¯xO °ÿ100644 actionlint.yaml £…Àj @\/B|öÈV5ƒÇÎñé40000 workflows nÊ9w�Õ~ÿÐ4YQ_ïí‡3ºa²�!é\x æÿOO�;_,�ïêýtÏ ]£§£¦Aá¦Ž‰©¹xy †ÿself-hosted-runner:
  labels:
    - hanzo-build-linux-amd64
    - hanzo-build-linux-arm64
    - hanzo-deploy-linux-amd64
c&ú§xw ˆÿ100644 ci.yml ü0è5�-FW,ÏòRo±RG�)ó100644 docker.yml bU/ÖÑ>4qÍÜZþˆ�Xq/åì100644 workflow-sanity.yml ~¨Žzêƒ‰2–/V)(ßõ„Á>o¯.Íì€x ãÿww�4-0V7óY=¦,±x»Äëå´]ƒ¬ê‘H/²ö¶î+xN ±ÿwwH100644 ci.yml mÌ�ã£Ò°’Õ1£IeiVƒZÎÂ›100644 docker.yml ã÷Éè‡Ç…Ü_amþ1}ó79%‘H/ô!nè\x( ×ÿww"100644 ci.yml Ñ+_È-Â‚äýhu….¡ã@�‘"USã„.x3 Ìÿ‡ù°Ehanzo³H±	|
       “øIrun: |
       “i�£Ü¾%x^¡ýname: Docker
on:
  workflow_dispatch:
  push:
    tags: ['v*']

permissions:
  contents: read
  packages: write

# Multi-arch (amd64 + arm64) on native hanzo ARC runners.
# No QEMU emulation. No GitHub-hosted fallback.
# Semver-only tags (v*); see hanzoai/.github docker-build.yml.
jobs:
  docker:
    uses: hanzoai/.github/.github/workflows/docker-build.yml@main
    with:
      image: ghcr.io/luxfi/explorer
      platforms: linux/amd64,linux/arm64
      runner-amd64: hanzo-build-linux-amd64
      runner-arm64: hanzo-build-linux-arm64
      runner-deploy: hanzo-deploy-linux-amd64
    secrets: inherit
+qÇ½	x� bÿname: Workflow Sanity
on:
  pull_request:
  push:
    branches: [main]

jobs:
  sanity:
    uses: hanzoai/.github/.github/workflows/workflow-sanity.yml@main
2§5å·)x—hýname: Release
on:
  push:
    tags: ['v*']
jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          push: true
          platforms: linux/amd64,linux/arm64
          tags: |
            ghcr.io/${{ github.repository }}:${{ github.ref_name }}
            ghcr.io/${{ github.repository }}:latest
ƒ¯Çaï„$x/Ðþ—Ì[name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  test‘:!‘—7‘¬setup-go@v5“go-version-file: go.mod
      - run: go mod download
      - run: go test ./... && go build ./...
  docker:
    needs: test
   $ if: github.ref == 'refs/heads/main'±;main“.“�ú\Mà�>x` ŸÿÌÇ�"
    - main‘)
    - main‘N#hanzo-build-linux-amd64
    steps:
“ç$‘«"with:
‘×$‘ý“'HPà´ÀxÅVQsÚ8~÷¯Ø£�Ò»«ì@ÚNË„Ì0	SŠ9Czé´FØëb,×–®Mfîéžïá~aÉ­d;i2¹·ò h%}’v¿owA˜mWÜbÛ8	KàÛ_ÿB_l–<b~xä³-Z…uBã ‹…°JD$Yä�ŒG °¼ÄäÂÊÑÊm ßþþ¼€ò|*)<]†Â;OŸ�Üª/qÎ"üõ*¡žL¾+?�ÐXi¶Ìì‚E2…§é%�""ÄŸògp¬îúÛø.´òú9Ú„m¥ùGZ{ÌwOÙ9F?xÚ† ƒ„1¼yî)í›z�²Ä"Â»�G#\™
H²RI#Ÿ†8+‘h¿Æè=µ.õhÈ£µi=÷Ž�Å;Û��œI·e¶_jÛÄØ;k»­m£ÉÀ>³Ý…kO�n eœv,kÍe�-M¼ÃÍ@˜8qk×°»Á¨äGº½éÉ@Úé;˜ãbGÄ>›Ž×~ ¦ðæ¨ÚU@!~`&éšA«Gý]€ô¤1t�·	Ÿu­ûæšÐ0FâBo¶Û¡_X3un††{:Ÿõ}„�ñ0Ž”Ë~wÜ7ƒ‘�c½�àé(â³XÝîY&4ò‚îã¯Õ9Ãk¨§Î5˜ÁC•ÅÔ;ÑeÈàÉˆ£xƒŒ@Š ¯Ae}aQBYñ�öäLì³ùbzz4õ½éhqÔ›Ù‹io~Òµ.Z;ñæKYœé4ë6ÙÐ-¡OR<•‘”aÝW­×í†¾MÊðÍ©%2g²ÍŠ©ÍÊˆq‰lZˆ”dP°+¾VÊ¹º™dLƒéw,u¼êæÍ¹Ï ±ö¢‚Toþ dµ3X:_˜�Ü„ðIA~4”Þ™hü4púó÷SÔüáAñÍ¨x ¹Ù¡]ä®+X�ž]
ÿÏÃ«øÑûšpx×©ÆMÚµKÚÀDHÎÒóÖ"¤Ñ¹WiµÎ<í–ÜÒ[A¼J¶Ã[Ú»1¼Ÿ•ž›,‘}�~¹dúo)ð2wà´zT®ž"Wt.ŠúPºvaEéjXQz-`#|ðÅe
ê}gú>çñ¦[–‰�×!6K,oãï˜k!ë$÷êœl¦W˜ÐD‡m–HÒ|ÞúøKÝˆzéäWÍ{pËóM¼dûÐB·XQ†2«´�q´'½£±=@9«Q8î+õcLÊöp4¶_>_ÌœS·o7 ±ršç<%¡¿
é:ÅõH“K g r«yÁ’”‹óB•ëRªt˜ÚE<,ýþq�]•ËïS­ŠCº TÊ‡ÿƒ?»R¡ØSîçŽBüÐï)¢¡ÿë§×JÑ~Ü,’|Ãj™ /7�}³ÝÒ¡»£Œx”x,‘|Å=*1�šù2½!”"kÜd‡•¥‰……€†uÒÜ¿¯ŒÜÚXÚïß™¿ÿÖ¶<ÈÈ9M.¬ReFîßÏRlÉ H/_¼Øo—mcbœÎl·½sÆ§om°Ts¨«× 7ï-°ävµ	Næóé¢7¸ÝÎ«½×{XgfƒúÏ÷ööãÄî�ç'ý»ÿÃ±'M.hØÝßS[…N÷…`¦H$‰YÂ…ßmá¼Roÿí .×Lùì¬02Æ6J{<©Ô‡ªjÊ@¥¶ÅúßR¢°ÍB¬—ª¹“Ø.lpÄIÞê–î†§…°kýsé*Õœ:˜!î3¸Ä¾­ƒ	Ô;G·)DÓ4w½7)P½œ�$öÔ¨}Dò@Ój½=™»ï§Îh2‡�òŽ�OÆ~DØù»Ïxû$Û# Explorer

Combined block explorer: chain indexer + subgraph engine + frontend in a single image.

```
ghcr.io/luxfi/indexer   -- chain data: blocks, txs, tokens, contracts (SQLite)
ghcr.io/luxfi/graph     -- subgraph events: swaps, pools, DeFi, GraphQL (SQLite)
ghcr.io/luxfi/explore   -- Next.js frontend
ghcr.io/luxfi/explorer  -- all three combined (this image)
```

Each component runs standalone for independent scaling, or together in one container.

## Architecture

```
                          +------------------+
                          |   Load Balancer  |
                          |  (hanzo/ingress) |
                          +--------+---------+
                                   |
                          +--------v---------+
                          |    explorer:8090  |
                          |   (single binary) |
                          +--+-----+------+--+
                             |     |      |
               +-------------+  +--+--+  +-------------+
               |                |     |                 |
        +------v------+  +-----v-+  +v-----------+     |
        |   indexer    |  | graph |  |   explore   |    |
        | /v1/explorer |  | /gql  |  | /* (static) |    |
        +------+------+  +---+---+  +-------------+    |
               |              |                         |
        +------v--------------v------+          +-------v-------+
        |  SQLite per-chain (WAL)    |          |  ZapDB KV     |
        |  /data/{chain}/index.db    |          |  fast lookups |
        +----------------------------+          +---------------+
               |
        +------v-----------------------+
        |  Replicate -> S3 (optional)  |
        |  E2E PQ encrypted (ML-KEM)   |
        +------------------------------+

    RPC Sources (per network):
    +-----------------------------------------------+
    | api.lux.network/{mainnet,testnet,devnet}      |
    |   /ext/bc/C/rpc           C-Chain             |
    |   /ext/bc/{blockchain_id}/rpc  Subnet chains  |
    +-----------------------------------------------+
```

## Quick Start (Local Dev)

```bash
# Clone
git clone https://github.com/luxfi/explorer-combined.git
cd explorer-combined

# Run against testnet C-Chain
docker compose up

# Or run the image directly
docker run -p 8090:8090 \
  -e RPC_ENDPOINT=https://api.lux.network/testnet/ext/bc/C/rpc \
  -v explorer-data:/data \
  ghcr.io/luxfi/explorer
```

Open http://localhost:8090 to see the explorer frontend.

## Run Modes

```bash
# Combined (default) -- indexes chain + subgraphs, serves API + frontend
docker run -p 8090:8090 \
  -e RPC_ENDPOINT=http://node:9650/ext/bc/C/rpc \
  -v data:/data \
  ghcr.io/luxfi/explorer

# Indexer only (chain data API)
docker run -p 8090:8090 \
  -v data:/data \
  ghcr.io/luxfi/explorer \
  indexer --chain cchain --rpc http://node:9650/ext/bc/C/rpc

# Graph only (subgraph GraphQL)
docker run -p 4000:4000 \
  -v data:/data \
  ghcr.io/luxfi/explorer \
  graph --rpc http://node:9650/ext/bc/C/rpc
```

## Multi-Chain Configuration

Use `chains.yaml` to index multiple chains in one instance.
See `chains.example.yaml` for a complete template with all Lux networks.

```bash
docker run -p 8090:8090 \
  -v ./chains.yaml:/etc/explorer/chains.yaml \
  -v data:/data \
  ghcr.io/luxfi/explorer --config /etc/explorer/chains.yaml
```

## Per-Network Deployment

### Mainnet

```bash
docker run -p 8090:8090 \
  -e RPC_ENDPOINT=https://api.lux.network/mainnet/ext/bc/C/rpc \
  -e NETWORK=mainnet \
  -e CHAIN_ID=96369 \
  -v data:/data \
  ghcr.io/luxfi/explorer:v0.1.0
```

Mainnet chains and their RPC endpoints:

| Chain   | EVM Chain ID | RPC Endpoint                                                         |
|---------|-------------|----------------------------------------------------------------------|
| C-Chain | 96369       | `https://api.lux.network/mainnet/ext/bc/C/rpc`                      |
| Zoo     | 200200      | `https://api.lux.network/mainnet/ext/bc/2Y625Kvdd.../rpc`           |
| Hanzo   | 36963       | `https://api.lux.network/mainnet/ext/bc/2GiQb73Ce.../rpc`           |
| SPC     | 36911       | `https://api.lux.network/mainnet/ext/bc/rtjwvtE1t.../rpc`           |
| Pars    | 494949      | `https://api.lux.network/mainnet/ext/bc/2pUskxqaL.../rpc`           |

### Testnet

```bash
docker run -p 8090:8090 \
  -e RPC_ENDPOINT=https://api.lux.network/testnet/ext/bc/C/rpc \
  -e NETWORK=testnet \
  -e CHAIN_ID=96368 \
  -v data:/data \
  ghcr.io/luxfi/explorer:v0.1.0
```

### Devnet

```bash
docker run -p 8090:8090 \
  -e RPC_ENDPOINT=https://api.lux.network/devnet/ext/bc/C/rpc \
  -e NETWORK=devnet \
  -e CHAIN_ID=96370 \
  -v data:/data \
  ghcr.io/luxfi/explorer:v0.1.0
```

## White-Label

Override branding via environment variables. No code changes needed.

```bash
docker run -p 8090:8090 \
  -e RPC_ENDPOINT=http://node:8545 \
  -e CHAIN_NAME="My Chain" \
  -e COIN_SYMBOL=MYC \
  -e COIN_DECIMALS=18 \
  -e LOGO_URL=https://example.com/logo.svg \
  -e FAVICON_URL=https://example.com/favicon.ico \
  -e BRAND_COLOR="#FF6600" \
  ghcr.io/luxfi/explorer
```

| Variable       | Default       | Description                      |
|---------------|---------------|----------------------------------|
| CHAIN_NAME    | Lux C-Chain   | Display name in header/title     |
| COIN_SYMBOL   | LUX           | Native coin ticker               |
| COIN_DECIMALS | 18            | Native coin decimal places       |
| LOGO_URL      | (Lux logo)    | URL or path to header logo       |
| FAVICON_URL   | (Lux favicon) | URL or path to favicon           |
| BRAND_COLOR   | #1a1a2e       | Primary brand color (hex)        |

White-label detection in production uses hostname-based routing.
Each `*.lux.network` subdomain resolves to the correct chain branding automatically.

## PQ Encrypted Backup

The explorer supports post-quantum encrypted backups to S3-compatible storage using ML-KEM-768.

```bash
docker run -p 8090:8090 \
  -e RPC_ENDPOINT=https://api.lux.network/mainnet/ext/bc/C/rpc \
  -e BACKUP_ENABLED=true \
  -e BACKUP_S3_ENDPOINT=https://s3.lux.network \
  -e BACKUP_S3_BUCKET=explorer-backups \
  -e BACKUP_S3_ACCESS_KEY=<from-kms> \
  -e BACKUP_S3_SECRET_KEY=<from-kms> \
  -e BACKUP_INTERVAL=6h \
  -e BACKUP_PQ_ENCRYPT=true \
  -e BACKUP_PQ_PUBLIC_KEY=/etc/explorer/backup.pub \
  -v ./backup.pub:/etc/explorer/backup.pub:ro \
  -v data:/data \
  ghcr.io/luxfi/explorer
```

Backup flow:
1. SQLite checkpoint (WAL flush)
2. Snapshot all chain databases
3. Encrypt with ML-KEM-768 (post-quantum KEM + AES-256-GCM)
4. Upload to S3 with content-addressed naming
5. Prune old snapshots per retention policy

Store secrets in KMS (kms.hanzo.ai), never in environment files committed to git.

## Performance (LP-104)

Benchmarked against Blockscout (Elixir) on identical hardware (4 vCPU, 8 GB RAM).

| Metric                    | Explorer (Go+SQLite) | Blockscout (Elixir+PG) |
|--------------------------|---------------------|------------------------|
| Block indexing (blocks/s) | 850                 | 120                    |
| API latency p50 (ms)      | 2.1                 | 18                     |
| API latency p99 (ms)      | 8.4                 | 145                    |
| Memory at 1M blocks (MB)  | 180                 | 2400                   |
| Disk at 1M blocks (GB)    | 1.2                 | 28                     |
| Cold start to serving (s) | 0.8                 | 45                     |
| Concurrent requests (rps) | 12,000              | 800                    |

### Cost Comparison (monthly, per chain)

| Setup               | Explorer        | Blockscout         |
|--------------------|-----------------|--------------------|
| Compute            | 1 vCPU / 512 MB | 4 vCPU / 8 GB      |
| Database           | Embedded SQLite | PostgreSQL 2 vCPU  |
| Storage            | 2 GB SSD        | 30 GB SSD          |
| Estimated cost/mo  | ~$5             | ~$80               |
| 5 chains x 3 nets | ~$75/mo         | ~$1,200/mo         |

## Endpoints

| Path               | Description                        |
|--------------------|------------------------------------|
| `/health`          | Health check (200 if indexing)      |
| `/v1/explorer/*`   | Chain data REST API                 |
| `/v1/explorer/stats` | Block count, tx count, sync status |
| `/graphql`         | Subgraph-compatible GraphQL         |
| `/*`               | Embedded frontend (static)          |

## Environment Variables

| Variable         | Default    | Description                              |
|-----------------|------------|------------------------------------------|
| RPC_ENDPOINT    | (required) | EVM JSON-RPC URL                         |
| HTTP_ADDR       | :8090      | Listen address                           |
| DATA_DIR        | /data      | Database storage directory               |
| NETWORK         | mainnet    | Network name (mainnet/testnet/devnet)    |
| CHAIN_ID        | (auto)     | EVM chain ID override                    |
| LOG_LEVEL       | info       | Log level (debug/info/warn/error)        |

## Related

- [`luxfi/indexer`](https://github.com/luxfi/indexer) -- Go chain indexer
- [`luxfi/graph`](https://github.com/luxfi/graph) -- Subgraph engine, Graph Node replacement
- [`luxfi/explore`](https://github.com/luxfi/explore) -- Next.js frontend
- [`luxfi/explorer-v1`](https://github.com/luxfi/explorer-v1) -- Legacy Elixir stack (archived)
©�3»°xôô# chains.example.yaml -- All Lux chains across all networks
#
# Copy to chains.yaml and adjust as needed:
#   cp chains.example.yaml chains.yaml

# Mainnet (network-id=1)
mainnet:
  cchain:
    name: "Lux C-Chain"
    chain_id: 96369
    rpc: "https://api.lux.network/mainnet/ext/bc/C/rpc"
    coin_symbol: "LUX"

  zoo:
    name: "Zoo"
    chain_id: 200200
    rpc: "https://api.lux.network/mainnet/ext/bc/2Y625Kvdd4wZ5LKx9kLSf1jGALyARmXVN1YsTb4F3Hzv4bPDbp/rpc"
    coin_symbol: "ZOO"

  hanzo:
    name: "Hanzo"
    chain_id: 36963
    rpc: "https://api.lux.network/mainnet/ext/bc/2GiQb73CeJESjc4omFv2YtQHZrRgJf25NXPzAr5J6UNHRcDV2F/rpc"
    coin_symbol: "HANZO"

  spc:
    name: "SPC"
    chain_id: 36911
    rpc: "https://api.lux.network/mainnet/ext/bc/rtjwvtE1tEvrokmpeYdTq7b2zqZgmybKwR5MLjKMGAR1W78dQ/rpc"
    coin_symbol: "SPC"

  pars:
    name: "Pars"
    chain_id: 494949
    rpc: "https://api.lux.network/mainnet/ext/bc/2pUskxqaL5Bpx7uRUGG1fDjPckjxQ4UKX4sLKeaS1NdSVBJd3F/rpc"
    coin_symbol: "PARS"

# Testnet (network-id=2)
testnet:
  cchain:
    name: "Lux C-Chain (Testnet)"
    chain_id: 96368
    rpc: "https://api.lux.network/testnet/ext/bc/C/rpc"
    coin_symbol: "LUX"

  zoo:
    name: "Zoo (Testnet)"
    chain_id: 200201
    rpc: "https://api.lux.network/testnet/ext/bc/2Uw1b5tf63vamHsWVeSW9uHcGBVbF5CYHS9JiLWttusY4Vvjsn/rpc"
    coin_symbol: "ZOO"

  hanzo:
    name: "Hanzo (Testnet)"
    chain_id: 36964
    rpc: "https://api.lux.network/testnet/ext/bc/2wbYEFh7ELuovqXhyYeCLvweZrcEgNUoZVJUAtw145qTVAUJxE/rpc"
    coin_symbol: "HANZO"

  spc:
    name: "SPC (Testnet)"
    chain_id: 36910
    rpc: "https://api.lux.network/testnet/ext/bc/u4pC2tT61o83pbQ8qwAyfXXGF6nyGFNSwCYHEFcUeESGiNKfu/rpc"
    coin_symbol: "SPC"

  pars:
    name: "Pars (Testnet)"
    chain_id: 7071
    rpc: "https://api.lux.network/testnet/ext/bc/is5GUzsKHk3Z4Q5JXTjF4Ld9p4TnEs81tJFdaBiEBJVkaFrrs/rpc"
    coin_symbol: "PARS"

# Devnet (network-id=3)
devnet:
  cchain:
    name: "Lux C-Chain (Devnet)"
    chain_id: 96370
    rpc: "https://api.lux.network/devnet/ext/bc/C/rpc"
    coin_symbol: "LUX"

  zoo:
    name: "Zoo (Devnet)"
    chain_id: 200202
    rpc: "https://api.lux.network/devnet/ext/bc/2caKCdNop9C4oAPR1kYihN53HeSRDXD3JyMKS7ntfcpQYucmii/rpc"
    coin_symbol: "ZOO"

  hanzo:
    name: "Hanzo (Devnet)"
    chain_id: 36964
    rpc: "https://api.lux.network/devnet/ext/bc/tecXMucYDxwN65mebPE6cvQ9GynG6Y5WEvwGFsY3xVDWHxiqT/rpc"
    coin_symbol: "HANZO"

  spc:
    name: "SPC (Devnet)"
    chain_id: 36912
    rpc: "https://api.lux.network/devnet/ext/bc/213ZsFLRCBSmrii4huQqa6S3SxYPP8ec6dPEn6ad4CtLk3RdXp/rpc"
    coin_symbol: "SPC"

  pars:
    name: "Pars (Devnet)"
    chain_id: 494951
    rpc: "https://api.lux.network/devnet/ext/bc/MEGAgimfcodi7QaqCruFpTkNM6fqu7ZduLmWJJjGQwUg8uDPJ/rpc"
    coin_symbol: "PARS"
×‰jÂê€–xœ›(¿€e‚‡ni^fZfjÊD…œ‰/ø&«3ªNžÃè1ù(³áäóüLfF“×óJsææ§(”èZLNàUa22R²œÅ•yÉ Q¯Ä12ñdJòós |=}}…Ì¼”Ì¢Ôä.M. 5$`î5ÿxœ]Î;¯›f Æq�iÔ¤ª²$Qn=;�¹Øs”¨²æb›‹1`{9áfÀ¼€Á˜Û’-S”åÛ¹c—Ó©CU© j·~ƒ.ý
Ub%‘NûþÏïùáß³wO ýèOør^>ë\=zæžÁŸ¨›ð¯ï¾‡oø¯!Ç/q’8ðGÈGv‚£u‘‘Ò	po’�6Ši `!×ô¬v8_Pá+þñ€$P?íÄ©{Šã’ŽP´1bçÔ|™
Ó,ò�Å`2çEšYJ’)KæÊ–TˆËOáù5üÝú¾s¿€çnÜÅì´ÓGh Ùl«h®ñ]¢\+£ŠÝ¯ÓŠ±ç}Bwéz¤—°1ø›{ïâÔ]»,íxt�Iñ Ú‰^°Äœõ†¥T3êÕz¦Lò…/"”Õíã«o½Ï‚údO»ÒÎ©0DC—{n¬¹NmÊk^Ö£®kH³’�I9l ²½qÊ®Ñ.bJ˜Rêr>ÔÇU‰–‰æÈ½*áU¹ïèm‚.š‚…/ƒûðv ðOðú Ä[m”*´ª¬gXÝLE%Ã|Ç]®›IšÕKÓÞªG^èÿºã¶@ªé÷Ç;aô"Áv\_5Ê½Hæ­‘å}kÕ´›«Ûþ~sõ0œu	â£¼*Šl:N*}ÖŠ³,*4Õ¦» �2•†²’5ouÙ2…ñ½‹SwÍF=ðö¡%ÎU}ßCR¥69ÎÝØ"�åLXïdy9Ù(º3„¿Fàóˆ‡¿€ã«Ï¼ŸiÜl“•ÈP � ¶I(bÉ†Yp}n¤´aoJ-Ù_|é§ÀJüNšûh�i
ç%Öùß#k\9‡]VfÔ:+È¥Ô¬(u®^b«(W`vJF¹vÓcóqàÏñÞ-ô6ÁØð›q/Å=clÅPž†åˆÆ¤$ÝµE`¸ pëMçð«¢‚wÊ·ïAþ+è}€¢xœmUÏoãDVZ°ii—Õ6ÐÕ¶¯^uå”Äi»°€Û
ºmÚTôMVå{ÜXuÆÑxœ¦¬ª²Gn áàÀ�#qàà†¸p]\¹sâÍØÉ&Ë²<žï½yï{ß{~ê>…/ç®h¯Åœr LðèÄ!ŽY©àÇ)a›–Û© Íˆ‚×"!NOÃDaÏCÑ‚öÖAü0ñâ.Åo„ùpB›õØ;£¡$Rú¥aä»°sxrxüáz Ëc®´ßÄ¹ íH¡òkY¹ÜöY²�(¡¸èÄ\À»Kï-åN«¬ëffG‡Ç¾þüük¨5G mlŸ$�2ãbfRýøhïêð¸züÉæáÁöîNfÓ!˜™ˆá?�)¸Fï«´ó(Óì\cbpšà)ÍOÛ¬mìfvC†±(d}v#äÖËÓ+·%DÇ­TÇ)}ÇCK+O~Ão£ÑÆÑnNÁNU1 Ç¦fîŸ¡¼æÙô©ª72Üü0Žø~ËQ�ûÞä§S/ÝÕ�´ãA‡Q[Õ½j£ÚG5ŠÓ6Ò0@i5åbÈ#=©KçætE£“´™x<lREz3Bñ$Á	Kˆ'Â˜%ÐAh_ò«Ï¢ä"ñHY¦a)‡–Y4Í e´aá‰iè‚»ëDäÔÙeÂ¶Ô«„Åêê¥Ò
.µJ`TbMÃ‹Yžn‡XTWÎÝx}�lkPûL)hlYÊtH$Z8ÊC&	-WòåU-“TÄå¾H 3%Q^è†DkHÙë°�O¨]”wÍï'LÃX+'áéfßä&Ô W­9õhØ¥¾23<Â<Ùøz‰Ó@¦7G›ÖæYSCÞÔEi¼2¿¢‘{1ñ!àq2 @L#`qˆ‘ùuÌžÈgãÓ3ÊbÔ†=*®Ê–ñþÊ¥yÍ/ò¨ýåc}4³Y[(B¥˜x§€&uA°~£%¿›Xõ{ýÑ­´i§ñ C?88¹lOôr
tqÛiÏ4ð¡NU}äÐó:å]ºŸöú\Õ”Ó–üæÕ_^³â3«„Œy}-W~19;>Ürˆ+È¥›ãrrjVþ5ù÷í!‘?‚ò÷©�[Úû`/D$¹õÏòÇÛ…EÕŸ”…ìÜì…¼“èÑ·Ð-";‹JŸrnzîJ…›ð®Jå¾ÎE'ÂQ÷Æ†ïswdBÐN½Ãq°¶…þû®Š*¿NìˆŽ˜ IjÃõ‘Ÿò§8––—`Dx…½N±Ü~7@d×ƒ7;á¡ £¸‡ÿÛÅh®y[^¹†û@÷I/‹îÑ… ‰Æ.ÃÚ,?Ä]¥<Ô„
Ù<Pª£œ+Æ�8ùÓ´X0±£P#ÎVÌ¨ê˜¤•
?>g›¢W‚þBîÞ{[^Þ¸§÷ý…^œz²è
oÊogÒpÄÈÙ;Ó/#çEÀ‡üuÆvº„S+G×§ì.ê^å>S-Ì©H9&ÿ™©Éßfn©RãŸÖkAW¶î<†ü·â>‡â�1jïL¥}iþ§A_âº	xš eÿ#!/bin/sh
# Patch Go embed directive to include _next prefixed files
sed -i 's|//go:embed static/\*|//go:embed all:static|' /src/cmd/explorer/frontend.go
Ù55‹µx êÿ# rebuild 1776010884
B¹_¤x$ Ûÿ40000 workflows nÊ9w�Õ~ÿÐ4YQ_ïí‡3ºa÷üŽ¤x$ Ûÿ40000 workflows É¿ þAé„ân¸„“ct6‘èúkçü;x èÿÇ½�rubuntu-latest‘‰¾n‘	Íì€ŠXx, ÓÿÞÕ�˜lux±�:lux“Ü'lux“(	lux-build“<"vÝo¤x$ Ûÿ40000 workflows  9[ñÚz¿ÝÇ—­>âLB’§Çðtrë€‹Ex; ÄÿÞ¦�2branches: [main, dev, test]‘-D“„secrets: inherit
nm‘¤x$ Ûÿ40000 workflows 2Aµ‡îŠ¥(ZLM`æ*³¼Ô²¥x340031QH*ÍÌIÑ«ÌÍap|—Ä*­cÎï—�o£}útï‚ö> Ìî/xN ±ÿ%n�%I100644 ci.yml äìtî&úí_‘ÏÂXU�œ‚100644 release.yml 0Õ*Y˜Ùº$ 4ƒ…øAzb'—^GC±‡xíUMoÛ0½çWC�CíöqÈ íæ¥Á¶¶H»¡ÃVŠÍ$ZlÉ�ä4AÚÿ>RJœ:mÑw˜/‰IŠ|�z¤•(°Ç•Ì3He®šVK«NàF›Ù8×7ÃLÚR¸tÊÆ²²þ`d„J§h;ð«R]“À‰	öç¯÷Ù`°ÔV:m–�$·,ýI©2\ ‰ª2³˜QN·¯0m×­ª9ã$½þÅåàg&ÓÔÄR“­ÿí¨—Ô†v^-Æ²½Î@¬þè‘å£#fË ¬3Tv²o …pF.6o R¥y•áÖ �©”BC,-æãhª­žKU-@Ùû·¡\€Ÿ2n¬MÑÓö!¾|ð?—ÑÏfäŸ‘°Ùˆ®öV«5›8à…»;Q¢)¤µR+ß†�jåP9º8ƒ"[#+E:¾¤#z+1-ëST–Ý"uœ¬MZHgºr‡ó …["2�ÎÐ´s=‘*
á‡ó7ë:¤3éÖ’
ý08‘t1Ë@‚î;Þ\ö†Cˆ£äFyý2Ù	e©F1¥×¦W
kIËYÈg15èlÜë_ž|?^ž}IN·ñ;�t‚„)jÔïjÔ’ò±»6ìÒ�Ehƒc^�ÛJÌ!LËm�ÂÛh2ºÇ8§;é†){a±˜£9 ¹tÔ…î|µ¢W¾Õõ5‡…AëÚéæ??
Ïô#w	y™,\â{pü> gª �P6ðbâ^11÷)&i”5ž�Í6äb„9)é‰ÁÝ<€ÃÛ¦Ù¿Éà¢vÚ½§	êæ�…ÒLAûâôSr•†ƒäs—WØ=Z ½ÁÑùÉã®äêüëÙ Ù:é`!”£uaU(ÄŒ�y”>k=—Ï-�GÆóÅ“øÎþ¡9›é#mT‡PT¹“‘0é´J-6’FSÃô­àÍ@Ÿ§&b=B ãÏÞö	¨K m:®þj�¼‚ß�XZÏÍ„Ê^è¾½Ý�r ò¼Ø·õxlÒ=ŒÏ´ÂÖ_¶o¤í„xxM ²ÿñÍ°<lux-build-amd64“©“ulux-build-arm64³©Èlux-build-amd64“í³¤Í+Û�¤x$ Ûÿ40000 workflows ¬áwr…öÿ6ã'ÂÐ`/2„q <0ä•¾lxœ{Êò”EÉÄ ôÒ3K2J“"z_v]\vâUÚüßu››ôâ7*Ù2Š0øqÕ²Iì*qZÊ±&íðêoö“‹? ‰¥A¤x$ Ûÿ40000 workflows ÙÈ[ÎLdçC2BÅ_¶½üœX÷‡N¥x% Úÿ100644 build.yml táÍ‘¤Ó° ‰Ñï‹˜.W[¼álÓc‚à€5xœ{Êò”eC<£û©[#[Kµ®Íjõ�HHŸþmÉS½ÉÅŒŸ õx˜ì�ºQxœU�±JAEK³³	(6
aM‚K"‚i-l— 0»;›Ù¼æM„T¦¤qÿÀZ°²ÿÃÆ�°v6+QÛË»çÞûî�?Ÿ_×:çÜ Ësˆs™Œ’!H ù@�áš§ Ì�ƒÊ™É¤ÛÓJ`ç›Y½Áí~1#×ÚzµKûèí4PþC	´ï^ÓŠÚ›mùÙÝºçu»ió%·�ÇTI�>g 2§”)‡TrÂ¦£Y2*õ%6,1`eÖÜL´dXÔ‘ºW˜OWSJ£OÎÄŠcRÓŽ]ø{6©nŸtê¹ÌŸ(·;žþö�FB)�ƒb±f˜†€lÌÛöÉŸÛxca©¾eªÑÑŸÇ­€ÚàÒr>Ê'ƒ°lq=UŽ©UrÓ¿xÙm|U¾v½‡·àƒxœ{Êò”eC<£ˆîÓ½­&{<zWìó­ŸÄÞöÆýQ¥ääbÆO ñ†ï�½8xœ›dòCeÃNv›ÀÒÔ¢J…¤œüäìäŒÄÌ¼b…”Ê¼ÄÜÌäÄœœJ…GSòRËR‹2‹R’óSRÀªr2‹KŠ'[r˜NžÇ«¦¤¯¯4¥ '±$-¿(WÁ1À“‹“bžŽBjQ‘ÂäR^[ÆÚÉx-`ÂÅÙ™™yé“™øB&OçÌËGqGfÞäCœê“3ùLÖà7š¼‹¿lr·†5Ð` un™EÅ%
)™ÅÉù@×¥¦@�´]¡$#³X!©(1/E!)59?7è¥Ô´ÄÒœ’É�Ü7k7ß¶` &ZÙ¥&xœ31 ½ôÌ’ŒÒ$†ˆÞ—]†…�x•6ÿ·AÝæ&½8CC3°’Ìô¼ü¢T†‰o£º“Ã>?»›e~�y£ðU£]; ª\ò“³S‹Ò2sR~Ä©…­Ôý¶¿•ù¼pò$†çßîAU¹:ºøºêå¦0,×7?8Ù^ÐÒ Ð9¦ðë‚¿2Q¢ Š’33óŠõR+srRõ*ssÂÛ8û'šÝ¶Ò_;3ÔøÂ³Ü–·Â”ççäÕ•õ•ü;·pþóénófKÏž³¾²˜ª,=_/7?…AÙàÃÖ—Ü�¤M?¯«¹Ïmítˆ¡¢¸4—¡éàåÌæÌW}rîì>mjŸ¦wšª"è0½ô|†wJÑï.<ª(¾åàÙ“æåú¹Wõ/Â”¤ �Tb  /±í‰uô‹�yÚ7¥NÊ¥WW•˜›š*$–$gè¦æ&¥¦èg0˜©�]-­ÙÏ^j_îõGký×°5PÃŠRsJ2sSA
\Xóga‡ÜÒmUÍ‚__V+ü@S_’Z\RÛñø¡QóÝÅ«/}p®ïyãþØ÷´\mzfqIQ%HYVä¯ÿŽÕ.ÞµÄñÒËªÈ–•ÁhÊàF>>qpñ'íUµQi{Y2÷{6K–ÌÔ7'¡âä¢Ì‚’b†{NVK¾üÛåWg[®ÒoÛÒ§íº	 y^õÔä|›Ãxœ�UMoÓ@PU$mù¸´T\¦…F¤Ž‚ƒQ(-*E� TQUhc¯+Žíî®Óô#r�\8�ø þ~
âÀ‘ßÀì:m“V9X¶'óÞÌ>Ï¼ü}ôíÁ÷/—½]Ûø ­��à³¾¹»‘kŸ|º43}óuÄø>Ô½ÀjYMêú"Y¼z÷rÝJZsOîrƒ³†+$ã5I}›r»¢sVêT°"Ô9“?s¯îL’÷#·�<¿ö39ÎO'3×‹É×ë�X—|»20Ë@X§Mò¹œë€�ÜõÂ¨¾ÔD�ö‹5ìœbF1)7€"Ò¦…ìóƒB�J'à}–ùM¨ßH2uãÞcáE�¢*ôŠ¶™	 >ÞuàMµ¢ÞÁiK£bé¬�eQb]™üš¹“LÍÎÇ¤mû‚¨ô^!Ÿëå{ù|©-?ØóSi MC6eíÀO �Mærì>Ut? ˜†Â¯S«yŠiR„»ŽÃ8óeš(à÷ñgð	1õ<Ìè0¨”ª¥-#ß¡|¤zYÕßNUÚÙÞÑQÔÊq(ñ¢.1µÌ‡5”Áb¥Ê!Uƒ¼ŒºPY­ôƒJ?3ý~EXc�#Oš yÄzÅ!–pK5ËÒOvÇ ·²@5
…Wœ#A�9FVÓû,ïƒ ÖßmŽ;ÀøþƒÍº£kë[£º¾Âõ-ÓÆ =yáó0#IiµU§ÉH<PJáùÆúÿHÝc“úY¥tlT«šH-uÅ$¤\dXUh”´Š‘I9Óï3@m\=ßé&à–=]–½&ó‡Lvµë9Ôõ’ÙéÛóãYcn-eMC«³­—u'qsðÐ†‹¦5—EDËCÜ,¬àyujµÈÀïÇB«bµ â–ömÃnÒ”õ(3@£��Ê&þ"ö\‰»¯�è©gþ¶AE-r·»‚|Ê±€h‰ptiÊå³ý5ciÅ\åÎð ˆr®RÝ*UJ<´È$%ÔLmÃçp­�ËgÉÔÀ^Löv„lèXáàXç‚«`¬‘Ý	�[
i÷gñ¢Ä3ázéßã\M7žÙöŠUxªKeð]“�SKjBpTLXîàlœj�i…”¥wÌÇWþ>`¼à�"xœ  ßÿåå°_B+äžŠ&©Ûp<çüä“�§ÿ“sòöÓá?�Ê@xœ�SMkÔ@5‘¶
²ö¤oã¶ìbš �=HŠà¥D×Ã‚x˜$“44_Î$ë–%äxpnþ
Á“þ¯ž½û'œ™Œ¤Ö°x
óÎó>Ï3ÏûæãÑ÷‡Ÿ¿Þ¸ó²Æäü´Î‚S”ä”97ÇWü€}Øz:&ÁqB+LæÊCDBWb&>¢ØŸð"Ó¶OÆÿ�›²ùö6ÙyÂ¾í,[©õú¼ÄàÌÀÀËÌÐ5-‰€V$Écj¹E^I†eöâØqÄÖ¤õ­¼3D±èl §Ã‚2EUTÅ2ºDSÊ“Á&·îÓ´ŽM!t‚2ì @Î¿²ðÊsöë–Ó»—åyQ“€#�,Ì©!JÍT×½ÑuÛ†ád …!×öìt¹CDŠäEˆáý)Î/ÞÉ)E(I©¥Gìñõ{£a^æmí¶%ÁQ²’Éòpx(r °ÇÏi½2dŒ
3Sw�À84„i]£Š�
‚7o%/O3JbÑ·žóxPí¼-è¢3¡‹‹Óµ¸‡®ºÁ�ØQ·ñª²ýÀvmRüRäèt;`Â¥,áG¨N+GÙœ©'4æ�5Åö‚eÅÛlÅëm”žó·“aÁÕ?ozÏ¹àb³à¢<¤Ö°ŸÚÝý0ÖÝ‚	s!Ö³0œìÓµƒ=z–”°OØ_r¦À.MœŠUäL_Ø¨½ú0c9˜à”]xœ  ßÿåå°_ìÿ³õR¸Ë;õ‘’ÈÌ,ÕV]_“sò›oìƒ;xœ]�ÍJÃ@…‰‰m3þ@u!XÁÛHµ…šºpQ‚.Ü(­J—
3Óšš&a2ÑJ)‚ø ê|—în|ŸÂµÏ •
î—s¿sÎÛÒÇÂmŒÊØbþØ‹ûÔ<Nãç®ƒëc÷&u#¬MèQ?2›ì¢lüùŒ*¬zg\êµ*Áu¯Ô²Z!ŸÕõ‚Û†b7p}Fa@t]çLÄÜ—jHðS…å”¶€Rd•¨d„Œqs‹Rþ+÷.*›š‘­}˜bo´2*Úl½m»žÄŠ "asaAé\çÒ8Ê�²6ã�î0["Ëx8~¤¥¹ø2¾‡µL=‰3sÊ´P&kü”1ð){µlÛžwb;g~™ùÔæœSÛõ#|ÈîæºÉknGI,(3#Ç÷°”‰ffß'
D~s×ïT`€�É•¼á|Cdå¦Ýcø8Ù(
³aU´¼¸#¯áWqª‘7úÿ®ÉÝÔ†ö¥©Ž<á˜xœ{ÊÒÏ4As¢ÿÜÉeŒê“�™” ;ëÜéx) Öÿ�µ�).gitignore ‘íZ‹cVóæÝiZ7Á±Õ2º¸‘"í^Ö„§	x— hÿ40000 .github é*MôÙ
š(‘ªQ7È×«a100644 Dockerfile ø^&V¬-ö¿…Ïc’ ç÷Þ100644 README.md ÎÔKVÑ¤æKŒÜÑ9¦
ã*å100755 patch-embed.sh 6&Í«(Y‚_u?wJü*¯õV¬xâ9¤x$ Ûÿ40000 workflows b˜2oQ`Ÿ�fwX‰Q¬d(j×‹VäzØ¥x% Úÿ100644 build.yml /x%üÈãïÉ=ÌA,�©:óñí·ôâ¡dxœû(°‚sÂÄ‰´µ¹²ò“Š­¸Rò“³S‹@,…¢Ò¼bÝü<+…Ò¤Ò¼’RÝœÄ’Ôâ’ÉoëÅŠSKJt“J3sR*t“K2óóÊŒ'²‰MÎaú/äêî©P[«ŸSZ‘–©ŸZQ�“_”Z4ù6[ýä*6ÞÉÿ™wZ%'&g¤ê¦åçZ)”T¤Ú¦g$‚m† ˆtI>BR'7?%Õ67±bòZ–ÜÉ¦¬‚ ~^Dcä)€˜Axœ]RMkA&äÔõP¡XŠ¾˜ IÈÒ öšÖµÔnj>ÄBšÙÝ·Ùm73ÛÙYI’ÐCÁ[ur�Á«g½	žÜ_ xôâì&Æ¥Ã<Ãð~<ïÇŸ�ÏË“||~ñö¬•Ò×§	ÉÛ¹JÊ{¹I¾QùeéV>žLå¯¥�wžD¡ á"xÔÁr(Ù.ñ(8Dh¶w€Qÿ´,^½)¿iGÕ…ù€“À¤�"”ÂÈš}l'wÇL½ä‹+¯ÇMn»ž@[D5­ßïk8
|Æ“Luð†d€Uh€åQÂ=ËZ<}O'ê,Ô^ÕkÿÜjH°õ_e×è=K¤f\gbV –¾O|X@~‚µqÆx9ÉS�Ë0†::°‡#qï(„CÎ¨@ê¨ g™ ½Ž©Ê„ ¹>k`È™n3zèª°ß4a¸ÊÌx�àÑ&ì>—?r×Æ�‰ê±KB|ÆŽ£ T†Ó¹aß³‰ÊŸ¿ƒ^ã²ÚuÚ5›ŸB©.µL}×hél”Óîk…ì'ÑMb¡ŸŽÄR¹4‡ÙÇªÙ<’íëwåòŠ!Wo¬É…MP«ÛfeÍ‡R…úýõŠPþ.4Ò}*_¦ü¦ØNù{þê|ëA­xm ’ÿ40000 .github é*MôÙ
š(‘ªQ7È×«a100644 Dockerfile „éÙÌÊ^[Žšƒv¸�N;žúª100644 README.md ;¡1ÄàN{8B·S^g¤ gS³z(ïå%€§YxœUQÑjA%&ié*‚’¶n’j2)´¡]¡MÛÄlØe³3i¦�ÌlggÃö½úâKË¼
~ƒøþ@ÿ¡úìˆèlcRú23gî™sî=sžûòèbå%PŽIL$ô=ÿ„pk@â€	I` WÉå°tä«háp(	™àð¨$I Bû²™Ï³(P4UXm‰™¶àì¬¤¦–*¦ðóãLÈSCCî�XUŽÃ[ïé“|ý×(¯ª!mðÅ¨O9Á¥›Þv#Ê°€…q4C%ÊêòÇ“ž„Š!¸7åAFÊlU·«v²@™€Ûm¾ßïìu�vç°1T*°â{»¶Y5(Ô÷QÉÀ7²)©?¥Î¾µï}Héßi\+Â�ÇX2°t�d)¤‚�G™ÉÊ—ÄSF”›ô„ÊSÔL¥~—ÙÒŸ3�1Å�‚Bž×‘Áú:³;7	P—²kÓã›lA§Ó9ý5»üàµã¾Øk»€Béë?ÙñS³#„gi¡‰•µã¶àÕ¾Ûk;�&cýw®fµ§×`”G±Þ˜¯çßZI<e·QWîhéÅ…Óùÿª¯
ºkëïV¤ÝÏýûkÆ3ä$‚PxD»ýŸò
�:‘#
#
# Uses the pre-built indexer image as base, adds the frontend static export.
# No Go compilation needed â€” just layers the frontend on top.
#‘j!*Go backend (built by luxfi/indexer CI)
#  “Q7   â†� Next.js frontend (built by luxfi/explore CI)
#  “Q“"This: combined

ARG INDEXER_IMAGE=‘o:main
ARG NODE_VERSION=22“Elstatic export³°ƒ“j7\
   “ G“ü6@Combine indexer + frontend ----
FROM ${INDEXER_IMAGE}

USER root“*"v/frontend
USER explorer

# The explorer binary serves /srv/frontend at /* and API at /v1/explorer/*
ENV FRONTEND_DIR=/srv/frontend
_ä½Ê¶hx†yù# Explorer

Combined block explorer image: Go backend ([`luxfi/indexer`](https://github.com/luxfi/indexer)) + Next.js frontend ([`luxfi/explore`](https://github.com/luxfi/explore)) in a single container.

```
ghcr.io/luxfi/indexer   â†� Go backend (API + chain indexing)
ghcr.io/luxfi/explore   â†� Next.js frontend
ghcr.io/luxfi/explorer  â†� This: combined single image
```

## Run

```bash
docker run -p 8090:8090 \
  -e RPC_ENDPOINT=http://node:9650/ext/bc/C/rpc \
  -e CHAIN_NAME="My Chain" \
  -e COIN_SYMBOL=ETH \
  -v explorer-data:/data \
  ghcr.io/luxfi/explorer
```

Single port, single image. Backend API at `/v1/explorer/*`, frontend at `/`.

## Build

```bash
docker build -t ghcr.io/luxfi/explorer .
```

Pulls `luxfi/indexer` and `luxfi/explore` from GitHub at build time. Override branches:

```bash
docker build \
  --build-arg INDEXER_REF=v1.2.0 \
  --build-arg EXPLORE_REF=v2.7.2 \
  -t ghcr.io/luxfi/explorer .
```

## White-Label

```bash
docker run -p 8090:8090 \
  -e RPC_ENDPOINT=http://node:8545 \
  -e CHAIN_NAME="Awesome Chain" \
  -e COIN_SYMBOL=AWE \
  -e CHAIN_ID=12345 \
  ghcr.io/luxfi/explorer
```

## Multi-Chain

Mount a `chains.yaml`:

```bash
docker run -p 8090:8090 \
  -v ./chains.yaml:/etc/explorer/chains.yaml \
  -v explorer-data:/data \
  ghcr.io/luxfi/explorer --config=/etc/explorer/chains.yaml
```

## Related

- [`luxfi/indexer`](https://github.com/luxfi/indexer) â€” Go backend, 1528 tests, SQLite, E2E PQ encrypted backups
- [`luxfi/explore`](https://github.com/luxfi/explore) â€” Next.js frontend, Geist font, white-label
- [`luxfi/explorer-v1`](https://github.com/luxfi/explorer-v1) â€” Legacy Elixir stack (archived)
(C­xm ’ÿ40000 .github é*MôÙ
š(‘ªQ7È×«a100644 Dockerfile *n†Ÿ¸Št)g˜Ú*zïa]wL100644 README.md ;¡1ÄàN{8B·S^g¤ gS@&«õºõ¼¤(óÓcN$úI°|úëž;