An autonomous agent that lives in the Solana namespace.
It holds its own key, registers its own names, issues identities beneath them,
resolves and pays other wallets by name, and writes down every decision it makes.
Most "onchain agents" are a language model with a wallet bolted to the side. You tell it what to do, it produces a transaction, and the only thing standing between a bad completion and an empty wallet is that nobody has thought of the right sentence yet.
Resolver is built the other way round. It is an agent with a job — it operates in the Solana Name Service, where names are the assets, identities are the product and counterparties are addressed by something a human can read. The model is the smallest part of it. What the agent may do is a closed set of typed intents, decided by a policy layer that runs before anything is signed, recorded in an append-only journal that is written before the transaction goes out rather than after it comes back.
It owns its name. It registers more. It issues subdomains under the ones it
holds and takes fees for them. It resolves bob.sol to the address bob
actually agreed to be paid at, not merely the one somebody wrote down. And it
does all of that on a loop, unattended, inside a budget denominated in lamports.
Agent wallet: ressmWY859teqC9xL4GUYa1xV9VGGSWjr7NqJnocMau — resolver.sns
The agent addresses itself by name, so that is the handle worth keeping. The wallet behind it can be verified either way round:
resolver resolve resolver.sns
resolver resolve ressmWY859teqC9xL4GUYa1xV9VGGSWjr7NqJnocMau| Division | Path | What it is responsible for |
|---|---|---|
| Kernel | src/kernel/ |
Domain contracts, the intent set, faults, charter. Imports nothing |
| Wallet | src/wallet/ |
Custody, the policy gate, the action journal, the executor |
| Registry | src/registry/ |
SNS derivation, registry decoding, registration pricing |
| Naming | src/naming/ |
Name shape, generation, availability sweeps, valuation |
| Records | src/records/ |
Record codecs, ownership proofs, resolution |
| Peers | src/peers/ |
Counterparties, settled history, standing |
| Desks | src/desks/ |
Twenty desks in nine divisions. Four work the namespace, sixteen the reserve |
| Intel | src/intel/ |
Provenance, exposure, ranking, Monte Carlo, memos |
| Floor | src/floor/ |
The terminal |
| Briefs | briefs/ |
Long-form reports, BM25-indexed, cited by the operator |
Built by kyle-vbc — @kyle_vbc · github.
- What it actually does
- Autonomy, and what bounds it
- Names as assets
- Resolution, and the rule that protects the balance
- The desks
- Peers
- What it will not do
- Requirements
- Install
- Getting started
- Command surface
- Methodology
- Briefs
- Configuration
- Repository map
- Development
- Status and known limits
- Security
- License
One pass, five stages. Nothing in the loop is a model call.
1 · Reconcile. The first thing the agent does on every pass is read its own journal and check anything marked submitted-but-unconfirmed against the chain. Nothing new happens until it knows what it already did. This is the entire difference between an agent that restarts and an agent that registers the same name twice and pays for both.
2 · Canvass. Every desk is asked what it can see. The namespace desks look
at the name book — what is free, what is held, what is lapsing, what is listed.
The capital desks look at the renewal reserve. They run concurrently with
independent timeouts and settle independently: a dead marketplace API removes
its own rows and reports itself unhealthy in resolver desks, and the pass
continues.
3 · Mark and rank. Every candidate is priced, and every price is split.
Registering a name has a known cost and a modelled value; the gap between them
is booked as registration-spread, and the part that rests on nothing but "a
buyer will appear" is booked as speculative-premium and is not counted as
real. A name earning subdomain fees is a different asset from a name somebody
might want, and Resolver will not average the two into one number.
4 · Form intents. The top of the book becomes a list of Intents — a closed
union in src/kernel/domain.ts with nine members. The
mapping from a ranking to an intent is a fifteen-line switch in
src/cli/commands/run.ts, and it is that short so
that it can be read in one sitting by somebody deciding whether to fund the
wallet.
5 · Decide, sign, settle. Each intent is priced, passed to the policy gate,
written to the journal, and only then — if the verdict is allow — handed to a
signer. A verdict of escalate puts it in a queue a human can read. A verdict
of deny is final and says which rule fired.
$ resolver run --take 3
41 opportunities · acting on 3 · 0 unsettled
renew resolver.sns
confirmed · 0.1041 SOL · within execute autonomy and inside every budget
register relaypoint.sol
confirmed · 0.1063 SOL · within execute autonomy and inside every budget
bid on lumen.sol
escalated · 2.4000 SOL · 2.4000 SOL exceeds the 0.5000 SOL per-action cap
"Fully autonomous" is a claim about who is in the loop. It is not a claim that anything goes, and an agent that cannot tell you the difference should not be holding a key.
Four levels, set in config or RESOLVER_AUTONOMY:
| Level | May sign |
|---|---|
observe |
nothing. Reads only |
propose |
nothing. Decides and queues everything for a human (default) |
execute |
reversible actions: register, renew, records, subdomains, payments |
unattended |
disposals too: transfer, list, bid |
Above that sit three rules that no level relaxes:
- Burning a name is never autonomous. It is the one action with no recovery path, and it always escalates.
- Budgets are denominated in lamports, not calls. Rate limits are the wrong control for an agent with a wallet — ten cheap registrations are fine and one expensive one may not be. There is a per-action cap, a rolling daily cap computed from settled journal rows, and a reserve floor that renewals are paid out of and nothing else may touch.
- The gate is deny-by-default and pure. No network, no clock but the one
passed in, no model. The same intent under the same budget decides the same
way every time, which means a decision can be replayed from the journal and
checked. See
src/wallet/policy.ts— it is 200 lines and worth reading before you fund anything.
The key itself lives in src/wallet/keypair.ts and is
deliberately narrow: read from disk once, never logged, never serialised, never
returned by any accessor, toJSON overridden to say [redacted]. There is
exactly one method that produces a signature and it takes bytes — nothing in
that class knows what a transaction is, so nothing in it can be talked into
signing a different one.
A .sol name is two things at once, and confusing them is how agents end up
renewing worthless names and letting good ones lapse.
As an identity, a name is a stable handle that survives the owner rotating keys. As an asset, it has a cost (length-priced registration, plus rent), a value (what somebody would pay), and a cash flow (what the subdomains and records under it earn). Resolver models both, separately, and the split is visible everywhere:
$ resolver name generate relay --check --limit 4
relay.sol $144 mark · $20/yr · modeled
relay is a standalone root; nothing has to be explained to a buyer.
relays.sol $34 mark · $20/yr · modeled
relays extends "relay" into a namespace one owner can hold end to end.
getrelay.sol $26 mark · $20/yr · modeled
getrelay is the same root with a prefix that widens the buyer set.
gorelay.sol $26 mark · $20/yr · modeled
gorelay is the same root with a prefix that widens the buyer set.
A seed narrows the search rather than adding to it. Ask for names around
relay and you get names built from relay — not the three-letter roots and
short numerics that mark higher than anything seeded and would otherwise crowd
the seed off the page. Omit the seed and the whole lexicon is in play.
modeled is doing real work in that output. A mark with no comparables behind
it is an opinion, and it is labelled as one — the secondary desk requires a 60%
discount to a modeled mark before it will bid, against 20% for a measured
one. Most listings fail that test. That is the desk working, not the desk
finding nothing.
The generator is deterministic and local. Given the same seed and the same held
book it produces the same list in the same order, so a registration can be
audited backwards. It also refuses two categories outright: anything that reads
as somebody else's mark, and anything whose skeleton collides with a name the
agent already owns — s0lomon and resolver are the same string to a human and
owning both is a cost, not a moat.
Availability is checked in batches through getMultipleAccounts, so a thousand
candidates is a handful of round trips rather than a thousand, and every result
carries the slot it was observed at. "Available" is a claim about a moment. A
registration built on a five-minute-old availability check is a race the agent
has already lost.
There are two answers to "where does bob.sol point", and they are not the
same one.
The owner is whoever controls the name account. The payee is whoever the
owner has said should receive value — the sol-record. Usually they are
identical. When they are not, paying the owner is wrong, and paying an
unverified record is worse, because anyone can write a record pointing anywhere.
So Resolver has one hard rule and it is not configurable away by accident:
A
sol-recordis used as the payee only if it is a V2 record whose right-of-association signature verifies. Otherwise the owner is the payee, and the caller is told why.
$ resolver resolve bob.sol --records
bob.sol (favourite)
owner 9xQ…7mK
pays to 4Fw…2pR via a verified sol-record
sol-record 4Fw…2pR verified
url https://bob.example V1 unverified
twitter @bob V2 unverified
V1 records are shown and never trusted for payment: the format has no way to
prove the target agreed to be pointed at. That is not a hypothetical — it is the
attack, and it is cheap. The codec is
src/records/codec.ts; the decision is one function,
decide, in src/records/profile.ts, and every
payment path in the codebase goes through it.
Reverse resolution follows the same discipline. A wallet with no favourite domain is reported as an unnamed counterparty rather than being addressed by whichever of its names happened to sort first.
Nine divisions. Each desk implements one interface — Desk in
src/kernel/domain.ts — and is four methods wide.
| Division | Desks | The edge it is claiming |
|---|---|---|
namespace |
Registration, renewal, subdomain | The primary market, keeping what is held, and issuing under it |
market |
Secondary | An ask is a fact; a mark is an opinion. Act on the gap |
staking |
Marinade, Jito, Sanctum | Consensus rewards and MEV on the renewal reserve |
credit |
Kamino, marginfi, Save | Borrowers pay interest; the curve says when to care |
liquidity |
Orca, Raydium, Meteora | Trading fees, against inventory risk |
derivatives |
Drift | Funding and the basis, as a rate rather than a direction |
arbitrage |
Venue spread, LST basis, funding carry | The same asset priced twice, net of both legs |
trading |
Momentum, mean reversion | Regime, with the invalidation level committed up front |
treasury |
In-house | The agent's own wallet, read from chain state |
The namespace divisions come first because they are the business. The capital divisions are the treasury function: the renewal reserve has to sit somewhere, and somewhere is not a wallet earning nothing.
Two of them are worth calling out.
Renewal is the least interesting decision in the business and the most expensive one to get wrong. A name that lapses does not lose some value — it becomes somebody else's name, immediately and permanently, along with every subdomain and record beneath it. So renewal is modelled as an opportunity like any other, and it usually wins by an enormous margin. That is not a trick to game the ranker; spending $20 to not lose $600 is the best trade on the page. The desk also emits the negative case: a name marked below its renewal cost gets a zero rate and a note, so the agent lets it go on purpose rather than by accident.
Subdomain issuance is the only desk in the building whose return is entirely real. Subdomain fees are paid by users, not implied by a future buyer, so they survive the speculative mark going to zero. It is also the smallest number on the page — which is the usual shape of that distinction, and the reason the agent keeps the two apart instead of blending them into one APY.
A desk that cannot honestly split subsidy from real revenue says so in
confidence rather than guessing, and the ranker discounts it. Writing one:
docs/desks.md.
Counterparties are keyed by address, never by name. Names are how the agent addresses a peer; they are not identity, because a name can be sold to somebody else on a Tuesday and the reputation must not go with it.
Standing is 0–100 and is built from settled on-chain facts only — how many interactions have actually confirmed, how long the agent has known them, and whether they publish a name at all. Nothing a counterparty says about itself enters the calculation. Deliberately not a term: how much value moved. A large trade is not a trustworthy trade, and pricing standing off size is exactly how an agent gets set up.
$ resolver peers
bob.sol 72 · 9 settled · +1.240 SOL
registry.sol 58 · 4 settled · -0.400 SOL
8kR…3nQ 21 · 2 settled · +0.015 SOL
This list is a design constraint, not a roadmap gap.
- No transaction the model wrote. The operator emits
Intents. There is no method anywhere that accepts a pre-built transaction from a model, so a compromised completion cannot express "drain the wallet" — that sentence has no representation in the type. - No burn without a human. At any autonomy level, ever.
- No payment to an unproven record. See resolution.
- No name that reads as somebody's mark. Screened in the generator and screened again in the policy gate, because defence in depth is cheaper than a lawyer.
- No number without a provenance. Every mark carries
measured|reported|modeled|stale, and every rate carries the split between what is earned and what is hoped for. - No shell, no filesystem write, no arbitrary host. The operator's tool
registry is a closed set in
src/ops/tools.tsand the model cannot extend it.
- Node.js
>= 22.6— required for native TypeScript type-stripping, which is howbin/resolver.jsruns an unbuilt checkout.24.xis tested in CI. - A Helius API key for anything that touches Solana. Reverse lookups and
subdomain enumeration need an indexed
getProgramAccounts, which a vanilla RPC will refuse — that is the practical reason the dependency exists. ExportHELIUS_API_KEY, or pointrpcUrlat your own indexed node. - A keypair, but only for the commands that write.
resolve,name check,name generate,peers,bookanddoctorall work without one — the agent should not need custody of a wallet to answer a question about somebody else's. - An Anthropic API key only for
resolver askand the floor's conversation pane. The autonomous loop does not call a model. - A terminal that reports truecolor for the intended palette. Resolver reads
COLORTERMand downshifts to a 16-colour ramp rather than emitting truecolor escapes at a terminal that will render them as mud.NO_COLORis honoured.
npm install -g resolver
export HELIUS_API_KEY=…
resolver doctorFrom a checkout, no build step required:
git clone https://github.com/kyle-vbc/resolver.git
cd resolver
npm ci
node bin/resolver.js doctor1 · Give it an identity.
resolver identity createThis mints a keypair into $RESOLVER_HOME/keys/agent.json at mode 0600 and
prints the address. It is not backed up anywhere and there is no seed phrase.
Copy the file somewhere safe before you fund it; if it is lost, every name
registered under it is gone.
2 · Fund it, and look at it.
resolver identity Ag3nT7…9kQz
1.4820 SOL · autonomy propose
no favourite domain set; peers will see an address, not a name
holds no names — `resolver identity claim` registers one
3 · See what it would do, before it can do anything.
The default autonomy is propose: the agent decides and writes nothing.
resolver run --dry --take 5
resolver queueresolver queue show <id> expands one row into the full decision — the intent,
what it costs in lamports and dollars, which rule fired, and every sentence the
policy layer emitted on the way to its verdict.
4 · Let it claim its name.
resolver --home ~/.resolver config set autonomy execute
resolver identity claimThat registers the best available candidate for the configured handle and writes
a sol-record pointing at the agent's wallet, in that order — a name registered
without a record is an identity nobody can pay.
5 · Let it run.
resolver run --every 60One pass an hour, inside the budgets, journaling as it goes. Ctrl-C between
passes exits cleanly; it will never be interrupted mid-transaction, and anything
in flight is reconciled on the next start.
resolver # interactive floor
resolver identity # address, balance, names, autonomy
resolver identity create # mint the agent's keypair
resolver identity claim [name] # register its own name and point it home
resolver name generate [seed] # candidates, ranked by modelled value
resolver name check <names...> # who owns these, at a stated slot
resolver name register <name> # register to the agent's wallet
resolver name renew <name> # extend a registration it holds
resolver resolve <name|address> # both directions, owner vs payee
resolver peers # counterparties and standing
resolver pay <name|address> <sol> # pay through the record rules
resolver run # one autonomous pass
resolver queue # what is waiting on a human
resolver queue show <id> # the full decision, rule by rule
resolver treasury [wallet] # holdings, idle share, concentration
resolver book [wallet] # ranked opportunities
resolver allocate [wallet] # a plan, with the rejection log
resolver memo <opportunity-id> # mechanics, exposures, the tradeoff
resolver simulate [wallet] # seeded Monte Carlo over the plan
resolver brief [query] # BM25 search over the corpus
resolver ask [prompt] # one headless operator turn
resolver watch [wallet] # refresh on an interval, diff changes
resolver desks # per-desk health and latency
resolver config <show|get|set|unset|path>
resolver doctor # keys, connectivity, cache, versions
Useful flags on the acting commands:
resolver run --dry # form intents and price them, submit nothing
resolver run --take <n> # how many ranked rows to act on
resolver run --every <minutes> # loop forever on an interval
resolver name generate --check # look each candidate up on-chain
resolver resolve --records # print every record under the name
resolver queue --all # include settled and denied rows
Global options, valid on every subcommand:
-w, --wallet <address>
-a, --mandate <preservation|conservative|balanced|growth|degen>
--json single JSON document on stdout, nothing else
--no-tui force the static renderer
--refresh bypass the cache for this run
--log-level <level> trace|debug|info|warn|error (stderr)
--no-color equivalent to NO_COLOR=1
--home <path> override RESOLVER_HOME for this run
--autonomy <level> observe|propose|execute|unattended, this run only
Every command speaks --json: one document on stdout and nothing else.
resolver name generate --check --json | jq '.candidates[] | select(.mark.usd > 500)'
resolver queue --json | jq '.actions[] | select(.state == "escalated")'
resolver resolve bob.sol --json | jq '.resolution.payTo'
resolver doctor --json | jq '.checks[] | select(.ok == false)'The parts worth arguing with are in src/naming/ and
src/intel/, kept separate from data collection so they can be
read and disputed on their own. Both are pure: no I/O, no network, no
import-time work, deterministic given the same input.
Valuation. A name's mark is a product of independent multipliers over a
length-anchored base, not a base plus a stack of bonuses. That is how the
secondary market actually behaves: a four-letter dictionary word is not
"four-letter price plus dictionary bonus", it is a four-letter name that a much
larger set of buyers can use. Multipliers compose; addends do not. Length is
counted in code points — 💀.sol is a one-character name, and pricing it as
four because UTF-8 said so is how an agent overpays for a skull.
Provenance. Real means someone paid for a service: subdomain fees, lease
income, hosting, the discount to fair value captured at registration. Speculative
means the number exists because a story does. realShare is the ratio, and it
is the single most load-bearing figure in the product. A name whose entire return
is speculative-premium can be excluded by one line in a mandate, which is the
whole point of splitting it.
Exposure. Factors combine as 1 − Π(1 − wᵢ) rather than summing, scaled to
0–100, then discounted by a maturity multiplier and an audit multiplier with
diminishing returns. Saturating combination is the point: ten cosmetic concerns
should never outrank one custody risk. The namespace factors —expiry,
custody, trademark, front-run, namespace-authority — sit in the same
table as the DeFi ones and are weighted on the same scale.
Front-running is a modelled cost, not a footnote. Availability is public.
Anything the agent can see, a sweeper can see, and short names carry a 0.8
weight on that factor for exactly that reason.
Score. (effectiveApy − hurdleApy) / (riskAversion × riskPenalty), then
multiplied by liquidity, confidence, capacity and breakeven terms. If you
disagree with a ranking, the output names the term to argue with.
Full derivations: docs/methodology.md.
Long-form reports ship in briefs/, indexed at build time into a
BM25 store — field-boosted, stemmed, phrase-aware — that the operator searches
before it explains anything.
resolver brief "adverse selection"
resolver brief read real-yield-vs-emissionsThey are versioned with the code on purpose: when the model's explanation and a brief disagree, the brief is the source of truth. The worked examples are illustrative — the mechanisms and the arithmetic are real, the specific figures are teaching numbers rather than a record of what any venue printed on a day.
Resolution order, lowest priority first: built-in defaults, then
$RESOLVER_HOME/config.json, then a project-local .resolver.json, then
environment variables, then CLI flags. Secrets are only ever read from the
environment or from a key file. Nothing that can be committed holds a key.
| Setting | Purpose |
|---|---|
HELIUS_API_KEY |
Chain data. Required for anything that touches Solana |
RESOLVER_KEYPAIR |
Path to the signing key (default $RESOLVER_HOME/keys/agent.json) |
RESOLVER_AUTONOMY |
observe | propose | execute | unattended |
RESOLVER_HANDLE |
The handle the agent claims its identity under |
ANTHROPIC_API_KEY |
Model provider. Required only for ask and the floor |
RESOLVER_HOME |
State root, must be absolute (default ~/.resolver) |
RESOLVER_WALLET |
A wallet to read instead of the agent's own |
RESOLVER_MANDATE |
Default mandate band |
RESOLVER_RPC_URL |
Bring your own indexed RPC instead of Helius |
RESOLVER_COLOR |
truecolor or off |
NO_COLOR |
Honoured |
Budgets, protected names and the blocklist live in config.json:
{
"autonomy": "execute",
"handle": "resolver",
"budget": { "perActionSol": 0.5, "perDaySol": 2, "reserveSol": 0.25 },
"protectedNames": ["resolver.sns"],
"blocklist": [],
"roots": {}
}protectedNames cannot be transferred, listed or burned automatically at any
autonomy level. reserveSol is a floor, not a budget: renewals are paid from it
and nothing else may spend into it.
roots supplies the TLD root accounts. .sol has a built-in default; .sns
does not, because it is a newer TLD with its own root account and Resolver will
read against a fallback but will not write into a tree it had to guess. The
agent's own name lives under .sns, so set roots.sns before asking it to
write anything there — reads resolve without it, writes refuse.
State under RESOLVER_HOME: config.json, keys/ (0700), actions.jsonl
(the append-only action journal), peers.json, cache/, minutes/,
statements/, logs/.
| Path | Purpose |
|---|---|
src/kernel/ |
Domain contracts, the intent set, fault taxonomy, charter. Imports nothing |
src/wallet/ |
Keypair custody, policy gate, action journal, executor |
src/registry/ |
SNS program constants, PDA derivation, registry decoding, pricing |
src/naming/ |
Name shape, lexicon, generator, availability sweeps, valuation |
src/records/ |
Record codecs, V2 proof checking, resolution |
src/peers/ |
Counterparty directory and standing |
src/venues/ |
Helius RPC + DAS, base58 and account decoders, marks, treasury assembly |
src/desks/ |
The nine divisions |
src/book/ |
Discovery, sizing, capital allocation |
src/intel/ |
Exposure, provenance, ranking, Monte Carlo, deterministic memos |
src/ops/ |
Anthropic streaming client, tool registry, operator loop, directive |
src/ledger/ |
Disk cache, minutes, statements |
src/floor/ |
Ink panels, hooks, and the static non-TTY renderer |
src/common/ |
Journal (redacting logger), formatting, concurrency primitives |
src/identity/ |
Palette sampled from assets/mark.png, wordmark, drawing primitives |
src/cli/ |
Commander program, per-command handlers, output contract |
The dependency arrow points at src/kernel/ and never out of it. Nothing in
src/naming/ or src/intel/ imports a network client.
npm ci
npm run typecheck # tsc --noEmit, 0 errors
npm run lint # eslint, 0 warnings
npm run build # tsc -p tsconfig.build.json -> dist/
npm test # hermetic node:test suite, no network
npm run run:dry # one pass, priced, nothing submitted
npm run briefs:catalog # rebuild briefs/catalog.json
node bin/resolver.js … # run the checkout without buildingThe stable suite is hermetic by construction: src/naming/ and src/intel/ are
pure, the simulator is seeded, the policy gate takes its clock as a parameter,
and desk tests run against recorded fixtures. Any test that reaches the network
is a bug, so CI runs without keys and fails loudly rather than passing quietly.
Beta, 0.3.0. Type-clean under strict with noUncheckedIndexedAccess and
exactOptionalPropertyTypes; zero any, zero @ts-ignore.
Stated plainly, because a tool that hides these is worse than useless:
- Instruction assembly is a seam, not a shipped implementation. Everything
above the signer — pricing, policy, journalling, reconciliation — is complete
and tested.
Executor.#buildis one method, and it is where the SNS instruction builders land. Until it is filled in,runwithout a broadcaster signs and stops, which is the correct dry-run behaviour and is not a substitute for the real thing. - Name marks are modelled, not fitted. The multipliers in
src/naming/valuation.tsare hand-calibrated against how the secondary market behaves, not regressed against a sales index.confidencesaysmodeledbecause it is, and the desks discount it accordingly. - SNS names do not currently expire, so the renewal desk is modelling a mechanism the registrar has shipped before rather than one it is running today. The grace and auction branches exist so that the desk does not have to be rewritten the day that changes.
- Subdomain demand is a curve, not an observation. Expected takers per namespace are a function of parent length. It is a defensible shape and it is not a forecast.
- Standing is thin by design. Three terms, all observable. A thin signal that cannot be forged beats a rich one that can, but it is still thin.
- The key never leaves
src/wallet/keypair.ts. Read once, never logged, never serialised,[redacted]in every dump. - Every write passes the policy gate. Deny-by-default, pure, replayable.
- Every action is journalled before it is submitted, so a crash between signing and confirming leaves evidence rather than a mystery, and the next pass reconciles rather than repeats.
- API keys are read from the environment only. The journal walks nested objects
and arrays, redacting secret-shaped field names — including the
x-api-keyheader form — and stripping credentials from any URL.config setrefuses a value that carries a credential by shape. - Report a vulnerability privately:
SECURITY.md.
Nothing here is financial advice. Names are illiquid, thinly bid, and frequently worth less than the model says. Every position Resolver takes can lose money, and the ones that score highest are usually being paid the most to carry a risk that has not shown up yet.
MIT. See LICENSE. Contributions: CONTRIBUTING.md.
Built by kyle-vbc · @kyle_vbc ·
github.com/kyle-vbc ·
resolver.sns · ressmWY859teqC9xL4GUYa1xV9VGGSWjr7NqJnocMau
