디앱
저장소에 있는 앱은 cairn-app/입니다: 지갑용으로 wagmi와 viem을 쓰는, Tailwind v4 기반의
Next.js 16 프런트엔드(App Router)이며, 로컬에서 포트 3100으로 서비스됩니다. 2026-10-02에 이전의
React + Vite 디앱을 대체했습니다. 앱은 프로젝트의 새 이름인 Cairn.fun을 쓰며, 이 이름 아래에서
프로토콜 토큰은 $CAIRN입니다; 이 문서는 StockFun이라는 이름을 유지합니다.
앱은 배포가 설정될 때까지 "Illustrative"로 표시된 데모 데이터로 실행되며, 배포된 것은 없습니다. 이 페이지는 현재 상태 그대로의 앱을 설명합니다.
화면
| 라우트 | 화면 |
|---|---|
/ |
랜딩: 다음 드롭, 마켓 테이프, 드롭의 공개 기록, 수수료 계산기, 바스켓, 프로토콜 토큰, FAQ |
/markets |
탐색: 트레저리 펄스, 프로토콜 토큰 스트립, 필터, 정렬과 검색, 마켓 테이블 |
/market/[slug] |
마켓 상세: 차트, 드롭 패널, 거래 내역, 서명 전에 수수료 배분을 보여 주는 거래 패널, 밴드 1 진행도, 컨트랙트, 지갑의 포지션 |
/launch |
런칭 양식: 아이덴티티, 바스켓 선택기, 실시간 미리보기, 되돌릴 수 없음을 확인하는 체크리스트. 여덟 번째 감사 루프 이후로 체인에서 읽은 바스켓만 제시하며, 보내기 전에 선택된 바스켓을 체인과 다시 대조함 |
/claim |
측정 구간별 홀더의 에어드랍 청구, 그리고 크리에이터 수수료 |
/drops |
측정 구간별 모든 드롭 |
/docs |
메커니즘, 수수료, 바스켓, 드롭, 프로토콜 토큰, 한계 |
에어드랍 결정 이후 폐기되었으며 이전 디앱이 2026-09-28에도 여전히 표시하던 Treasury Ratio는 저장소에 있는 앱에 나타나지 않습니다. 이를 대체할 지표는 아직 결정되지 않았습니다.
청구 화면
2026-10-05부터 /claim은 연결된 지갑이 청구할 수 있는 모든 측정 구간을 마켓별로 나열합니다: 측정
구간의 끝, 그 주식들, 수량과 그 달러 가치. 수량은 오직 에어드랍 컨트랙트에서
가져오며(claimable), 페이지가 열려 있는 동안 60초마다, 그리고 지갑의 각 트랜잭션 뒤에 다시
읽습니다. 긴급 이동 이후 에어드랍 컨트랙트에 부족한 주식은 "awaiting settlement"로 표시되며
청구에서 제외됩니다.
목록 아래에는 합계와 "Claim all"이 있으며, 이는 claimMany를 측정 구간 5개 단위의 배치로
보내고(Ethereum의 Glamsterdam 업그레이드가 2026-10-06에 Sepolia에 도달하기 전까지는 10개; 아래
참고), 정산을 기다리는 주식이 있는 측정 구간에 대해서는 나머지 주식을 지정한 claim을 보냅니다.
모든 호출은 먼저 시뮬레이션됩니다: 실패할 배치는 측정 구간별로, 측정 구간은 그 나머지 주식으로
나뉘므로, 지금 지급할 수 없는 측정 구간이나 주식은 건너뛰고 그 이름을 밝힐 뿐, 결코 나머지까지
가라앉히지 않습니다. 영수증을 읽을 수 없는 트랜잭션은 해시를 유지한 채 전송된 것으로 표시되며, 그
확인은 아직 읽히지 않은 상태입니다; 아무것도 다시 보내지 않으며, 페이지는 그 트랜잭션 자체나 그
자리의 트랜잭션이 채굴될 때까지 그것을 추적합니다: 열 번째 감사 루프 이후로, 지갑에서의 가속은
청구 자체로 받아들여지고 "View tx"가 그것을 가리키며, 취소나 다른 모든 교체는 그 청구를 완료되지
않은 것으로 끝내고 "View tx"는 채굴된 것을 가리킵니다(아래 참고). 결과는 무엇이 지급되었는지,
연기되어 계속 지급 대상으로 남는 주식, 그리고 건너뛴 측정 구간과 그 이유를 알려 줍니다.
2026-10-06부터:
- 지갑의 거부만이 실행을 멈춥니다. 열 번째 감사 루프 이후로는 지갑에서의 취소나 교체도 실행을 멈춥니다. 실패한 트랜잭션은 집계되고("did not go through"), 그 트랜잭션이 다루던 것은 목록에 남으며, 다른 트랜잭션들은 전송됩니다
- 아직 이동 중인 주식을 위한 여유.
claimMany는 실행되는 시점에 측정 구간이 나열하는 모든 주식을 지급하며, 아직 전달을 받는 측정 구간에는 견적과 블록 포함 사이에 주식 하나가 더 적립될 수 있습니다: 앱은 측정 구간이 아직 나열하지 않은 바스켓 주식 하나마다 400,000 가스(Glamsterdam 업그레이드 전까지는 100,000 가스)를 더합니다. 쓰이지 않은 가스는 청구되지 않습니다 - Glamsterdam에 맞춘 가스. Sepolia는 2026-10-06에 Ethereum의 Glamsterdam 업그레이드를 활성화했으며, 이 업그레이드는 새 스토리지 슬롯의 가스를 예전의 약 다섯 배로 만듭니다; 메인넷에는 아직 일정이 없었습니다. 청구가 지급하는 각 주식은 새 슬롯을 세 개까지 쓸 수 있으므로, 앱은 아직 이동 중인 주식 하나마다 400,000 가스를 남겨 두고 트랜잭션 하나에 측정 구간 5개를 청구합니다. 주식 다섯 개로 이루어진 측정 구간 10개에는 최대 약 1,400만 가스가 들 수 있기 때문입니다
- 쓰기 트랜잭션마다 자신의 가스. 이 업그레이드에서는 0에서부터 쓰는 스토리지 슬롯이 약 110,000 가스가 들며, 거래는 그 견적이 보지 못한 스토리지 쓰기를 만날 수 있습니다: 직전의 청구로 비워진 훅의 수수료 적립분(그중 두 가지 청구는 누구나 호출할 수 있음), 다음 시간의 공급량 마크, 같은 블록에서 바뀐 트레이더 자신의 기록. 아홉 번째 감사 루프 이후로 앱은 각 쓰기 트랜잭션에 그 견적에 50,000 가스를 더한 값을 주며, 최신 블록에서 견적된 거래에는 아직 일어날 수 있는 그런 쓰기 각각의 가스도, 같은 블록에서 읽어 더해 줍니다(적립분 하나당 112,000, 마크에 150,000, 기록에 135,000; 읽기가 실패하면 그 전부). 견적이 실패한 쓰기 트랜잭션은 가장 무거운 경우를 기준으로 정한 고정 한도로 나가며, 여유분이 없는 지갑 자신의 견적은 결코 쓰지 않습니다; 이때 런칭은 보내지 않으며, 페이지는 다시 시도하라고 알려 줍니다. 그 전까지는 모든 쓰기 트랜잭션이 견적보다 150,000 가스(업그레이드 전에는 35,000) 많은 가스를 받았습니다. 지갑은 서명하기 전에 한도에 수수료를 곱한 금액을 따로 잡아 둡니다; 청구되는 것은 실제로 쓰인 가스뿐입니다
- 지난 청구에서 지급되지 않은 주식. 토큰이 이 지갑으로의 전송을 거부해서(예를 들어 발행사의 동결) 청구가 연기한 주식은 브라우저가 기억하며, "not paid at your last claim"으로 표시되고 "Claim all"에서 제외됩니다. 각 청구는 먼저 그 주식만 따로 시도하고, 그 청구가 통과할 수 있게 되면 다시 포함합니다; 달리 지급될 것이 없을 때 버튼에는 "Try the assets not paid again"이 표시됩니다
- 확인되지 않음은 거부가 아닙니다. 시뮬레이션은 노드가 호출이 실패할 이유를 밝힐 때에만 의미가 있습니다. 확인 중 RPC 오류를 만난 측정 구간은 아직 확인되지 않은 것으로 목록에 남으며, 어떤 시뮬레이션도 응답하지 않았을 때는 아무것도 보내지 않습니다
- 읽지 못한 것은 0이 아닙니다. 수치를 읽지 못한 마켓은 이름이 밝혀지며, 결코 지급할 것이 없는 것처럼 표시되지 않습니다: "Nothing to claim"은 모든 마켓을 읽었을 때에만 나타나며, 크리에이터 섹션은 "You haven't launched a market" 대신 그 마켓들을 읽지 못했다고 알려 줍니다
크리에이터 수수료는 같은 페이지에서 마켓별로 청구합니다.
표시할 수 없는 수치
2026-10-05부터 볼트가 자신의 뷰 함수에 응답하지 않는 트레저리는, 예를 들어 잘못된 업그레이드 이후, 수치 대신 "Figures unavailable"을 표시합니다: 마켓 목록의 행과 카드, 프로토콜 토큰의 배너, 그리고 마켓 페이지에서. 그 주식과 ETH는 여전히 나열되며, 마켓 목록 페이지의 합계는 몇 개의 트레저리를 제외했는지 알려 줍니다. 앱은 각 볼트를 별도의 호출로 읽는 Lens로부터 이를 알게 됩니다: 아키텍처를 참고하십시오.
2026-10-06부터, 볼트가 지급을 거부한 뒤 훅과 유동성 락이 그 볼트를 위해 보관하는 ETH는 그 트레저리, 그 가치, 그리고 합계에 포함됩니다: "ETH owed"라는 행으로 표시되며, 아직 볼트에 들어가지 않았고 볼트가 받는 즉시 볼트로 옮겨진다는 메모가 붙습니다. 이는 훅과 락에서 읽으므로, 볼트가 응답하지 않는 동안에도 최신 상태로 유지됩니다. 응답하지 않으며 한 번도 읽힌 적이 없는 볼트에는 프로토콜의 브리지 설정이 암시하는 레일이 적용되며, 드롭 패널이 이를 알려 줍니다: 그 주식이 어디에 있는지는 추정된 것이고, 그 보유 자산은 불완전할 수 있습니다. Lens가 프로토콜 토큰의 트레저리를 읽을 수 없을 때, 프로토콜 토큰의 마켓은 출시되지 않은 것으로 표시되는 대신 마지막으로 읽은 상태로 화면에 남으며, 오래된 것으로 표시됩니다.
마켓 페이지에 표시되는 것
2026-10-06부터:
- 회수된 풀에는 차트가 없습니다. 락의 종료 모드가 풀의 유동성을 꺼낸 뒤에는 누구나 그 가격을 공짜로 움직일 수 있습니다. 페이지는 이미 그 풀에 대해 가격도 거래도 표시하지 않았습니다; 이제는 24시간 변동과 차트도 표시하지 않고 그 이유를 밝히며, Worker는 그 가격의 샘플링을 멈춥니다
- 거래 중 주식으로 가는 몫은 그 거래 자신의 택스 항목들이 트레저리로 보낸 것이며, 오늘의 설정이 아니라 그 거래가 지불한 시점의 설정을 따릅니다; 알 수 없을 때는 "—"입니다
- 시가총액은 유통 공급량, 즉 전체에서 소각 주소가 보유한 양을 뺀 것을 셉니다. 헤더에서도 차트에서도 마찬가지입니다; 일곱 번째 감사 루프 이후로 공급량을 읽지 못했을 때는 $0이 아니라 "—"를 표시합니다
- 읽지 못한 잔액은 결코 0이 아닙니다: 포지션 패널은 이를 읽지 못했다고 알려 주고, 거래 패널은 마지막으로 읽은 잔액을 "last read"로 표시해 보여 주며, 그보다 큰 매도를 거부하지 않습니다; 실제 잔액 초과는 트랜잭션 시뮬레이션이 거부합니다
- 마지막으로 읽은 상태로 유지되는 프로토콜 토큰의 마켓은 그 사실을 알려 줍니다. Lens가 그 트레저리를 읽을 수 없는 동안, 그 가격과 시가총액은 표시되는 모든 곳에서 "(last read)"로 표시되고 24시간 변동은 없으며, 그 차트에는 "Now" 대신 "Last read"가 표시됩니다. Lens가 다시 응답할 때까지 Worker는 그 가격을 기록하지 않으므로, 이 중단으로 차트에 점이 더해지지 않습니다. 현재 그 택스는 알 수 없습니다: 페이지는 안티 스나이프 태그를 표시하지 않고, 거래 패널은 초과분 항목 없이 현재 적용되는 일반 택스를 표시합니다. 그 토큰 자체의 수치는 계속 토큰에서 읽으므로, 랜딩의 "Burned by buyback"은 최신 상태로 유지됩니다; 그 시가총액은 마지막으로 읽은 값입니다. 일곱 번째 감사 루프 이후로 이 중단은 차트에 끊긴 구간으로 나타나고, 마지막으로 읽은 가격에는 그 경과 시간이 표시되며, 토큰의 수치는, 마켓이 읽히든 마지막으로 읽은 상태로 유지되든, 응답이 없을 때 마지막으로 읽은 값을 쓰며 결코 0이나 빈 이름이 되지 않습니다; 잘못된 업그레이드 이후 실패하는 프로토콜 컨트랙트가 있어도 그 수치가 더 이상 비워지지 않습니다
일곱 번째 감사 루프 이후로, 2026-10-06부터:
- 차트는 각 점을 그 시각에 놓습니다. 각 가격 샘플은 그 시각이 범위 안에서 해당하는 위치에 놓입니다: 1H, 24H, 7D는 현재에서 끝나고, "All"은 마켓의 나이가 아니라 첫 샘플에서 시작합니다. 마우스를 올린 점은 그 경과 시간을 알려 주며("3h 20m ago"), 샘플이 빠진 곳(중단, 아무도 페이지를 열지 않은 밤)에서는 선이 끊깁니다. 가만히 있을 때 차트는 마켓의 가격, "Now", 또는 마지막으로 읽은 상태로 유지되는 마켓에 대해서는 "Last read"를 표시합니다. 그 전까지는 점들이 균등하게 배치되고 그 위치로 날짜가 매겨졌으므로, 중단 전에 마지막으로 읽은 가격이 현재 가격으로 표시되었습니다
- 24시간 거래량은 셀 수 없는 동안 "—"를 표시합니다. Worker의 거래 로그 읽기가 계속 실패하면 그 거래 집계 창이 더 이상 나아가지 않습니다; 그러면 거래량은 마켓별로도 합계에서도, 거래가 멈춘 것처럼 줄어드는 수치 대신 "—"를 표시하며, 따라잡는 첫 읽기와 함께 돌아옵니다
여덟 번째 감사 루프 이후로, 2026-10-06부터:
- 화이트리스트 지갑에는 일반 택스가 보입니다. 마켓의 안티 스나이프 기간 동안, 그 마켓의 화이트리스트에 있는 연결된 지갑은 거래 패널이 거래에 쓰는 StockFun 라우터를 통해 일반 택스를 냅니다: 이제 패널은 그 지갑에 대해 안티 스나이프 초과분을 표시하지 않으며, 그 이유를 메모로 알려 줍니다. 견적은 원래부터 맞았고, 초과분 항목이 그와 모순되었습니다. 화이트리스트나 지갑을 알 수 없는 동안 패널은 면제를 고려하지 않은 택스를 표시합니다
- 주식에 가격이 없는 이유. Robinhood Chain에서 오라클은 기업 행위 동안 그 주식의 가격을 보류하며, 시퀀서가 멈춰 있거나 막 복구된 동안에는 모든 주식의 가격을 보류합니다. 후자의 검사는 Chainlink가 그 체인의 업타임 피드를 공개할 때까지 꺼져 있습니다(Robinhood 레일 참고). 그 이유로 주식의 가치가 빠져 있으면 앱은 그렇다고 알려 줍니다: "No price for NVDA right now: corporate action in progress", 또는 Robinhood Chain 시퀀서가 멈춤, 복구 중, 상태를 알 수 없음이라는 것을, 드롭 패널의 바스켓 표 아래와 랜딩의 드롭 카드 아래, 그리고 청구 페이지에서는 그 주식 옆에 표시합니다. 랜딩의 카드는 그런 주식을 $0.00으로 표시했지만, 이제는 "—"를 표시합니다
- 24시간 거래량은 각 블록을 한 번만 셉니다. 앞선 노드보다 몇 블록 뒤처진 노드가 응답한 Worker의 읽기가 더 이상 거래 집계 창이 같은 블록을 두 번 세게 만들지 않습니다. 그 한 번의 읽기에서는 최신 거래가 피드에서 빠질 수 있으며, 다음 읽기가 이를 되돌려 놓습니다
아홉 번째 감사 루프 이후로, 2026-10-06부터:
- 추정치인 트레저리 금액은 그렇다고 알려 줍니다. 트레저리의 일부를 읽거나 가격을 매길 수 없을 때(오라클이 가격을 보류하는 주식, 오래된 ETH/USD 피드, 마지막으로 읽은 상태로 유지되는 미러 볼트), 그 수치는 가격을 매길 수 있었던 것을 세며, 이제 표시되는 모든 곳에서 "+"가 붙습니다: 마켓 목록의 행과 카드, 프로토콜 토큰의 카드, 트레저리 펄스, 랜딩, 드롭 패널, 그리고 홀더의 몫. 마켓 목록은 그런 금액이 가격을 매길 수 있었던 것을 기준으로 정렬된다고 알려 주며, 트레저리 펄스는 몇 개의 트레저리를 그렇게 셌는지 알려 줍니다. 그 전까지는 드롭 패널만 "an estimate"라고 알려 주었습니다
- 읽지 못한 승인은 0이 아닙니다. StockFun 라우터를 통한 매도에는 먼저 그 라우터에 대한 승인 한도(allowance)가 필요합니다. 예전에는 그 읽기가 실패하면 승인이 없는 것으로 셌습니다: 이미 승인한 홀더는 서명 두 번으로 다시 승인하라는 요청을 받았고, 두 번째 실패는 쓸모없는 승인을 보냈습니다. 이제 거래 패널은 승인을 읽지 못했다고 알려 주고, 아무것도 견적하지 않으며, 읽을 수 있을 때까지 승인도 매도도 보내지 않습니다. 열 번째 감사 루프 이후로 승인 직후의 매도는 승인의 블록보다 이르지 않은 블록에서 승인 한도를 읽고, 가격이 견적되며, 확인을 받으므로(아래 참고), 한 블록 뒤처진 노드가 더 이상 0이라고 답할 수 없습니다
열 번째 감사 루프 이후로, 2026-10-06부터:
- 지갑이 취소한 트랜잭션은 결코 완료로 표시되지 않습니다. 지갑은 대기 중인 트랜잭션을 취소하거나(같은 nonce로 자기 자신에게 아무것도 보내지 않는 전송) 가속할 수 있습니다(더 높은 수수료로 같은 호출). 이제 앱은 그 자리에 채굴된 것을 읽습니다: 가속은 그 작업 자체로 받아들여지고 "View tx"가 그것을 가리킵니다; 취소나 다른 모든 교체는 그 흐름을 완료되지 않은 것으로 끝내고, "Cancelled in your wallet." 또는 "Replaced by another transaction in your wallet."를 표시하며, "View tx"는 채굴된 것을 가리키고, 지갑의 거부처럼 청구 실행을 멈춥니다. 그 전까지는 취소된 거래가 "Done"으로 표시되었고, 취소된 승인은 주어진 것으로 세어졌으며, 취소된 크리에이터 청구는 "Claimed"로 표시되고 그 세션 동안 ETH를 숨겼고, 취소된 런칭은 데모의 가상 토큰 주소와 함께 "$TICKER is live."로 표시되었습니다; 평소의 3분 대기 뒤에 가속된 트랜잭션은 결코 확인되지 않았으며, 패널은 페이지를 다시 불러올 때까지 잠겨 있었습니다
- 대기 뒤에도 추적됩니다. 평소의 대기가 3분 뒤에 끝나면, 앱은 트랜잭션을 그 nonce로 추적합니다: 지갑이 보낸 트랜잭션 수가 그 nonce를 넘어섰는데 트랜잭션에 영수증이 없으면, 그 nonce에서 채굴된 것을 찾되 전송 이전의 블록에서는 결코 찾지 않으며, 같은 규칙을 적용합니다. 취소나 교체는 실제로 블록에서 찾은 트랜잭션에서만 읽히며, 영수증이 없다는 것에서는 결코 읽히지 않습니다
- 런칭은 체인이 그렇다고 할 때만 가동됩니다. 런칭은 마켓의 생성을 팩토리 자체의 이벤트에서 읽습니다; 그것이 없으면 트랜잭션의 링크와 함께 양식으로 돌아가며, 데모의 주소는 데모 밖에서 결코 표시되지 않습니다
- 1분 동안, 마지막 트랜잭션의 블록. 자신의 트랜잭션 하나가 채굴된 뒤 60초 동안, 앱은 그 체인에서 다음에 보낼 것의 확인, 가스 견적, 가격 견적을 그 블록보다 이르지 않은 블록에서 하며, 매도의 승인 한도도 그곳에서 읽습니다. 부하 분산된 엔드포인트는 한 블록 뒤처진 노드에서 답할 수 있습니다: 승인 직후의 매도가 예전에는 그곳에서 거부되었고("Approve it first"), 두 번째 시도가 두 번째 승인을 보낼 수 있었습니다. 그 블록에 이르지 못한 노드는 이제 늦은 것으로 세어져 다시 질의됩니다; 몇 초 안에 어느 노드도 그 블록에 이르지 못하면, 아무것도 보내지 않습니다. 승인 직후의 매도에 대해서는 이때 페이지가 "Your approval went through, but no quote could be read from the pool, so the sale was not sent…"라고 알려 주며(그 가격 견적이 그곳에서 이루어지는 첫 읽기입니다), 자신의 확인이 그 블록에 이르지 못하는 쓰기 트랜잭션(승인, 청구, 런칭)에 대해서는 "This could not be checked against the block of your last transaction just now, so nothing was sent. Try again in a moment."라고 알려 줍니다
- 남은 것. 거짓 "Replaced"는 다음이 모두 성립하는 좁은 경우 하나에서 여전히 가능합니다: 어떤 노드도 매도를 보여 주지 않아 앱이 지갑의 수에서 그 nonce를 추측했고; 다른 노드들보다 뒤처졌지만 전송 전에 읽은 블록보다는 뒤처지지 않은 노드가 그 수를 답했으며; 그 노드가 보지 못한 같은 지갑의 다른 트랜잭션이 추측한 nonce를 차지했고; 비공개 릴레이를 통해 보낸 매도가 3분과 약 36초의 유예 뒤에도 아직 채굴되지 않았을 때입니다. 취소는 결코 완료로 표시되지 않으며, 잠긴 채로 남는 것은 없습니다
런칭
런칭 양식은 체인의 레지스트리에서 읽은 바스켓만을 레지스트리 자체의 번호 그대로 제시합니다: 첫 읽기 전에는 아무것도 제시하지 않으며("Reading the baskets from the chain…"), 컨트랙트가 배포되기 전에는 그것들이 가동되면 런칭이 열린다고 알려 줍니다. 보내기 직전에, 양식은 팩토리에서 생성 수수료와 선택된 바스켓(그 이름, 그리고 비중과 함께 그 주식들)을 다시 읽으며, 둘 중 하나라도 표시된 것과 다르면 아무것도 보내지 않고 전송을 거부합니다; 그 메시지는 체인이 그 번호에 보유한 바스켓을 알려 줍니다. 여덟 번째 감사 루프 전까지 양식은 첫 읽기 전에 설정된 바스켓을 설정된 번호로 제시했으므로, 레지스트리가 바스켓에 다른 번호를 매기는 배포에서는 표시된 것과 다른 바스켓으로 마켓이 런칭될 수 있었습니다. 양식은 생성 수수료만 지불합니다: 크리에이터 자신의 매수가 없고, 안티 스나이프 화이트리스트도 지정하지 않으며, 그렇다고 알려 줍니다(팩토리는 직접 호출에서는 둘 다 받아들입니다: 마켓 런칭 참고). 확인 화면은 보낼 때 대조한 바스켓을 알려 줍니다.
아트 디렉션
앱의 디자인 규칙은 앱 자체의 DESIGN.md에 있습니다: 흰 캔버스 위의 따뜻한 라이트 테마, 드롭과
핵심 수치에는 테라코타, 인터페이스에는 Poppins, 큰 금융 수치에는 Instrument Serif, 테이블에는
고정폭 숫자.
값의 출처
수수료, 런칭 파라미터, 공급량, 바스켓 구성은 컴포넌트에 절대 직접 쓰지 않습니다: 앱의 구성에
들어 있거나 체인에서 가져오며, 수수료 항목은 시작할 때, 컨트랙트를 그대로 반영하는
@stockfun/shared에서 생성된 상수와 대조해 검사합니다. 페이지에 퍼센트를 직접 입력하는 것, 바로
그렇게 해서 제품은 컨트랙트가 구현하지 않은 수수료 구성을 광고하게 됩니다. 2026-10-05부터 이
수치들은 오너의 설정입니다: shared 패키지는 그 기본값을 담고 있으며, 실시간 값은 그 값을 보유한
컨트랙트에서 읽습니다.
프로토콜 상태는 체인에서 가져오며, 인덱서는 없습니다. Worker인 cairn-worker/가 열려 있는 모든
페이지를 위해 이를 한 번 읽습니다: 스냅숏 하나, 그다음 변경 사항만 푸시하는 WebSocket. Worker가
실패하면 페이지는 공개 RPC를 통해 체인을 직접 읽습니다. 잔액에서 청구할 수 있는 것에 이르기까지,
지갑 자신의 데이터는 브라우저가 읽습니다. 일곱 번째 감사 루프 이후로 Worker는 자신의 체인을
서비스한다고 답하는 엔드포인트만 읽습니다: 다른 체인에 있는 엔드포인트는 다운된 것으로 세므로,
잘못된 네트워크에 있는 백업 때문에 모든 마켓이 사라지는 일은 결코 없습니다. 여덟 번째 이후로
Worker는 여덟 번째 데이터 스키마를 공개하며, 이 스키마는 주식의 가격이 빠진 이유를 더합니다; 더
오래된 스키마를 읽는 앱도 그 이유 없이 계속 동작합니다. 아홉 번째 감사 루프 이후로 Worker의
감시자는 Cloudflare 객체가 두 읽기 사이에 잠드는 동안에도 RPC에 대해 알게 된 것(어느 엔드포인트가
언제부터 실패하고 있는지, 얼마나 기다려야 하는지, 각각 어느 체인을 서비스하는지)을 유지하지만,
엔드포인트의 주소는 결코 저장하지 않습니다: 기본 RPC의 중단에 드는 비용은 읽을 때마다 요청 세 개가
아니라 두 배씩 늘어나는 대기마다 탐색 한 번이며, 정상적인 읽기는 아무것도 두 번 묻지 않습니다.
Worker는 또한 Robinhood Chain 자체의 블록 번호를 기록하며, 예전에는 Robinhood Chain이 정산하는
체인의 블록 번호를 기록했습니다.
카피 감사
pnpm audit:copy는 백엔드, 키퍼, shared, 컨트랙트 소스를 검사합니다. 금지 단어가 하나라도 있거나,
정적으로 확인할 수 있는 시각적 금지 사항을 하나라도 어기면 실패합니다. 필요할 때 실행하는
도구이며, pnpm build에는 포함되지 않습니다. 이전 디앱이 저장소를 떠난 뒤로는 어떤 프런트엔드도
검사하지 않습니다: 앱에서는 금지 어휘가 앱의 PRODUCT.md에 적힌 검토 규칙입니다.
이는 스타일 린터가 아닙니다: 금지 어휘는 법적 제약입니다.