LoginSignup
2
1

More than 5 years have passed since last update.

Let's become an anchor on stellar network

Last updated at Posted at 2017-09-08

Let's become an anchor on stellar network

合同会社kumanoteの田中です。
今回は前回に引き続きstellarのサービス調査を行なったので、そちらのメモになります。

Anchorとは

Anchorとはstellar network上の資産をやりとりする機関のことです。

stellarではcreditという概念があります。例えばUSD creditとはUSDをstellar上でcredit化したものだといえます。
paypalなどのticketに近いという説明がありました。
100USD分paypalのticketを買うと、自由に100USD使えますよね。
paymoにたまっているお金もそんな感じですよね。
このような感じで、実際の通貨をcredit化することでやりとりを行います。

実際の通貨とstellar上のcreditを橋渡しをするという意味で anchor みたいです。

例)
1USD creditを1EUR creditに交換する規則を決めてサービスを展開すれば、両替所になります。
しかも、(法的にOKかはおいといて)場所を選ばないので海外送金サービスとしても使えます。

Bridge Serverを試してみる

Bridge Serverとはサービスとstellerネットワークの仲介役を行うServerのことっぽいです。
なぜ必要なのかはおいおい考えます。

事前準備

mysqlの準備

mysql dbを作ります。
環境汚したくないので、dockerで作ります。

version: "2"
services:
  mysql:
    image: mysql:5.6
    environment:
      MYSQL_ROOT_PASSWORD: stellar_bridge
      MYSQL_DATABASE: stellar_bridge
      MYSQL_USER: stellar_bridge
      MYSQL_PASSWORD: stellar_bridge
      TZ: JST
    volumes:
      - ./mysql/conf.d:/etc/mysql/conf.d
      - mysql-data:/var/lib/mysql
    container_name: mysql
volumes:
  mysql-data:
    driver: local

bridge serverの準備

  • これが公式で準備されているので、ありがたく使います。 中身細かくみたいですが、また後で
git submodule add https://github.com/stellar/bridge-server bridge-server

せっかくなのでDockerにのせます。

Dockerfile

FROM golang:1.8-alpine
MAINTAINER kumanote,LLC.

ENV SRC_DIR /usr/local/src/bridge-server
ENV DIST_DIR /usr/local/bin/bridge-server

RUN apk add --update --no-cache git g++ openssh-client bash
RUN go get github.com/constabulary/gb/...

ADD . ${SRC_DIR}/
ADD ./docker/ ${DIST_DIR}/

WORKDIR ${SRC_DIR}

RUN gb build && cp -f ./bin/bridge ${DIST_DIR}/bridge

WORKDIR ${DIST_DIR}

CMD ["/bin/bash"]

bridge.cfg前回同様2つのアカウントを作って対応します。

node console を立ち上げて以下たたけば作成されます。

(function() {
  var StellarSdk = require('stellar-sdk');
  var keypair = StellarSdk.Keypair.random();
  var request = require('request');
  request.get({
    url: 'https://horizon-testnet.stellar.org/friendbot',
    qs: { addr: keypair.publicKey() },
    json: true
  }, function(error, response, body) {
    if (error || response.statusCode !== 200) {
      console.error('ERROR!', error || body);
    }
    else {
      console.log('SUCCESS! You have a new account :)\n', {"Seed": keypair.secret(), "ID": keypair.publicKey()});
    }
  });
})();
port = 8001
horizon = "https://horizon-testnet.stellar.org"
network_passphrase = "Test SDF Network ; September 2015"
# We'll fill this in once we set up a compliance server
compliance = ""

# This describes the assets that can be sent and received.
# Repeat this section to add support for more asset types.
[[assets]]
code="KumaDollar"
issuer="GAOOFELY4CO2L4YWOVOEF2J233NF4XHVWTKFUUJU5QJO7UKJ2T2MBTR7"

[database]
type = "mysql"
url = "mysql://stellar_bridge:stellar_bridge@tcp(mysql:3306)/stellar_bridge"

[accounts]
# The secret seed for your base account, from which payments are made
base_seed = "SB5PJSVV3K62V3MSQGG6DKMNHR4BONGAEK5TTSMFPQCQZJE6RVHXWETN"
# The account ID that receives payments on behalf of your customers. In this
# case, it is the account ID that matches `base_seed` above.
receiving_account_id = "GATLCZMS3ITTVSCM2VB55KT2I7OHHXOWD2QM6O4BNK5LQTD2A3T7DINC"
# A secret seed that can authorize trustlines for assets you issue. For more,
# see https://stellar.org/developers/guides/concepts/assets.html#controlling-asset-holders
authorizing_seed = "SAV3UAQ63QJNMBMU2NB2AO6ZO33CGS675KJLDKZ2AU4N22XFBAJJBEN4"
# The ID of the account that issues your assets
issuing_account_id = "GAOOFELY4CO2L4YWOVOEF2J233NF4XHVWTKFUUJU5QJO7UKJ2T2MBTR7"

[callbacks]
# The server will send POST requests to this URL to notify you of payments
receive = "http://localhost:8005/receive"

動作させるのに現状のソースコードだとschema(mysql://)が入ってきてしまうので、そこは除くようにしてからビルドするようにしました。

実際にはurlの指定が間違っていただけでした。(tcpはいらない)

- url = "mysql://stellar_bridge:stellar_bridge@tcp(mysql:3306)/stellar_bridge"
+ url = "stellar_bridge:stellar_bridge@(mysql:3306)/stellar_bridge"

最終的にはdocker-composeを使って立ち上げることしました。
今回作成したものはここにあげてあります。

version: "2"
services:
  bridge:
    build: ./bridge-server
    ports:
      - "8001:8001"
    command: ./wait-for-it.sh mysql:3306 -t 600 -s -- ./bridge
    depends_on:
      - mysql
    container_name: bridge
  mysql:
    image: mysql:5.6
    environment:
      MYSQL_ROOT_PASSWORD: stellar_bridge
      MYSQL_DATABASE: stellar_bridge
      MYSQL_USER: stellar_bridge
      MYSQL_PASSWORD: stellar_bridge
      TZ: JST
    volumes:
      - ./mysql/conf.d:/etc/mysql/conf.d
      - mysql-data:/var/lib/mysql
    container_name: mysql
volumes:
  mysql-data:
    driver: local

try out

再度 node cosole を立ち上げて以下たたいてみます。

GDNUPLANU2ZIZBDWSLORMNIPNSU2JEDFTXB6MYEOZQKG3OIJWX4MQ2KYのアカウントに対して 1 USD を送ってみます。

(function() {
  var request = require('request');
  request.post({
    url: 'http://localhost:8001/payment',
    form: {
      amount: '1',
      asset_code: 'USD',
      asset_issuer: 'GAOOFELY4CO2L4YWOVOEF2J233NF4XHVWTKFUUJU5QJO7UKJ2T2MBTR7',
      destination: 'GDNUPLANU2ZIZBDWSLORMNIPNSU2JEDFTXB6MYEOZQKG3OIJWX4MQ2KY',
      source: 'SB5PJSVV3K62V3MSQGG6DKMNHR4BONGAEK5TTSMFPQCQZJE6RVHXWETN'
    }
  }, function(error, response, body) {
    if (error || response.statusCode !== 200) {
      console.error('ERROR!', error || body);
    }
    else {
      console.log('SUCCESS!', body);
    }
  });
})();
> ERROR! {
  "code": "transaction_bad_auth",
  "message": "Invalid network or too few signatures."
}

エラーが・・

Remember that the receiving account will need to trust the asset first. See issuing assets for more details.

多分これのことだろうと思います。

Issuing Assets | Stellar Developersを見て見ます。

However, other people can’t receive your asset until they’ve chosen to trust it. Because a Stellar asset is really a credit, you should trust that the issuer can redeem that credit if necessary later on. You might not want to trust your neighbor to issue banana assets if they don’t even have a banana plant, for example.

ありました。「受け取る人は、発行者を信頼していない限り受け取ることができない」みたいです。

では、信頼させてみることにします。

(function() {
  var StellarSdk = require('stellar-sdk');
  StellarSdk.Network.useTestNetwork();
  var server = new StellarSdk.Server('https://horizon-testnet.stellar.org');
  var issuingKeys = StellarSdk.Keypair.fromSecret('SAV3UAQ63QJNMBMU2NB2AO6ZO33CGS675KJLDKZ2AU4N22XFBAJJBEN4');
  var receivingKeys = StellarSdk.Keypair.fromSecret('SCY7QSROTEO2FW7Q5GQBGJWGKNJA47CVV7YSS4J5K2SPC64BTPYHFOOK');
  var kumaDollar = new StellarSdk.Asset('KumaDollar', issuingKeys.publicKey());
  server.loadAccount(receivingKeys.publicKey()).then(function(receiver) {
    var trust = { asset: kumaDollar, limit: '1000' };
    var transaction = new StellarSdk.TransactionBuilder(receiver).addOperation(StellarSdk.Operation.changeTrust(trust)).build();
    transaction.sign(receivingKeys);
    return server.submitTransaction(transaction);
  }).catch(function(error) {
    console.error('Error!', error);
  });
})();
(function() {
  var StellarSdk = require('stellar-sdk');
  StellarSdk.Network.useTestNetwork();
  var server = new StellarSdk.Server('https://horizon-testnet.stellar.org');
  var issuingKeys = StellarSdk.Keypair.fromSecret('SAV3UAQ63QJNMBMU2NB2AO6ZO33CGS675KJLDKZ2AU4N22XFBAJJBEN4');
  var receivingKeys = StellarSdk.Keypair.fromSecret('SCY7QSROTEO2FW7Q5GQBGJWGKNJA47CVV7YSS4J5K2SPC64BTPYHFOOK');
  var kumaDollar = new StellarSdk.Asset('KumaDollar', issuingKeys.publicKey());
  server.loadAccount(issuingKeys.publicKey()).then(function(issuer) {
    var pay = { destination: receivingKeys.publicKey(), asset: kumaDollar, amount: '10'}
    var transaction = new StellarSdk.TransactionBuilder(issuer).addOperation(StellarSdk.Operation.payment(pay)).build();
    transaction.sign(issuingKeys);
    return server.submitTransaction(transaction);
  }).catch(function(error) {
    console.error('Error!', error);
  });
})();
(function() {
  var StellarSdk = require('stellar-sdk');
  StellarSdk.Network.useTestNetwork();
  var server = new StellarSdk.Server('https://horizon-testnet.stellar.org');
  var receivingKeys = StellarSdk.Keypair.fromSecret('SCY7QSROTEO2FW7Q5GQBGJWGKNJA47CVV7YSS4J5K2SPC64BTPYHFOOK');
  server.loadAccount(receivingKeys.publicKey()).then(function(account) {
    console.log('Balances for account: ' + receivingKeys.publicKey());
    account.balances.forEach(function(balance) {
      console.log('Type:', balance.asset_type, 'AssetCode:', balance.asset_code || "", ', Balance:', balance.balance);
    });
  });
})();

> Balances for account: GDNUPLANU2ZIZBDWSLORMNIPNSU2JEDFTXB6MYEOZQKG3OIJWX4MQ2KY
Type: credit_alphanum12 AssetCode: KumaDollar , Balance: 10.0000000
Type: native AssetCode:  , Balance: 9989.9999800

KumaDollar10おくられているのが確認できました。

では気をとりなおして、bridgeサーバーにて
KumaDollarを発行したので、USDではなくKumaDollarを送って見ます。

(function() {
  var request = require('request');
  request.post({
    url: 'http://localhost:8001/payment',
    form: {
      amount: '1',
      asset_code: 'KumaDollar',
      asset_issuer: 'GAOOFELY4CO2L4YWOVOEF2J233NF4XHVWTKFUUJU5QJO7UKJ2T2MBTR7',
      destination: 'GDNUPLANU2ZIZBDWSLORMNIPNSU2JEDFTXB6MYEOZQKG3OIJWX4MQ2KY',
      source: 'SB5PJSVV3K62V3MSQGG6DKMNHR4BONGAEK5TTSMFPQCQZJE6RVHXWETN'
    }
  }, function(error, response, body) {
    if (error || response.statusCode !== 200) {
      console.error('ERROR!', error || body);
    }
    else {
      console.log('SUCCESS!', body);
    }
  });
})();
> ERROR! {
  "code": "transaction_bad_auth",
  "message": "Invalid network or too few signatures."
}

同じエラー><
現状だとbaseアカウントとissueアカウントの関係が曖昧で、うまく実行できてない?気がしてきました。

A base account used to transact with other Stellar accounts. It holds a balance of assets issued by the issuing account.

issueアカウント -> baseアカウントへ流れるようにしないとダメ??

(function() {
  var StellarSdk = require('stellar-sdk');
  StellarSdk.Network.useTestNetwork();
  var server = new StellarSdk.Server('https://horizon-testnet.stellar.org');
  var issuingKeys = StellarSdk.Keypair.fromSecret('SAV3UAQ63QJNMBMU2NB2AO6ZO33CGS675KJLDKZ2AU4N22XFBAJJBEN4');
  var receivingKeys = StellarSdk.Keypair.fromSecret('SB5PJSVV3K62V3MSQGG6DKMNHR4BONGAEK5TTSMFPQCQZJE6RVHXWETN');
  var kumaDollar = new StellarSdk.Asset('KumaDollar', issuingKeys.publicKey());
  server.loadAccount(receivingKeys.publicKey()).then(function(receiver) {
    var trust = { asset: kumaDollar, limit: '1000' };
    var transaction = new StellarSdk.TransactionBuilder(receiver).addOperation(StellarSdk.Operation.changeTrust(trust)).build();
    transaction.sign(receivingKeys);
    return server.submitTransaction(transaction);
  }).catch(function(error) {
    console.error('Error!', error);
  });
})();
(function() {
  var StellarSdk = require('stellar-sdk');
  StellarSdk.Network.useTestNetwork();
  var server = new StellarSdk.Server('https://horizon-testnet.stellar.org');
  var receivingKeys = StellarSdk.Keypair.fromSecret('SB5PJSVV3K62V3MSQGG6DKMNHR4BONGAEK5TTSMFPQCQZJE6RVHXWETN');
  server.loadAccount(receivingKeys.publicKey()).then(function(account) {
    console.log('Balances for account: ' + receivingKeys.publicKey());
    account.balances.forEach(function(balance) {
      console.log('Type:', balance.asset_type, 'AssetCode:', balance.asset_code || "", ', Balance:', balance.balance);
    });
  });
})();

ここにきて原因発覚

confファイルの以下の項目を適当に今年に書き換えたのが原因でした><(なんでなにも考えずに2017にしたんでしょうか。。)
くっそはまりました><

  • 誤: network_passphrase = "Test SDF Network ; September 2017"
  • 正: network_passphrase = "Test SDF Network ; September 2015"
(function() {
  var request = require('request');
  request.post({
    url: 'http://localhost:8001/payment',
    form: {
      amount: '1',
      asset_code: 'KumaDollar',
      asset_issuer: 'GAOOFELY4CO2L4YWOVOEF2J233NF4XHVWTKFUUJU5QJO7UKJ2T2MBTR7',
      destination: 'GDNUPLANU2ZIZBDWSLORMNIPNSU2JEDFTXB6MYEOZQKG3OIJWX4MQ2KY',
      source: 'SB5PJSVV3K62V3MSQGG6DKMNHR4BONGAEK5TTSMFPQCQZJE6RVHXWETN'
    }
  }, function(error, response, body) {
    if (error || response.statusCode !== 200) {
      console.error('ERROR!', error || body);
    }
    else {
      console.log('SUCCESS!', body);
    }
  });
})();
> ERROR! {
  "code": "payment_underfunded",
  "message": "Not enough funds to send this transaction."
}

baseアカウントに振り込んでおきましょう。

(function() {
  var StellarSdk = require('stellar-sdk');
  StellarSdk.Network.useTestNetwork();
  var server = new StellarSdk.Server('https://horizon-testnet.stellar.org');
  var issuingKeys = StellarSdk.Keypair.fromSecret('SAV3UAQ63QJNMBMU2NB2AO6ZO33CGS675KJLDKZ2AU4N22XFBAJJBEN4');
  var receivingKey = 'GATLCZMS3ITTVSCM2VB55KT2I7OHHXOWD2QM6O4BNK5LQTD2A3T7DINC';
  var kumaDollar = new StellarSdk.Asset('KumaDollar', issuingKeys.publicKey());
  server.loadAccount(issuingKeys.publicKey()).then(function(issuer) {
    var pay = { destination: receivingKey, asset: kumaDollar, amount: '100'}
    var transaction = new StellarSdk.TransactionBuilder(issuer).addOperation(StellarSdk.Operation.payment(pay)).build();
    transaction.sign(issuingKeys);
    return server.submitTransaction(transaction);
  }).catch(function(error) {
    console.error('Error!', error);
  });
})();
(function() {
  var request = require('request');
  request.post({
    url: 'http://localhost:8001/payment',
    form: {
      amount: '1',
      asset_code: 'KumaDollar',
      asset_issuer: 'GAOOFELY4CO2L4YWOVOEF2J233NF4XHVWTKFUUJU5QJO7UKJ2T2MBTR7',
      destination: 'GDNUPLANU2ZIZBDWSLORMNIPNSU2JEDFTXB6MYEOZQKG3OIJWX4MQ2KY',
      source: 'SB5PJSVV3K62V3MSQGG6DKMNHR4BONGAEK5TTSMFPQCQZJE6RVHXWETN'
    }
  }, function(error, response, body) {
    if (error || response.statusCode !== 200) {
      console.error('ERROR!', error || body);
    }
    else {
      console.log('SUCCESS!', body);
    }
  });
})();
undefined
> SUCCESS! {
  "hash": "be59e7e469561b8a6f7cb69684231004c4447f8aafe62c9695ef150341bebf42",
  "result_xdr": "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAA=",
  "ledger": 3819871
}

おおお、ついに成功しました。

(function() {
  var StellarSdk = require('stellar-sdk');
  StellarSdk.Network.useTestNetwork();
  var server = new StellarSdk.Server('https://horizon-testnet.stellar.org');
  var targets = [];
  targets.push('GATLCZMS3ITTVSCM2VB55KT2I7OHHXOWD2QM6O4BNK5LQTD2A3T7DINC');
  targets.push('GDNUPLANU2ZIZBDWSLORMNIPNSU2JEDFTXB6MYEOZQKG3OIJWX4MQ2KY');
  for (var i = 0; i < targets.length; i++) {
    server.loadAccount(targets[i]).then(function(account) {
      console.log('Balances for account: ' + targets[i]);
      account.balances.forEach(function(balance) {
        console.log('Type:', balance.asset_type, 'AssetCode:', balance.asset_code || "", ', Balance:', balance.balance);
      });
    });
  }
})();
> Balances for account: undefined
Type: credit_alphanum12 AssetCode: KumaDollar , Balance: 11.0000000
Type: native AssetCode:  , Balance: 9989.9999800
Balances for account: undefined
Type: credit_alphanum12 AssetCode: KumaDollar , Balance: 99.0000000
Type: native AssetCode:  , Balance: 9999.9999700

ちょっと色々と説明はぶいていますが・・・
以上の流れで、steller上でtokenを発行し、それを配布する流れがつくれました。

最後に

stellarでは、token発行者側は以下をするのが Better Practice のようです。

  • 発行用のアカウントと取引用のアカウントをわける。
  • token所持者のアカウントへの振込を代行する。
    • その際に、memoフィールドをうまく使って、対象アカウントを特定する。(federationという仕組みらしい)
    • 自分が管理しているアカウント経由でしかやりとりさせないようにする。
  • tokenは自前で管理しているアカウント以外にはやりとりさせない。

引き続きこの仕組みを追って行きたいと思います。

以上になります。

2
1
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1