VeilSwap
← All posts
실전 가이드2026-11-039 min read

URI·클립보드·QR: 암호화폐 주소는 전달 과정에서 이렇게 깨진다

주소는 지갑에 도착할 때까지 URI, 클립보드, QR 세 인코더를 거칩니다. 각각이 어떻게 깨뜨리는지 실측하고 전송 전에 확인할 점을 정리했습니다.

주소는 건네주는 '물건'이 아닙니다. 전송 버튼을 누르기 전에 이미 여러 번 인코딩되고, 복사되고, 렌더링되고, 스크린샷으로 찍히고, 다시 디코딩된 문자열입니다. 교환 주문 페이지에서 그 문자열이 나가는 문은 딱 세 개입니다. 복사 버튼, 200픽셀 QR 코드, 그리고 선택 가능한 텍스트. 셋은 각각 다르게 깨지고, 깨져도 보이지 않습니다. 깨진 버전도 여전히 주소처럼 보이기 때문입니다. 우리는 이 셋을 실제 사이트에서 측정했습니다. 클립보드가 무엇을 남기는지, QR이 무엇을 인코딩하는지, 아홉 가지 주소 형식이 열두 가지 변형에 어떻게 반응하는지. 대가는 구체적입니다. 오늘 18:06 UTC, 100 USDT-TRC20은 0.13937044 XMR로 견적되었습니다. 한 글자 빠진 수취 주소는 주문 실패가 아니라, 목적지 없는 결제 완료 주문이 됩니다. 같은 시각 카탈로그는 434개 자산·62개 네트워크였고, 그중 6개는 메모가 필수입니다. (XMR→USDT 출구 루트)

먼저 URI입니다. 금액을 실을 수 있는 유일한 통로이기 때문입니다. BIP-21의 문법은 bitcoin:<주소>?amount=<십진 BTC>입니다. 금액은 십진 BTC, 최대 여덟 자리, 사토시 단위는 안 되고, 클라이언트는 모르는 파라미터를 거부가 아니라 무시해야 합니다. EIP-681은 ethereum:<주소>@<체인 ID>/transfer?address=<수취인>&uint256=<최소 단위>입니다. 여기서 금액은 토큰 최소 단위의 정수입니다. 1 USDT는 1000000이지 1이 아니며, value=2.014e18 같은 지수 표기가 명시적으로 허용됩니다. 모네로는 monero:<주소>?tx_amount=<십진 XMR>&tx_payment_id=<64자리 hex>이고, wallet2.cpp의 파서는 모르는 파라미터를 unknown_parameters에 쌓아두고도 성공을 반환합니다. 이 한 줄이 고장의 전부입니다. tx_amount를 amount로 쓴 URI는 거부되지 않고 '금액 없음'으로 접수되며, 금액 입력은 사람에게 돌아옵니다. (XMR→USDT 출구 루트)

사이트 검증기로 아홉 형식 × 열두 변형, 총 90칸을 돌렸습니다. URI 접두사가 붙은 것은 모든 체인에서 거부됐고, 사유도 "not a Bitcoin address (expect bc1…, 1… or 3…)"처럼 소박했습니다. 붙여넣기 칸으로서는 옳은 판단입니다. 교환소는 자기가 만들지 않은 문자열 안의 금액을 실행해서는 안 됩니다. URI의 금액은 지시이지 목적지가 아니기 때문입니다. 대가는 편도라는 점입니다. 지갑이나 익스플로러가 bitcoin:bc1q…?amount=0.01을 건네도 이 칸은 받지 않습니다. scheme과 쿼리를 떼고 경로만 남기세요. 반대로 우리 QR은 맨 주소만 인코딩합니다. 따라서 스캔한 코드가 틀린 금액을 담을 수 없고, 맞는 금액도 담지 않습니다.

다음은 클립보드입니다. 셋 중 가장 신뢰할 수 없고 가장 많이 쓰입니다. 복사 버튼은 navigator.clipboard.writeText를 호출하고, 오류를 삼키는 try/catch로 감쌉니다. 보안 컨텍스트가 아니거나 권한이 거부되면 아무것도 복사되지 않고 아무 안내도 나오지 않습니다. 검증기는 비교 전에 trim을 하므로 네 가지 흔한 잔여물이 조용히 구제됩니다. 끝의 개행, 끝의 공백, 양쪽의 줄바꿈 없는 공백(U+00A0), 앞의 BOM(U+FEFF)이고, 이 넷은 아홉 형식 전부에서 통과했습니다. 통과하지 못한 것은 셋입니다. 제로 폭 공백(U+200B), 오른쪽에서 왼쪽 표시(U+200F), 소프트 하이픈(U+00AD)은 중간에 넣든 끝에 붙이든 전 형식에서 거부됐습니다. 그리고 이것들이야말로 PDF, 채팅 앱, 양쪽 정렬된 웹페이지에서 복사할 때 가장 잘 끼어드는 문자입니다. 두 번째 함정은 대소문자입니다. bech32 전체 대문자는 합법이라 통과하고, 혼합 대소문자는 체크섬에서 떨어지며, 모네로 주소 전체 대문자는 즉시 거부되고, 대문자 X를 쓴 0X는 EVM 주소가 아닙니다.

QR은 별도 절이 필요합니다. 기하가 조용히 성패를 결정하기 때문입니다. 주문 페이지는 react-qr-code를 size=200, level 미지정으로 렌더링합니다. 즉 라이브러리 기본값인 L, 복원력 약 7%입니다. 이 컴포넌트는 모듈만 그리고 viewBox를 모듈 수로 잡으므로 코드 자체에는 콰이어트 존이 없습니다. 흰 여백은 컨테이너의 16픽셀 패딩입니다. 200 CSS 픽셀에서 그 여백은 29모듈 코드에 2.32모듈, 33에 2.64, 37에 2.96, 41에 3.28입니다. 규격은 4를 요구합니다. 모듈 크기는 6.90픽셀에서 4.88픽셀까지 내려갑니다. 106자라는 길이도 우연이 아닙니다. 버전 5의 L 바이트 용량과 정확히 같아서, 95자 모네로 주소와 106자 통합 주소가 모두 37모듈에 들어가고, 한 글자만 늘거나 level을 M으로 올리면 41로 뛰어오릅니다.

그리고 기하를 믿지 않고 실제로 디코딩했습니다. 200픽셀 실제 레이아웃을 네 배로 렌더링하고 열화시킨 뒤 jsQR 1.4.0으로 칸당 20회 무작위 흔들림을 주어 읽힌 결과는 깔끔한 곡선이었습니다. 116픽셀 폭, 즉 렌더링 크기의 절반까지는 스물네 조합이 매번 정확히 읽혔습니다. 품질 40 JPEG, 2도 회전, 서브픽셀 이동을 겹쳐도 그랬습니다. 93픽셀에 0.8픽셀 블러, JPEG 40, 3도 회전에서는 짧은 코드가 버텼습니다. 29모듈 20/20, 33모듈 19~20/20입니다. 긴 코드는 무너졌습니다. 37모듈은 9/20과 8/20, 41모듈은 2/20과 0/20으로 떨어졌습니다. 정정 레벨을 L에서 M으로 올리면 이 긴 코드들은 오히려 나빠졌습니다. 같은 200픽셀에 네 개 많은 모듈을 밀어 넣어야 하기 때문입니다. 반면 폭의 18%를 덮는 흰 원을 치우쳐 놓은 경우는 살아남았습니다. 짧은 코드 16~19/20, 긴 코드 20/20으로, 잉여 면적이 큰 쪽이 강했습니다.

마지막 구멍은 기하가 아니라 구조입니다. QR이 인코딩하는 것은 입금 주소뿐입니다. 컴포넌트에 넘어가는 값이 그것 하나이기 때문입니다. 메모는 그 아래 별도 복사 행에 있습니다. 카탈로그 434개 자산 중 6개가 메모 필수입니다. XRP, XLM, HBAR, ATOM, STX, NEIRO-eth이고, 공용 입금 주소가 이 구멍을 넓힙니다. 태그로 라우팅하는 체인에서는 여러 주문이 한 주소를 공유하므로, QR에서 멈춘 사람은 태그 없이 보내게 됩니다. XRP에서 그것은 지연이 아니라 풀이 귀속시킬 수 없는 입금입니다. 코드를 보면 모양도 그대로입니다. QR은 depositAddress에서, 메모는 depositExtraId에서, 서로 다른 요소로 그려지고 스캔 가능한 것은 하나뿐입니다.

그래서 실무 절차는 짧습니다. 주소를 복사해 붙여넣은 뒤, 보내기 전에 앞 네 글자와 뒤 네 글자를 화면과 대조하세요. 이 한 동작이 잘림과 글자 치환을 한 번에 잡습니다. 주소 칸에 URI를 붙여넣지 말고, 자기가 만들지 않은 URI의 금액을 받아들이지 마세요. QR을 읽는다면 금액은 0으로 두고 주문 페이지에서 다시 입력해 여덟 자리를 모두 맞추세요. 0.13937044는 0.1394가 아니고, 100 USDT 주문에서 그 차이는 도착액의 약 0.3%입니다. 메모가 필요한 자산은 아예 스캔하지 말고 주소와 메모를 따로 복사하고, 메모는 두 번 확인하세요. 체크섬이 없는 유일한 칸이기 때문입니다. 마지막으로 QR을 스크린샷해 채팅으로 넘기는 행동이 바로 93픽셀 사례를 현실로 만듭니다. 이미지는 남고, 주소는 남지 않을 수 있습니다. (XMR→USDT 출구 루트)

특별한 이야기가 아닙니다. 인코더 셋, 고장 유형 셋, 그 사이에서 어디가 깨졌는지 볼 수 없는 사람 하나. URI는 금액을 조용히 떨어뜨리고, 클립보드는 폭 없는 문자를 싣고, QR은 200픽셀 상자에 37모듈을 깝니다. 해법은 더 좋은 소프트웨어만이 아니라, 이 문자열이 어느 문으로 나왔는지 알아서 그 문이 깨뜨릴 수 있는 부분을 확인하는 것입니다. 자기가 관리하는 지갑에서 보내고, 환불 주소는 같은 체인에 두세요. 하나만 기억한다면 이렇게 기억하세요. 화면의 주소와 지갑의 주소는 대조하기 전까지 서로 다른 객체입니다. (모네로 확인 대기)

Frequently asked questions

주소는 기기 사이에서 어떤 세 가지 방식으로 깨집니까?

URI, 클립보드, QR은 각각 문자열의 다른 부분을 손상시킵니다. URI는 단위를 틀리거나 금액을 조용히 떨어뜨리고, 클립보드는 U+200B 같은 제로 폭 문자를 끼워 넣을 수 있으며(사이트 검증기는 실측한 아홉 형식 전부에서 거부), 200픽셀 QR은 주소가 약 80자를 넘고 이미지가 절반 이하로 줄면 판독 신뢰도가 급락합니다(37모듈은 열화 이미지에서 9/20). 세 경로가 공통으로 지키는 것은 잃으면 안 되는 부분, 즉 주소 자체입니다. 그래서 확인은 항상 같습니다. 목적지를 대조하고, 금액은 직접 다시 입력하세요.

주소 칸은 bitcoin: 이나 monero: 로 시작하는 URI를 받습니까?

아니요. 실측에서 URI 접두사가 붙은 변형은 아홉 형식 전부에서 거부됐고, 오류는 "not a Bitcoin address" 형태였습니다. 통과하는 것은 맨 주소뿐이며, 전송 전에 프런트엔드가 앞뒤 공백을 trim합니다. 지갑이 URI를 준다면 scheme부터 쿼리까지 떼고 주소만 남긴 뒤, 금액은 주문 페이지에서 직접 입력하세요.

QR 정정 레벨을 올리면 왜 긴 주소가 더 읽기 어려워집니까?

픽셀 예산이 고정되어 있기 때문입니다. 200 CSS 픽셀 렌더링에서 95자 주소를 L에서 M으로 올리면 심볼은 37에서 41모듈로 커지고, 모듈 하나는 5.41에서 4.88픽셀로 줄어듭니다. 열화 이미지 시행에서 성공률은 9/20에서 2/20으로 떨어졌습니다. 정정은 모듈이 충분히 클 때만 의미가 있습니다. 그 문턱 아래에서는 잉여가 더 작은 점으로 지불되므로 거래는 반대 방향으로 작동합니다.

Keep reading

All articles →

Ready to swap?

No account · No KYC · No email

Exchange now
URI·클립보드·QR: 암호화폐 주소는 전달 과정에서 이렇게 깨진다 — VeilSwap