mirror of
https://github.com/Twigpine/openlaunch.git
synced 2026-11-10 17:57:44 -10:00
Launches on Base can now be priced in GITLAWB (Gitlawb's token), next to ETH and Coinbase tokenized stocks. Same contracts, same ERC-20 quote path (Permit2, salt ordering); trading fees are paid in GITLAWB, or burned when no beneficiary is named. - lib/launchpad/gitlawb.ts: token facts, the WETH/GITLAWB Uniswap v4 pool key (dynamic-fee hook, tick spacing 200; pool id asserted = keccak(abi.encode(key))), USD from sqrtPriceX96 x ETH/USD with a sanity window, dollar->GITLAWB presets, tile - lib/launchpad/gitlawbServer.ts: StateView.getSlot0 read, 60s cache, fail-soft - config: quote key "gitlawb", Base quotes [ETH, GITLAWB], presets, quoteKeyOf() - queries: live USD factor in SQL sorts/volume/mcap + totals, gitlawb list filter - launch form: GITLAWB chip with live price, dollar start-cap presets, compact units - token page + share card: "priced in GITLAWB" note/pill with the Gitlawb mark - fmtQuote: 18-dec amounts >= 100K read as 1.2M (GITLAWB is millions per dollar) - copy: rules (new FAQ entry), agents, llms.txt, README - tests: gitlawb.test (pool id, price, presets), filter + card cases (256 total); contracts/test/LaunchFactory.gitlawb.fork.t.sol proves launch/buy/sell/collect on live Base with the 1% fee burned as GITLAWB
108 lines
5.3 KiB
Solidity
108 lines
5.3 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.26;
|
|
|
|
import {Test} from "forge-std/Test.sol";
|
|
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
|
|
import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
|
|
import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol";
|
|
import {LaunchFactory} from "src/LaunchFactory.sol";
|
|
import {LaunchLocker} from "src/LaunchLocker.sol";
|
|
import {LaunchToken} from "src/LaunchToken.sol";
|
|
|
|
interface IERC20Meta {
|
|
function decimals() external view returns (uint8);
|
|
function symbol() external view returns (string memory);
|
|
function balanceOf(address) external view returns (uint256);
|
|
function approve(address, uint256) external returns (bool);
|
|
function transfer(address, uint256) external returns (bool);
|
|
}
|
|
|
|
/// Can a launch be quoted in GITLAWB (Gitlawb's token on Base)? Against the LIVE Base
|
|
/// factory: launch a token priced in GITLAWB with NO beneficiary, buy it with GITLAWB,
|
|
/// sell some back, collect — and prove the 1% fee was BURNED as GITLAWB (sent to 0x…dEaD).
|
|
/// GITLAWB is borrowed from the Uniswap v4 PoolManager (which provably holds it: the deepest
|
|
/// GITLAWB market is a v4 pool) via prank — no mocks anywhere.
|
|
/// FORK_TESTS=true BASE_RPC_URL=… forge test --match-contract LaunchGitlawbQuote -vv
|
|
contract LaunchGitlawbQuote is Test {
|
|
address constant FACTORY = 0x815542E8b392389A1389E22E588E4B62A67Ade72;
|
|
address constant LOCKER = 0xcd1680D26922fcd9CabFbb8a56bA40C333fD842a;
|
|
address constant PM = 0x498581fF718922c3f8e6A244956aF099B2652b2b;
|
|
address constant GITLAWB = 0x5F980Dcfc4c0fa3911554cf5ab288ed0eb13DBa3; // Gitlawb (18 dec)
|
|
address constant DEAD = 0x000000000000000000000000000000000000dEaD;
|
|
|
|
bool forked;
|
|
LaunchFactory factory = LaunchFactory(FACTORY);
|
|
PoolSwapTest router;
|
|
address creator = makeAddr("gitlawb-creator");
|
|
address buyer = makeAddr("gitlawb-buyer");
|
|
|
|
function setUp() public {
|
|
if (!vm.envOr("FORK_TESTS", false)) return;
|
|
vm.createSelectFork(vm.envOr("BASE_RPC_URL", string("https://mainnet.base.org")));
|
|
forked = true;
|
|
router = new PoolSwapTest(IPoolManager(PM));
|
|
vm.deal(creator, 1 ether);
|
|
vm.deal(buyer, 1 ether);
|
|
// borrow 200M GITLAWB (~$3.4K at $1.7e-5) from the PoolManager for the buyer
|
|
uint256 pmBal = IERC20Meta(GITLAWB).balanceOf(PM);
|
|
assertGt(pmBal, 200_000_000e18, "PoolManager holds GITLAWB (the v4 WETH/GITLAWB pool)");
|
|
vm.prank(PM);
|
|
IERC20Meta(GITLAWB).transfer(buyer, 200_000_000e18);
|
|
}
|
|
|
|
function test_fork_launchQuotedInGITLAWB_buySellBurn() public {
|
|
if (!forked) return;
|
|
assertEq(IERC20Meta(GITLAWB).decimals(), 18);
|
|
assertEq(IERC20Meta(GITLAWB).symbol(), "GITLAWB");
|
|
LaunchFactory.LaunchParams memory p;
|
|
p.name = "Lawb Fan Token";
|
|
p.symbol = "LAWBFAN";
|
|
p.quote = GITLAWB;
|
|
p.lpFee = 10_000;
|
|
// 1B supply at an FDV of 600M GITLAWB (≈ $10k): tokens per GITLAWB = 1e9/6e8 ≈ 1.667 → tick ≈ ln(1.667)/ln(1.0001) ≈ 5,108 → 5,200
|
|
p.startTick = 5_200;
|
|
// recipients empty = fees burned
|
|
(bytes32 salt,) = factory.findSalt(creator, keccak256("lawbfan"), p.name, p.symbol, 0, "", GITLAWB, 64);
|
|
p.salt = salt;
|
|
vm.prank(creator);
|
|
(address token, uint256 tokenId) = factory.launch(p);
|
|
assertGt(uint160(token), uint160(GITLAWB), "token sorts above the quote");
|
|
PoolKey memory key = factory.poolKeyOf(token);
|
|
|
|
uint256 deadBefore = IERC20Meta(GITLAWB).balanceOf(DEAD);
|
|
|
|
// buy with 100M GITLAWB
|
|
vm.startPrank(buyer);
|
|
IERC20Meta(GITLAWB).approve(address(router), 100_000_000e18);
|
|
router.swap(
|
|
key,
|
|
SwapParams({zeroForOne: true, amountSpecified: -100_000_000e18, sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1}),
|
|
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),
|
|
""
|
|
);
|
|
uint256 got = LaunchToken(token).balanceOf(buyer);
|
|
assertGt(got, 100_000_000e18, "more than 100M tokens for 100M GITLAWB at about 1.6 tokens per GITLAWB");
|
|
// sell half back for GITLAWB
|
|
LaunchToken(token).approve(address(router), got / 2);
|
|
uint256 glBefore = IERC20Meta(GITLAWB).balanceOf(buyer);
|
|
router.swap(
|
|
key,
|
|
SwapParams({
|
|
zeroForOne: false, amountSpecified: -int256(got / 2), sqrtPriceLimitX96: TickMath.MAX_SQRT_PRICE - 1
|
|
}),
|
|
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),
|
|
""
|
|
);
|
|
vm.stopPrank();
|
|
assertGt(IERC20Meta(GITLAWB).balanceOf(buyer), glBefore, "got GITLAWB back");
|
|
|
|
// fees: 1% of the GITLAWB that came in, burned (no beneficiary) → lands at 0x…dEaD
|
|
(uint256 quoteOut,) = LaunchLocker(payable(LOCKER)).collect(tokenId);
|
|
assertApproxEqRel(quoteOut, 1_000_000e18, 2e16, "about 1% of 100M GITLAWB collected");
|
|
assertEq(IERC20Meta(GITLAWB).balanceOf(DEAD) - deadBefore, quoteOut, "every collected GITLAWB fee was burned");
|
|
assertEq(IERC20Meta(GITLAWB).balanceOf(creator), 0, "creator got nothing: no beneficiary");
|
|
}
|
|
}
|