어떤 단순한 실수가 1,000 USDC를 1 XLM으로 보이게 하는가

어떤 단순한 실수가 1,000 USDC를 1 XLM으로 보이게 하는가

영어에서 번역됨

어느 날, 한 사용자가 rabbit.io 고객 지원팀에 연락하여 거래 후 1,000 USDC 대신 1 XLM을 받았다고 주장했습니다.

이는 심각한 손실처럼 보였습니다. 1 루멘의 가치는 1달러보다 훨씬 낮습니다. 현재 XLM은 약 0.22달러에 거래되고 있습니다. 이런 식으로 전체 금액에 가까운 손실을 입는 것은 누구에게나 경악할 만한 일입니다.

당연히 우리는 즉시 사건 조사를 시작했습니다. 만약 우리의 잘못으로 인해 잘못된 금액이나 잘못된 자산이 전송된 것으로 밝혀졌다면, 우리는 즉시 조치를 취해 사용자가 올바른 자금을 받을 수 있도록 했을 것입니다.

하지만 이야기는 훨씬 더 흥미롭게 흘러갔습니다.

우리 시스템은 의도한 대로 정확하게 작동했습니다. 우리 측의 오류나 실패 없이 사용자가 제공한 주소로 1,000 USDC가 전송되었습니다. 그럼에도 불구하고, 스텔라(Stellar) 네트워크에는 간과할 경우 말 그대로 1,000 USDC를 1 XLM으로 바꿀 수 있는 미묘한 구조적 세부 사항이 존재합니다.

이것이 어떻게 발생하는지 설명해 드리겠습니다.

이더리움 습관

rabbit.io의 이 클라이언트는 초보자가 아니었습니다. 반대로, 그는 거래소가 아닌 비수탁형(non-custodial) 지갑에 자금을 의도적으로 보관하는 숙련된 암호화폐 사용자였습니다. 그러나 그의 경험 대부분은 EVM 기반 네트워크에서 쌓은 것이었습니다.

이더리움 세계에서 사용자들은 주소로 전송된 모든 토큰이 성공적으로 도착한다는 단순한 모델에 익숙해집니다. 최악의 경우에도 지갑 인터페이스에 토큰을 수동으로 추가하기만 하면 됩니다. 지갑 앱이 특정 토큰을 전혀 지원하지 않더라도, 해당 토큰을 지원하는 다른 지갑으로 시드 구문(seed phrase)을 가져올 수 있습니다.

스텔라는 매우 다르게 작동합니다.

그 구조는 근본적으로 이더리움, 바이낸스 스마트 체인(BSC), 폴리곤 등과 다릅니다. 주소의 개념조차 다릅니다. EVM 네트워크에서 주소는 사실상 무료입니다. 스텔라에서 원장(ledger)에 주소를 생성하려면 1 XLM의 비용이 듭니다.

그리고 거래 후 우리 클라이언트가 자신의 지갑에서 본 금액이 바로 정확히 1 XLM이었습니다.

스텔라가 다른 점

스텔라와 EVM 기반 네트워크의 가장 중요한 차이점은 자산을 받는 방식에 있습니다.

스텔라에서 지갑은 새로운 토큰이 입금되기 전에 이를 명시적으로 승인해야 합니다. 예를 들어, 처음으로 USDC를 받으려면 스텔라 계정은 USDC 발행자와의 트러스트라인(trustline)을 생성해야 합니다. 트러스트라인을 생성하려면 해당 원장 항목을 확보하기 위해 0.5 XLM을 예치금(reserve)으로 잠금 설정해야 합니다.

원래는 기존 트러스트라인이 없는 주소로 토큰을 보내면 트랜잭션이 단순히 실패했습니다. 전액이 보낸 사람에게 그대로 남았습니다.

스텔라 프로토콜 15부터는 클레임 가능 잔액(claimable balances)이 도입되면서 이 동작이 변경되었습니다. 목표는 사용성을 개선하고 "준비되지 않은" 계정에도 토큰을 보낼 수 있도록 하는 것이었습니다.

우리가 USDC 트러스트라인이 없는 클라이언트의 스텔라 주소로 USDC를 보냈을 때, 프로토콜은 트랜잭션을 다음과 같이 처리했습니다.

  • 우리 지갑에서 자금이 인출되었습니다.
  • 자금이 수취인의 잔액으로 입금되지 않았습니다.
  • 1,000 USDC는 클레임 가능 잔액 항목(Claimable Balance Entry)이라는 특수 원장 객체에 보관되었습니다.

즉, 자금은 원장에 기록되어 누군가가 명시적으로 클레임(청구)하기를 기다리며 그곳에 머물러 있었습니다. 누가 클레임할 수 있는지는 트랜잭션 조건에 따라 정의됩니다. 기본적으로 보낸 사람과 받는 사람 모두 클레임 자격이 있습니다.

그런데 받는 사람의 계정에 있는 1 XLM은 어디서 온 것일까요? 답은 우리에게서 온 것입니다. 우리가 의도적으로 보낸 것은 아니었지만 말입니다.

이러한 트랜잭션이 실제로 어떻게 처리되는지는 다음과 같습니다.

1단계. 보낸 사람이 USDC 전송을 시작하고 다음 매개변수로 트랜잭션에 서명합니다.

  • 작업(Operation): payment(결제)
  • 자산(Asset): USDC
  • 대상(Destination): 스텔라 네트워크에 아직 존재하지 않는 주소
  • 금액: 1,000 USDC

이 단계에서 보낸 사람은 트랜잭션 처리에 필요한 XLM 금액을 지불하는 데 암묵적으로 동의합니다. 실제로 스텔라의 트랜잭션 수수료는 대개 무시할 수 있는 수준이기 때문에 사용자들은 이 세부 사항에 거의 주의를 기울이지 않습니다.

2단계. 스텔라는 작업이 실행될 수 있는지 확인하기 위해 일련의 기본 유효성 검사를 수행합니다.

  • 받는 사람 계정이 존재합니까? 아니요
  • 전송된 자산이 기본 토큰인 XLM입니까? 아니요
  • 받는 사람이 보내는 자산에 대한 트러스트라인을 가지고 있습니까? 아니요

이러한 조건들을 종합하면 직접 결제는 불가능하다는 의미입니다.

3단계. 스텔라는 트랜잭션을 즉시 거부하는 대신 클레임 가능 잔액 메커니즘을 활성화합니다.

이를 위해 네트워크는 다음을 수행합니다.

  • 1,000 USDC에 대한 클레임 가능 잔액을 생성합니다.
  • 누가 이를 클레임할 수 있는지 지정합니다(보낸 사람과 받는 사람 모두).

그러나 받는 사람이 잠재적 클레임 대상자로 등재되려면 받는 사람의 계정이 네트워크에 존재해야 합니다. 트랜잭션 당시 계정은 받는 사람의 지갑 앱 내부에만 존재했습니다. 스텔라 원장에는 아직 생성되지 않은 상태였습니다.

이러한 조건 하에서 계정은 동일한 트랜잭션의 일부로 생성됩니다.

앞서 언급했듯이 스텔라에서 계정 생성은 무료가 아닙니다. 계정 생성에 필요한 1 XLM은 작업 소스, 즉 보낸 사람의 잔액에서 차감됩니다.

  • 보낸 사람에게서 1 XLM이 차감됩니다.
  • 그 1 XLM이 새로 생성된 계정으로 입금됩니다.
  • 이제 계정은 활성 상태로 간주됩니다.

이 1 XLM은 트랜잭션 실행 비용의 일부이며, 우리가 트랜잭션에 서명할 때 암묵적으로 수락한 비용입니다. 기술적으로는 받는 사람의 잔액에 남게 되지만, 받는 사람은 이를 실제로 사용할 수 없습니다. 특히, 받는 사람은 USDC 트러스트라인을 만들고 자금을 클레임하기 위해 그 금액의 절반을 떼어낼 수 없습니다.

받는 사람의 계정이 이미 네트워크에 존재하지만 단순히 USDC 트러스트라인만 없었다면, 추가적인 1 XLM은 필요하지 않았을 것입니다. 그 경우 받는 사람은 지갑에서 눈에 띄는 변화를 전혀 보지 못했을 것입니다.

문제를 해결한 방법

첫 번째 단계는 받는 사람이 USDC 자산에 대해 changeTrust 작업을 서명하는 것이었습니다. 이 작업이 성공하려면 지갑에 최소 0.5 XLM의 가용 잔액이 있어야 하며, 트러스트라인이 생성될 때 이 금액은 예치금으로 잠깁니다.

우리 클라이언트는 가용 XLM이 전혀 없었습니다. 더 정확히 말하면, 계정에 있는 유일한 XLM은 거래 결과로 나타난 1 XLM이었지만, 그 금액은 기본 예치금으로 완전히 잠겨 있어 사용할 수 없었습니다.

그래서 우리는 간단한 우회 방법을 제안했습니다. 사용 가능한 암웅화폐 소액을 루멘으로 교환하는 것이었습니다. 그렇게 한 후, 클라이언트는 마침내 지갑에서 트러스트라인을 생성하기에 충분한 가용 XLM을 확보하게 되었습니다.

USDC 트러스트라인이 설정되자 사용자는 1,000 USDC를 클레임할 수 있었습니다. 이 사례는 관련된 모든 당사자에게 해피엔딩으로 마무리되었습니다.

이것은 일회성 사례일까요?

우리 클라이언트가 겪은 상황은 유일한 사례가 아닙니다. 그리고 그는 자신이 완전히 통제할 수 있는 비수탁형 지갑을 사용하고 있었다는 점에서 정말 운이 좋았습니다. 그것이 바로 문제를 비교적 쉽게 해결할 수 있게 만든 핵심이었습니다.

불과 며칠 전 다른 사용자와 관련된 매우 유사한 사례를 읽었기 때문에 이 이야기가 떠올랐습니다. 금액은 1,000 USDC로 정확히 같았지만, 상황 때문에 문제를 해결하기가 훨씬 더 어려웠습니다.

3일 전, Uphold의 CEO Simon McLoughlin의 최신 포스트 아래 링크드인 댓글이 달렸는데, 한 Uphold 사용자가 다음과 같은 상황을 설명했습니다.

  • 그는 스텔라 네트워크를 통해 Uphold 거래소에 USDC를 입금하려고 시도했습니다.
  • 실수로 Uphold 인터페이스에서 USDC 입금 주소 대신 XLM 입금 주소를 선택했습니다.
  • 우리 사례와 마찬가지로, 그는 1,000 USDC 대신 1 XLM이 자신의 잔액에 입금된 것을 보았습니다.
  • 수석 전문가를 포함한 Uphold 고객 지원팀은 단호하게 답변했습니다. “우리는 귀하의 토큰을 가지고 있지 않습니다. 우리는 그것들을 볼 수 없습니다. 우리는 그것들을 돌려줄 수 없습니다.”
  • 자금이 전송된 지갑은 이러한 시나리오를 위한 클레임(Claim) 기능을 제공하지 않았습니다. 아마도 기능이 제한된 수탁형(custodial) 지갑이었을 것이며, 다른 CEX일 가능성도 있습니다.
  • 모든 표준 채널을 동원한 후에도 해결되지 않자 사용자는 CEO에게 직접 문제를 제기하려고 시도했습니다.

증상이 얼마나 일치하는지 고려할 때, 근본적인 문제는 정확히 동일하다고 믿습니다.

Uphold는 요청에 따라 입금 주소를 생성하는 것으로 보이며, 사용자가 자금을 보낸 XLM 주소는 아직 스텔라 원장에 생성되지 않았을 가능성이 큽니다. 그것은 왜 트랜잭션 결과 계정에 1 XLM이 나타났는지를 설명해 줍니다. 그러나 1,000 USDC는 나타나지 않았고, 상황을 해결하려면 Uphold가 해당 주소에 대해 수동으로 USDC 트러스트라인을 생성해야 합니다.

왜 고객 지원팀은 도움을 거부했을까요?

제 추측으로는 보안 아키텍처 때문입니다. 암호화폐를 다루는 모든 기업은 지갑 보안에 막대한 투자를 합니다. Uphold는 예상되는 표준 입금 흐름 이외의 트러스트라인 생성 프로세스를 구현하지 않은 것으로 보입니다. 모든 내부 보안 요구 사항을 충족하면서 소급하여 이러한 기능을 추가하는 것은 거래소 입장에서 사용자가 잃어버린 1,000 USDC보다 훨씬 더 많은 비용이 들 수 있습니다.

오늘 현재 CEO의 포스트 아래에 달린 댓글은 삭제되었습니다. 사용자 본인이 삭제한 것인지 거래소 측에서 삭제한 것인지는 알 수 없습니다. 문제가 해결되어 사용자가 스스로 댓글을 삭제했기를 바랍니다. 하지만 만약 Uphold가 댓글을 삭제했다면, 그것은 우려스러운 신호가 될 것입니다.

이러한 문제들은 조용히 묻혀서는 안 됩니다. 오히려 다른 사용자들이 같은 실수를 반복하지 않도록 주의를 환기시켜야 합니다. 그것이 제가 우리 이야기와 Uphold 사용자의 이야기를 여기서 공유하기로 결심한 이유입니다.

클레임 가능 잔액의 덫에 빠졌을 때 대처법

보낸 사람이나 받는 사람의 지갑 중 적어도 하나가 자금 소유자에 의해 완전히 통제되는 경우 상황은 비교적 간단합니다.

  • 받는 사람은 지갑에 소량의 XLM을 추가하고 필요한 트러스트라인을 생성한 다음 클레임 가능 잔액을 클레임할 수 있습니다.
  • 보낸 사람은 단 한 번의 작업으로 클레임 가능 잔액을 회수(reclaim)한 다음, 토큰을 받을 준비가 제대로 된 다른 수취인 주소로 자금을 다시 보낼 수 있습니다.

그러나 CEX 지갑과 같은 수탁형 지갑이 관련된 경우 상황은 훨씬 더 복잡해집니다.

수탁 서비스는 사용자의 실수를 바로잡기 위해 지갑 인프라에 수동으로 개입할 의무가 없으며, 특히 그렇게 하는 것이 추가적인 운영 또는 보안 위험을 초래하는 경우 더욱 그렇습니다. 결과적으로 자금이 기술적으로 원장에 여전히 존재하더라도 도움을 거절당할 수 있습니다.

그럼에도 불구하고 여전히 시도해 볼 가치는 있습니다. 거래소가 개인 키를 제어하는 한, 자금을 복구할 가능성이 있습니다.

만약 이런 상황에 처하게 된다면:

  • 사건을 최대한 가시화하십시오. X, LinkedIn 또는 Medium에 이에 대해 쓰십시오. 공식 계정을 태그하십시오. 평판은 1,000달러 이상의 가치가 있습니다.
  • 엔지니어의 언어로 말하십시오. 요청은 다음과 같이 들려야 합니다. “저의 트랜잭션 결과 ID [...]인 클레임 가능 잔액 항목(Claimable Balance Entry)이 생성되었습니다. 자금은 원장에 기록되어 있으며 귀하의 지갑 개인 키로 제어됩니다. 엔지니어링 또는 DevOps 팀에 이 티켓을 전달하여 소유권을 확인하고 claim_claimable_balance 작업을 수행할 수 있도록 요청드립니다. 복구 수수료를 지불할 의사가 있습니다.”
  • 커뮤니티에 도움을 요청하십시오. 스텔라 생태계에는 긴밀하게 연결된 개발자 커뮤니티가 있습니다. 때로는 Discord나 다른 플랫폼에서의 공개 토론이 주요 거래소의 기술 팀을 개인적으로 아는 사람들의 관심을 끌기도 합니다.

궁극적으로 가장 좋은 해결책은 여전히 예방입니다. 스스로 자산을 관리하는 것(self-custodian)은 많은 장점이 있지만, 그만큼 높은 책임감이 따릅니다. 암호화폐를 보낼 때 세부 사항에 주의를 기울이는 것이 중요합니다. 어떤 실수는 저지르기는 쉽지만 수정하기는 훨씬 더 어렵기 때문입니다.