pinetwork 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.
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: []