← Contracts

Contracts · MIT license

SightlineLauncher.v1.sol

The first launcher, exactly as deployed. Its tokens are still listed.

Download .sol
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.28;
3
4import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
5import {IPonsFactory, IPonsCurve} from "./IPons.sol";
6
7/// @title SightlineLauncher
8/// @notice Launches event tokens on pons (Robinhood Chain's bonding-curve launchpad)
9/// and remembers which real-world event each one is pinned to.
10///
11/// Every token launched here carries Sightline's signature:
12/// - it is deployed by this contract, so pons records Sightline as the launcher;
13/// - its onchain website link is https://trysightline.io.
14/// Everything else follows pons: the creator (msg.sender) is the creator fee
15/// recipient and chooses the creator tax; trading and graduation happen on pons.
16///
17/// This contract holds no funds between transactions and has no owner.
18contract SightlineLauncher is ReentrancyGuard {
19 struct LaunchInput {
20 string name;
21 string symbol;
22 string logo; // image URI (https:// or ipfs://), optional
23 string description;
24 string twitter; // optional creator socials
25 string telegram;
26 uint16 creatorTaxBps; // extra creator fee on trades, capped by pons
27 bool buybackEnabled;
28 uint256 launchConfigId;
29 bytes32 expectedEconomics; // pons.previewLaunchEconomics(launchConfigId, ETH); guards against config changes
30 // Where and what on Sightline's globe.
31 string category;
32 string eventId;
33 int32 latE6;
34 int32 lngE6;
35 }
36
37 struct Launch {
38 address token;
39 address curve;
40 address creator;
41 uint64 createdAt;
42 uint64 createdBlock;
43 int32 latE6;
44 int32 lngE6;
45 string category;
46 string eventId;
47 }
48
49 string public constant WEBSITE = "https://trysightline.io";
50 uint256 public constant MAX_EVENT_ID_LENGTH = 128;
51 uint256 public constant MAX_CATEGORY_LENGTH = 24;
52 /// pons' marker for a native-ETH quote.
53 address internal constant ETH = address(0);
54
55 IPonsFactory public immutable pons;
56
57 Launch[] private _launches;
58 mapping(address token => uint256 indexPlusOne) private _index;
59 mapping(address creator => uint256) public launchNonce;
60
61 event Launched(
62 address indexed token, address indexed creator, uint256 indexed index, address curve, string eventId
63 );
64
65 error InsufficientLaunchFee();
66 error BadEvent();
67 error BadCoordinates();
68 error UnknownToken();
69 error RefundFailed();
70
71 constructor(IPonsFactory pons_) {
72 pons = pons_;
73 }
74
75 /// @notice Launch a token on pons for a real-world event. Send the pons launch fee,
76 /// plus any ETH to buy with in the same transaction (the creator's dev buy, which
77 /// pons exempts from its anti-sniping tax).
78 function launch(LaunchInput calldata p, uint256 minTokensOut)
79 external
80 payable
81 nonReentrant
82 returns (address token, address curve, uint256 tokensOut)
83 {
84 uint256 eventIdLen = bytes(p.eventId).length;
85 uint256 categoryLen = bytes(p.category).length;
86 if (eventIdLen == 0 || eventIdLen > MAX_EVENT_ID_LENGTH || categoryLen == 0 || categoryLen > MAX_CATEGORY_LENGTH) {
87 revert BadEvent();
88 }
89 if (p.latE6 < -90_000_000 || p.latE6 > 90_000_000 || p.lngE6 < -180_000_000 || p.lngE6 > 180_000_000) {
90 revert BadCoordinates();
91 }
92 uint256 fee = pons.launchFee();
93 if (msg.value < fee) revert InsufficientLaunchFee();
94
95 (token, curve) =
96 pons.launchToken{value: fee}(_tokenParams(p), p.launchConfigId, ETH, new address[](0));
97
98 uint256 devBuy = msg.value - fee;
99 if (devBuy > 0) tokensOut = IPonsCurve(curve).buy{value: devBuy}(devBuy, minTokensOut, msg.sender);
100
101 _launches.push(
102 Launch({
103 token: token,
104 curve: curve,
105 creator: msg.sender,
106 createdAt: uint64(block.timestamp),
107 createdBlock: uint64(block.number),
108 latE6: p.latE6,
109 lngE6: p.lngE6,
110 category: p.category,
111 eventId: p.eventId
112 })
113 );
114 _index[token] = _launches.length;
115 emit Launched(token, msg.sender, _launches.length - 1, curve, p.eventId);
116
117 // If the curve hands back unspent ETH (e.g. it filled up mid-buy), pass it on.
118 uint256 leftover = address(this).balance;
119 if (leftover > 0) {
120 (bool ok,) = msg.sender.call{value: leftover}("");
121 if (!ok) revert RefundFailed();
122 }
123 }
124
125 function _tokenParams(LaunchInput calldata p) internal returns (IPonsFactory.TokenParams memory t) {
126 t.name = p.name;
127 t.symbol = p.symbol;
128 t.logo = p.logo;
129 t.description = p.description;
130 t.socials.twitter = p.twitter;
131 t.socials.telegram = p.telegram;
132 t.socials.website = WEBSITE;
133 t.creatorFeeRecipient = msg.sender;
134 t.creatorTaxBps = p.creatorTaxBps;
135 t.buybackEnabled = p.buybackEnabled;
136 t.expectedEconomics = p.expectedEconomics;
137 // pons namespaces salts by launcher, so every Sightline launch shares one namespace.
138 // Deriving the salt from the creator and their nonce keeps launches from colliding
139 // and stops anyone from squatting another creator's next address.
140 t.salt = keccak256(abi.encode(msg.sender, launchNonce[msg.sender]++));
141 }
142
143 // ----------------------------------------------------------------- views
144
145 function launchCount() external view returns (uint256) {
146 return _launches.length;
147 }
148
149 function getLaunch(address token) external view returns (Launch memory) {
150 uint256 i = _index[token];
151 if (i == 0) revert UnknownToken();
152 return _launches[i - 1];
153 }
154
155 /// @notice Page through launches, oldest first. Returns fewer than `limit` at the end.
156 function getLaunches(uint256 offset, uint256 limit) external view returns (Launch[] memory page) {
157 uint256 total = _launches.length;
158 if (offset >= total) return new Launch[](0);
159 uint256 end = offset + limit > total ? total : offset + limit;
160 page = new Launch[](end - offset);
161 for (uint256 i = offset; i < end; i++) {
162 page[i - offset] = _launches[i];
163 }
164 }
165
166 /// Receives refunds from pons curves during a launch.
167 receive() external payable {}
168}