circle.rb 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a6e1e4f41b558e12b4015b4c7b83a4672933c31d12ef2a1ad87e3ddbb416e2f4
4
+ data.tar.gz: b347435f333a4f4b6717d6a09bc3243224fe42ea3b281a20f06b040218007297
5
+ SHA512:
6
+ metadata.gz: 14063ad72ee93bc1ed8cf8fe6f900811d6f6b5fce19d2203b32cf0a2fb5006a1be6aa3f566890b50f52662824a4493a1857d8f1652df3401fc6a9ad0a82d331b
7
+ data.tar.gz: 751ea1419846e1093c54188f6b08c23827622dbd1b72e08ccf4bdb7d0a1b48a71c1ac521fc2e3ae2d061fd3fd588f9cd9bb91dba1c9441f2d59e2e585d6fdb67
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Filippo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ class Client
5
+ attr_reader :connection, :api_key, :entity_secret
6
+
7
+ def initialize(api_key: nil, entity_secret: nil, base_url: nil, **options)
8
+ config = Circle.configuration || Configuration.new
9
+ @api_key = api_key || config.api_key
10
+ @entity_secret = entity_secret || config.entity_secret
11
+
12
+ raise ArgumentError, "api_key is required" unless @api_key
13
+
14
+ @connection = Connection.new(
15
+ api_key: @api_key,
16
+ base_url: base_url || config.base_url,
17
+ open_timeout: options[:open_timeout] || config.open_timeout,
18
+ read_timeout: options[:read_timeout] || config.read_timeout,
19
+ user_agent: options[:user_agent] || config.user_agent
20
+ )
21
+
22
+ @public_key_cache = nil
23
+ end
24
+
25
+ def generate_entity_secret_ciphertext
26
+ raise ArgumentError, "entity_secret is required" unless entity_secret
27
+
28
+ @public_key_cache ||= developer_account.get_public_key.data["publicKey"]
29
+ EntitySecret.encrypt(entity_secret_hex: entity_secret, public_key_pem: @public_key_cache)
30
+ end
31
+
32
+ # Developer-controlled wallet resources
33
+ def developer_account
34
+ @developer_account ||= Resources::DeveloperAccount.new(self)
35
+ end
36
+
37
+ def wallet_sets
38
+ @wallet_sets ||= Resources::WalletSets.new(self)
39
+ end
40
+
41
+ def wallets
42
+ @wallets ||= Resources::Wallets.new(self)
43
+ end
44
+
45
+ def transactions
46
+ @transactions ||= Resources::Transactions.new(self)
47
+ end
48
+
49
+ def signing
50
+ @signing ||= Resources::Signing.new(self)
51
+ end
52
+
53
+ def tokens
54
+ @tokens ||= Resources::Tokens.new(self)
55
+ end
56
+
57
+ def monitored_tokens
58
+ @monitored_tokens ||= Resources::MonitoredTokens.new(self)
59
+ end
60
+
61
+ def subscriptions
62
+ @subscriptions ||= Resources::Subscriptions.new(self)
63
+ end
64
+
65
+ def faucet
66
+ @faucet ||= Resources::Faucet.new(self)
67
+ end
68
+
69
+ def address_validation
70
+ @address_validation ||= Resources::AddressValidation.new(self)
71
+ end
72
+
73
+ # User-controlled wallet resources
74
+ def users
75
+ @users ||= Resources::Users.new(self)
76
+ end
77
+
78
+ def user_pin
79
+ @user_pin ||= Resources::UserPin.new(self)
80
+ end
81
+
82
+ def user_wallets
83
+ @user_wallets ||= Resources::UserWallets.new(self)
84
+ end
85
+
86
+ def user_transactions
87
+ @user_transactions ||= Resources::UserTransactions.new(self)
88
+ end
89
+
90
+ def user_signing
91
+ @user_signing ||= Resources::UserSigning.new(self)
92
+ end
93
+
94
+ # Smart Contract Platform resources
95
+ def contracts
96
+ @contracts ||= Resources::Contracts.new(self)
97
+ end
98
+
99
+ def contract_templates
100
+ @contract_templates ||= Resources::ContractTemplates.new(self)
101
+ end
102
+
103
+ def event_monitors
104
+ @event_monitors ||= Resources::EventMonitors.new(self)
105
+ end
106
+
107
+ def event_logs
108
+ @event_logs ||= Resources::EventLogs.new(self)
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ class Configuration
5
+ attr_accessor :api_key, :entity_secret, :base_url, :open_timeout, :read_timeout, :user_agent
6
+
7
+ def initialize
8
+ @base_url = "https://api.circle.com"
9
+ @open_timeout = 30
10
+ @read_timeout = 60
11
+ @user_agent = "circle.rb/#{Circle::VERSION}"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+
5
+ module Circle
6
+ class Connection
7
+ def initialize(api_key:, base_url:, open_timeout:, read_timeout:, user_agent:)
8
+ @conn = Faraday.new(url: base_url) do |f|
9
+ f.request :json
10
+ f.response :json, content_type: /\bjson$/
11
+ f.request :authorization, "Bearer", api_key
12
+ f.headers["User-Agent"] = user_agent
13
+ f.options.open_timeout = open_timeout
14
+ f.options.timeout = read_timeout
15
+ f.adapter Faraday.default_adapter
16
+ end
17
+ end
18
+
19
+ def get(path, params = {})
20
+ handle_response { @conn.get(path, params) }
21
+ end
22
+
23
+ def post(path, body = {})
24
+ handle_response { @conn.post(path, body) }
25
+ end
26
+
27
+ def put(path, body = {})
28
+ handle_response { @conn.put(path, body) }
29
+ end
30
+
31
+ def delete(path, params = {})
32
+ handle_response { @conn.delete(path) { |req| req.params = params } }
33
+ end
34
+
35
+ private
36
+
37
+ def handle_response
38
+ response = yield
39
+ return Response.new(response) if response.success?
40
+
41
+ raise Error.from_response(response)
42
+ rescue Faraday::Error => e
43
+ raise ConnectionError.new(e.message)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "base64"
5
+
6
+ module Circle
7
+ class EntitySecret
8
+ def self.encrypt(entity_secret_hex:, public_key_pem:)
9
+ entity_secret_bytes = [entity_secret_hex].pack("H*")
10
+ rsa_key = OpenSSL::PKey::RSA.new(public_key_pem)
11
+ encrypted = rsa_key.encrypt(
12
+ entity_secret_bytes,
13
+ {
14
+ "rsa_padding_mode" => "oaep",
15
+ "rsa_oaep_md" => "sha256",
16
+ "rsa_mgf1_md" => "sha256"
17
+ }
18
+ )
19
+ Base64.strict_encode64(encrypted)
20
+ end
21
+
22
+ def self.generate
23
+ SecureRandom.hex(32)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ class Error < StandardError
5
+ attr_reader :http_status, :error_code, :raw_response
6
+
7
+ def initialize(message = nil, http_status: nil, error_code: nil, raw_response: nil)
8
+ @http_status = http_status
9
+ @error_code = error_code
10
+ @raw_response = raw_response
11
+ super(message)
12
+ end
13
+
14
+ def self.from_response(response)
15
+ body = response.body
16
+ message = body.is_a?(Hash) ? body["message"] : body.to_s
17
+ error_code = body.is_a?(Hash) ? body["code"] : nil
18
+
19
+ klass = case response.status
20
+ when 400 then ValidationError
21
+ when 401 then AuthenticationError
22
+ when 403 then ForbiddenError
23
+ when 404 then NotFoundError
24
+ when 409 then ConflictError
25
+ when 429 then RateLimitError
26
+ when 500..599 then ServerError
27
+ else Error
28
+ end
29
+
30
+ klass.new(message, http_status: response.status, error_code: error_code, raw_response: body)
31
+ end
32
+ end
33
+
34
+ class AuthenticationError < Error; end
35
+ class ForbiddenError < Error; end
36
+ class NotFoundError < Error; end
37
+ class ValidationError < Error; end
38
+ class ConflictError < Error; end
39
+ class RateLimitError < Error; end
40
+ class ServerError < Error; end
41
+ class ConnectionError < Error; end
42
+ class WebhookSignatureError < Error; end
43
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ class Pagination
5
+ include Enumerable
6
+
7
+ DEFAULT_PAGE_SIZE = 10
8
+
9
+ def initialize(resource:, path:, params: {}, key: nil)
10
+ @resource = resource
11
+ @path = path
12
+ @params = params
13
+ @key = key
14
+ end
15
+
16
+ def each(&block)
17
+ return enum_for(:each) unless block_given?
18
+
19
+ params = @params.dup
20
+ loop do
21
+ response = @resource.send(:get_request, @path, params)
22
+ items = @key ? response.data[@key] : response.data
23
+ break if items.nil? || !items.is_a?(Array) || items.empty?
24
+
25
+ items.each(&block)
26
+
27
+ page_size = params[:page_size] || params[:pageSize] || DEFAULT_PAGE_SIZE
28
+ break if items.size < page_size
29
+
30
+ params = params.merge(page_after: items.last["id"])
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Circle
6
+ class Resource
7
+ API_PATH = "/v1/w3s"
8
+
9
+ attr_reader :client
10
+
11
+ def initialize(client)
12
+ @client = client
13
+ end
14
+
15
+ private
16
+
17
+ def get_request(path, params = {})
18
+ client.connection.get("#{API_PATH}#{path}", Util.deep_snake_to_camel(params))
19
+ end
20
+
21
+ def post_request(path, body = {}, inject_entity_secret: false)
22
+ body = Util.deep_snake_to_camel(body)
23
+ body["idempotencyKey"] ||= SecureRandom.uuid
24
+ if inject_entity_secret && client.entity_secret
25
+ body["entitySecretCiphertext"] = client.generate_entity_secret_ciphertext
26
+ end
27
+ client.connection.post("#{API_PATH}#{path}", body)
28
+ end
29
+
30
+ def put_request(path, body = {})
31
+ client.connection.put("#{API_PATH}#{path}", Util.deep_snake_to_camel(body))
32
+ end
33
+
34
+ def delete_request(path, body = {})
35
+ client.connection.delete("#{API_PATH}#{path}", Util.deep_snake_to_camel(body))
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class AddressValidation < Resource
6
+ def validate(**params)
7
+ post_request("/transactions/validateAddress", params)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class ContractTemplates < Resource
6
+ def deploy(template_id, **params)
7
+ post_request("/contracts/templates/#{template_id}/deploy", params, inject_entity_secret: true)
8
+ end
9
+
10
+ def estimate_deployment_fee(template_id, **params)
11
+ post_request("/contracts/templates/#{template_id}/deploy/estimateFee", params)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class Contracts < Resource
6
+ def deploy(**params)
7
+ post_request("/contracts/deploy", params, inject_entity_secret: true)
8
+ end
9
+
10
+ def estimate_deployment_fee(**params)
11
+ post_request("/contracts/deploy/estimateFee", params)
12
+ end
13
+
14
+ def import(**params)
15
+ post_request("/contracts/import", params)
16
+ end
17
+
18
+ def list(**params)
19
+ get_request("/contracts", params)
20
+ end
21
+
22
+ def get(id)
23
+ get_request("/contracts/#{id}")
24
+ end
25
+
26
+ def update(id, **params)
27
+ put_request("/contracts/#{id}", params)
28
+ end
29
+
30
+ def query(**params)
31
+ post_request("/contracts/query", params)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class DeveloperAccount < Resource
6
+ def get_public_key
7
+ get_request("/config/entity/publicKey")
8
+ end
9
+
10
+ def register_entity_secret(**params)
11
+ post_request("/config/entity/entitySecret", params)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class EventLogs < Resource
6
+ def list(**params)
7
+ get_request("/contracts/events", params)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class EventMonitors < Resource
6
+ def create(**params)
7
+ post_request("/contracts/monitors", params)
8
+ end
9
+
10
+ def list(**params)
11
+ get_request("/contracts/monitors", params)
12
+ end
13
+
14
+ def get(id)
15
+ get_request("/contracts/monitors/#{id}")
16
+ end
17
+
18
+ def update(id, **params)
19
+ put_request("/contracts/monitors/#{id}", params)
20
+ end
21
+
22
+ def delete(id)
23
+ delete_request("/contracts/monitors/#{id}")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class Faucet < Resource
6
+ FAUCET_PATH = "/v1/faucet"
7
+
8
+ def request_tokens(**params)
9
+ body = Util.deep_snake_to_camel(params)
10
+ body["idempotencyKey"] ||= SecureRandom.uuid
11
+ client.connection.post("#{FAUCET_PATH}/drips", body)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class MonitoredTokens < Resource
6
+ def create(**params)
7
+ post_request("/config/entity/monitoredTokens", params)
8
+ end
9
+
10
+ def list(**params)
11
+ get_request("/config/entity/monitoredTokens", params)
12
+ end
13
+
14
+ def update(**params)
15
+ put_request("/config/entity/monitoredTokens", params)
16
+ end
17
+
18
+ def delete(**params)
19
+ delete_request("/config/entity/monitoredTokens", params)
20
+ end
21
+
22
+ def update_scope(**params)
23
+ put_request("/config/entity/monitoredTokens/scope", params)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class Signing < Resource
6
+ def sign_message(**params)
7
+ post_request("/developer/sign/message", params, inject_entity_secret: true)
8
+ end
9
+
10
+ def sign_typed_data(**params)
11
+ post_request("/developer/sign/typedData", params, inject_entity_secret: true)
12
+ end
13
+
14
+ def sign_transaction(**params)
15
+ post_request("/developer/sign/transaction", params, inject_entity_secret: true)
16
+ end
17
+
18
+ def sign_delegate_action(**params)
19
+ post_request("/developer/sign/delegateAction", params, inject_entity_secret: true)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class Subscriptions < Resource
6
+ NOTIFICATIONS_PATH = "/v2/notifications"
7
+
8
+ def create(**params)
9
+ body = Util.deep_snake_to_camel(params)
10
+ body["idempotencyKey"] ||= SecureRandom.uuid
11
+ client.connection.post("#{NOTIFICATIONS_PATH}/subscriptions", body)
12
+ end
13
+
14
+ def list
15
+ client.connection.get("#{NOTIFICATIONS_PATH}/subscriptions")
16
+ end
17
+
18
+ def get(id)
19
+ client.connection.get("#{NOTIFICATIONS_PATH}/subscriptions/#{id}")
20
+ end
21
+
22
+ def update(id, **params)
23
+ client.connection.put("#{NOTIFICATIONS_PATH}/subscriptions/#{id}", Util.deep_snake_to_camel(params))
24
+ end
25
+
26
+ def delete(id)
27
+ client.connection.delete("#{NOTIFICATIONS_PATH}/subscriptions/#{id}")
28
+ end
29
+
30
+ def get_notification_signature(id)
31
+ client.connection.get("#{NOTIFICATIONS_PATH}/subscriptions/#{id}/notificationSignature")
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class Tokens < Resource
6
+ def get(id)
7
+ get_request("/tokens/#{id}")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class Transactions < Resource
6
+ def create_transfer(**params)
7
+ post_request("/developer/transactions/transfer", params, inject_entity_secret: true)
8
+ end
9
+
10
+ def create_contract_execution(**params)
11
+ post_request("/developer/transactions/contractExecution", params, inject_entity_secret: true)
12
+ end
13
+
14
+ def list(**params)
15
+ get_request("/transactions", params)
16
+ end
17
+
18
+ def get(id, **params)
19
+ get_request("/transactions/#{id}", params)
20
+ end
21
+
22
+ def accelerate(id, **params)
23
+ post_request("/developer/transactions/#{id}/accelerate", params, inject_entity_secret: true)
24
+ end
25
+
26
+ def cancel(id, **params)
27
+ post_request("/developer/transactions/#{id}/cancel", params, inject_entity_secret: true)
28
+ end
29
+
30
+ def estimate_transfer_fee(**params)
31
+ post_request("/transactions/transfer/estimateFee", params)
32
+ end
33
+
34
+ def estimate_contract_execution_fee(**params)
35
+ post_request("/transactions/contractExecution/estimateFee", params)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class UserPin < Resource
6
+ def create(**params)
7
+ post_request("/user/pin", params)
8
+ end
9
+
10
+ def create_with_wallets(**params)
11
+ post_request("/user/pin/wallets", params)
12
+ end
13
+
14
+ def update(**params)
15
+ put_request("/user/pin", params)
16
+ end
17
+
18
+ def restore(**params)
19
+ post_request("/user/pin/restore", params)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class UserSigning < Resource
6
+ def sign_message(**params)
7
+ post_request("/user/sign/message", params)
8
+ end
9
+
10
+ def sign_typed_data(**params)
11
+ post_request("/user/sign/typedData", params)
12
+ end
13
+
14
+ def sign_transaction(**params)
15
+ post_request("/user/sign/transaction", params)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class UserTransactions < Resource
6
+ def create_transfer(**params)
7
+ post_request("/user/transactions/transfer", params)
8
+ end
9
+
10
+ def create_contract_execution(**params)
11
+ post_request("/user/transactions/contractExecution", params)
12
+ end
13
+
14
+ def list(**params)
15
+ get_request("/transactions", params)
16
+ end
17
+
18
+ def get(id, **params)
19
+ get_request("/transactions/#{id}", params)
20
+ end
21
+
22
+ def accelerate(id, **params)
23
+ post_request("/user/transactions/#{id}/accelerate", params)
24
+ end
25
+
26
+ def cancel(id, **params)
27
+ post_request("/user/transactions/#{id}/cancel", params)
28
+ end
29
+
30
+ def estimate_transfer_fee(**params)
31
+ post_request("/transactions/transfer/estimateFee", params)
32
+ end
33
+
34
+ def estimate_contract_execution_fee(**params)
35
+ post_request("/transactions/contractExecution/estimateFee", params)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class UserWallets < Resource
6
+ def create(**params)
7
+ post_request("/user/wallets", params)
8
+ end
9
+
10
+ def list(**params)
11
+ get_request("/wallets", params)
12
+ end
13
+
14
+ def get(id, **params)
15
+ get_request("/wallets/#{id}", params)
16
+ end
17
+
18
+ def update(id, **params)
19
+ put_request("/wallets/#{id}", params)
20
+ end
21
+
22
+ def get_token_balance(id, **params)
23
+ get_request("/wallets/#{id}/balances", params)
24
+ end
25
+
26
+ def get_nft_balance(id, **params)
27
+ get_request("/wallets/#{id}/nfts", params)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class Users < Resource
6
+ def create(**params)
7
+ post_request("/users", params)
8
+ end
9
+
10
+ def list(**params)
11
+ get_request("/users", params)
12
+ end
13
+
14
+ def get(id)
15
+ get_request("/users/#{id}")
16
+ end
17
+
18
+ def get_status(user_token:)
19
+ get_request("/user", { user_token: user_token })
20
+ end
21
+
22
+ def create_token(**params)
23
+ post_request("/users/token", params)
24
+ end
25
+
26
+ def refresh_token(**params)
27
+ post_request("/user/token/refresh", params)
28
+ end
29
+
30
+ def create_device_token_social(**params)
31
+ post_request("/users/social/token", params)
32
+ end
33
+
34
+ def create_device_token_email(**params)
35
+ post_request("/users/email/token", params)
36
+ end
37
+
38
+ def resend_otp(**params)
39
+ post_request("/users/resendOTP", params)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class WalletSets < Resource
6
+ def create(**params)
7
+ post_request("/developer/walletSets", params, inject_entity_secret: true)
8
+ end
9
+
10
+ def list(**params)
11
+ get_request("/walletSets", params)
12
+ end
13
+
14
+ def get(id)
15
+ get_request("/walletSets/#{id}")
16
+ end
17
+
18
+ def update(id, **params)
19
+ put_request("/walletSets/#{id}", params)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Resources
5
+ class Wallets < Resource
6
+ def create(**params)
7
+ post_request("/developer/wallets", params, inject_entity_secret: true)
8
+ end
9
+
10
+ def list(**params)
11
+ get_request("/wallets", params)
12
+ end
13
+
14
+ def get(id)
15
+ get_request("/wallets/#{id}")
16
+ end
17
+
18
+ def update(id, **params)
19
+ put_request("/wallets/#{id}", params)
20
+ end
21
+
22
+ def derive(id, **params)
23
+ post_request("/developer/wallets/#{id}/derive", params, inject_entity_secret: true)
24
+ end
25
+
26
+ def get_token_balance(id, **params)
27
+ get_request("/wallets/#{id}/balances", params)
28
+ end
29
+
30
+ def get_nft_balance(id, **params)
31
+ get_request("/wallets/#{id}/nfts", params)
32
+ end
33
+
34
+ def list_with_balances(**params)
35
+ get_request("/wallets/balances", params)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ class Response
5
+ attr_reader :data, :status, :raw_body
6
+
7
+ def initialize(faraday_response)
8
+ @status = faraday_response.status
9
+ @raw_body = faraday_response.body
10
+ @data = extract_data(@raw_body)
11
+ end
12
+
13
+ private
14
+
15
+ def extract_data(body)
16
+ return body unless body.is_a?(Hash)
17
+
18
+ body.key?("data") ? body["data"] : body
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ module Util
5
+ module_function
6
+
7
+ def deep_snake_to_camel(params)
8
+ case params
9
+ when Hash
10
+ params.each_with_object({}) do |(key, value), result|
11
+ camel_key = snake_to_camel(key.to_s)
12
+ result[camel_key] = deep_snake_to_camel(value)
13
+ end
14
+ when Array
15
+ params.map { |item| deep_snake_to_camel(item) }
16
+ else
17
+ params
18
+ end
19
+ end
20
+
21
+ def snake_to_camel(str)
22
+ return str unless str.include?("_")
23
+
24
+ parts = str.split("_")
25
+ parts[0] + parts[1..].map(&:capitalize).join
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Circle
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "base64"
5
+ require "json"
6
+
7
+ module Circle
8
+ class Webhook
9
+ def self.verify!(payload:, signature_b64:, public_key_b64:)
10
+ public_key_der = Base64.decode64(public_key_b64)
11
+ key = OpenSSL::PKey.read(public_key_der)
12
+ signature_bytes = Base64.decode64(signature_b64)
13
+ key.verify("SHA256", signature_bytes, payload)
14
+ rescue OpenSSL::PKey::PKeyError
15
+ false
16
+ end
17
+
18
+ def self.construct_event(payload:, signature:, public_key:)
19
+ verified = verify!(payload: payload, signature_b64: signature, public_key_b64: public_key)
20
+ raise WebhookSignatureError, "Invalid webhook signature" unless verified
21
+
22
+ JSON.parse(payload)
23
+ end
24
+ end
25
+ end
data/lib/circle.rb ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "circle/version"
4
+ require_relative "circle/configuration"
5
+ require_relative "circle/errors"
6
+ require_relative "circle/util"
7
+ require_relative "circle/response"
8
+ require_relative "circle/connection"
9
+ require_relative "circle/resource"
10
+ require_relative "circle/entity_secret"
11
+ require_relative "circle/webhook"
12
+ require_relative "circle/pagination"
13
+ require_relative "circle/client"
14
+
15
+ # Resources
16
+ require_relative "circle/resources/developer_account"
17
+ require_relative "circle/resources/wallet_sets"
18
+ require_relative "circle/resources/wallets"
19
+ require_relative "circle/resources/transactions"
20
+ require_relative "circle/resources/signing"
21
+ require_relative "circle/resources/tokens"
22
+ require_relative "circle/resources/monitored_tokens"
23
+ require_relative "circle/resources/subscriptions"
24
+ require_relative "circle/resources/faucet"
25
+ require_relative "circle/resources/address_validation"
26
+ require_relative "circle/resources/users"
27
+ require_relative "circle/resources/user_pin"
28
+ require_relative "circle/resources/user_wallets"
29
+ require_relative "circle/resources/user_transactions"
30
+ require_relative "circle/resources/user_signing"
31
+ require_relative "circle/resources/contracts"
32
+ require_relative "circle/resources/contract_templates"
33
+ require_relative "circle/resources/event_monitors"
34
+ require_relative "circle/resources/event_logs"
35
+
36
+ module Circle
37
+ class << self
38
+ attr_accessor :configuration
39
+
40
+ def configure
41
+ self.configuration ||= Configuration.new
42
+ yield(configuration)
43
+ end
44
+
45
+ def reset_configuration!
46
+ self.configuration = Configuration.new
47
+ end
48
+ end
49
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: circle.rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Filippo
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '3.0'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '2.0'
43
+ - - "<"
44
+ - !ruby/object:Gem::Version
45
+ version: '3.0'
46
+ description: A Ruby SDK for Circle's Web3 Services platform, providing access to developer-controlled
47
+ wallets, user-controlled wallets, smart contract platform, and more.
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files: []
51
+ files:
52
+ - LICENSE.txt
53
+ - lib/circle.rb
54
+ - lib/circle/client.rb
55
+ - lib/circle/configuration.rb
56
+ - lib/circle/connection.rb
57
+ - lib/circle/entity_secret.rb
58
+ - lib/circle/errors.rb
59
+ - lib/circle/pagination.rb
60
+ - lib/circle/resource.rb
61
+ - lib/circle/resources/address_validation.rb
62
+ - lib/circle/resources/contract_templates.rb
63
+ - lib/circle/resources/contracts.rb
64
+ - lib/circle/resources/developer_account.rb
65
+ - lib/circle/resources/event_logs.rb
66
+ - lib/circle/resources/event_monitors.rb
67
+ - lib/circle/resources/faucet.rb
68
+ - lib/circle/resources/monitored_tokens.rb
69
+ - lib/circle/resources/signing.rb
70
+ - lib/circle/resources/subscriptions.rb
71
+ - lib/circle/resources/tokens.rb
72
+ - lib/circle/resources/transactions.rb
73
+ - lib/circle/resources/user_pin.rb
74
+ - lib/circle/resources/user_signing.rb
75
+ - lib/circle/resources/user_transactions.rb
76
+ - lib/circle/resources/user_wallets.rb
77
+ - lib/circle/resources/users.rb
78
+ - lib/circle/resources/wallet_sets.rb
79
+ - lib/circle/resources/wallets.rb
80
+ - lib/circle/response.rb
81
+ - lib/circle/util.rb
82
+ - lib/circle/version.rb
83
+ - lib/circle/webhook.rb
84
+ homepage: https://github.com/fconforti/circle.rb
85
+ licenses:
86
+ - MIT
87
+ metadata:
88
+ rubygems_mfa_required: 'true'
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '3.1'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubygems_version: 4.0.8
104
+ specification_version: 4
105
+ summary: Ruby client for Circle Web3 Services API
106
+ test_files: []