From 4fc378512bc18cd49f93b9d321b13d77f84c62ac Mon Sep 17 00:00:00 2001 From: Madao-3 Date: Thu, 31 Oct 2024 21:39:45 +0800 Subject: [PATCH] feat: Implement Circle Web3 Services Ruby Client - Add basic API client with Faraday for HTTP requests - Implement wallet sets API endpoints (get/create) - Add configuration system with entity secret encryption - Add response handling and error types - Add documentation with usage examples Key features: - Automatic entity secret ciphertext generation - Structured API responses - Error handling for API calls - Sandbox environment support --- Gemfile.lock | 123 ++++++++ README.md | 88 ++++-- circle-w3s-0.1.0.gem | Bin 11264 -> 13824 bytes circle-w3s.gemspec | 4 +- lib/circle-w3s.rb | 55 ++-- lib/circle-w3s/api/signing_api.rb | 60 ++++ lib/circle-w3s/api/token_lookup_api.rb | 65 +++++ lib/circle-w3s/api/transactions_api.rb | 133 +++++++++ lib/circle-w3s/api/wallet_sets_api.rb | 8 +- lib/circle-w3s/api/wallets_api.rb | 144 ++++------ lib/circle-w3s/api_client.rb | 188 ++----------- lib/circle-w3s/api_response.rb | 11 + lib/circle-w3s/client.rb | 5 +- lib/circle-w3s/configuration.rb | 263 +++++++++++++----- .../models/create_wallet_request.rb | 11 + .../models/create_wallet_set_request.rb | 22 ++ lib/circle-w3s/models/transaction.rb | 160 +++++++++++ lib/circle-w3s/models/wallet.rb | 92 ++++++ lib/circle-w3s/models/wallet_set.rb | 69 +++++ lib/circle_wallets/api/wallets_api.rb | 142 ---------- 20 files changed, 1117 insertions(+), 526 deletions(-) create mode 100644 Gemfile.lock create mode 100644 lib/circle-w3s/api/signing_api.rb create mode 100644 lib/circle-w3s/api/token_lookup_api.rb create mode 100644 lib/circle-w3s/api/transactions_api.rb create mode 100644 lib/circle-w3s/api_response.rb create mode 100644 lib/circle-w3s/models/create_wallet_request.rb create mode 100644 lib/circle-w3s/models/create_wallet_set_request.rb create mode 100644 lib/circle-w3s/models/transaction.rb create mode 100644 lib/circle-w3s/models/wallet.rb create mode 100644 lib/circle-w3s/models/wallet_set.rb delete mode 100644 lib/circle_wallets/api/wallets_api.rb diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..6762bee --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,123 @@ +PATH + remote: . + specs: + circle-w3s (0.1.0) + eth (~> 0.5.9) + faraday (~> 2.0) + json (~> 2.0) + jwt (~> 2.7) + +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.2) + base64 (0.2.0) + diff-lcs (1.5.1) + dotenv (3.1.4) + eth (0.5.12) + forwardable (~> 1.3) + keccak (~> 1.3) + konstructor (~> 1.0) + openssl (>= 2.2, < 4.0) + rbsecp256k1 (~> 6.0) + scrypt (~> 3.0) + faraday (2.12.0) + faraday-net_http (>= 2.0, < 3.4) + json + logger + faraday-net_http (3.3.0) + net-http + ffi (1.17.0) + ffi (1.17.0-aarch64-linux-gnu) + ffi (1.17.0-aarch64-linux-musl) + ffi (1.17.0-arm-linux-gnu) + ffi (1.17.0-arm-linux-musl) + ffi (1.17.0-arm64-darwin) + ffi (1.17.0-x86-linux-gnu) + ffi (1.17.0-x86-linux-musl) + ffi (1.17.0-x86_64-darwin) + ffi (1.17.0-x86_64-linux-gnu) + ffi (1.17.0-x86_64-linux-musl) + ffi-compiler (1.3.2) + ffi (>= 1.15.5) + rake + forwardable (1.3.3) + json (2.7.5) + jwt (2.9.3) + base64 + keccak (1.3.1) + konstructor (1.0.2) + language_server-protocol (3.17.0.3) + logger (1.6.1) + mini_portile2 (2.8.7) + net-http (0.4.1) + uri + openssl (3.2.0) + parallel (1.26.3) + parser (3.3.5.0) + ast (~> 2.4.1) + racc + pkg-config (1.5.7) + racc (1.8.1) + rainbow (3.1.1) + rake (13.2.1) + rbsecp256k1 (6.0.0) + mini_portile2 (~> 2.8) + pkg-config (~> 1.5) + rubyzip (~> 2.3) + regexp_parser (2.9.2) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.2) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.1) + rubocop (1.68.0) + json (~> 2.3) + language_server-protocol (>= 3.17.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.4, < 3.0) + rubocop-ast (>= 1.32.2, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.33.0) + parser (>= 3.3.1.0) + ruby-progressbar (1.13.0) + rubyzip (2.3.2) + scrypt (3.0.8) + ffi-compiler (>= 1.0, < 2.0) + rake (>= 9, < 14) + unicode-display_width (2.6.0) + uri (0.13.1) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + ruby + x86-linux-gnu + x86-linux-musl + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + circle-w3s! + dotenv + rake (~> 13.0) + rspec (~> 3.0) + rubocop (~> 1.21) + +BUNDLED WITH + 2.5.20 diff --git a/README.md b/README.md index eb10a15..da97f00 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,89 @@ -# CircleW3s +# Circle Web3 Services Ruby Client -TODO: Delete this and the text below, and describe your gem - -Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/circle-w3s`. To experiment with that code, run `bin/console` for an interactive prompt. +Ruby client for Circle's Web3 Services API. ## Installation -TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org. - -Install the gem and add to the application's Gemfile by executing: +Add this line to your application's Gemfile: +```ruby +gem "circle-w3s-ruby" +``` +And then execute: ```bash -bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG +bundle install ``` -If bundler is not being used to manage dependencies, install the gem by executing: - +Or install it yourself as: ```bash -gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG +gem install circle-w3s-ruby ``` ## Usage -TODO: Write usage instructions here +### Configuration + +First, configure the client with your API credentials: + +```ruby +CircleW3s.configure do |config| + config.access_token = "TEST_API_KEY:your-key-id:your-key-secret" + config.host = "https://api-sandbox.circle.com" + config.entity_secret = "your-entity-secret" # 32-byte hex string +end +``` -## Development +### Examples -After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. +#### List Wallet Sets -To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). +```ruby +# Create API client +client = CircleW3s::ApiClient.new(CircleW3s.configuration) +wallet_sets_api = CircleW3s::WalletSetsApi.new(client) -## Contributing +# Get wallet sets +response = wallet_sets_api.get_wallet_sets +puts response.data # Access the wallet sets data +``` + +#### Create Wallet Set + +```ruby +# Create API client +client = CircleW3s::ApiClient.new(CircleW3s.configuration) +wallet_sets_api = CircleW3s::WalletSetsApi.new(client) + +# Create request with automatically generated entity_secret_ciphertext +request = CircleW3s::CreateWalletSetRequest.new( + name: "My First Wallet Set", + entity_secret_ciphertext: CircleW3s.configuration.generate_entity_secret_ciphertext +) + +# Create wallet set +response = wallet_sets_api.create_wallet_set(request) +puts response.data # Access the created wallet set data +``` -Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/circle-w3s. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/circle-w3s/blob/main/CODE_OF_CONDUCT.md). +## Response Structure -## License +All API responses are wrapped in a structured object with the following attributes: +- `data`: The actual response data +- `success`: Boolean indicating if the request was successful +- `status`: HTTP status code -The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). +## Error Handling -## Code of Conduct +The client will raise exceptions for various error cases: +- `CircleW3s::UnauthorizedError`: 401 authentication errors +- `CircleW3s::ApiError`: Other API errors -Everyone interacting in the CircleW3s project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/circle-w3s/blob/main/CODE_OF_CONDUCT.md). +```ruby +begin + response = wallet_sets_api.get_wallet_sets +rescue CircleW3s::UnauthorizedError => e + puts "Authentication failed: #{e.message}" +rescue CircleW3s::ApiError => e + puts "API error: #{e.message}" +end +``` \ No newline at end of file diff --git a/circle-w3s-0.1.0.gem b/circle-w3s-0.1.0.gem index d86dae703cf3ce577b536ba5e18db5f1729bfc2e..988b62bb768ccb7d33d08b61a24e63419aa8c83f 100644 GIT binary patch delta 10979 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 delta 8060 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 ZZa1t(Nupw1+J{~dD%E`<{9e*p|Bmc9T0 diff --git a/circle-w3s.gemspec b/circle-w3s.gemspec index 82440d9..842a861 100644 --- a/circle-w3s.gemspec +++ b/circle-w3s.gemspec @@ -21,6 +21,6 @@ Gem::Specification.new do |spec| spec.add_dependency "eth", "~> 0.5.9" spec.add_dependency "jwt", "~> 2.7" - spec.add_development_dependency "rspec", "~> 3.0" - spec.add_development_dependency "dotenv", "~> 2.7" + spec.add_development_dependency "rspec" + spec.add_development_dependency "dotenv" end \ No newline at end of file diff --git a/lib/circle-w3s.rb b/lib/circle-w3s.rb index 99b41b0..14c6e8f 100644 --- a/lib/circle-w3s.rb +++ b/lib/circle-w3s.rb @@ -1,38 +1,33 @@ -require "faraday" -require "json" -require "eth" -require "jwt" - -require_relative "circle-w3s/version" -require_relative "circle-w3s/configuration" -require_relative "circle-w3s/client" -require_relative "circle-w3s/errors/api_error" - -# Resources -require_relative "circle-w3s/resources/wallets" -require_relative "circle-w3s/resources/transactions" -require_relative "circle-w3s/resources/user_tokens" -require_relative "circle-w3s/resources/challenge_ids" -require_relative "circle-w3s/resources/security_questions" -require_relative "circle-w3s/resources/pin_codes" +require 'circle-w3s/version' +require 'circle-w3s/configuration' +require 'circle-w3s/api_client' +require 'circle-w3s/api/wallets_api' +require 'circle-w3s/api/wallet_sets_api' +require 'circle-w3s/api/transactions_api' +require "circle-w3s/models/wallet" +require "circle-w3s/models/wallet_set" +require "circle-w3s/models/transaction" +require "circle-w3s/models/create_wallet_request" +require "circle-w3s/models/create_wallet_set_request" module CircleW3s class Error < StandardError; end - - class << self - attr_accessor :configuration - end + class ApiError < Error; end + class UnauthorizedError < ApiError; end + class NotFoundError < ApiError; end + class BadRequestError < ApiError; end - def self.configure - self.configuration ||= Configuration.new - yield(configuration) if block_given? - end + class << self + def configuration + @configuration ||= Configuration.new + end - def self.reset - self.configuration = Configuration.new - end + def configure + yield(configuration) if block_given? + end - def self.client - @client ||= Client.new(configuration) + def reset + @configuration = Configuration.new + end end end \ No newline at end of file diff --git a/lib/circle-w3s/api/signing_api.rb b/lib/circle-w3s/api/signing_api.rb new file mode 100644 index 0000000..fc9d4f0 --- /dev/null +++ b/lib/circle-w3s/api/signing_api.rb @@ -0,0 +1,60 @@ +module CircleW3s + class SigningApi + attr_reader :api_client + + def initialize(api_client = ApiClient.default) + @api_client = api_client + end + + # Sign message + # @param sign_message_request [SignMessageRequest] Request body for signing a message + # @return [SignatureResponse] + def sign_message(sign_message_request) + api_client.fill_entity_secret_ciphertext(sign_message_request) + api_client.fill_idempotency_key(sign_message_request) + + response = api_client.call_api( + path: '/v1/w3s/developer/sign/message', + http_method: :post, + body: sign_message_request, + auth_names: ['BearerAuth'], + return_type: 'SignatureResponse' + ) + response.data + end + + # Sign transaction + # @param sign_transaction_request [SignTransactionRequest] Request body for signing a transaction + # @return [SignTransactionResponse] + def sign_transaction(sign_transaction_request) + api_client.fill_entity_secret_ciphertext(sign_transaction_request) + api_client.fill_idempotency_key(sign_transaction_request) + + response = api_client.call_api( + path: '/v1/w3s/developer/sign/transaction', + http_method: :post, + body: sign_transaction_request, + auth_names: ['BearerAuth'], + return_type: 'SignTransactionResponse' + ) + response.data + end + + # Sign typed data + # @param sign_typed_data_request [SignTypedDataRequest] Request body for signing typed data + # @return [SignatureResponse] + def sign_typed_data(sign_typed_data_request) + api_client.fill_entity_secret_ciphertext(sign_typed_data_request) + api_client.fill_idempotency_key(sign_typed_data_request) + + response = api_client.call_api( + path: '/v1/w3s/developer/sign/typedData', + http_method: :post, + body: sign_typed_data_request, + auth_names: ['BearerAuth'], + return_type: 'SignatureResponse' + ) + response.data + end + end +end \ No newline at end of file diff --git a/lib/circle-w3s/api/token_lookup_api.rb b/lib/circle-w3s/api/token_lookup_api.rb new file mode 100644 index 0000000..ebc4e44 --- /dev/null +++ b/lib/circle-w3s/api/token_lookup_api.rb @@ -0,0 +1,65 @@ +module CircleW3s + class TokenLookupApi + attr_reader :api_client + + def initialize(api_client = ApiClient.default) + @api_client = api_client + end + + # Get token details + # @param id [String] The universally unique identifier (UUID v4) of the token + # @return [TokenResponse] Token details + def get_token_id(id) + # Auto fill required fields + api_client.fill_entity_secret_ciphertext(id) + api_client.fill_idempotency_key(id) + + response = api_client.call_api( + path: "/v1/w3s/tokens/#{id}", + http_method: :get, + auth_names: ['BearerAuth'], + return_type: 'TokenResponse' + ) + response.data + end + + # Get token details with HTTP info + # @param id [String] The universally unique identifier (UUID v4) of the token + # @param opts [Hash] Optional parameters + # @option opts [Boolean] :_return_http_data_only Only return the response data + # @option opts [Boolean] :_preload_content Preload response content + # @option opts [Integer, Array] :_request_timeout Request timeout + # @return [ApiResponse] Response with HTTP info + def get_token_id_with_http_info(id, opts = {}) + # Auto fill required fields + api_client.fill_entity_secret_ciphertext(id) + api_client.fill_idempotency_key(id) + + # Validate parameters + raise ArgumentError, "Missing required parameter 'id'" if id.nil? + + # Process parameters + path_params = { 'id' => id } + query_params = {} + header_params = {} + form_params = {} + body_params = nil + + # Set HTTP headers + header_params['Accept'] = api_client.select_header_accept(['application/json']) + + # Make API call + api_client.call_api( + path: '/v1/w3s/tokens/{id}', + http_method: :get, + path_params: path_params, + query_params: query_params, + header_params: header_params, + body: body_params, + auth_names: ['BearerAuth'], + return_type: 'TokenResponse', + opts: opts + ) + end + end +end \ No newline at end of file diff --git a/lib/circle-w3s/api/transactions_api.rb b/lib/circle-w3s/api/transactions_api.rb new file mode 100644 index 0000000..218f4a7 --- /dev/null +++ b/lib/circle-w3s/api/transactions_api.rb @@ -0,0 +1,133 @@ +module CircleW3s + class TransactionsApi + attr_reader :api_client + + def initialize(api_client = ApiClient.default) + @api_client = api_client + end + + # Accelerate a transaction + # @param id [String] Transaction ID + # @param accelerate_transaction_request [AccelerateTransactionForDeveloperRequest] Request body + # @return [AccelerateTransactionForDeveloper] + def create_developer_transaction_accelerate(id, accelerate_transaction_request) + path = "/v1/w3s/developer/transactions/#{id}/accelerate" + response = api_client.call_api( + path: path, + http_method: :post, + body: accelerate_transaction_request, + auth_names: ['BearerAuth'], + return_type: 'AccelerateTransactionForDeveloper' + ) + response.data + end + + # Cancel a transaction + # @param id [String] Transaction ID + # @param cancel_transaction_request [CancelTransactionForDeveloperRequest] Request body + # @return [CancelTransactionForDeveloper] + def create_developer_transaction_cancel(id, cancel_transaction_request) + path = "/v1/w3s/developer/transactions/#{id}/cancel" + response = api_client.call_api( + path: path, + http_method: :post, + body: cancel_transaction_request, + auth_names: ['BearerAuth'], + return_type: 'CancelTransactionForDeveloper' + ) + response.data + end + + # Create a contract execution transaction + # @param request [CreateContractExecutionTransactionForDeveloperRequest] Request body + # @return [CreateContractExecutionTransactionForDeveloper] + def create_developer_transaction_contract_execution(request) + response = api_client.call_api( + path: '/v1/w3s/developer/transactions/contractExecution', + http_method: :post, + body: request, + auth_names: ['BearerAuth'], + return_type: 'CreateContractExecutionTransactionForDeveloper' + ) + response.data + end + + # Create a transfer transaction + # @param request [CreateTransferTransactionForDeveloperRequest] Request body + # @return [CreateTransferTransactionForDeveloperResponse] + def create_developer_transaction_transfer(request) + response = api_client.call_api( + path: '/v1/w3s/developer/transactions/transfer', + http_method: :post, + body: request, + auth_names: ['BearerAuth'], + return_type: 'CreateTransferTransactionForDeveloperResponse' + ) + response.data + end + + # List all transactions + # @param opts [Hash] Optional parameters + # @option opts [String] :blockchain Filter by blockchain + # @option opts [String] :custody_type Filter by custody type + # @option opts [String] :destination_address Filter by destination address + # @option opts [Boolean] :include_all Include all resources + # @option opts [String] :operation Filter by operation + # @option opts [String] :state Filter by state + # @option opts [String] :tx_hash Filter by transaction hash + # @option opts [String] :tx_type Filter by transaction type + # @option opts [String] :wallet_ids Filter by wallet IDs + # @option opts [DateTime] :from Start date + # @option opts [DateTime] :to End date + # @option opts [String] :page_before Page before token + # @option opts [String] :page_after Page after token + # @option opts [Integer] :page_size Page size (1-50) + # @return [Transactions] + def list_transactions(opts = {}) + query_params = {} + + # Add query parameters + query_params[:blockchain] = opts[:blockchain] if opts[:blockchain] + query_params[:custodyType] = opts[:custody_type] if opts[:custody_type] + query_params[:destinationAddress] = opts[:destination_address] if opts[:destination_address] + query_params[:includeAll] = opts[:include_all] if opts[:include_all] + query_params[:operation] = opts[:operation] if opts[:operation] + query_params[:state] = opts[:state] if opts[:state] + query_params[:txHash] = opts[:tx_hash] if opts[:tx_hash] + query_params[:txType] = opts[:tx_type] if opts[:tx_type] + query_params[:walletIds] = opts[:wallet_ids] if opts[:wallet_ids] + query_params[:from] = opts[:from].iso8601 if opts[:from] + query_params[:to] = opts[:to].iso8601 if opts[:to] + query_params[:pageBefore] = opts[:page_before] if opts[:page_before] + query_params[:pageAfter] = opts[:page_after] if opts[:page_after] + query_params[:pageSize] = opts[:page_size] if opts[:page_size] + + response = api_client.call_api( + path: '/v1/w3s/transactions', + http_method: :get, + query_params: query_params, + auth_names: ['BearerAuth'], + return_type: 'Transactions' + ) + response.data + end + + # Get a single transaction + # @param id [String] Transaction ID + # @param tx_type [String] Filter by transaction type + # @return [TransactionResponse] + def get_transaction(id, tx_type = nil) + query_params = {} + query_params[:txType] = tx_type if tx_type + + response = api_client.call_api( + path: "/v1/w3s/transactions/#{id}", + http_method: :get, + query_params: query_params, + auth_names: ['BearerAuth'], + return_type: 'TransactionResponse' + ) + response.data + end + end +end \ No newline at end of file diff --git a/lib/circle-w3s/api/wallet_sets_api.rb b/lib/circle-w3s/api/wallet_sets_api.rb index 8cf716a..3eab210 100644 --- a/lib/circle-w3s/api/wallet_sets_api.rb +++ b/lib/circle-w3s/api/wallet_sets_api.rb @@ -14,7 +14,8 @@ def create_wallet_set(create_wallet_set_request, x_request_id: nil) headers['X-Request-Id'] = x_request_id if x_request_id response = api_client.call_api( - path: '/v1/w3s/developer/walletSets', + :post, + '/v1/w3s/developer/walletSets', http_method: :post, headers: headers, body: create_wallet_set_request, @@ -52,11 +53,12 @@ def get_wallet_sets(x_request_id: nil, from: nil, to: nil, query_params[:pageAfter] = page_after if page_after query_params[:pageSize] = page_size if page_size - headers = { 'Accept' => 'application/json' } + headers = {} headers['X-Request-Id'] = x_request_id if x_request_id response = api_client.call_api( - path: '/v1/w3s/walletSets', + :get, + '/v1/w3s/walletSets', http_method: :get, query_params: query_params, headers: headers, diff --git a/lib/circle-w3s/api/wallets_api.rb b/lib/circle-w3s/api/wallets_api.rb index 2015a37..a843ed9 100644 --- a/lib/circle-w3s/api/wallets_api.rb +++ b/lib/circle-w3s/api/wallets_api.rb @@ -6,16 +6,20 @@ def initialize(api_client = ApiClient.default) @api_client = api_client end - # Creates a new developer-controlled wallet or batch of wallets + # Create wallets + # @param [CreateWalletRequest] create_wallet_request Schema for the request payload + # @param [String] x_request_id Optional request ID for support + # @return [Wallets] def create_wallet(create_wallet_request, x_request_id: nil) - raise ArgumentError, "create_wallet_request is required" if create_wallet_request.nil? - - headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' } + auto_fill(create_wallet_request) + + path = "/v1/w3s/developer/wallets" + headers = { 'Accept' => 'application/json' } headers['X-Request-Id'] = x_request_id if x_request_id - + response = api_client.call_api( - path: '/v1/w3s/developer/wallets', - http_method: :post, + :post, + path, headers: headers, body: create_wallet_request, auth_names: ['BearerAuth'], @@ -24,17 +28,18 @@ def create_wallet(create_wallet_request, x_request_id: nil) response.data end - # Retrieves an existing wallet by ID - def get_wallet(id, x_request_id: nil) - raise ArgumentError, "id is required" if id.nil? - + # Get wallet by ID + # @param [String] id Wallet ID + # @param [String] x_request_id Optional request ID + # @return [WalletResponse] + def get_wallet(id, x_request_id: nil) + path = "/v1/w3s/wallets/#{id}" headers = { 'Accept' => 'application/json' } headers['X-Request-Id'] = x_request_id if x_request_id response = api_client.call_api( - path: '/v1/w3s/wallets/{id}', - http_method: :get, - path_params: { 'id' => id }, + :get, + path, headers: headers, auth_names: ['BearerAuth'], return_type: 'WalletResponse' @@ -42,23 +47,33 @@ def get_wallet(id, x_request_id: nil) response.data end - # Lists all wallets with optional filters - def get_wallets(address: nil, blockchain: nil, wallet_set_id: nil, ref_id: nil, - from: nil, to: nil, page_before: nil, page_after: nil, page_size: nil) + # List wallets with filters + # @param [Hash] opts Optional parameters + # @option opts [String] :address Filter by blockchain address + # @option opts [String] :blockchain Filter by blockchain + # @option opts [String] :wallet_set_id Filter by wallet set + # @option opts [Integer] :page_size Items per page (max 50) + # @return [Wallets] + def get_wallets(opts = {}) + path = "/v1/w3s/wallets" query_params = {} - query_params[:address] = address if address - query_params[:blockchain] = blockchain if blockchain - query_params[:walletSetId] = wallet_set_id if wallet_set_id - query_params[:refId] = ref_id if ref_id - query_params[:from] = from.iso8601 if from - query_params[:to] = to.iso8601 if to - query_params[:pageBefore] = page_before if page_before - query_params[:pageAfter] = page_after if page_after - query_params[:pageSize] = page_size if page_size + + # Add supported query parameters + %i[address blockchain wallet_set_id ref_id page_before page_after page_size].each do |key| + query_params[key] = opts[key] if opts[key] + end + + # Handle datetime parameters + if opts[:from] + query_params[:from] = opts[:from].strftime(api_client.configuration.datetime_format) + end + if opts[:to] + query_params[:to] = opts[:to].strftime(api_client.configuration.datetime_format) + end response = api_client.call_api( - path: '/v1/w3s/wallets', - http_method: :get, + :get, + path, query_params: query_params, headers: { 'Accept' => 'application/json' }, auth_names: ['BearerAuth'], @@ -67,76 +82,11 @@ def get_wallets(address: nil, blockchain: nil, wallet_set_id: nil, ref_id: nil, response.data end - # Gets token balance for a wallet - def list_wallet_balance(id, include_all: nil, name: nil, token_address: nil, - standard: nil, page_before: nil, page_after: nil, page_size: nil) - raise ArgumentError, "id is required" if id.nil? - - query_params = {} - query_params[:includeAll] = include_all unless include_all.nil? - query_params[:name] = name if name - query_params[:tokenAddress] = token_address if token_address - query_params[:standard] = standard if standard - query_params[:pageBefore] = page_before if page_before - query_params[:pageAfter] = page_after if page_after - query_params[:pageSize] = page_size if page_size - - response = api_client.call_api( - path: '/v1/w3s/wallets/{id}/balances', - http_method: :get, - path_params: { 'id' => id }, - query_params: query_params, - headers: { 'Accept' => 'application/json' }, - auth_names: ['BearerAuth'], - return_type: 'Balances' - ) - response.data - end - - # Gets NFTs for a wallet - def list_wallet_nfts(id, include_all: nil, name: nil, token_address: nil, - standard: nil, page_before: nil, page_after: nil, page_size: nil) - raise ArgumentError, "id is required" if id.nil? - - query_params = {} - query_params[:includeAll] = include_all unless include_all.nil? - query_params[:name] = name if name - query_params[:tokenAddress] = token_address if token_address - query_params[:standard] = standard if standard - query_params[:pageBefore] = page_before if page_before - query_params[:pageAfter] = page_after if page_after - query_params[:pageSize] = page_size if page_size - - response = api_client.call_api( - path: '/v1/w3s/wallets/{id}/nfts', - http_method: :get, - path_params: { 'id' => id }, - query_params: query_params, - headers: { 'Accept' => 'application/json' }, - auth_names: ['BearerAuth'], - return_type: 'Nfts' - ) - response.data - end - - # Updates a wallet's metadata - def update_wallet(id, update_wallet_request, x_request_id: nil) - raise ArgumentError, "id is required" if id.nil? - raise ArgumentError, "update_wallet_request is required" if update_wallet_request.nil? + private - headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' } - headers['X-Request-Id'] = x_request_id if x_request_id - - response = api_client.call_api( - path: '/v1/w3s/wallets/{id}', - http_method: :put, - path_params: { 'id' => id }, - headers: headers, - body: update_wallet_request, - auth_names: ['BearerAuth'], - return_type: 'WalletResponse' - ) - response.data + def auto_fill(request) + api_client.fill_entity_secret_ciphertext(request) + api_client.fill_idempotency_key(request) end end end \ No newline at end of file diff --git a/lib/circle-w3s/api_client.rb b/lib/circle-w3s/api_client.rb index 59f4e1b..5aaa17f 100644 --- a/lib/circle-w3s/api_client.rb +++ b/lib/circle-w3s/api_client.rb @@ -1,182 +1,42 @@ -require 'date' -require 'json' -require 'tempfile' -require 'uri' require 'faraday' +require 'json' +require 'ostruct' module CircleW3s class ApiClient - attr_accessor :config, :default_headers, :user_agent - - def initialize(configuration = Configuration.new) - @config = configuration - @user_agent = "CircleW3s Ruby Gem #{CircleW3s::VERSION}" - @default_headers = { - 'Content-Type' => 'application/json', - 'User-Agent' => @user_agent - } - @default_headers['Authorization'] = "Bearer #{@config.api_key}" if @config.api_key - end - - # 发起请求 - def call_api(http_method, path, opts = {}) - response = connection.send(http_method.to_s.downcase) do |req| - # 构建请求 URL - req.url build_request_url(path, opts[:query]) - - # 设置请求头 - req.headers = build_request_headers(opts[:header]) - - # 设置请求体 - if [:post, :put, :patch].include?(http_method) - req.body = build_request_body(opts[:body]) - end - - # 设置超时 - req.options.timeout = @config.timeout if @config.timeout - req.options.open_timeout = @config.open_timeout if @config.open_timeout - end - - handle_response(response) - rescue Faraday::Error => e - raise ApiError.new(response: e.response) - end - - private - - def connection - @connection ||= Faraday.new do |faraday| - faraday.url_prefix = @config.host - faraday.request :json - faraday.response :json, content_type: /\bjson$/ - faraday.adapter Faraday.default_adapter - - if @config.proxy - faraday.proxy = @config.proxy - end - - if @config.ssl_ca_cert - faraday.ssl.ca_file = @config.ssl_ca_cert - end - - if @config.cert_file - faraday.ssl.client_cert = @config.cert_file - end - - if @config.key_file - faraday.ssl.client_key = @config.key_file - end - - faraday.ssl.verify = @config.verify_ssl - end - end + attr_accessor :config - def build_request_url(path, query_params = nil) - url = path.to_s - - if query_params && !query_params.empty? - # 将查询参数转换为字符串 - query_string = query_params.map do |key, value| - "#{URI.encode_www_form_component(key.to_s)}=#{URI.encode_www_form_component(value.to_s)}" - end.join('&') - - url += "?#{query_string}" + def initialize(config = Configuration.default) + @config = config + @conn = Faraday.new(url: config.host) do |f| + f.request :json + f.response :json + f.adapter Faraday.default_adapter end - - url end - def build_request_headers(custom_headers = nil) - headers = default_headers.dup - - if custom_headers - headers.merge!(custom_headers) + def call_api(method, path, opts = {}) + response = @conn.send(method, path) do |req| + req.headers['Authorization'] = "Bearer #{config.access_token}" + req.body = opts[:body] if opts[:body] end - # 添加认证信息 - if (auth_settings = @config.auth_settings).any? - auth_settings.each do |auth| - if auth[:in] == :header - headers[auth[:key]] = auth[:value] - end - end - end - - headers + handle_response(response) end - def build_request_body(data) - return nil if data.nil? - - if data.is_a?(String) - data - else - sanitize_for_serialization(data) - end - end + private def handle_response(response) - case response.status - when 200..299 - response.body - else - raise ApiError.new( - code: response.status, - response: response - ) - end - end - - def sanitize_for_serialization(obj) - case obj - when nil - nil - when String, Integer, Float, TrueClass, FalseClass - obj - when Date - obj.iso8601 - when Time, DateTime - obj.strftime('%Y-%m-%dT%H:%M:%S.%L%z') - when Array - obj.map { |item| sanitize_for_serialization(item) } - when Hash - obj.transform_values { |value| sanitize_for_serialization(value) } - else - if obj.respond_to?(:to_hash) - sanitize_for_serialization(obj.to_hash) - else - raise ApiError.new(message: "Unable to serialize #{obj.class} object") - end - end - end - - def deserialize(response_data, return_type) - case return_type - when nil - nil - when 'String' - response_data.to_s - when 'Integer' - response_data.to_i - when 'Float' - response_data.to_f - when 'Boolean' - response_data == true - when 'DateTime' - DateTime.parse(response_data) - when 'Date' - Date.parse(response_data) - when 'Object' - response_data - when /\AArray<(.+)>\z/ - inner_type = $1 - response_data.map { |item| deserialize(item, inner_type) } - when /\AHash\\z/ - inner_type = $1 - response_data.transform_values { |value| deserialize(value, inner_type) } + if response.success? + data = response.body + OpenStruct.new({ + data: data['data'], + success: true, + status: response.status + }) else - # 假设是一个模型类 - const_get("CircleW3s::Models::#{return_type}").new(response_data) + raise UnauthorizedError.new(response.body) if response.status == 401 + raise ApiError.new("HTTP #{response.status}: #{response.body}") end end end diff --git a/lib/circle-w3s/api_response.rb b/lib/circle-w3s/api_response.rb new file mode 100644 index 0000000..d7d6b4f --- /dev/null +++ b/lib/circle-w3s/api_response.rb @@ -0,0 +1,11 @@ +module CircleW3s + class ApiResponse + attr_reader :data, :code, :headers + + def initialize(data:, code:, headers:) + @data = data + @code = code + @headers = headers + end + end +end \ No newline at end of file diff --git a/lib/circle-w3s/client.rb b/lib/circle-w3s/client.rb index 15d8384..c299bcb 100644 --- a/lib/circle-w3s/client.rb +++ b/lib/circle-w3s/client.rb @@ -13,9 +13,6 @@ def get(path, params = {}) end def post(path, body = {}) - puts "Request URL: #{config.api_url}#{path}" - puts "Request Headers: #{connection.headers}" - puts "Request Body: #{body.inspect}" handle_response { connection.post(path, body) } end @@ -44,7 +41,7 @@ def connection faraday.request :json faraday.response :json faraday.adapter Faraday.default_adapter - + p config.api_key, "config.api_key" faraday.headers["Authorization"] = "Bearer #{config.api_key}" faraday.headers["Content-Type"] = "application/json" faraday.headers["Accept"] = "application/json" diff --git a/lib/circle-w3s/configuration.rb b/lib/circle-w3s/configuration.rb index 8baa20f..728c5bd 100644 --- a/lib/circle-w3s/configuration.rb +++ b/lib/circle-w3s/configuration.rb @@ -1,100 +1,237 @@ require 'logger' require 'uri' +require 'openssl' +require 'base64' module CircleW3s class Configuration - DEFAULT_BASE_PATH = "https://api.circle.com".freeze - DEFAULT_USER_AGENT = "CircleW3s Ruby Gem #{CircleW3s::VERSION}".freeze - - attr_accessor :api_key, :api_key_prefix - attr_accessor :username, :password - attr_accessor :access_token - attr_accessor :entity_secret - attr_accessor :public_key - attr_accessor :verify_ssl - attr_accessor :ssl_ca_cert - attr_accessor :cert_file - attr_accessor :key_file - attr_accessor :timeout - attr_accessor :debugging - attr_accessor :logger - attr_accessor :proxy - attr_accessor :proxy_headers - attr_writer :host - - def initialize - @host = DEFAULT_BASE_PATH - @verify_ssl = true - @timeout = 60 - @debugging = false - @api_key = {} - @api_key_prefix = {} - @logger = Logger.new($stdout) - @logger.level = Logger::WARN - reset_api_client - end + class << self + def default + @default ||= new + end - def host - uri = URI.parse(@host) - "#{uri.scheme}://#{uri.host}#{[uri.port].compact.join(':')}" + attr_writer :default end - def debugging=(debug_flag) - @debugging = debug_flag - if @debugging - @logger.level = Logger::DEBUG - else - @logger.level = Logger::WARN - end - end + attr_accessor :host, :base_path, :server_index, :server_operation_index, + :server_variables, :server_operation_variables, + :api_key, :api_key_prefix, :username, :password, + :access_token, :entity_secret, :public_key, + :verify_ssl, :ssl_ca_cert, :cert_file, :key_file, + :debugging, :logger, :temp_folder_path, + :datetime_format, :date_format, :proxy, :proxy_headers, + :connection_pool_size + + # @param [Hash] opts the options to create a new instance of Configuration + def initialize(opts = {}) + @host = opts[:host] || "https://api.circle.com" + @base_path = @host + @server_index = opts[:server_index] || 0 + @server_operation_index = opts[:server_operation_index] || {} + @server_variables = opts[:server_variables] || {} + @server_operation_variables = opts[:server_operation_variables] || {} + + # API Authentication + @api_key = opts[:api_key] || {} + @api_key_prefix = opts[:api_key_prefix] || {} + @username = opts[:username] + @password = opts[:password] + @access_token = opts[:api_key] + @entity_secret = opts[:entity_secret] + @public_key = opts[:public_key] + + # SSL/TLS + @verify_ssl = opts[:verify_ssl].nil? ? true : opts[:verify_ssl] + @ssl_ca_cert = opts[:ssl_ca_cert] + @cert_file = opts[:cert_file] + @key_file = opts[:key_file] + + # Logging + @debugging = opts[:debugging] || false + @logger = opts[:logger] || Logger.new($stdout) + @logger.level = @debugging ? Logger::DEBUG : Logger::WARN + + # HTTP + @proxy = opts[:proxy] + @proxy_headers = opts[:proxy_headers] || {} + @connection_pool_size = opts[:connection_pool_size] || (Etc.nprocessors * 5) + @temp_folder_path = opts[:temp_folder_path] + + # Date Formats + @datetime_format = opts[:datetime_format] || '%Y-%m-%dT%H:%M:%S.%L%z' + @date_format = opts[:date_format] || '%Y-%m-%d' + + @read_timeout = opts[:read_timeout] || 60 # 默认60秒 + @open_timeout = opts[:open_timeout] || 60 # 默认60秒 - def api_key_with_prefix(identifier) - if api_key[identifier] && api_key_prefix[identifier] - "#{api_key_prefix[identifier]} #{api_key[identifier]}" - elsif api_key[identifier] - api_key[identifier] - end end + def generate_entity_secret_ciphertext + public_key = fetch_public_key unless @public_key + cipher = OpenSSL::Cipher.new('aes-256-gcm') + cipher.encrypt + key = cipher.random_key + iv = cipher.random_iv + cipher.auth_data = "" + + encrypted = cipher.update(@entity_secret) + cipher.final + tag = cipher.auth_tag + + public_key_obj = OpenSSL::PKey::RSA.new(@public_key) + encrypted_key = public_key_obj.public_encrypt(key) + combined = encrypted_key + iv + tag + encrypted + Base64.strict_encode64(combined) + end + + # Get Basic Auth token + # @return [String] Basic Auth token def basic_auth_token - return unless username || password - - require 'base64' - Base64.strict_encode64("#{username}:#{password}") + return nil if @username.nil? || @password.nil? + ["#{@username}:#{@password}"].pack('m').delete("\r\n") end + # Get API key with prefix + # @param [String] identifier API key identifier + # @param [String] alias_name Alternative identifier + # @return [String] API key with prefix + def api_key_with_prefix(identifier, alias_name = nil) + key = @api_key[identifier] || @api_key[alias_name] + return nil unless key + + prefix = @api_key_prefix[identifier] + prefix ? "#{prefix} #{key}" : key + end + + # Get Auth Settings + # @return [Hash] Auth Settings def auth_settings - { - 'BearerAuth' => { + auth = {} + if @access_token + auth['BearerAuth'] = { type: :bearer, in: :header, format: 'PREFIX:ID:SECRET', key: 'Authorization', - value: "Bearer #{access_token}" - }.compact - }.select { |_, v| v[:value] } + value: "Bearer #{@access_token}" + } + end + auth + end + + # Get Host URL + # @param [Integer] index Server index + # @param [Hash] variables Server variables + # @return [String] Host URL + def get_host_from_settings(index = nil, variables = nil) + return @base_path if index.nil? + + variables ||= {} + servers = host_settings + + server = servers[index] || raise(ArgumentError, "Invalid index #{index}") + url = server[:url] + + # Replace variables in URL + server.fetch(:variables, {}).each do |name, variable| + value = variables[name] || variable[:default_value] + + if variable[:enum_values] && !variable[:enum_values].include?(value) + raise ArgumentError, "Invalid value #{value} for variable #{name}" + end + + url = url.gsub("{#{name}}", value.to_s) + end + + url + end + + def host=(value) + @host = value + @base_path = value + end + + def host + @host + end + + def api_key=(value) + @api_key = value + @access_token = value + end + + def api_key + @api_key end - def reset_api_client - @api_client = nil + def read_timeout + @read_timeout || 30 end + def open_timeout + @open_timeout || 30 + end + # Get Host Settings + # @return [Array] Host settings + def host_settings + [ + { + url: "https://api.circle.com", + description: "No description provided" + } + ] + end + + # Debug Report + # @return [String] Debug information def debug_report <<~REPORT Ruby SDK Debug Report: OS: #{RUBY_PLATFORM} Ruby Version: #{RUBY_VERSION} API Version: 1.0 - SDK Package Version: #{CircleW3s::VERSION} + SDK Package Version: 2.1.0 REPORT end - class << self - attr_accessor :default + def get_public_key + @public_key + end - def default - @default ||= Configuration.new + def set_public_key(key) + @public_key = key + end + + private + + def fetch_public_key + conn = Faraday.new(url: @host) do |f| + f.request :json + f.response :json + end + response = conn.get('/v1/w3s/config/entity/publicKey') do |req| + req.headers['Authorization'] = "Bearer #{@access_token}" + end + if response.success? + @public_key = response.body['data']['publicKey'] + else + raise "Failed to fetch public key: #{response.body}" end + @public_key + end + end + + class << self + def configure + Configuration.default ||= Configuration.new + yield(Configuration.default) + end + + def configuration + Configuration.default + end + + def reset + Configuration.default = Configuration.new end end end \ No newline at end of file diff --git a/lib/circle-w3s/models/create_wallet_request.rb b/lib/circle-w3s/models/create_wallet_request.rb new file mode 100644 index 0000000..b8266b6 --- /dev/null +++ b/lib/circle-w3s/models/create_wallet_request.rb @@ -0,0 +1,11 @@ +module CircleW3s + class CreateWalletRequest + attr_accessor :blockchain, :wallet_set_id, :name + + def initialize(blockchain:, wallet_set_id:, name:) + @blockchain = blockchain + @wallet_set_id = wallet_set_id + @name = name + end + end +end \ No newline at end of file diff --git a/lib/circle-w3s/models/create_wallet_set_request.rb b/lib/circle-w3s/models/create_wallet_set_request.rb new file mode 100644 index 0000000..46a0d42 --- /dev/null +++ b/lib/circle-w3s/models/create_wallet_set_request.rb @@ -0,0 +1,22 @@ +require 'securerandom' + +module CircleW3s + class CreateWalletSetRequest + attr_accessor :name, :idempotency_key, :entity_secret_ciphertext + + def initialize(name:, entity_secret_ciphertext: nil, idempotency_key: nil) + @name = name + @entity_secret_ciphertext = entity_secret_ciphertext + + @idempotency_key = idempotency_key || SecureRandom.uuid + end + + def to_json(*_args) + { + name: @name, + idempotencyKey: @idempotency_key, + entitySecretCiphertext: @entity_secret_ciphertext + }.compact.to_json + end + end +end \ No newline at end of file diff --git a/lib/circle-w3s/models/transaction.rb b/lib/circle-w3s/models/transaction.rb new file mode 100644 index 0000000..5311d86 --- /dev/null +++ b/lib/circle-w3s/models/transaction.rb @@ -0,0 +1,160 @@ +module CircleW3s + class Transaction + attr_accessor :id, :abi_function_signature, :abi_parameters, :amounts, + :amount_in_usd, :block_hash, :block_height, :blockchain, + :contract_address, :create_date, :custody_type, + :destination_address, :error_reason, :error_details, + :estimated_fee, :fee_level, :first_confirm_date, + :network_fee, :network_fee_in_usd, :nfts, :operation, + :ref_id, :source_address, :state, :token_id, + :transaction_type, :tx_hash, :update_date, :user_id, + :wallet_id, :transaction_screening_evaluation + + # @param attributes [Hash] Model attributes + def initialize(attributes = {}) + attributes = attributes.transform_keys(&:to_sym) + + @id = attributes[:id] + @abi_function_signature = attributes[:abiFunctionSignature] || attributes[:abi_function_signature] + @abi_parameters = parse_abi_parameters(attributes[:abiParameters] || attributes[:abi_parameters]) + @amounts = attributes[:amounts] + @amount_in_usd = attributes[:amountInUSD] || attributes[:amount_in_usd] + @block_hash = attributes[:blockHash] || attributes[:block_hash] + @block_height = attributes[:blockHeight] || attributes[:block_height] + @blockchain = attributes[:blockchain] + @contract_address = attributes[:contractAddress] || attributes[:contract_address] + @create_date = parse_datetime(attributes[:createDate] || attributes[:create_date]) + @custody_type = attributes[:custodyType] || attributes[:custody_type] + @destination_address = attributes[:destinationAddress] || attributes[:destination_address] + @error_reason = attributes[:errorReason] || attributes[:error_reason] + @error_details = attributes[:errorDetails] || attributes[:error_details] + @estimated_fee = parse_transaction_fee(attributes[:estimatedFee] || attributes[:estimated_fee]) + @fee_level = attributes[:feeLevel] || attributes[:fee_level] + @first_confirm_date = parse_datetime(attributes[:firstConfirmDate] || attributes[:first_confirm_date]) + @network_fee = attributes[:networkFee] || attributes[:network_fee] + @network_fee_in_usd = attributes[:networkFeeInUSD] || attributes[:network_fee_in_usd] + @nfts = attributes[:nfts] + @operation = attributes[:operation] + @ref_id = attributes[:refId] || attributes[:ref_id] + @source_address = attributes[:sourceAddress] || attributes[:source_address] + @state = attributes[:state] + @token_id = attributes[:tokenId] || attributes[:token_id] + @transaction_type = attributes[:transactionType] || attributes[:transaction_type] + @tx_hash = attributes[:txHash] || attributes[:tx_hash] + @update_date = parse_datetime(attributes[:updateDate] || attributes[:update_date]) + @user_id = attributes[:userId] || attributes[:user_id] + @wallet_id = attributes[:walletId] || attributes[:wallet_id] + @transaction_screening_evaluation = parse_screening_decision( + attributes[:transactionScreeningEvaluation] || attributes[:transaction_screening_evaluation] + ) + + validate! + end + + private + + def parse_datetime(value) + return nil if value.nil? + value.is_a?(Time) ? value : Time.parse(value) + end + + def parse_abi_parameters(params) + return nil if params.nil? + params.map { |param| AbiParametersInner.from_hash(param) } + end + + def parse_transaction_fee(fee) + return nil if fee.nil? + TransactionFee.from_hash(fee) + end + + def parse_screening_decision(decision) + return nil if decision.nil? + TransactionScreeningDecision.from_hash(decision) + end + + def validate! + raise ArgumentError, "id is required" if id.nil? + raise ArgumentError, "blockchain is required" if blockchain.nil? + raise ArgumentError, "create_date is required" if create_date.nil? + raise ArgumentError, "state is required" if state.nil? + raise ArgumentError, "transaction_type is required" if transaction_type.nil? + raise ArgumentError, "update_date is required" if update_date.nil? + + validate_user_id if user_id + validate_amounts if amounts + end + + def validate_user_id + unless user_id.is_a?(String) && user_id.length.between?(5, 50) + raise ArgumentError, "user_id must be a string between 5 and 50 characters" + end + end + + def validate_amounts + unless amounts.is_a?(Array) && amounts.length >= 1 + raise ArgumentError, "amounts must be an array with at least one element" + end + end + + public + + # @return [Hash] Returns the object in the form of hash + def to_hash + { + id: id, + abiFunctionSignature: abi_function_signature, + abiParameters: abi_parameters&.map(&:to_hash), + amounts: amounts, + amountInUSD: amount_in_usd, + blockHash: block_hash, + blockHeight: block_height, + blockchain: blockchain, + contractAddress: contract_address, + createDate: create_date&.iso8601, + custodyType: custody_type, + destinationAddress: destination_address, + errorReason: error_reason, + errorDetails: error_details, + estimatedFee: estimated_fee&.to_hash, + feeLevel: fee_level, + firstConfirmDate: first_confirm_date&.iso8601, + networkFee: network_fee, + networkFeeInUSD: network_fee_in_usd, + nfts: nfts, + operation: operation, + refId: ref_id, + sourceAddress: source_address, + state: state, + tokenId: token_id, + transactionType: transaction_type, + txHash: tx_hash, + updateDate: update_date&.iso8601, + userId: user_id, + walletId: wallet_id, + transactionScreeningEvaluation: transaction_screening_evaluation&.to_hash + }.compact + end + + # @return [String] Returns the JSON string representation of the object + def to_json(*_args) + to_hash.to_json + end + + # @param [String] json_string JSON string + # @return [Transaction] Returns the Transaction instance + def self.from_json(json_string) + new(JSON.parse(json_string)) + end + + # @param [Hash] hash The Hash to create an instance from + # @return [Transaction] Returns the Transaction instance + def self.from_hash(hash) + new(hash) + end + + def to_s + to_hash.to_s + end + end +end \ No newline at end of file diff --git a/lib/circle-w3s/models/wallet.rb b/lib/circle-w3s/models/wallet.rb new file mode 100644 index 0000000..59324db --- /dev/null +++ b/lib/circle-w3s/models/wallet.rb @@ -0,0 +1,92 @@ +module CircleW3s + class Wallet + attr_accessor :id, :address, :blockchain, :create_date, :update_date, + :custody_type, :name, :ref_id, :state, :user_id, :wallet_set_id + + # @param attributes [Hash] Model attributes in the form of hash + def initialize(attributes = {}) + # Convert keys to symbols + attributes = attributes.transform_keys(&:to_sym) + + @id = attributes[:id] + @address = attributes[:address] + @blockchain = attributes[:blockchain] + @create_date = attributes[:createDate] || attributes[:create_date] + @update_date = attributes[:updateDate] || attributes[:update_date] + @custody_type = attributes[:custodyType] || attributes[:custody_type] + @name = attributes[:name] + @ref_id = attributes[:refId] || attributes[:ref_id] + @state = attributes[:state] + @user_id = attributes[:userId] || attributes[:user_id] + @wallet_set_id = attributes[:walletSetId] || attributes[:wallet_set_id] + + validate! + end + + # Validates the required properties + # @raise [ArgumentError] if required properties are missing + def validate! + raise ArgumentError, "id is required" if id.nil? + raise ArgumentError, "address is required" if address.nil? + raise ArgumentError, "blockchain is required" if blockchain.nil? + raise ArgumentError, "create_date is required" if create_date.nil? + raise ArgumentError, "update_date is required" if update_date.nil? + raise ArgumentError, "custody_type is required" if custody_type.nil? + raise ArgumentError, "state is required" if state.nil? + raise ArgumentError, "wallet_set_id is required" if wallet_set_id.nil? + + validate_user_id if user_id + end + + # Validates user_id format + def validate_user_id + unless user_id.is_a?(String) && user_id.length.between?(5, 50) + raise ArgumentError, "user_id must be a string between 5 and 50 characters" + end + end + + # Convert to JSON string + # @return [String] JSON string representation of the object + def to_json(*_args) + to_hash.to_json + end + + # Convert to Hash + # @return [Hash] Returns the object in the form of hash + def to_hash + { + id: id, + address: address, + blockchain: blockchain, + createDate: create_date, + updateDate: update_date, + custodyType: custody_type, + name: name, + refId: ref_id, + state: state, + userId: user_id, + walletSetId: wallet_set_id + }.compact + end + + # Creates an instance of Wallet from a JSON string + # @param [String] json_string JSON string + # @return [Wallet] Instance of Wallet + def self.from_json(json_string) + new(JSON.parse(json_string)) + end + + # Creates an instance of Wallet from a Hash + # @param [Hash] hash Hash containing attributes + # @return [Wallet] Instance of Wallet + def self.from_hash(hash) + new(hash) + end + + # Returns string representation of the object + # @return [String] String representation of the object + def to_s + to_hash.to_s + end + end +end \ No newline at end of file diff --git a/lib/circle-w3s/models/wallet_set.rb b/lib/circle-w3s/models/wallet_set.rb new file mode 100644 index 0000000..722d6f4 --- /dev/null +++ b/lib/circle-w3s/models/wallet_set.rb @@ -0,0 +1,69 @@ +module CircleW3s + class WalletSet + attr_accessor :id, :create_date, :update_date + + # Initialize a new WalletSet instance + # @param attributes [Hash] Model attributes in the form of hash + def initialize(attributes = {}) + # Convert keys to symbols + attributes = attributes.transform_keys(&:to_sym) + + @id = attributes[:id] + @create_date = parse_datetime(attributes[:createDate] || attributes[:create_date]) + @update_date = parse_datetime(attributes[:updateDate] || attributes[:update_date]) + + validate! + end + + private + + def parse_datetime(value) + return nil if value.nil? + value.is_a?(Time) ? value : Time.parse(value) + end + + def validate! + raise ArgumentError, "id is required" if id.nil? + raise ArgumentError, "create_date is required" if create_date.nil? + raise ArgumentError, "update_date is required" if update_date.nil? + end + + public + + # Convert instance to JSON string + # @return [String] JSON string representation of the object + def to_json(*_args) + to_hash.to_json + end + + # Convert instance to Hash + # @return [Hash] Returns the object in the form of hash + def to_hash + { + id: id, + createDate: create_date&.iso8601, + updateDate: update_date&.iso8601 + }.compact + end + + # Create an instance of WalletSet from a JSON string + # @param [String] json_string JSON string + # @return [WalletSet] Instance of WalletSet + def self.from_json(json_string) + new(JSON.parse(json_string)) + end + + # Create an instance of WalletSet from a Hash + # @param [Hash] hash Hash containing attributes + # @return [WalletSet] Instance of WalletSet + def self.from_hash(hash) + new(hash) + end + + # String representation of the object + # @return [String] String representation of the object + def to_s + to_hash.to_s + end + end +end \ No newline at end of file diff --git a/lib/circle_wallets/api/wallets_api.rb b/lib/circle_wallets/api/wallets_api.rb deleted file mode 100644 index 2015a37..0000000 --- a/lib/circle_wallets/api/wallets_api.rb +++ /dev/null @@ -1,142 +0,0 @@ -module CircleW3s - class WalletsApi - attr_reader :api_client - - def initialize(api_client = ApiClient.default) - @api_client = api_client - end - - # Creates a new developer-controlled wallet or batch of wallets - def create_wallet(create_wallet_request, x_request_id: nil) - raise ArgumentError, "create_wallet_request is required" if create_wallet_request.nil? - - headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' } - headers['X-Request-Id'] = x_request_id if x_request_id - - response = api_client.call_api( - path: '/v1/w3s/developer/wallets', - http_method: :post, - headers: headers, - body: create_wallet_request, - auth_names: ['BearerAuth'], - return_type: 'Wallets' - ) - response.data - end - - # Retrieves an existing wallet by ID - def get_wallet(id, x_request_id: nil) - raise ArgumentError, "id is required" if id.nil? - - headers = { 'Accept' => 'application/json' } - headers['X-Request-Id'] = x_request_id if x_request_id - - response = api_client.call_api( - path: '/v1/w3s/wallets/{id}', - http_method: :get, - path_params: { 'id' => id }, - headers: headers, - auth_names: ['BearerAuth'], - return_type: 'WalletResponse' - ) - response.data - end - - # Lists all wallets with optional filters - def get_wallets(address: nil, blockchain: nil, wallet_set_id: nil, ref_id: nil, - from: nil, to: nil, page_before: nil, page_after: nil, page_size: nil) - query_params = {} - query_params[:address] = address if address - query_params[:blockchain] = blockchain if blockchain - query_params[:walletSetId] = wallet_set_id if wallet_set_id - query_params[:refId] = ref_id if ref_id - query_params[:from] = from.iso8601 if from - query_params[:to] = to.iso8601 if to - query_params[:pageBefore] = page_before if page_before - query_params[:pageAfter] = page_after if page_after - query_params[:pageSize] = page_size if page_size - - response = api_client.call_api( - path: '/v1/w3s/wallets', - http_method: :get, - query_params: query_params, - headers: { 'Accept' => 'application/json' }, - auth_names: ['BearerAuth'], - return_type: 'Wallets' - ) - response.data - end - - # Gets token balance for a wallet - def list_wallet_balance(id, include_all: nil, name: nil, token_address: nil, - standard: nil, page_before: nil, page_after: nil, page_size: nil) - raise ArgumentError, "id is required" if id.nil? - - query_params = {} - query_params[:includeAll] = include_all unless include_all.nil? - query_params[:name] = name if name - query_params[:tokenAddress] = token_address if token_address - query_params[:standard] = standard if standard - query_params[:pageBefore] = page_before if page_before - query_params[:pageAfter] = page_after if page_after - query_params[:pageSize] = page_size if page_size - - response = api_client.call_api( - path: '/v1/w3s/wallets/{id}/balances', - http_method: :get, - path_params: { 'id' => id }, - query_params: query_params, - headers: { 'Accept' => 'application/json' }, - auth_names: ['BearerAuth'], - return_type: 'Balances' - ) - response.data - end - - # Gets NFTs for a wallet - def list_wallet_nfts(id, include_all: nil, name: nil, token_address: nil, - standard: nil, page_before: nil, page_after: nil, page_size: nil) - raise ArgumentError, "id is required" if id.nil? - - query_params = {} - query_params[:includeAll] = include_all unless include_all.nil? - query_params[:name] = name if name - query_params[:tokenAddress] = token_address if token_address - query_params[:standard] = standard if standard - query_params[:pageBefore] = page_before if page_before - query_params[:pageAfter] = page_after if page_after - query_params[:pageSize] = page_size if page_size - - response = api_client.call_api( - path: '/v1/w3s/wallets/{id}/nfts', - http_method: :get, - path_params: { 'id' => id }, - query_params: query_params, - headers: { 'Accept' => 'application/json' }, - auth_names: ['BearerAuth'], - return_type: 'Nfts' - ) - response.data - end - - # Updates a wallet's metadata - def update_wallet(id, update_wallet_request, x_request_id: nil) - raise ArgumentError, "id is required" if id.nil? - raise ArgumentError, "update_wallet_request is required" if update_wallet_request.nil? - - headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' } - headers['X-Request-Id'] = x_request_id if x_request_id - - response = api_client.call_api( - path: '/v1/w3s/wallets/{id}', - http_method: :put, - path_params: { 'id' => id }, - headers: headers, - body: update_wallet_request, - auth_names: ['BearerAuth'], - return_type: 'WalletResponse' - ) - response.data - end - end -end \ No newline at end of file