티스토리 뷰

아직 송금은 따라하지 말아주세요. 제대로 입금되었는 지 확인이 안되었습니다!!!!!

12시 05분 13초 입금 확인하였습니다. 이제 마음놓고 따라해주시면 됩니다!!!(도망)

1. 들어가기에 앞서

  1. 이 글은 리플 공식 개발자 가이드 를 보고 제가 직접 간단한 코드를 만들어보면서 작성하였음을 알려드립니다.
  2. 이 글에서는 기술로써의 리플(ripple)과 코인으로써의 리플(XRP)이라는 단어를 혼용해서 사용합니다. 혼용 중 헷갈리더라도 너그럽게 이해해주세요.
  3. 저는 장난식으로 썼지만 제가 참고한 공식 개발 사이트는 엄격근엄진지합니다. 오해 없으시길 바랍니다.

2. 이 글의 범위

  • 리플 지갑의 정체 확인
  • rippleAPI 사용

3. 참고자료

4. 리플 지갑(Account)의 생김새

리플 지갑이 어떻게 생겨먹었는지에 대해 이야기해보겠습니다.

rsG1sNifXJxGS2nDQ9zHyoe1S5APrtwpjV

이건 제 빗썸의 리플 지갑(계정)의 주소입니다. 리플 지갑의 주소는 다음과 같은 형식을 따라갑니다.

  • 58진법
    • 숫자 0, 알파벳 대문자 O, 알파벳 대문자 I, 알파벳 소문자 l(L이에요)을 제외한 영문숫자
    • 이 58진법 숫자 배열은 아스키 코드 순서와 다릅니다. 자세한 값은 4.4.에 나와있습니다.
  • 25 ~ 35자
  • r로 시작하는 문자열
  • 대소문자 구분
  • 4바이트 체크섬 포함

이 지갑을 rippled 서버에 검증 요청을 하면 다음과 같이 값이 나옵니다.

{
    "result": {
        "account_data": {
            "Account": "rsG1sNifXJxGS2nDQ9zHyoe1S5APrtwpjV",
            "Balance": "277586591522520",
            "Flags": 8519680,
            "LedgerEntryType": "AccountRoot",
            "OwnerCount": 0,
            "PreviousTxnID": "1376113C1A153083A0A26BCF18FF6D5CA80946EE58E4B1BC0AB9BB8F8D3928E1",
            "PreviousTxnLgrSeq": 35471091,
            "Sequence": 118995,
            "index": "8FCC397A28CE040133BC71CDF5C20C5693AD9EB42197E487AF8687E3A0A79DAA"
        },
        "ledger_hash": "1405E45AB0EE7426578D2E77251787A32840E12431DD0321A31FBE70559DCD64",
        "ledger_index": 35471092,
        "status": "success",
        "validated": true
    }
}

여기 나오는 값에 대한 정확한 정보는 다음 문서에 있습니다.

결과를 번역기를 돌려가면서 자체 해석해보니 다음과 같이 됩니다.

  • account_data.Account는 식별 주소를 의미합니다.
  • account_data.Balance는 해당 계정에서 드롭된 XRP의 수를 문자열로 나타낸 것입니다.
    • 1 XRP는 1,000,000 입니다.
    • 근데 뭔가 좀 이상하네요. 2억개라니… 저 그만큼 없는데… 이 부분에 대한 내용은 4.2. 에 있습니다.
  • account_data.Flags는 이 계정에서 사용할 수 있는 부울 비트맵이라고 합니다.
  • account_data.LedgerEntryType은 0x0061 = 'AccountRoot' 입니다.
  • account_data.OwnerCount는 해당 계정의 원장의 갯수를 의미합니다.
  • account_data.PreviousTxnID는 가장 최근에 수정된 트랜잭션의 ID입니다.
  • account_data.PreviousTxnLgrSeq는 위의 트랜잭션의 ledger index 라고 합니다.
  • account.Sequence는 이 계정에 대한 다음 유효한 트랜잭션의 시퀀스라고 합니다. 초기 값은 1이며 트랜잭션이 발생할 때마다 1씩 증가한다고 합니다.

지갑 이외의 정보는 다음에 계속 이야기하도록 하겠습니다.

4.1. 특별한 주소값

리플에는 특별히 할당된 주소값이 있습니다.

Address Name Meaning Black Hole?
rrrrrrrrrrrrrrrrrrrrrhoLvTp ACCOUNT_ZERO 이 값은 변환하면 0이며, XRP의 발행인 주소로 사용됩니다. y
rrrrrrrrrrrrrrrrrrrrBZbvji ACCOUNT_ONE 이 값은 변환하면 1이며, 리플상태에서 placeholder로 사용됩니다. y
rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh 기원 계정 스탠드 얼론 등의 이유로 rippled가 처음부터 원장을 시작할 때 모든 리플을 보유합니다. 마스터 계정 같은겁니다. n
rrrrrrrrrrrrrrrrrNAMEtxvNvQ 블랙홀 구 리플에서 이름을 예약하기 위해 이 값을 썼다고 합니다. n
rrrrrrrrrrrrrrrrrrrn5RM1rHd NaN 구 리플에서 NaN으로 생성된 주소였습니다. n

4.2. 지갑의 영속성

생성된 지갑은 영원히 존재한다고 합니다. 이전 트랜잭션을 두 번 처리할 수 없기 때문이라고 합니다.

또한 다른 암호화폐와 달리 새 지갑이 생길 때마다 리플 원장의 크기가 점점 커진다고 합니다. 따라서 기관에서는 소스태그와 데스티네이션 태그를 이용하여 소수의 계정만 써서 고객들을 구분할 수 있다고 합니다.

이제 저 위의 2억개의 리플의 미스테리가 풀렸네요. 거래소 등은 리플의 제약사항으로 인해 몇 개의 지갑으로 돌려막기 하는겁니다. 좀 찝찝해도 지갑 생성으로 리플이 무거워진다니 이해해줍시다.

4.3. 트랜잭션 기록

트랜잭션은 트랜잭션의 해시와 원장 인덱스들이 연결된 "thread(스레드)"로 추적됩니다. AccountRoot(아까 그 account_data)에 가장 최근에 바뀐 트랜젝션의 메타정보가 들어있습니다. 또한 AccountRoot 값을 직접 변경하는 것들이 있는데 이 내용은 아래에 있습니다.

  • 지갑의 Sequence 번호가 수정됩니다. 본문에서 되게 어렵게 써놨는데(미천한 영어실력으로 해석을 못하겠습니다 ㅠㅠ) 쉽게 말해 트랜잭션이 발생할 때마다 Sequence와 Balance가 바뀐다는 겁니다.
  • XRP Balance는 거래뿐 아니라 PaymentChannelClaim과 EscrowFinish 등의 트랜잭션으로도 바뀝니다.

제 영어 실력이 딸려서 해석이 힘드네요. 직접 해석 부탁드립니다. 링크

4.4. 주소 변환 방법

가이드는 다음 링크입니다. https://ripple.com/build/accounts/#address-encoding

리플 원장의 주소는 base58로 인코딩되어있습니다. 아래 58자가 그 사전입니다.

rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz

따라서 지갑 유효성을 확인할 정규식은 다음과 같습니다.

^r[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{24,34}$

리플 지갑의 주소가 생성되는 과정은 가이드의 그림을 참고하시기 바랍니다.
https://cdn.ripple.com/wp-content/uploads/2017/05/key-address-rels.png
(리플 CDN에서 가져왔습니다. 짤릴 수 있습니다.)

리플 사이트에서는 node.js에서 실행 가능한 주소 변환 코드와 그 설명법을 배포하고 있습니다. 이걸 사용해서 괜히 요청 안하고 직접 유효성을 검증할 수 있습니다. 는 귀찮으니 그냥 AccountInfo API를 씁시다 ㅋㅋㅋ

4.5. 그래서 내 지갑엔 얼마가 있는거야?

거래소 명의의 내 지갑에 얼마가 있는 지 알아보기 위해 트랜잭션 리스트를 조회해보려고 했는데… 에러가 발생하는군요. 이건 거래소를 믿어야 할 거 같습니다.

5. rippleAPI 사용

전편에서 rippled 에 접속하는 방법으로 Websocket 및 JSON-RPC에 대해 알아봤습니다. 다른 언어를 사용하여 구현한다면 위의 방법을 사용하겠지만 지금 개발환경이 node.js 라면 이야기는 달라집니다. 리플 사에서는 node.js에서 쉽게 rippled에 접근할 수 있는 라이브러리를 제공해주고 있습니다. ripple-lib인데 이에 대해 알아보도록 하겠습니다. 이 라이브러리는 node.js 6.9.0 이상을 권장합니다. ES6을 사용하기 때문이죠.

5.1. 설치 및 예제코드 실행

아까 지갑 보기를 위의 rippleAPI를 사용해서 해보려고 합니다.

  • 5.1.1. ripple-lib 설치
npm install --save ripple-lib
  • 5.1.2. 보일러플레이트 구하기:
    리플 사에서는 가이드 코드를 제공해줍니다. 얘를 써보죠.
const RippleAPI = require('ripple-lib').RippleAPI;

const api = new RippleAPI({
  server: 'wss://s1.ripple.com' // Public rippled server hosted by Ripple, Inc.
});
api.on('error', (errorCode, errorMessage) => {
  console.log(errorCode + ': ' + errorMessage);
});
api.on('connected', () => {
  console.log('connected');
});
api.on('disconnected', (code) => {
  // code - [close code](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) sent by the server
  // will be 1000 if this was normal closure
  console.log('disconnected, code:', code);
});
api.connect().then(() => {
  /* insert code here */
}).then(() => {
  return api.disconnect();
}).catch(console.error);

new RippleAPI에 들어갈 옵션은 가이드에서 직접 확인하시기 바랍니다.

  • 5.1.3. 코드 삽입하기:
    저 밑의 /* insert code here */ 부분에 다음과 같이 넣어줍시다. 전 node.js 8 이상이라 async/await으로 쓰겠습니다. 밑의 코드는 바로 Ctrl CV로 실행 가능합니다.
const RippleAPI = require('ripple-lib').RippleAPI;

const api = new RippleAPI({
  server: 'wss://s1.ripple.com' // Public rippled server hosted by Ripple, Inc.
});
api.on('error', (errorCode, errorMessage) => {
  console.log(errorCode + ': ' + errorMessage);
});
api.on('connected', () => {
  console.log('connected');
});
api.on('disconnected', (code) => {
  // code - [close code](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) sent by the server
  // will be 1000 if this was normal closure
  console.log('disconnected, code:', code);
  process.exit(0);
});
api.connect().then(async () => {
  /* insert code here */
  let transactions = await api.getAccountInfo('rsG1sNifXJxGS2nDQ9zHyoe1S5APrtwpjV');
  console.log(transactions);
  return true;
}).then(() => {
  return api.disconnect();
}).catch(console.error);

결과로 다음과 같은 값을 받을 수 있습니다.

{ sequence: 119066,
  xrpBalance: "278185212.988075",
  ownerCount: 0,
  previousAffectingTransactionID: "5F457D1CEFCEC622F27710D2A780DEBFD947CA8E2BC87953BBE8229D688978ED",
  previousAffectingTransactionLedgerVersion: 35473205 }

5.2. Websocket, JSON-RPC로 불러온 값과 달라요

당장 쓸 데 없는 값을 rippleAPI에서 걸러내는 거 같습니다. 그냥 그러려니 합시다. 참고용으로 어제 썼던 웹소켓용 코드와 그 결과값을 쓰겠습니다.

code

const WebSocket = require('ws');

const ws = new WebSocket('wss://s1.ripple.com', {
  perMessageDeflate: false
});

ws.on('open', function() {
    console.log('connected');

    let sendData = {
        "id": Math.floor(Math.random() * 10000),
        "command": "account_info",
        "account": "rsG1sNifXJxGS2nDQ9zHyoe1S5APrtwpjV",
        "strict": true,
        "ledger_index": "validated"
      };
    ws.send(JSON.stringify(sendData));
});

ws.on('message', function(data) {
    console.log(JSON.parse(data));
    ws.close();
});

result

{ id: 4888,
  result:
   { account_data:
      { Account: "rsG1sNifXJxGS2nDQ9zHyoe1S5APrtwpjV",
        Balance: "278160977322871",
        Flags: 8519680,
        LedgerEntryType: "AccountRoot",
        OwnerCount: 0,
        PreviousTxnID: "3DAF30ACD310F23D5DF713897B940317EC87157BEB1A703C2B7C30778C0EBCA0",
        PreviousTxnLgrSeq: 35473287,
        Sequence: 119071,
        index: "8FCC397A28CE040133BC71CDF5C20C5693AD9EB42197E487AF8687E3A0A79DAA" },
     ledger_hash: "9CEFE5321CF69617BC20CBF309FCCB4E5C42BA6F66F2BEA6E41FB9D4DC295211",
     ledger_index: 35473312,
     validated: true },
  status: "success",
  type: "response" }

6. 타 계좌에 리플(XRP) 송금하기

그동안 삘 나서 급히 쓴 거라 순서가 엉망이라 죄송합니다. 이제서야 제일 중요한 리플 송금하기를 다루게 되네요.

이 파트는 다음 글을 참고하였습니다.

가이드와 순서를 다르게 한 것에 대해 양해 부탁드립니다.

주의! 송금 시 수수료가 부과됩니다. 거래 시마다 0.00001 XRP(2018.01.01. 오후 12:00 빗썸시세 2762원 기준 0.02762원이 수수료입니다.) 가 빠져나가며 이는 영원히 사라집니다. 이를 감안하고 실행하시기 바랍니다.

6.1. 거래(트랜잭션) 흐름

RippleAPI를 이용한 거래의 순서는 다음과 같습니다.

  1. Prepare - 승인되지 않은 거래를 위의 순서에 따라 생성합니다. 다음 함수들을 이용하여 생성할 수 있습니다.
    • preparePayment
    • prepareTrustline
    • prepareOrder
    • prepareOrderCancellation
    • prepareSettings
    • prepareEscrowCreation
    • prepareEscrowCancellation
    • prepareEscrowExecution
  2. Sign - 위의 생성된 트랜잭션에 로컬에서 서명을 하고 트랜잭션 ID를 저장합니다. 서명이 여러 개 필요한 경우에는 combine 함수를 이용하여 회수할 수 있습니다.
  3. Submit - 위의 승인된 트랜잭션을 리플 서버에 제출합니다.
  4. Verify - getTransaction 을 이용하여 트랜잭션을 확인합니다. 제대로 제출했는데도 뻑나는 경우가 있어서 확인해보라고 합니다.

6.2. 거래(트랜잭션) 종류

트랜잭션의 종류는 다음과 같습니다.

종류 설명
payment payment는 한 계정에서 타인의 계정으로 금액을 보내는 걸 의미합니다.
order order는 제한된 주문을 생성합니다.
orderCancellation orderCancellation은 order를 취소할 때 사용합니다.
trustline trustline은 두 계정간 신뢰 라인을 생성 및 수정합니다.
settings settings은 XRP 원장의 계정 설정을 수정합니다.
escrowCreation 에스크로를 생성합니다. 에스크로는 3부의 가짜 쇼핑몰 만들기 편에서 진행하겠습니다.
escrowCancellation 에스크로를 취소합니다.
escrowExecution 에스크로를 승인합니다.

6.3. 거래 수수료

수수료는 위에 적혀있는 대로입니다. 물론 트랜잭션 부하 상태에 따라 더 부과될 수 있다고 합니다. getFee 함수를 사용하여 정확한 수수료 값을 확인할 수 있다고 합니다.

6.4. 거래 지침

어떻게 거래를 실행하는 지에 대한 지침입니다.

Name Type Description
fee value (Optional) 트랜잭션에 지불해야 하는 정확한 수수료 값입니다.
maxFee value (Optional) 트랜잭션에 지불해야 하는 최대 수수료입니다.
maxLedgerVersion integer, null (Optional) 트랜잭션을 포함할 수 있는 최대 버전입니다. 기본값은 3 혹은 그 이상입니다.
maxLedgerVersionOffset integer (Optional) 현재 검증된 원장 버전에서 위의 버전까지의 오프셋입니다.
sequence sequence (Optional) 해당 거래를 위한 계정의 시퀀스 시작 번호입니다.
signersCount integer (Optional) 거래 승인에 필요한 승인자 수입니다.

리플 재단에서는 나중에 실패한 트랜잭션이 성공되지 않는다는 걸 신속하게 증명하기 위해 maxLedgerVersion 값을 지정하는 것을 추천합니다.

6.5. 트랜잭션 ID

"F4AB442A6D4CBB935D66E1DA7309A5FC71C7143ED4049053EC14E3875B0CF9BF"
트랜잭션 ID는 유니크한 64비트 hex값입니다. 해당 ID는 강력한 해시 함수에 의해 생성됩니다. 이 트랜잭션 ID를 보기 위해 getTransaction 함수를 사용할 수 있습니다.

6.6. 트랜잭션 메모

트랜젝션에 매모를 할 수 있습니다. 귀차니즘이 발동한 관계로 공식 문서를 참고하시기 바랍니다.

하지만 빗썸 지갑으로는 저희가 해당 기능을 사용할 수 없다는 것입니다. 따라서 지갑을 생성해서 송금하는 과정을 진행해보도록 하겠습니다.

7. 지갑 생성하기

그냥 막 배워가면서 쓰다보니 송금을 지갑보다 먼저 다루는 삽질을 저지르고 말았습니다. 그래서 지갑을 생성하는 방법을 이제야 쓰게 되네요. 이 점에 대해서는 죄송합니다…

이 파트는 다음 글을 참고하였습니다.

리플 지갑 주소를 생성합니다. 이 함수는 오프라인에서 사용할 수 있습니다. 위에서 링크만 준 내용을 저희가 바로 쓰기엔 무리가 있어서 리플의 API를 이용하도록 하겠습니다. (결정적으로 비밀키를 직접 생성해가면서 테스트하기엔 좀 그렇습니다…)

다음 코드를 이용하여 주소와 secret을 얻을 수 있습니다. 이 값은 반드시 다른 곳에 메모합시다. 잊어버리면 비트코인 하드디스크를 쓰레기장에 버린 사람처럼 피눈물 흘립니다.

const RippleAPI = require('ripple-lib').RippleAPI;

const api = new RippleAPI();
const address = api.generateAddress();

console.log(address);

해당 API는 오프라인에서 사용 가능합니다. 그래서 api 선언할 때 아무것도 넣지 않았습니다. 거래를 승인하기 전까지는 아무 영향을 갖지 않으나 실제로 거래 승인 + 제출 + 완료가 된 이후에는 공식으로 등록이 되고, 블록체인의 용량이 늘어나게 됩니다. 그러므로 거래가 1회 이상 일어난 지갑은 조심조심 보관하셔야 합니다.

  • 그러다보니 몇몇 거래소에서는 20 XRP 이상 보유해야만 계좌를 생성해주거나 하기도 합니다.

위의 API로 생성한 address를 getAccountInfo API 를 사용하여 확인하면 없다고 뜹니다. 아래 과정들을 통해 거래를 하고 나면

7.a. 지갑에 돈을 넣어보자

빗썸 지갑에서 저희가 위의 기능으로 생성한 지갑에 돈을 넣어보도록 하겠습니다. 빗썸에서 리플 송금하기를 눌러서 값을 입력하고(아직은 데스티네이션 태그를 넣지 않았으니 미사용에 체크) 송금버튼을 눌러봅시다. 그 다음 거래내역을 보면 다음과 같이 나옵니다.

거래번호: [692E2AE4EA768ABAF6F591D1E5CD672985C7DF218606F1B110FA12596E1E39AD](https://xrpcharts.ripple.com/#/transactions/692E2AE4EA768ABAF6F591D1E5CD672985C7DF218606F1B110FA12596E1E39AD) 
주소: rfct6fDdS9mjERh7n3byyyo9ysLDxjaAUm
금액: -22.0XRP //더러운 놈들... 최소 21개부터 송금이 가능하다고 하고 지들 멋대로 수수료 1씩 받아먹습니다.

이 요청이 끝나고 저희가 생성한 주소를 getAccountInfo API에 넣어봅시다. 코드는 위에서 이야기했을테니 결과만 봅시다.

{ sequence: 1,
  xrpBalance: '21',
  ownerCount: 0,
  previousAffectingTransactionID: '692E2AE4EA768ABAF6F591D1E5CD672985C7DF218606F1B110FA12596E1E39AD',
  previousAffectingTransactionLedgerVersion: 35964571 }

8. 다른 지갑에 돈을 보내보자

#6에서 적었던 내용을 이제 진짜로 해보도록 합시다.

8.1. payment 생성하기(prepare)

preparePayment(address: string, payment: Object, instructions: Object): Promise<Object>
payment 트랜잭션을 생성해보도록 합시다. preparePayment API를 사용하면 됩니다. 예시와 함께 보도록 합시다.

let transcation = await api.preparePayment('rfct6fDdS9mjERh7n3byyyo9ysLDxjaAUm', {
  "source": {
    "address": "rfct6fDdS9mjERh7n3byyyo9ysLDxjaAUm",  //출발지 주소
    //"tag" : unsigned 32bit integer,
    "maxAmount": {
      "value": "1",  //몇 개 보낼거임?
      "currency": "XRP" //돈의 단위, XRP인 경우에는 counterparty 필드를 생략해도 됩니다.
    }
  },
  "destination": {
    "address": "rsG1sNifXJxGS2nDQ9zHyoe1S5APrtwpjV",  //도착지 주소(다시 빗썸으로....)
    "tag" : 1000763131, //도착지의 destination tag 입니다. 제 거입니다. 기부해주시면 잘 쓰겠습니다 ㅎㅎ
    "amount": {
      "value": "1",
      "currency": "XRP"
    }
  }
});

결과는 다음과 같이 나옵니다

{ txJSON: '{"TransactionType":"Payment","Account":"rfct6fDdS9mjERh7n3byyyo9ysLDxjaAUm","Destination":"rsG1sNifXJxGS2nDQ9zHyoe1S5APrtwpjV","Amount":"1000000","Flags":2147483648,"DestinationTag":1000763131,"LastLedgerSequence":35965715,"Fee":"12","Sequence":2}',
  instructions: { fee: '0.000012', sequence: 2, maxLedgerVersion: 35965715 } }
{ txJSON: '{"TransactionType":"Payment","Account":"rfct6fDdS9mjERh7n3byyyo9ysLDxjaAUm","Destination":"rsG1sNifXJxGS2nDQ9zHyoe1S5APrtwpjV","Amount":"1000000","Flags":2147483648,"DestinationTag":1000763131,"LastLedgerSequence":35965723,"Fee":"12","Sequence":2}',
  instructions: { fee: '0.000012', sequence: 2, maxLedgerVersion: 35965723 } }

txJSON은 해당 트랜잭션의 JSON 텍스트입니다. insturctions는 수수료, 시퀀스, 레저 버전이 나옵니다. sequence, maxLedgerVersion은 사인, 제출에 참조할 가능성이 높습니다. 함수의 3번째 파라미터는 instructions인데 오프라인에서 함수를 사용할 경우 직접 명시해줘야 한다고 합니다.

그리고 제가 일부러 2번 넣었습니다. txJSON의 LastLedgerSequence하고 instructions의 maxLedgerVersion이 바뀐 걸 볼 수 있습니다. 수수료 정책이 바뀌면 txJSON의 Fee와 instructions의 fee도 바뀌겠죠. sequence의 변화는 #8을 몇 번 더 해서 확인해보도록 하겠습니다.

아, 그리고 sequence가 2인 이유는 제가 송금을 실제로 몇 번 해봤기 때문입니다. 테스트하면서 주소 입력을 잘못하는 바람에 제 1리플 + 수수료 0.000012가 날아가는 대참사가 있었습니다. ㅜㅜ

8.2. 거래 승인하기(sign)

sign(txJSON: string, secret: string, options: Object): {signedTransaction: string, id: string}
아직 승인받지 않은 거래에 사인하는 과정입니다. txJSON을 secret을 이용하여 강력한 해시로 만든다고 보면 되겠습니다. secret은 아까 지갑 주소 생성할 때 같이 나왔던 값입니다. 이 값이 제일 중요하므로 숨기라고 무려 빨간색 으로 경고하고 있습니다. 역시나 예제로 보겠습니다.

let signedTransaction = api.sign(transaction.txJSON, accountInfo.secret);

세번째 인자로 options가 있는데 다중 사인이 필요할 경우 사인자 정보를 넣을 수 있습니다. 결과는 다음과 같습니다.

{ signedTransaction: '120000228000000024000000022E3BA66EFB201B0224CB386140000000000F424068400000000000000C732103514F098A2140B4BC0E8A1926519A3596DF8728C97F0B8A33F2E50E99CA82CCD1744730450221008782B13A0F95DDD4548C155E2E74D1388CDD91001781BB833ECF25FBB4DC5D8302202CA80AC5A79C9DBF52E2D33E56F05155FBEEBD040204FC21C1A5B01C6B131D538114489A76982ACC1AB4F0AB13C2B540D6679A0C7B26831418F042D8514C49E75CF1E92F20970D6CAD3C04A6',
  id: '8D36F1AD354D5C031B24FB72E4A3C6351A9CD715AE5E710CCCA096190ED9AA67' }

id는 트랜잭션의 ID입니다. 트랜잭션 생성 이후 조회할 때 필요합니다. signedTransaction은 암호화된 트랜잭션 값입니다.

8.3. 트랜잭션 요청하기(submit)

submit(signedTransaction: string): Promise<Object>
이제 리플 서버에 해당 트랜잭션을 보내보도록 하겠습니다. payment는 낙장불입입니다!!! 신중을 기해 발송을 해보도록 합니다. 이 블로그를 쓰면서도 주소 입력을 잘못하는 바람에 1리플을 날려먹었습니다 ㅠㅠ. 코드는 다음과 같습니다.

let submitResult = await api.submit(signedTransaction.signedTransaction);

저도 겁나므로 여러 번 시행 안하고 한 번에 보도록 하겠습니다.

{ resultCode: 'tesSUCCESS',
  resultMessage: 'The transaction was applied. Only final in a validated ledger.' }

성공했습니다. ID는 결과에 나오지 않으니 아까 sign할 때 찾으셔야 합니다. 제가 방금 만들었던 트랜잭션ID는 3B69CA0C16B8ECC7E9FE131B00D9209B08F176922E74CFDA5902ED2C687C1667 입니다. 해당 URL 을 통해 확인해보도록 합시다.

실패해도 sequence가 늘어나고 수수료가 날아가니 요청은 신중하게 합시다!!!

8.3.b. resultCode가 'tecUNFUNDED_PAYMENT’인데요?

20리플 이상 갖고 있지 않은 계좌는 예치금 부족으로 송금이 제한된다고 합니다. 자세한 내용은 검색을 해봐야 할 거 같습니다.

8.4. 확인

빗썸과 같은 거래소의 경우, 개인지갑과는 달리 발송 후 확인하는데 꽤 시간이 걸립니다. 다른 코인은 잘 모르겠는데 리플의 경우, 한 지갑으로 여러 계정이 돌려막기를 하는 관계로…(destination tag 참고) 최근 트랜잭션 내역을 서버에서 조회하고 누구한테 입금이 되었는 지 확인하여 반영하는 과정 등이 있어서 오래 걸린다고 봅니다.

9. 결론

여기까지 간단하게 거래소 및 개인 지갑의 유효성 확인, 간단한 송금 절차를 밟아봤습니다. 이렇게 지갑을 생성하고, 돈을 입금받고, 송금하는 과정을 rippleAPI로 손쉽게(진짜?) 구현할 수 있습니다. 여기에 조금 더 배운 지식을 넣으면 근사한 프로그램이 하나 나오지 않을까 합니다. 다시 한 번 주의해야 할 점은 생성한 지갑의 secret은 꽁꽁 숨겨놔야 한다는 것입니다!

  • 10시 46분에 입금한 리플이 11시 11분 현재까지 처리가 안되고 있습니다. 빌어먹을 ㅠㅠ
  • 12시 05분에 처리되었습니다. 더럽게 오래 걸리네요. 결제는 거래소 끼고 하는 게 아닙니다 ㅠㅠ

10. 3부 혹은 그 이후에서 다룰 것

3부를 쓸 지 안쓸 지 모르겠지만 3부 뒤에는 다음과 같은 내용을 넣으려고 합니다.

감사합니다.

번외. 리플의 가격은.

리플의 가격을 실시간으로 알고싶으세요? 다음과 같은 방법으로 알아볼 수 있습니다.

  1. 각 거래소별 API 이용하기
  2. Coinmarketcap의 API 이용하기
  3. 리플의 DATA API 이용하기

여기서는 coinmarketcap의 API를 이용해보려고 합니다. 해당 기능의 장단점은 다음과 같습니다.

  • 장점은 거의 대부분의 코인의 가격을 다양한 통화(원, 달러 등)으로 볼 수 있다는 것입니다.
  • 단점은 거래소들의 평균값을 반영한지라 한국 프리미엄(이하 김프)를 알 수 없다는 것입니다.

API는 다음과 같습니다.
https://api.coinmarketcap.com/v1/ticker/ripple/?convert=KRW

딱히 인증절차는 없기 때문에 그냥 브라우저의 URL 바에 입력하고 누르면 됩니다. 그러면 다음과 같이 결과가 출력됩니다.

[
    {
        "id": "ripple", 
        "name": "Ripple", 
        "symbol": "XRP", 
        "rank": "3", 
        "price_usd": "1.5328", 
        "price_btc": "0.00012223", 
        "24h_volume_usd": "2145790000.0", 
        "market_cap_usd": "59379358101.0", 
        "available_supply": "38739142811.0", 
        "total_supply": "99993093880.0", 
        "max_supply": "100000000000", 
        "percent_change_1h": "-1.09", 
        "percent_change_24h": "0.38", 
        "percent_change_7d": "-22.16", 
        "last_updated": "1516502342", 
        "price_krw": "1636.18736", 
        "24h_volume_krw": "2290523535500.0000000000", 
        "market_cap_krw": "63384495804593"
    }
]

이름, 시총순위, 달러화, 사토시화, 원화 등등 다양한 정보가 나옵니다. 이를 참고하시면 좀 더 깔끔하게 볼 수 있지 않을까 합니다.

감사합니다.

댓글
댓글쓰기 폼