pinetwork 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/pi_network.rb +202 -0
  3. metadata +71 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1943d6bdfac24dbd81680d389d742a9c6229c2cb22a57efe0bb5d45fc2b7265c
4
+ data.tar.gz: 83498c391acf04457cd2816e76c4972f147b6774906170f6cbac45d8f324fd7f
5
+ SHA512:
6
+ metadata.gz: '05395f7554e9eaf8bbd3abb653ea078f2e5849046e861f971ef920b415498e9d04cbe402821fc76a1ccdc429b5ad865b682d3aaf4bc0af1d81452d8b961698f5'
7
+ data.tar.gz: 685cf11f2c3c9e7c0e944459157efff6290bbf381ad67b564fae4eba34f60a24d0c9557530877a3d523c2983e20a673067807d278f70a9b55b6e7831f7eec9e2
data/lib/pi_network.rb ADDED
@@ -0,0 +1,202 @@
1
+ gem_dir = Gem::Specification.find_by_name("pi_network").gem_dir
2
+ require "#{gem_dir}/lib/errors"
3
+
4
+ class PiNetwork
5
+ require 'faraday'
6
+ require 'json'
7
+ require 'stellar-sdk'
8
+
9
+ attr_reader :api_key
10
+ attr_reader :client
11
+ attr_reader :account
12
+ attr_reader :base_url
13
+ attr_reader :from_address
14
+
15
+ def initialize(api_key, wallet_private_key, options = {})
16
+ validate_private_seed_format!(wallet_private_key)
17
+ @api_key = api_key
18
+ @account = load_account(wallet_private_key)
19
+ @base_url = options[:base_url] || "https://api.minepi.com"
20
+
21
+ @open_payments = {}
22
+ end
23
+
24
+ def get_payment(payment_id)
25
+ response = Faraday.get(
26
+ base_url + "/v2/payments/#{payment_id}",
27
+ {},
28
+ http_headers,
29
+ )
30
+
31
+ if response.status == 404
32
+ raise Errors::PaymentNotFoundError.new("Payment not found", payment_id)
33
+ end
34
+
35
+ handle_http_response(response, "An unknown error occurred while fetching the payment")
36
+ end
37
+
38
+ def create_payment(payment_data)
39
+ validate_payment_data!(payment_data, {amount: true, memo: true, metadata: true, uid: true})
40
+
41
+ request_body = {
42
+ payment: payment_data,
43
+ }
44
+
45
+ response = Faraday.post(
46
+ base_url + "/v2/payments",
47
+ request_body.to_json,
48
+ http_headers,
49
+ )
50
+
51
+ parsed_response = handle_http_response(response, "An unknown error occurred while creating a payment")
52
+
53
+ identifier = parsed_response["identifier"]
54
+ @open_payments[identifier] = parsed_response
55
+
56
+ return identifier
57
+ end
58
+
59
+ def submit_payment(payment_id)
60
+ payment = @open_payments[payment_id]
61
+
62
+ if payment.nil?
63
+ payment = get_payment(payment_id)
64
+ end
65
+
66
+ set_horizon_client(payment["network"])
67
+ @from_address = payment["from_address"]
68
+
69
+ transaction_data = {
70
+ amount: payment["amount"],
71
+ identifier: payment["identifier"],
72
+ recipient: payment["to_address"]
73
+ }
74
+
75
+ transaction = build_a2u_transaction(transaction_data)
76
+ txid = submit_transaction(transaction)
77
+
78
+ @open_payments.delete(payment_id)
79
+
80
+ return txid
81
+ end
82
+
83
+ def complete_payment(identifier, txid)
84
+ body = {"txid": txid}
85
+
86
+ response = Faraday.post(
87
+ base_url + "/v2/payments/#{identifier}/complete",
88
+ body.to_json,
89
+ http_headers
90
+ )
91
+
92
+ handle_http_response(response, "An unknown error occurred while completing the payment")
93
+ end
94
+
95
+ def cancel_payment(identifier)
96
+ response = Faraday.post(
97
+ base_url + "/v2/payments/#{identifier}/cancel",
98
+ {}.to_json,
99
+ http_headers,
100
+ )
101
+
102
+ handle_http_response(response, "An unknown error occurred while cancelling the payment")
103
+ end
104
+
105
+ def get_incomplete_server_payments
106
+ response = Faraday.get(
107
+ base_url + "/v2/payments/incomplete_server_payments",
108
+ {},
109
+ http_headers,
110
+ )
111
+
112
+ res = handle_http_response(response, "An unknown error occurred while fetching incomplete payments")
113
+ res["incomplete_server_payments"]
114
+ end
115
+
116
+ private
117
+
118
+ def http_headers
119
+ return nil if @api_key.nil?
120
+
121
+ {
122
+ "Authorization": "Key #{@api_key}",
123
+ "Content-Type": "application/json"
124
+ }
125
+ end
126
+
127
+ def handle_http_response(response, unknown_error_message = "An unknown error occurred while making an API request")
128
+ unless response.status == 200
129
+ error_message = JSON.parse(response.body).dig("error_message") rescue unknown_err_message
130
+ raise Errors::APIRequestError.new(error_message, response.status, response.body)
131
+ end
132
+
133
+ begin
134
+ parsed_response = JSON.parse(response.body)
135
+ return parsed_response
136
+ rescue StandardError => err
137
+ error_message = "Failed to parse response body"
138
+ raise Errors::APIRequestError.new(error_message, response.status, response.body)
139
+ end
140
+ end
141
+
142
+ def set_horizon_client(network)
143
+ host = network == "Pi Network" ? "api.mainnet.minepi.com" : "api.staging-testnet.socialchainapp.com"
144
+ horizon = network == "Pi Network" ? "https://api.mainnet.minepi.com" : "https://api.staging-testnet.socialchainapp.com"
145
+ client = Stellar::Client.new(host: host, horizon: horizon)
146
+ Stellar::default_network = network
147
+
148
+ @client = client
149
+ end
150
+
151
+ def load_account(private_seed)
152
+ account = Stellar::Account.from_seed(private_seed)
153
+ end
154
+
155
+ def build_a2u_transaction(transaction_data)
156
+ raise StandardError.new("You should use a private seed of your app wallet!") if self.from_address != self.account.address
157
+
158
+ validate_payment_data!(transaction_data, {amount: true, identifier: true, recipient: true})
159
+
160
+ amount = Stellar::Amount.new(transaction_data[:amount])
161
+ # TODO: get this from horizon
162
+ fee = 100000 # 0.01π
163
+ recipient = Stellar::KeyPair.from_address(transaction_data[:recipient])
164
+ memo = Stellar::Memo.new(:memo_text, transaction_data[:identifier])
165
+
166
+ payment_operation = Stellar::Operation.payment({
167
+ destination: recipient,
168
+ amount: amount.to_payment
169
+ })
170
+
171
+ my_public_key = self.account.address
172
+ sequence_number = self.client.account_info(my_public_key).sequence.to_i
173
+ transaction_builder = Stellar::TransactionBuilder.new(
174
+ source_account: self.account.keypair,
175
+ sequence_number: sequence_number + 1,
176
+ base_fee: fee,
177
+ memo: memo
178
+ )
179
+
180
+ transaction = transaction_builder.add_operation(payment_operation).set_timeout(180000).build
181
+ end
182
+
183
+ def submit_transaction(transaction)
184
+ envelope = transaction.to_envelope(self.account.keypair)
185
+ response = self.client.submit_transaction(tx_envelope: envelope)
186
+ txid = response._response.body["id"]
187
+ end
188
+
189
+ def validate_payment_data!(data, options = {})
190
+ raise ArgumentError.new("Missing amount") if options[:amount] && !data[:amount].present?
191
+ raise ArgumentError.new("Missing memo") if options[:memo] && !data[:memo].present?
192
+ raise ArgumentError.new("Missing metadata") if options[:metadata] && !data[:metadata].present?
193
+ raise ArgumentError.new("Missing uid") if options[:uid] && !data[:uid].present?
194
+ raise ArgumentError.new("Missing identifier") if options[:identifier] && !data[:identifier].present?
195
+ raise ArgumentError.new("Missing recipient") if options[:recipient] && !data[:recipient].present?
196
+ end
197
+
198
+ def validate_private_seed_format!(seed)
199
+ raise StandardError.new("Private Seed should start with \"S\"") unless seed.upcase.starts_with?("S")
200
+ raise StandardError.new("Private Seed should be 56 characters") unless seed.length == 56
201
+ end
202
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pinetwork
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Pi Core Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-01-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: stellar-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.29.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.29.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Pi Network backend library for Ruby-based webservers.
42
+ email: support@minepi.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - lib/pi_network.rb
48
+ homepage: https://rubygems.org/gems/pi_network
49
+ licenses:
50
+ - PiOS
51
+ metadata: {}
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 3.0.3.1
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: Pi Network Ruby
71
+ test_files: []