mobius-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ # Service class used to interact with account on Stellar network.
2
+ class Mobius::Client::Blockchain::Account
3
+ extend Dry::Initializer
4
+
5
+ # @!method initialize(keypair)
6
+ # @param keypair [Stellar::Keypair] account keypair
7
+ # @!scope instance
8
+ param :keypair
9
+
10
+ # Returns true if trustline exists for given asset and limit is positive.
11
+ # @param asset [Stellar::Asset] Stellar asset to check or :native
12
+ # @return [Boolean] true if trustline exists
13
+ def trustline_exists?(asset = Mobius::Client.stellar_asset)
14
+ balance = find_balance(asset)
15
+ (balance && !balance.dig("limit").to_f.zero?) || false
16
+ end
17
+
18
+ # Returns balance for given asset
19
+ # @param asset [Stellar::Asset] Stellar asset to check or :native
20
+ # @return [Float] Balance value.
21
+ def balance(asset = Mobius::Client.stellar_asset)
22
+ balance = find_balance(asset)
23
+ balance && balance.dig("balance").to_f
24
+ end
25
+
26
+ # Returns true if given keypair is added as cosigner to current account.
27
+ # @param to_keypair [Stellar::Keypair] Keypair in question
28
+ # @return [Boolean] true if cosigner added
29
+ # TODO: Add weight check/return
30
+ def authorized?(to_keypair)
31
+ !find_signer(to_keypair.address).nil?
32
+ end
33
+
34
+ # Returns Stellar::Account instance for given keypair.
35
+ # @return [Stellar::Account] instance
36
+ def account
37
+ @account ||=
38
+ if keypair.sign?
39
+ Stellar::Account.from_seed(keypair.seed)
40
+ else
41
+ Stellar::Account.from_address(keypair.address)
42
+ end
43
+ end
44
+
45
+ # Requests and caches Stellar::Account information from network.
46
+ # @return [Stellar::Account] account information.
47
+ def info
48
+ @info ||= Mobius::Client.horizon_client.account_info(account)
49
+ end
50
+
51
+ # Invalidates account information cache.
52
+ def reload!
53
+ @info = nil
54
+ end
55
+
56
+ # Invalidates cache and returns next sequence value for given account.
57
+ # @return [Integer] sequence value.
58
+ def next_sequence_value
59
+ reload!
60
+ info.sequence.to_i + 1
61
+ end
62
+
63
+ private
64
+
65
+ def find_balance(asset)
66
+ info.balances.find { |balance| balance_matches?(asset, balance) }
67
+ rescue Faraday::ResourceNotFound
68
+ raise Mobius::Client::Error::AccountMissing
69
+ end
70
+
71
+ def balance_matches?(asset, balance)
72
+ if [:native, Stellar::Asset.native].include?(asset)
73
+ balance["asset_type"] == "native"
74
+ else
75
+ code = balance["asset_code"]
76
+ issuer = balance["asset_issuer"]
77
+ asset_issuer_address = Mobius::Client.to_keypair(asset.issuer).address
78
+ code == asset.code && issuer == asset_issuer_address
79
+ end
80
+ end
81
+
82
+ # TODO: Think of adding weight check here
83
+ def find_signer(address)
84
+ info.signers.find { |s| s["public_key"] == address }
85
+ rescue Faraday::ResourceNotFound
86
+ raise Mobius::Client::Error::AccountMissing
87
+ end
88
+ end
@@ -0,0 +1,58 @@
1
+ # Adds account as cosigner to other account.
2
+ class Mobius::Client::Blockchain::AddCosigner
3
+ extend Dry::Initializer
4
+
5
+ # @!method initialize(keypair)
6
+ # @param keypair [Stellar::Keypair] Account keypair
7
+ # @param cosigner_keypair [Stellar::Keypair] Cosigner account keypair
8
+ # @param weight [Integer] Cosigner weight, default: 1
9
+ # @!scope instance
10
+ param :keypair
11
+ param :cosigner_keypair
12
+ param :weight, default: -> { 1 }
13
+
14
+ # Executes an operation
15
+ def call
16
+ client.horizon.transactions._post(
17
+ tx: tx.to_envelope(keypair).to_xdr(:base64)
18
+ )
19
+ rescue Faraday::ResourceNotFound
20
+ raise Mobius::Client::Error::AccountMissing
21
+ end
22
+
23
+ # Executes an operation
24
+ class << self
25
+ def call(*args)
26
+ new(*args).call
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ # TODO: weight must be params
33
+ def tx
34
+ Stellar::Transaction.set_options(
35
+ account: keypair,
36
+ sequence: account.next_sequence_value,
37
+ signer: signer,
38
+ master_weight: 10,
39
+ highThreshold: 10,
40
+ medThreshold: 1,
41
+ lowThreshold: 1
42
+ )
43
+ end
44
+
45
+ def signer
46
+ Stellar::Signer.new(
47
+ key: Stellar::SignerKey.new(:signer_key_type_ed25519, cosigner_keypair.raw_public_key), weight: weight
48
+ )
49
+ end
50
+
51
+ def account
52
+ @account ||= Mobius::Client::Blockchain::Account.new(keypair)
53
+ end
54
+
55
+ def client
56
+ @client ||= Mobius::Client.horizon_client
57
+ end
58
+ end
@@ -0,0 +1,42 @@
1
+ # Creates unlimited trustline for given asset.
2
+ class Mobius::Client::Blockchain::CreateTrustline
3
+ extend Dry::Initializer
4
+
5
+ # ruby-stellar-base needs to be fixed, it does not accept unlimited now
6
+ LIMIT = 922337203685
7
+
8
+ param :keypair
9
+ param :asset, default: -> { Mobius::Client.stellar_asset }
10
+
11
+ def call
12
+ client.horizon.transactions._post(tx: tx.to_envelope(keypair).to_xdr(:base64))
13
+ rescue Faraday::ResourceNotFound
14
+ raise Mobius::Client::Error::AccountMissing
15
+ end
16
+
17
+ # TODO: DRY
18
+ class << self
19
+ def call(*args)
20
+ new(*args).call
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def tx
27
+ Stellar::Transaction.change_trust(
28
+ account: keypair,
29
+ line: [:alphanum4, asset.code, Mobius::Client.to_keypair(asset.issuer)],
30
+ limit: LIMIT,
31
+ sequence: account.next_sequence_value
32
+ )
33
+ end
34
+
35
+ def account
36
+ @account ||= Mobius::Client::Blockchain::Account.new(keypair)
37
+ end
38
+
39
+ def client
40
+ @client ||= Mobius::Client.horizon_client
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ # Calls Stellar FriendBot
2
+ class Mobius::Client::Blockchain::FriendBot
3
+ extend Dry::Initializer
4
+
5
+ param :keypair
6
+
7
+ def call
8
+ Mobius::Client.horizon_client.horizon.friendbot._post(addr: keypair.address)
9
+ end
10
+
11
+ class << self
12
+ def call(*args)
13
+ new(*args).call
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,42 @@
1
+ # Transforms given value into Stellar::Keypair object.
2
+ module Mobius::Client::Blockchain::KeyPairFactory
3
+ class << self
4
+ # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
5
+ # Generates Stellar::Keypair from subject, use Stellar::Client.to_keypair as shortcut.
6
+ # @param subject [String||Stellar::Account||Stellar::PublicKey||Stellar::SignerKey||Stellar::Keypair] subject.
7
+ # @return [Stellar::Keypair] Stellar::Keypair instance.
8
+ def produce(subject)
9
+ case subject
10
+ when String
11
+ from_string(subject)
12
+ when Stellar::Account
13
+ subject.keypair
14
+ when Stellar::PublicKey
15
+ from_public_key(subject)
16
+ when Stellar::SignerKey
17
+ from_secret_key(subject)
18
+ when Stellar::KeyPair
19
+ subject
20
+ else
21
+ raise Mobius::Client::Error::UnknownKeyPairType, "Unknown KeyPair type: #{subject.class.name}"
22
+ end
23
+ rescue ArgumentError => e
24
+ raise Mobius::Client::Error::UnknownKeyPairType, "Unknown KeyPair type: #{e.message}"
25
+ end
26
+ # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
27
+
28
+ private
29
+
30
+ def from_string(subject)
31
+ subject[0] == "S" ? Stellar::KeyPair.from_seed(subject) : Stellar::KeyPair.from_address(subject)
32
+ end
33
+
34
+ def from_public_key(subject)
35
+ Stellar::KeyPair.from_public_key(subject.value)
36
+ end
37
+
38
+ def from_secret_key(subject)
39
+ Stellar::KeyPair.from_raw_seed(subject.value)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,49 @@
1
+ class Mobius::Client::Error < StandardError
2
+ # Raised if stellar account is missing on network
3
+ class AccountMissing < self
4
+ def to_s
5
+ @message || "Stellar account does not exists"
6
+ end
7
+ end
8
+
9
+ # Raised if there is insufficient balance for payment
10
+ class InsufficientFunds < self
11
+ end
12
+
13
+ # Raised if transaction in question has invalid structure
14
+ class MalformedTransaction < self
15
+ end
16
+
17
+ # Raised if transaction has expired.
18
+ class TokenExpired < self
19
+ end
20
+
21
+ # Raised if transaction has expired (strict mode).
22
+ class TokenTooOld < self
23
+ end
24
+
25
+ # Raises if account does not contain MOBI trustline
26
+ class TrustlineMissing < self
27
+ def to_s
28
+ @message || "Trustline not found"
29
+ end
30
+ end
31
+
32
+ # Raises if account does not contain MOBI trustline
33
+ class AuthorisationMissing < self
34
+ def to_s
35
+ @message || "Authorisation missing"
36
+ end
37
+ end
38
+
39
+ # Raised in transaction in question has invalid or does not have required signatures
40
+ class Unauthorized < self
41
+ def to_s
42
+ @message || "Given transaction signature invalid"
43
+ end
44
+ end
45
+
46
+ # Raised if unknown or empty value has passed to KeyPairFactory
47
+ class UnknownKeyPairType < self
48
+ end
49
+ end
@@ -0,0 +1,33 @@
1
+ # Calls Mobius FriendBot for given account.
2
+ class Mobius::Client::FriendBot
3
+ extend Dry::Initializer
4
+
5
+ param :seed
6
+ param :amount, default: -> { 1000 }
7
+
8
+ def call
9
+ response = http.post(ENDPOINT, addr: seed, amount: amount)
10
+ return true if response.success?
11
+ raise "FriendBot failed to respond: #{response.body}"
12
+ end
13
+
14
+ class << self
15
+ def call(*args)
16
+ new(*args).call
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def http
23
+ # Mobius::Client.mobius_host
24
+ Faraday.new(MOBIUS_HOST) do |c|
25
+ c.request :url_encoded
26
+ c.response :json, content_type: /\bjson$/
27
+ c.adapter Faraday.default_adapter
28
+ end
29
+ end
30
+
31
+ ENDPOINT = "/api/stellar/friendbot".freeze
32
+ MOBIUS_HOST = "https://mobius.network".freeze
33
+ end
@@ -0,0 +1,5 @@
1
+ module Mobius
2
+ module Client
3
+ VERSION = "0.1.0".freeze
4
+ end
5
+ end
@@ -0,0 +1,51 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "mobius/client/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "mobius-client"
7
+ spec.version = Mobius::Client::VERSION
8
+ spec.authors = ["Viktor Sokolov"]
9
+ spec.email = ["gzigzigzeo@gmail.com"]
10
+
11
+ spec.summary = %(Mobius Ruby Client)
12
+ spec.description = %(Mobius Ruby Client)
13
+ spec.homepage = "https://github.com/mobius-network/mobius-client-ruby"
14
+ spec.license = "MIT"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
20
+ else
21
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
22
+ end
23
+
24
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
25
+ f.match(%r{^(test|spec|features)/})
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_development_dependency "bundler", "~> 1.16"
32
+ spec.add_development_dependency "bundler-audit", "~> 0.6.0"
33
+ spec.add_development_dependency "httplog", "~> 1.0", ">= 1.0.2"
34
+ spec.add_development_dependency "rake", "~> 12.0"
35
+ spec.add_development_dependency "rspec", "~> 3.0"
36
+ spec.add_development_dependency "rubocop", "~> 0.53"
37
+ spec.add_development_dependency "rubocop-rspec", "~> 1.23"
38
+ spec.add_development_dependency "simplecov", ">= 0.16.1"
39
+ spec.add_development_dependency "simplecov-console", ">= 0.4.2"
40
+ spec.add_development_dependency "timecop", "~> 0.9", ">= 0.9.1"
41
+ spec.add_development_dependency "vcr", "~> 3.0", ">= 3.0.3"
42
+ spec.add_development_dependency "webmock", "~> 3.3"
43
+ spec.add_development_dependency "yard", "~> 0.9", ">= 0.9.12"
44
+
45
+ spec.add_dependency "dry-initializer", "~> 2.4"
46
+ spec.add_dependency "faraday", "~> 0.14"
47
+ spec.add_dependency "faraday_middleware", "~> 0.12", ">= 0.12.2"
48
+ spec.add_dependency "jwt", "~> 1.5", ">= 1.5.6"
49
+ spec.add_dependency "stellar-sdk", "~> 0.3"
50
+ spec.add_dependency "thor", "~> 0.20"
51
+ end
@@ -0,0 +1,109 @@
1
+ <html>
2
+ <head>
3
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.3.1/semantic.min.css">
4
+ <style>
5
+ .ui.container {
6
+ margin: 2em 0em;
7
+ }
8
+
9
+ .ui.container > h1 {
10
+ font-size: 3em;
11
+ }
12
+
13
+ .ui.container > h2.dividing.header {
14
+ font-size: 2em;
15
+ font-weight: normal;
16
+ margin: 2em 0em 1em;
17
+ }
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <div class="ui container">
22
+ <h1>Mobius Wallet App Dev Auth</h1>
23
+
24
+ <h2 class="ui dividing header">Application</h2>
25
+
26
+ <form class="ui form">
27
+ <div class="field">
28
+ <label>Auth endpoint:</label>
29
+ <input type="text" value="http://localhost:3000/auth" id="url"></input>
30
+ </div>
31
+ <div class="field">
32
+ <label>Redirect URI:</label>
33
+ <input type="text" value="http://localhost:3000" id="redirect_url"></input>
34
+ </div>
35
+ <div class="field">
36
+ <label>Public Key:</label>
37
+ <input type="text" value="<%= app_keypair.address %>"></input>
38
+ </div>
39
+ <div class="field">
40
+ <label>Private Key:</label>
41
+ <input type="text" value="<%= app_keypair.seed %>"></input>
42
+ </div>
43
+ </form>
44
+
45
+ <% {
46
+ "Normal Account" => normal,
47
+ "Zero Balance Account" => zero_balance,
48
+ "Unauthorized Account" => unauthorized
49
+ }.each do |label, keypair| %>
50
+
51
+ <h2 class="ui dividing header"><%= label %></h2>
52
+
53
+ <form class="ui form">
54
+ <div class="field">
55
+ <input type="text" value="<%= keypair.address %>" />
56
+ </div>
57
+ <div class="field">
58
+ <input type="text" value="<%= keypair.seed %>" class="seed" />
59
+ </div>
60
+ <div class="field">
61
+ <input type="submit" class="ui button green" value="Open" />
62
+ </div>
63
+ </form>
64
+
65
+ <% end %>
66
+ </div>
67
+ </body>
68
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/stellar-sdk/0.8.0/stellar-sdk.js"></script>
69
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
70
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
71
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.3.1/semantic.min.js"></script>
72
+ <script>
73
+ StellarSdk.Network.useTestNetwork()
74
+
75
+ $(function() {
76
+ $(".ui.button").on('click', function(e) {
77
+ e.preventDefault();
78
+ var seed = $(e.target).closest('.ui.form').find('.seed:first').val();
79
+ var keypair = StellarSdk.Keypair.fromSecret(seed);
80
+ var endpoint = $('#url').val();
81
+
82
+ var showError = function(err) {
83
+ if (err) {
84
+ alert(err);
85
+ }
86
+ }
87
+
88
+ // NOTE: this should be replaced with mobius js sdk calls
89
+ axios.get(endpoint).then(function(response) {
90
+ var xdr = response.data;
91
+ var tx = new StellarSdk.Transaction(xdr);
92
+ tx.sign(keypair);
93
+ var signedChallenge = tx.toEnvelope().toXDR("base64");
94
+ axios({
95
+ url: endpoint,
96
+ method: 'post',
97
+ params: {
98
+ xdr: signedChallenge,
99
+ public_key: keypair.publicKey()
100
+ }
101
+ }).then(function(response) {
102
+ var url = $('#redirect_url').val();
103
+ document.location = url + '?token=' + response.data;
104
+ }).catch(showError);
105
+ }).catch(showError);
106
+ });
107
+ });
108
+ </script>
109
+ </html>