11 Commits
Author SHA1 Message Date
Kevin Codex 31d645ac25 contracts: the lock's --update refuses a path with a space in it
From the CodeRabbit review of #90. --update wrote such a path into contracts/deployed.lock, and the check then
refused the line it had just written: an entry is "kind hash  path" with no space in the path, which is also the
only shape the guard reads. The path is refused before anything is written.
2026-10-12 20:14:49 +08:00
Kevin Codex 906d754a28 ci: pr-guard: close the gaps an audit of the merged guard found
A full read of the guard for changes that get no finding, or a lighter one than the rule promises. Each item was
reproduced before it was fixed, and has a test.

Refused for everyone:
- criss-cross (new): a branch with more than one common ancestor with main. Its diff showed nothing while merging
  it took a merged fix back out (a "git merge -s ours" of an old side branch). No merge on main has that shape.
- odd-path: any character outside ASCII in a file name. A Mac folds the long s onto s, so "contract<long s>/src/..."
  and ".v<long s>code/tasks.json" landed on the real files at checkout and matched no rule.
- install-script: binding.gyp. npm builds it at install time with no script in package.json.
- lockfile: an entry's name and version have to be a name and a version. A version that walks the path gave one
  package another package's tarball.
- unread (new): a file the guard cannot read (over 25 MB, past 1500 files, an error) was "unscanned", which an
  owner's own pull request passes. A lockfile and a text file over 2 MB are still read for credentials and hidden
  characters.
- hidden-unicode: a variation selector away from an emoji, and a carriage return inside a line (a compiler ends a
  "//" comment there).
- case-collision: two new names that differ only by case. A file renamed only in its case is no longer one.
- dev-machine: CLAUDE.local.md, AGENTS.override.md, .devcontainer.json, *.code-workspace, .clinerules/, .zed/,
  .roo/, .kiro/ and more of their kind.

For an owner to read:
- dot-file (new): any other new file or folder whose name begins with a dot (.coderabbit.yaml, .eslintrc.js). A
  .gitignore or .gitkeep is left alone.
- A file under pages/ is a page whatever it is called, so "x.test.tsx" there is not exempt as a test. LICENSES.ts is
  code, LICENSE-MIT is prose.
- auth-removed knows the app's own names (validate..., check..., admit..., assert...), and a check that is
  commented out counts as removed.
- new-env: process.env?.NAME, and a helper called with the name (intEnv("NAME", 5), vm.envOr("NAME", ...)).
- contract-rules: owner, paused, feeBps, setProtocolFee, written without a library.

Also: a finding never repeats a credential (a git dependency's token was quoted in the job log); the lock script
refuses a line that is not an entry in the one shape the guard reads; CONTRIBUTING says what "contracts" and
"binary" cover and lists the three new rules.

161 tests (146 before). Every merged pull request on main and all eight open ones keep their outcome.
2026-10-12 18:12:43 +08:00
Kevin Codex f2a5c065ba ci: pr-guard: hosts finished at run time, checks lost with a deleted file, lock paths as git spells them
From the third CodeRabbit review of #88, and from reviewing those fixes.

- new-host: a scheme with no host after it ("https://" + target) had no finding. It is "(computed)" now, as a
  template placeholder already was. So are an empty host ("https://@" + x), an address that ends its string and
  has more joined on ("https://known.org" + ".elsewhere.io"), and "//" + host. Also read: a host behind escaped
  slashes, an address with no scheme, a wildcard host. The base branch's hosts are read with the same rule.
  Not seen, on purpose: a scheme that is itself split ("https:" + "//" + host). A string that is only "https:" is
  how the app compares protocols, in eleven places.
- dangerous-code: the same things spelled another way. Function() without new, eval reached indirectly, a timer
  given its code as a string, createElement("script"), and the environment taken as one object
  ({ KEY } = process.env, env: process.env).
- auth-removed: a deleted file takes its checks with it, and that was never reported, since a deleted file is not
  read. Now it is, unless the same check turns up again in what the pull request adds (a move).
- check-deployed-sources.sh: --update refuses a file git does not track, writes each path as git spells it from
  the root (the guard compares the lock's paths letter for letter, so "./contracts/..." would have dropped the
  file's protection), and removes its temporary file when it refuses. The check says so when a locked file is
  present but untracked.
- CONTRIBUTING: the check turns green after the approval and a re-run of the job, as the job summary says.

146 tests. All 65 merged pull requests on main keep their outcome.
2026-10-12 15:53:46 +08:00
Kevin Codex 094d19229b ci: pr-guard review fixes: who pushed the head, off-registry specs, safe reports
Fixes from the CodeRabbit and CodeQL review of #88, and from a second read of the result.

- An owner's own pull request turns review findings into notices only when an owner pushed the head commit. The
  event's sender is that pusher only for a push event whose commit is still the head; for opened and reopened the
  branch's activity log is asked, and when GitHub cannot say, nobody pushed it. Before, reopening a pull request
  after someone else had pushed to it counted as the owner's push.
- Dependency specs: the rule now lists what a registry spec may hold instead of every way to leave the registry.
  Local paths (../../x, ./x, ~/x, /x, .), tarball names, scp-style git remotes and unknown protocols were only
  "review" before.
- A manifest or lockfile the guard cannot check (too large to read, nested too deep, past the file limit) is
  blocked. It used to be "unscanned", which one approval lets through. Both are read whole whatever their size.
- Reports: finding details are code spans in the job summary, so a dependency or script name cannot write a link or
  HTML into it. A line of the job log no longer starts with a file name, which the runner could read as a workflow
  command.
- The exit code is set, not forced: a JSON report sent into a pipe was cut at 8 KB on a Mac.
- check-deployed-sources.sh: a library with edited or added files, with a state git cannot read, or with files but
  no repository is drift. The lock's last line counts without its newline, and --update works on an empty lock.
- Workflows: every checkout drops its credentials, the guard job says what its two read permissions are for, and
  the tests pin the guard workflow line for line.
- Not changed: a review does not start the guard. pull_request_review runs the pull request's copy of the
  workflow, so it cannot be trusted to judge, and re-running the trusted job takes a write permission that no
  workflow here has. The workflow header and CONTRIBUTING.md say so, and say when to re-run by hand.

136 tests (113 before), on Node 20, 22 and 24 and in a Linux container.
2026-10-12 12:53:52 +08:00
Kevin Codex 220f612ab9 contracts: deployed.lock pins the live sources; check-deployed-sources.sh fails on any drift
The contracts on Base, Robinhood Chain and Arc are immutable and verified against the files in contracts/src. Until
now only a convention kept those files unedited: check-arc-factory.sh compares the Arc copy with LaunchFactory.sol, so
editing both together passed, and LaunchLocker.sol and LaunchToken.sol had no check at all.

contracts/deployed.lock records a SHA-256 for each deployed source, the Arc diff, foundry.toml, remappings.txt, the
three deployment records and .gitmodules, plus the commit each library submodule is pinned to. Before pinning, every
source file (libraries included, 57 of 57) was compared with the exact-match verified source on Sourcify for the live
factory and locker on all three chains; the settings match too (solc 0.8.26, 200 runs, via-IR, cancun).

script/check-deployed-sources.sh checks the lock, including that each checked-out library is at its pinned commit, and
rewrites it with --update when a new contract goes live.
2026-10-11 12:26:01 +08:00
Kevin Codex 86ba4b80ae arc: CodeRabbit follow-ups from PR 51
- Dockerfile: declare the Arc build args in the runtime stage too (ARG does
  not cross FROM; the image's own env for them was empty and production only
  worked because the same values are Fly secrets)
- indexer: a single token's Transfer logs in a block over the node's result
  cap are read from the block's receipts (eth_getBlockReceipts is uncapped;
  Arc's node has it) before the block is ever skipped
- verify.sh: a failed verification fails the script after every target ran
- form copy: "same launch, same rules" (the Arc factory carries a guard)
- LAUNCHPAD.md: why the Arc factory's copied NatSpec stays as deployed
2026-09-16 22:42:05 +08:00
Kevin Codex 06e598591b arc: unconditional guard in LaunchFactoryArc; second review round
LaunchFactoryArc is only ever deployed on Arc, so its native-quote refusal no
longer switches on the chain id: the diff to LaunchFactory.sol is now the
contract note, the name, the error and one revert line. The sweep demo in the
fork suite deploys the plain LaunchFactory on the Arc fork instead of cheating
the chain id.

App: the wallet switcher and the list/feed chain filters offer configured
chains only (all chains in development), the stock USD and decimals arms in the
list query are scoped by chain like every other arm, the indexer skips a
single token's holder update for a block whose Transfer logs exceed the node's
cap instead of wedging, blank confirmation env values read as unset, the
insufficient-funds error no longer says ETH, and the last two-chain strings
are gone. verify.sh scopes address overrides to Arc; the drift check ignores
trailing whitespace.
2026-09-16 21:26:04 +08:00
Kevin Codex 515088e170 contracts: LaunchFactoryArc as a separate file; LaunchFactory.sol back to the deployed source
The Arc guard lives in src/LaunchFactoryArc.sol, a verbatim copy of
src/LaunchFactory.sol plus the native-quote refusal, so the file Base and
Robinhood Chain were deployed and verified from stays byte-identical.
script/check-arc-factory.sh (run in CI) fails if the two files drift apart in
anything but the guard; docs/LaunchFactoryArc.diff records that diff.

The deploy script deploys LaunchFactoryArc on chain 5042 and LaunchFactory
elsewhere; verify.sh submits the Arc factory under its own source; the Arc
fork suite and the guard unit test target LaunchFactoryArc.
2026-09-16 21:26:04 +08:00
Kevin Codex 0d2b9ddbdb contracts: refuse native quotes on Arc; nonce guard; rug-proof and hazard fork tests
On Arc the native asset is USDC and the same balance is also the ERC-20 at
0x3600…0000. LaunchLocker keeps one ledger per currency, so a native-quoted
position there would read a credited ERC-20 share (a payout on Circle's
blocklist; three sanctioned addresses are blocklisted on Arc's USDC today) as
native surplus and pay it to another launch's recipients, after which every
smaller USDC collect underflows. Found in the pre-deploy contract review.

LaunchFactory.launch now reverts NativeQuoteUnsupported for quote = address(0)
when block.chainid is 5042 (Arc-only; Base and Robinhood unchanged, and the
locker is byte-identical everywhere). The Arc fork suite reproduces the sweep
with the guard cheated off, pins the refusal, and ports the rug-proof
assertions to Arc's PositionManager; a unit test pins the chain scoping.

The deploy script refuses to run from a non-zero deployer nonce unless
ALLOW_NONZERO_NONCE=true, since nonce 0 is what keeps the addresses identical;
verify.sh takes FACTORY/LOCKER overrides.
2026-09-16 21:26:04 +08:00
Kevin Codex 24b655fda7 contracts: Arc deploy target, verifier, and a fork suite under arc-foundry
DeployLaunchFactory knows Arc's PoolManager and PositionManager; verify.sh
targets the Arc Blockscout. The new fork suite deploys the factory on a fork of
Arc mainnet and proves a USDC-quoted launch, buy and fee collection on the live
Uniswap v4, that native and ERC-20 USDC are one balance, and that the live
Universal Router decodes the v2 ExactInputSingleParams layout and rejects v1.

It only runs under Circle's arc-foundry with the arc profile: USDC's ERC-20
face moves native balance through an Arc-only opcode, so upstream forge reverts
every transfer with OpcodeNotFound. Under plain forge the suite skips.
2026-09-16 21:26:04 +08:00
Kevin Codex 312489a7b4 Initial commit
openlaunch — launch a token. Free. Open source. On Base or Robinhood Chain.
Contracts (LaunchFactory / LaunchLocker / LaunchToken, Foundry), the site (Next.js),
brand assets. No platform fee, liquidity locked forever.
2026-09-07 11:28:24 +08:00