더블클릭은 두 번 민트한다 — 멱등성은 앱의 의무
체인은 **논스**를 중복 제거하지 **의도**는 못 합니다: Buy 를 더블클릭한 사용자는 유효한 트랜잭션 둘을 만들고, 둘 다 체결됩니다. 결제 API 들은 수십 년 전 idempotency key 로 해결한 문제 — 온체인 앱은 그 규율을 직접 다시 만들어야 합니다.
이 페이지는 카드에 적힌 내용을 펼쳐 보여줄 뿐입니다 — 돌아가는 코드도, 열어볼 데모도 아직 없습니다. 무엇을 왜 만들려는지가 아래에 있습니다.
사용자 액션마다 intent ID 를 발급하고 클라이언트·서버 양쪽에서 그것으로 중복 제거하는 체크아웃과, 그것이 없는 대조군을 만듭니다. 둘 다 더블클릭하고, RPC 를 느리게 해 경쟁 구간을 넓힌 뒤, 무엇이 체결되는지 셉니다.
기술 노트
각 항목이 실제로 무엇을 보여주고 어떻게 동작하는지 — 위 카드보다 자세한 기술 설명입니다.
목적: Stripe 이후의 모든 결제 API 가 idempotency key 를 싣는 이유는, 재시도와 더블클릭이 **실제 사용자의 행동**이기 때문입니다. 온체인에서는 문제가 더 나쁩니다: 지갑은 기꺼이 두 번 서명하고, 두 트랜잭션은 논스가 달라 체인에게는 **서로 다른 유효한 결제 둘**이며, 파이널리티 때문에 두 번째를 취소해 줄 사람이 없습니다. 해법은 체인에 있을 수 없습니다 — 체인에는 사용자 **의도**라는 개념이 없으니까요. 해법은 애플리케이션에 삽니다: 의도 하나, ID 하나, 그리고 모든 계층(버튼 상태, 서버, 제출 큐)이 이미 본 ID 에는 행동을 거부하는 것. 이것은 더 큰 진실의 가장 작은 예입니다: **애플리케이션 레벨의 보장은 정산 계층이 완벽해도 애플리케이션 레벨에서 만들어야 합니다.**
동작 방식: 체크아웃 하나, 빌드 둘 — intent ID 가 있는 버전과 없는 버전 — 그리고 일부러 넓힌 경쟁 구간. ### PoC anvil 을 겨냥한 민트/구매 버튼. 버전 A: 클릭 시 intent { id, action, params } 생성, 대기 중인 id 에는 버튼 비활성화, 서버(또는 클라이언트 큐)는 같은 id 의 두 번째 제출을 거부, id → txHash 매핑을 저장해 재시도에는 새 트랜잭션 대신 기존 영수증을 반환. 버전 B: 그것 전부 없음. 인위적으로 느린 RPC 아래에서 둘 다 더블클릭하고, A 는 정확히 하나, B 는 둘이 체결됨을 확인합니다. ### 무엇을 증명하나 의도 기준 중복 제거는 애플리케이션의 책임이고, 쌉니다: UUID 하나, 조회 테이블 하나, 비활성화된 버튼 하나. 체인 자체의 유일성(논스)은 **같은 서명 바이트의 재사용**을 막을 뿐 — **같은 인간의 소망이 두 번 서명되는 것**은 전혀 막지 못합니다.