The double-click mints twice — idempotency is the app's duty
The chain dedupes nonces, not intents: a user double-clicking Buy produces two valid transactions, and both settle. Payment APIs solved this decades ago with idempotency keys — on-chain apps have to rebuild that discipline themselves.
Build a checkout that issues an intent ID per user action, dedupes on it client-side and server-side, and a control version without it. Double-click both, slow the RPC to widen the race, and count what settles.
Why
Every payment API since Stripe ships idempotency keys because retries and double-clicks are how real users behave. On-chain the problem is worse: the wallet happily signs twice, both transactions carry different nonces so the chain sees two distinct valid payments, and finality means no one un-charges the second one.
The fix cannot live on the chain — the chain has no concept of the user's intent. It lives in the application: one intent, one ID, and every layer (button state, server, submission queue) refuses to act on an ID it has seen. This is the smallest possible example of a larger truth: application-level guarantees have to be built at the application level, even when the settlement layer is perfect.
How it works
One checkout, two builds — with and without intent IDs — and a race you widen on purpose.
PoC
A mint-or-buy button against anvil. Version A: on click, create intent { id, action, params }, disable the button on pending id, have the server (or client queue) refuse a second submission with the same id, and store the mapping id → txHash so a retry returns the existing receipt instead of a new transaction. Version B: none of that. Double-click both under a artificially slow RPC; assert version A settles exactly one transaction and version B settles two.
What it proves
Deduplication by intent is an application responsibility, and it is cheap: one UUID, one lookup table, one disabled button. The chain's own uniqueness (nonces) protects against replay of the same signed bytes — it does nothing against the same human wish signed twice.
Where it lands in Jayverse
Verex: give every Buy action an intent ID, deduped client- and server-side. Build the control-vs-fixed test the PoC describes — double-click under an artificially slow RPC and assert the fixed version settles exactly one order.
Token: apply the same intent-ID dedupe to the JYVE mint/AMM front end. A double-click on mint is the same bug with a worse name; store id → txHash so a retry returns the existing receipt.
Wallet: disable the action button on a pending intent ID. This is the cheapest layer of the fix — one UUID, one lookup table, one disabled button — and it belongs in Wallet's shared checkout component, not per-service.
Key expressions
Words and phrases from this page worth keeping, with the Korean meaning and the sentence they come from.
Expression
뜻 · 쓰이는 자리
idempotency key
멱등성 키(같은 요청을 여러 번 보내도 한 번만 처리되게 하는 식별자) · 결제 API의 중복 방지 표준 기법. "Payment APIs solved this decades ago with idempotency keys"
un-charge
청구를 취소하다·환불 처리하다 · 이미 확정된 결제를 되돌릴 방법이 없을 때. "finality means no one un-charges the second one"
cannot live on
~에만 맡겨둘 수 없다·~차원에서 해결될 수 없다 · 해결책이 다른 계층에 있어야 할 때. "The fix cannot live on the chain"
refuse to act on
~에 대해 처리를 거부하다 · 이미 본 ID에 대해 재처리를 막을 때. "refuses to act on an ID it has seen"
widen on purpose
일부러 (경합 조건을) 크게 벌리다 · 버그를 재현하려고 레이스 컨디션을 의도적으로 키울 때. "and a race you widen on purpose"
settle (a transaction)
거래가 확정·처리되다 · 몇 건의 트랜잭션이 실제로 체결됐는지 셀 때. "assert version A settles exactly one transaction"
smallest possible example of a larger truth
더 큰 원리를 보여주는 가장 작은 사례 · 작은 버그가 일반 원칙을 드러낼 때. "the smallest possible example of a larger truth"
double-click (trigger duplicate action)
두 번 눌러서 같은 동작이 중복 발생하는 것 · 사용자 실수로 두 개의 유효한 거래가 생길 때. "a user double-clicking Buy produces two valid transactions"
체인은 논스를 중복 제거하지 의도는 못 합니다: 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 하나, 조회 테이블 하나, 비활성화된 버튼 하나. 체인 자체의 유일성(논스)은 같은 서명 바이트의 재사용을 막을 뿐 — 같은 인간의 소망이 두 번 서명되는 것은 전혀 막지 못합니다.
Jayverse에서의 위치
Verex: 모든 Buy 액션에 클라이언트·서버 양쪽에서 중복 제거되는 의도 ID를 부여한다. PoC가 설명하는 대조군 테스트를 만든다 — 인위적으로 느린 RPC에서 더블클릭하고, 고정된 버전이 정확히 하나의 주문만 정산하는지 검증한다.
Token: JYVE 민트/AMM 프론트엔드에도 같은 의도 ID 중복 제거를 적용한다. 민트에서의 더블클릭은 같은 버그에 더 나쁜 이름이 붙은 것뿐이다. id → txHash를 저장해 재시도가 새 트랜잭션이 아니라 기존 영수증을 돌려주게 한다.
Wallet: 대기 중인 의도 ID가 있으면 액션 버튼을 비활성화한다. UUID 하나, 조회 테이블 하나, 비활성화된 버튼 하나로 끝나는 가장 저렴한 방어층이며, 서비스마다 따로가 아니라 Wallet의 공용 체크아웃 컴포넌트에 넣어야 한다.
핵심 표현
이 페이지의 영어 본문에서 배울 만한 단어와 표현, 뜻과 나온 자리.
Expression
뜻 · 쓰이는 자리
idempotency key
멱등성 키(같은 요청을 여러 번 보내도 한 번만 처리되게 하는 식별자) · 결제 API의 중복 방지 표준 기법. "Payment APIs solved this decades ago with idempotency keys"
un-charge
청구를 취소하다·환불 처리하다 · 이미 확정된 결제를 되돌릴 방법이 없을 때. "finality means no one un-charges the second one"
cannot live on
~에만 맡겨둘 수 없다·~차원에서 해결될 수 없다 · 해결책이 다른 계층에 있어야 할 때. "The fix cannot live on the chain"
refuse to act on
~에 대해 처리를 거부하다 · 이미 본 ID에 대해 재처리를 막을 때. "refuses to act on an ID it has seen"
widen on purpose
일부러 (경합 조건을) 크게 벌리다 · 버그를 재현하려고 레이스 컨디션을 의도적으로 키울 때. "and a race you widen on purpose"
settle (a transaction)
거래가 확정·처리되다 · 몇 건의 트랜잭션이 실제로 체결됐는지 셀 때. "assert version A settles exactly one transaction"
smallest possible example of a larger truth
더 큰 원리를 보여주는 가장 작은 사례 · 작은 버그가 일반 원칙을 드러낼 때. "the smallest possible example of a larger truth"
double-click (trigger duplicate action)
두 번 눌러서 같은 동작이 중복 발생하는 것 · 사용자 실수로 두 개의 유효한 거래가 생길 때. "a user double-clicking Buy produces two valid transactions"