near 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ab3d2b8796c8b9d6db6d842a843882a02b1497070454e67dda41a2105dbe7aa
4
- data.tar.gz: 66de38abb9b8bd7162116f110ae2a3f81412f7e580bb2e479db5133d2504f725
3
+ metadata.gz: 147f21ee313f56cb2e1327e8817df79796dd56fe90eff771e9057e6d7248c4f5
4
+ data.tar.gz: bf81f3240cb7e18c519d85696bf472b1bf2c8e56c3049aa942fe2aaae37e38f6
5
5
  SHA512:
6
- metadata.gz: 8cead3d3f26398a34925d6ba326c981f19be8e4324348f7dd8879a1c291ad11bd80080c11023477e3f4ee5bbfcf5bddfc421e85bea05f8e42acfd672e28bea0f
7
- data.tar.gz: f11b1a7a3c795865375feb3c873e9158009b6508a06150d91b3dff126b074920b2c91e658c6bc121e91098f64c198e47aaff0a7ee1348adb1e39364212eb2db5
6
+ metadata.gz: 4cfef8be7777c9dd2f2f839284581a44a0f2479f256f09e759628c21509a6d3bc9b95ad07c7e0e13a2e8c22aa5238bd826a224cdac9c6e649388bd060971569d
7
+ data.tar.gz: 4c5e69608fe79e7a1e0f7a9046477ea9c590d61d1ee6bf12b530054775fc26464d68ddd29c6c9655f05459faf2752a4c7119e5a29afb17a2500ba115618d0b3d
data/CHANGES.md CHANGED
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 0.3.0 - 2025-02-02
9
+
10
+ ### Added
11
+ - Implement block data accessors
12
+ - Implement transaction data accessors
13
+ - Implement action parsing
14
+
15
+ ### Changed
16
+ - Adopt Faraday as the HTTP client
17
+ - Improve HTTP error handling and retries
18
+
19
+ ## 0.2.0 - 2025-01-29
20
+
21
+ ### Added
22
+ - `Network#fetch(block)`
23
+ - `NEAR.testnet`, `NEAR.mainnet`
24
+
25
+ ### Changed
26
+ - Use the NEAR_CLI environmnet variable, if set
27
+ - Use a temporary file for `call_function`
28
+ - Enhance `call_function()`
29
+ - Enhance `create_account_with_funding()`
30
+ - Enhance `delete_account()`
31
+
32
+ ### Fixed
33
+ - Fix the formatting of balances
34
+
8
35
  ## 0.1.0 - 2025-01-19
9
36
 
10
37
  ## 0.0.0 - 2025-01-11
data/README.md CHANGED
@@ -6,10 +6,12 @@
6
6
  [![Documentation](https://img.shields.io/badge/rubydoc-latest-blue)](https://rubydoc.info/gems/near)
7
7
 
8
8
  **NEAR.rb** is a [Ruby] client library for the [NEAR Protocol].
9
- It wraps the [NEAR command-line interface] (CLI) into a Ruby interface.
9
+ It provides a [neardata.xyz] API client as well as wraps the
10
+ [NEAR command-line interface] (CLI) in a usable Ruby interface.
10
11
 
11
12
  ## ✨ Features
12
13
 
14
+ - Fetches block data from the [neardata.xyz] API.
13
15
  - Wraps the complete CLI features in an idiomatic Ruby interface.
14
16
  - Provides comprehensive account management operations.
15
17
  - Supports token operations for NEAR and other assets.
@@ -23,7 +25,7 @@ It wraps the [NEAR command-line interface] (CLI) into a Ruby interface.
23
25
 
24
26
  ## 🛠️ Prerequisites
25
27
 
26
- - [NEAR CLI] 0.17+
28
+ - [NEAR CLI] 0.19+
27
29
  - [Ruby] 3.0+
28
30
 
29
31
  ## ⬇️ Installation
@@ -36,16 +38,100 @@ gem install near
36
38
 
37
39
  ## 👉 Examples
38
40
 
41
+ - [Importing the library](#importing-the-library)
42
+ - [Fetching block information](#fetching-block-information)
43
+ - [Tracking block production](#tracking-block-production)
44
+ - [Tracking chain transactions](#tracking-chain-transactions)
45
+ - [Tracking chain actions](#tracking-chain-actions)
46
+ - [Tracking contract interactions](#tracking-contract-interactions)
47
+ - [Instantiating the CLI wrapper](#instantiating-the-cli-wrapper)
48
+ - [Viewing account details](#viewing-account-details)
49
+ - [Importing accounts](#importing-accounts)
50
+ - [Creating accounts](#creating-accounts)
51
+ - [Deleting accounts](#deleting-accounts)
52
+ - [Managing access keys](#managing-access-keys)
53
+ - [Managing tokens](#managing-tokens)
54
+ - [Calling read-only contract methods](#calling-read-only-contract-methods)
55
+ - [Calling mutative contract methods](#calling-mutative-contract-methods)
56
+ - [Deploying a smart contract](#deploying-a-smart-contract)
57
+
39
58
  ### Importing the library
40
59
 
41
60
  ```ruby
42
61
  require 'near'
62
+ ```
63
+
64
+ ### Fetching block information
65
+
66
+ Block data is fetched from the high-performance [neardata.xyz] API
67
+ that caches blocks using Cloudflare's network with more than 330
68
+ global edge locations:
69
+
70
+ ```ruby
71
+ block = NEAR.testnet.fetch(186_132_854)
72
+ ```
73
+
74
+ ### Tracking block production
75
+
76
+ Thanks to the built-in [neardata.xyz] API client, it is trivial to
77
+ track the current tip of the chain in a robust and efficient manner:
78
+
79
+ ```ruby
80
+ NEAR.testnet.fetch_blocks do |block|
81
+ puts [block.height, block.hash].join("\t")
82
+ end
83
+ ```
84
+
85
+ ### Tracking chain transactions
86
+
87
+ See [`examples/monitor_all_transactions.rb`](examples/monitor_all_transactions.rb).
88
+
89
+ ```ruby
90
+ NEAR.testnet.fetch_blocks do |block|
91
+ puts block.inspect
43
92
 
93
+ block.each_transaction do |transaction|
94
+ puts "\t" + transaction.inspect
95
+ end
96
+ end
97
+ ```
98
+
99
+ ### Tracking chain actions
100
+
101
+ See [`examples/monitor_all_actions.rb`](examples/monitor_all_actions.rb).
102
+
103
+ ```ruby
104
+ NEAR.testnet.fetch_blocks do |block|
105
+ puts block.inspect
106
+
107
+ block.each_action do |action|
108
+ puts "\t" + action.inspect
109
+ end
110
+ end
111
+ ```
112
+
113
+ ### Tracking contract interactions
114
+
115
+ See [`examples/index_evm_transactions.rb`](examples/index_evm_transactions.rb).
116
+
117
+ ```ruby
118
+ NEAR.testnet.fetch_blocks do |block|
119
+ puts block.inspect
120
+
121
+ block.find_actions(:FunctionCall, receiver: 'aurora', method_name: /^submit/) do |action|
122
+ puts "\t" + action.inspect
123
+ end
124
+ end
125
+ ```
126
+
127
+ ### Instantiating the CLI wrapper
128
+
129
+ ```ruby
44
130
  # Use the NEAR testnet (the default):
45
- testnet = NEAR::CLI.new(network: 'testnet')
131
+ testnet = NEAR::CLI.new(network: NEAR.testnet)
46
132
 
47
133
  # Use the NEAR mainnet (to test in prod):
48
- # mainnet = NEAR::CLI.new(network: 'mainnet')
134
+ # mainnet = NEAR::CLI.new(network: NEAR.mainnet)
49
135
  ```
50
136
 
51
137
  ### Viewing account details
@@ -230,3 +316,4 @@ git clone https://github.com/dryruby/near.rb.git
230
316
  [Ruby]: https://ruby-lang.org
231
317
  [mainnet]: https://docs.near.org/concepts/basics/networks#mainnet
232
318
  [testnet]: https://docs.near.org/concepts/basics/networks#testnet
319
+ [neardata.xyz]: https://neardata.xyz
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.3.0
data/lib/near/account.rb CHANGED
@@ -5,12 +5,23 @@
5
5
  #
6
6
  # @see https://nomicon.io/DataStructures/Account
7
7
  class NEAR::Account
8
+ def self.parse(id)
9
+ self.new(id.to_s)
10
+ end
11
+
8
12
  ##
9
13
  # @param [String, #to_s] id
10
14
  def initialize(id)
11
15
  @id = id.to_s
12
16
  end
13
17
 
18
+ ##
19
+ # @return [NEAR::Account]
20
+ def parent
21
+ return nil unless @id.include?('.')
22
+ self.class.new(@id.split('.').drop(1).join('.'))
23
+ end
24
+
14
25
  ##
15
26
  # The account name.
16
27
  #
@@ -0,0 +1,141 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ require 'base64'
4
+ require 'json'
5
+
6
+ ##
7
+ # Represents a NEAR action.
8
+ #
9
+ # @see https://nomicon.io/RuntimeSpec/Actions
10
+ class NEAR::Action
11
+ class CreateAccount < NEAR::Action; end
12
+
13
+ class DeployContract < NEAR::Action; end
14
+
15
+ class FunctionCall < NEAR::Action
16
+ ##
17
+ # @return [String]
18
+ attr_reader :method_name
19
+ def method_name
20
+ @data['method_name']
21
+ end
22
+
23
+ ##
24
+ # @return [String]
25
+ def json_args
26
+ @json_args ||= JSON.parse(self.args)
27
+ end
28
+
29
+ ##
30
+ # @param [Symbol, String, #to_s] key
31
+ # @return [Object]
32
+ def [](key)
33
+ self.json_args[key.to_s] rescue nil
34
+ end
35
+
36
+ ##
37
+ # @return [String]
38
+ attr_reader :args
39
+ def args
40
+ @args ||= Base64.decode64(@data['args'])
41
+ end
42
+
43
+ ##
44
+ # @return [Integer]
45
+ attr_reader :gas
46
+ def gas
47
+ @data['gas']
48
+ end
49
+
50
+ ##
51
+ # @return [NEAR::Balance]
52
+ attr_reader :deposit
53
+ def deposit
54
+ @deposit ||= NEAR::Balance.parse(@data['deposit'])
55
+ end
56
+ end
57
+
58
+ class Transfer < NEAR::Action
59
+ ##
60
+ # @return [NEAR::Balance]
61
+ attr_reader :deposit
62
+ def deposit
63
+ @deposit ||= NEAR::Balance.parse(@data['deposit'])
64
+ end
65
+ end
66
+
67
+ class Stake < NEAR::Action; end
68
+
69
+ class AddKey < NEAR::Action; end
70
+
71
+ class DeleteKey < NEAR::Action; end
72
+
73
+ class DeleteAccount < NEAR::Action; end
74
+
75
+ class Delegate < NEAR::Action; end
76
+
77
+ ##
78
+ # @param [String, Hash] data
79
+ # @return [NEAR::Action]
80
+ def self.parse(data)
81
+ case
82
+ when data.is_a?(String) then case
83
+ when 'CreateAccount' then CreateAccount.new
84
+ when 'DeployContract' then DeployContract.new
85
+ when 'FunctionCall' then FunctionCall.new
86
+ when 'Transfer' then Transfer.new
87
+ when 'Stake' then Stake.new
88
+ when 'AddKey' then AddKey.new
89
+ when 'DeleteKey' then DeleteKey.new
90
+ when 'DeleteAccount' then DeleteAccount.new
91
+ when 'Delegate' then Delegate.new
92
+ else self.new
93
+ end
94
+ when data['CreateAccount'] then CreateAccount.new(data['CreateAccount'])
95
+ when data['DeployContract'] then DeployContract.new(data['DeployContract'])
96
+ when data['FunctionCall'] then FunctionCall.new(data['FunctionCall'])
97
+ when data['Transfer'] then Transfer.new(data['Transfer'])
98
+ when data['Stake'] then Stake.new(data['Stake'])
99
+ when data['AddKey'] then AddKey.new(data['AddKey'])
100
+ when data['DeleteKey'] then DeleteKey.new(data['DeleteKey'])
101
+ when data['DeleteAccount'] then DeleteAccount.new(data['DeleteAccount'])
102
+ when data['Delegate'] then Delegate.new(data['Delegate'])
103
+ else self.new(data[data.keys.first])
104
+ end
105
+ end
106
+
107
+ ##
108
+ # @param [Hash, #to_h] data
109
+ def initialize(data = nil)
110
+ @data = data.to_h if data
111
+ end
112
+
113
+ ##
114
+ # @return [Symbol]
115
+ attr_reader :type
116
+ def type
117
+ self.class.name.split('::').last.to_sym
118
+ end
119
+
120
+ ##
121
+ # The action data.
122
+ #
123
+ # @return [Hash]
124
+ attr_reader :data
125
+
126
+ ##
127
+ # @return [Hash]
128
+ def to_h; @data || {}; end
129
+
130
+ ##
131
+ # @return [Symbol]
132
+ def to_sym; self.type; end
133
+
134
+ ##
135
+ # @return [String]
136
+ def inspect
137
+ data = self.to_h
138
+ data['args'] = '...' if data['args']
139
+ "#<#{self.class.name} #{data.to_json}>"
140
+ end
141
+ end # NEAR::Block
data/lib/near/balance.rb CHANGED
@@ -5,6 +5,10 @@ require 'bigdecimal'
5
5
  ##
6
6
  # Represents a NEAR balance.
7
7
  class NEAR::Balance
8
+ def self.parse(s)
9
+ self.new(s.to_f / 10**24)
10
+ end
11
+
8
12
  def self.from_near(s)
9
13
  self.new(s)
10
14
  end
@@ -23,19 +27,28 @@ class NEAR::Balance
23
27
  end
24
28
  end
25
29
 
30
+ ##
31
+ # The canonical zero balance.
32
+ ZERO = self.new(0).freeze
33
+
26
34
  ##
27
35
  # The balance as a Ⓝ-prefixed string.
28
36
  #
29
37
  # @return [String]
30
38
  def inspect
31
- "Ⓝ#{@quantity.to_f}"
39
+ "Ⓝ #{@quantity.to_f}"
32
40
  end
33
41
 
34
42
  ##
35
43
  # The balance as a string.
36
44
  #
37
45
  # @return [String]
38
- def to_s; @quantity.to_s; end
46
+ def to_s
47
+ case @quantity
48
+ when BigDecimal then @quantity.to_s('F')
49
+ else @quantity.to_s
50
+ end
51
+ end
39
52
 
40
53
  ##
41
54
  # The balance as a rational number.
data/lib/near/block.rb CHANGED
@@ -26,9 +26,11 @@ class NEAR::Block
26
26
  ##
27
27
  # @param [Integer, #to_i] height
28
28
  # @param [String, #to_s] hash
29
- def initialize(height: nil, hash: nil)
29
+ # @param [Hash, #to_h] data
30
+ def initialize(height: nil, hash: nil, data: nil)
30
31
  @height = height.to_i if height
31
32
  @hash = hash.to_s if hash
33
+ @data = data.to_h if data
32
34
  end
33
35
 
34
36
  ##
@@ -43,6 +45,133 @@ class NEAR::Block
43
45
  # @return [String]
44
46
  attr_reader :hash
45
47
 
48
+ ##
49
+ # The block data, if fetched.
50
+ #
51
+ # @return [Hash]
52
+ attr_reader :data
53
+
54
+ ##
55
+ # @return [String]
56
+ attr_reader :author
57
+ def author
58
+ self.data['block']['author']
59
+ end
60
+
61
+ ##
62
+ # The block header.
63
+ #
64
+ # @return [Hash]
65
+ attr_reader :header
66
+ def header
67
+ self.data['block']['header']
68
+ end
69
+
70
+ ##
71
+ # @return [Array<Hash>]
72
+ attr_reader :chunks
73
+ def chunks
74
+ self.data['block']['chunks']
75
+ end
76
+
77
+ ##
78
+ # @return [Array<Hash>]
79
+ attr_reader :shards
80
+ def shards
81
+ self.data['shards']
82
+ end
83
+
84
+ ##
85
+ # The set of signers in the transactions in this block.
86
+ #
87
+ # @return [Array<NEAR::Account>]
88
+ def signers
89
+ self.collect_transaction_field('signer_id')
90
+ .map { |id| NEAR::Account.new(id) }
91
+ end
92
+
93
+ ##
94
+ # The set of receivers in the transactions in this block.
95
+ #
96
+ # @return [Array<NEAR::Account>]
97
+ def receivers
98
+ self.collect_transaction_field('receiver_id')
99
+ .map { |id| NEAR::Account.new(id) }
100
+ end
101
+
102
+ ##
103
+ # Enumerates the transactions in this block.
104
+ #
105
+ # @yield [NEAR::Transaction]
106
+ # @yieldparam [NEAR::Transaction] transaction
107
+ # @yieldreturn [void]
108
+ # @return [Enumerator] if no block is given
109
+ def each_transaction(&)
110
+ return enum_for(:each_transaction) unless block_given?
111
+ self.each_chunk do |chunk|
112
+ next if !chunk['transactions']
113
+ chunk['transactions'].each do |tx|
114
+ yield NEAR::Transaction.parse(tx)
115
+ end
116
+ end
117
+ end
118
+
119
+ ##
120
+ # Enumerates the actions in this block.
121
+ #
122
+ # @yield [NEAR::Action, NEAR::Transaction]
123
+ # @yieldparam [NEAR::Action] action
124
+ # @yieldparam [NEAR::Transaction] transaction
125
+ # @yieldreturn [void]
126
+ # @return [Enumerator] if no block is given
127
+ def each_action(&)
128
+ return enum_for(:each_action) unless block_given?
129
+ self.each_transaction do |tx|
130
+ tx.each_action do |action|
131
+ yield action, tx
132
+ end
133
+ end
134
+ end
135
+
136
+ ##
137
+ # @param [Symbol, #to_sym] type
138
+ # @param [String, Regexp] signer
139
+ # @param [String, Regexp] receiver
140
+ # @param [String, Regexp] method_name
141
+ # @yield [NEAR::Action, NEAR::Transaction]
142
+ # @yieldparam [NEAR::Action] action
143
+ # @yieldparam [NEAR::Transaction] transaction
144
+ # @yieldreturn [void]
145
+ # @return [Enumerator] if no block is given
146
+ def find_actions(type, signer: nil, receiver: nil, method_name: nil, &)
147
+ return enum_for(:each_action) unless block_given?
148
+ type = type.to_sym
149
+ self.each_transaction do |tx|
150
+ next if signer && !(signer === tx.signer_id)
151
+ next if receiver && !(receiver === tx.receiver_id)
152
+ tx.each_action do |action|
153
+ next unless type == action.type
154
+ next if method_name && !(method_name === action.method_name)
155
+ yield action, tx
156
+ end
157
+ end
158
+ end
159
+
160
+ ##
161
+ # Enumerates the chunks in this block.
162
+ #
163
+ # @yield [Hash]
164
+ # @yieldparam [Hash] chunk
165
+ # @yieldreturn [void]
166
+ # @return [Enumerator] if no block is given
167
+ def each_chunk(&)
168
+ return enum_for(:each_chunk) unless block_given?
169
+ self.shards.each do |shard|
170
+ chunk = shard['chunk']
171
+ yield chunk if chunk
172
+ end
173
+ end
174
+
46
175
  ##
47
176
  # @return [Array<String>]
48
177
  def to_cli_args
@@ -51,9 +180,38 @@ class NEAR::Block
51
180
  ['now']
52
181
  end
53
182
 
183
+ ##
184
+ # @return [Integer]
185
+ def to_i; @height; end
186
+
54
187
  ##
55
188
  # @return [String]
56
189
  def to_s
57
190
  (@height || @hash || :now).to_s
58
191
  end
192
+
193
+ ##
194
+ # @return [Hash]
195
+ def to_h; @data; end
196
+
197
+ ##
198
+ # @return [String]
199
+ def inspect
200
+ "#<#{self.class.name} height: #{@height}, hash: #{@hash.inspect}>"
201
+ end
202
+
203
+ protected
204
+
205
+ ##
206
+ # @param [String] field
207
+ # @return [Array<String>]
208
+ def collect_transaction_field(field)
209
+ result = {}
210
+ self.shards.each do |shard|
211
+ shard['chunk']['transactions'].each do |tx|
212
+ result[tx['transaction']['receiver_id']] = true
213
+ end
214
+ end
215
+ result.keys
216
+ end
59
217
  end # NEAR::Block
@@ -88,18 +88,17 @@ module NEAR::CLI::Account
88
88
  ##
89
89
  # Creates a new account funded by another account.
90
90
  #
91
- # @param [String] new_account_id
92
- # @param [String] funding_account_id
93
- # @param [String] public_key
94
- # @param [String] deposit
91
+ # @param [NEAR::Account] new_account
92
+ # @param [NEAR::Account] signer Account that signs & funds the transaction
93
+ # @param [NEAR::Balance] deposit Amount of NEAR to attach
95
94
  # @return [String]
96
- def create_account_with_funding(new_account_id, funding_account_id, public_key, deposit)
95
+ def create_account_with_funding(new_account, signer:, deposit: nil)
97
96
  stdout, stderr = execute(
98
97
  'account',
99
98
  'create-account',
100
- 'fund-myself', new_account_id, deposit,
101
- 'use-manually-provided-public-key', public_key,
102
- 'sign-as', funding_account_id,
99
+ 'fund-myself', new_account.to_s, (deposit ? deposit.to_s : '0') + ' NEAR',
100
+ 'autogenerate-new-keypair', 'save-to-keychain',
101
+ 'sign-as', signer.to_s,
103
102
  'network-config', @network,
104
103
  'sign-with-keychain',
105
104
  'send'
@@ -126,14 +125,15 @@ module NEAR::CLI::Account
126
125
  ##
127
126
  # Deletes an account and transfers remaining balance to beneficiary.
128
127
  #
129
- # @param [String] account_id
130
- # @param [String] beneficiary_id
128
+ # @param [NEAR::Account] account
129
+ # @param [NEAR::Account] beneficiary
131
130
  # @return [String]
132
- def delete_account(account_id, beneficiary_id)
131
+ def delete_account(account, beneficiary: nil)
132
+ account = NEAR::Account.parse(account)
133
133
  stdout, stderr = execute(
134
134
  'account',
135
- 'delete-account', account_id,
136
- 'beneficiary', beneficiary_id,
135
+ 'delete-account', account.to_s,
136
+ 'beneficiary', (beneficiary || account.parent).to_s,
137
137
  'network-config', @network,
138
138
  'sign-with-keychain',
139
139
  'send'
@@ -1,21 +1,24 @@
1
1
  # This is free and unencumbered software released into the public domain.
2
2
 
3
+ require "base64"
4
+ require "pathname"
5
+
3
6
  ##
4
7
  # @see https://github.com/near/near-cli-rs/blob/main/docs/GUIDE.en.md#contract---Manage-smart-contracts-deploy-code-call-functions
5
8
  module NEAR::CLI::Contract
6
9
  ##
7
10
  # Calls a view method on a contract (read-only).
8
11
  #
9
- # @param [String] contract_id
12
+ # @param [NEAR::Account] contract
10
13
  # @param [String] method_name
11
14
  # @param [Hash] args JSON arguments for the method
12
15
  # @param [Block, Integer, String, Symbol] block
13
16
  # @return [String] The method call result
14
- def view_call(contract_id, method_name, args = {}, block: :now)
17
+ def view_call(contract, method_name, args = {}, block: :now)
15
18
  stdout, _ = execute(
16
19
  'contract',
17
20
  'call-function',
18
- 'as-read-only', contract_id, method_name,
21
+ 'as-read-only', contract.to_s, method_name,
19
22
  'json-args', args.to_json,
20
23
  'network-config', @network,
21
24
  *block_args(block)
@@ -26,22 +29,31 @@ module NEAR::CLI::Contract
26
29
  ##
27
30
  # Calls a state-changing method on a contract.
28
31
  #
29
- # @param [String] contract_id
32
+ # @param [NEAR::Account] contract
30
33
  # @param [String] method_name
31
- # @param [Hash] args JSON arguments for the method
32
- # @param [String] signer_id Account that signs the transaction
33
- # @param [String] deposit Amount of NEAR to attach
34
- # @param [String] gas Amount of gas to attach
34
+ # @param [Hash, Array, String, Pathname] args Arguments for the method
35
+ # @param [NEAR::Account] signer Account that signs the transaction
36
+ # @param [NEAR::Balance] deposit Amount of NEAR to attach
37
+ # @param [NEAR::Gas, #to_s] gas Amount of gas to attach
35
38
  # @return [String] Transaction result
36
- def call_function(contract_id, method_name, args = {}, signer_id:, deposit: '0 NEAR', gas: '30 TGas')
39
+ def call_function(contract, method_name, args = {}, signer:, deposit: nil, gas: '100.0 Tgas')
40
+ args = case args
41
+ when Hash, Array then ['json-args', args.to_json]
42
+ when String then case
43
+ when args.ascii_only? then ['text-args', args]
44
+ else ['base64-args', Base64.strict_encode64(args)]
45
+ end
46
+ when Pathname then ['file-args', args.to_s]
47
+ else raise ArgumentError, "Invalid argument type: #{args.inspect}"
48
+ end
37
49
  stdout, stderr = execute(
38
50
  'contract',
39
51
  'call-function',
40
- 'as-transaction', contract_id, method_name,
41
- 'json-args', args.to_json,
42
- 'prepaid-gas', gas,
43
- 'attached-deposit', deposit,
44
- 'sign-as', signer_id,
52
+ 'as-transaction', contract.to_s, method_name.to_s,
53
+ *args,
54
+ 'prepaid-gas', gas.to_s,
55
+ 'attached-deposit', (deposit ? deposit.to_s : '0') + ' NEAR',
56
+ 'sign-as', signer.to_s,
45
57
  'network-config', @network,
46
58
  'sign-with-keychain',
47
59
  'send'
data/lib/near/cli.rb CHANGED
@@ -22,6 +22,8 @@ class NEAR::CLI
22
22
  class ExecutionError < Error; end
23
23
 
24
24
  def self.find_program
25
+ return ENV['NEAR_CLI'] if ENV['NEAR_CLI']
26
+
25
27
  # First, check `$HOME/.cargo/bin/near`:
26
28
  path = File.expand_path('~/.cargo/bin/near')
27
29
  return path if File.executable?(path)
@@ -37,7 +39,7 @@ class NEAR::CLI
37
39
 
38
40
  ##
39
41
  # @param [String, #to_s] path
40
- # @param [String, #to_s] network
42
+ # @param [Network, #to_s] network
41
43
  def initialize(path: self.class.find_program, network: NEAR_ENV)
42
44
  @path, @network = path.to_s, network.to_s
43
45
  end
@@ -61,6 +63,7 @@ class NEAR::CLI
61
63
  # @param [Array<String>] args
62
64
  # @return [Array<String>]
63
65
  def execute(*args)
66
+ #command = [@path, '--quiet', *args.map(&:to_s)] # TODO: near-cli-rs 0.19+
64
67
  command = [@path, *args.map(&:to_s)]
65
68
  puts command.join(' ') if false
66
69
  stdout, stderr, status = Open3.capture3(*command)
data/lib/near/error.rb ADDED
@@ -0,0 +1,18 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ class NEAR::Error < StandardError
4
+ class InvalidBlock < NEAR::Error
5
+ def initialize(block_height = nil)
6
+ @block_height = block_height.to_i if block_height
7
+ super(block_height ?
8
+ "Block height ##{block_height.to_i} does not exist" :
9
+ "Block height does not exist")
10
+ end
11
+
12
+ attr_reader :block_height
13
+ end # NEAR::Error::InvalidBlock
14
+
15
+ class TemporaryProblem < NEAR::Error; end
16
+
17
+ class UnexpectedProblem < NEAR::Error; end
18
+ end # NEAR::Error
@@ -0,0 +1,17 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ require_relative 'network'
4
+
5
+ ##
6
+ # Represents the NEAR Mainnet.
7
+ #
8
+ # @see https://docs.near.org/concepts/basics/networks#mainnet
9
+ class NEAR::Mainnet < NEAR::Network
10
+ NEARDATA_URL = 'https://mainnet.neardata.xyz'.freeze
11
+
12
+ ##
13
+ # @return [void]
14
+ def initialize
15
+ super(:mainnet)
16
+ end
17
+ end # NEAR::Mainnet
@@ -0,0 +1,134 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ require 'faraday'
4
+ require 'faraday/follow_redirects'
5
+ require 'faraday/retry'
6
+
7
+ require_relative 'error'
8
+
9
+ ##
10
+ # Represents a NEAR Protocol network.
11
+ #
12
+ # @see https://docs.near.org/concepts/basics/networks
13
+ class NEAR::Network
14
+ ##
15
+ # @param [Symbol, #to_sym] id
16
+ # @return [void]
17
+ def initialize(id)
18
+ @id = id.to_sym
19
+ end
20
+
21
+ ##
22
+ # @return [String]
23
+ def to_s; @id.to_s; end
24
+
25
+ ##
26
+ # @return [String]
27
+ def neardata_url
28
+ self.class.const_get(:NEARDATA_URL)
29
+ end
30
+
31
+ ##
32
+ # Fetches a block range.
33
+ #
34
+ # The block data is fetched from the neardata.xyz API.
35
+ #
36
+ # @param [NEAR::Block, #to_i] from
37
+ # @param [NEAR::Block, #to_i] to
38
+ # @yield [NEAR::Block]
39
+ # @yieldparam [NEAR::Block] block
40
+ # @yieldreturn [void]
41
+ # @return [Enumerator]
42
+ def fetch_blocks(from: nil, to: nil, &)
43
+ return enum_for(:fetch_blocks) unless block_given?
44
+ from = self.fetch_latest.height unless from
45
+ from, to = from.to_i, to&.to_i
46
+ (from..to).each do |block_height|
47
+ yield self.fetch(block_height)
48
+ end
49
+ end
50
+
51
+ ##
52
+ # Fetches the block at the given height.
53
+ #
54
+ # The block data is fetched from the neardata.xyz API.
55
+ #
56
+ # @param [NEAR::Block, #to_i] block
57
+ # @return [NEAR::Block]
58
+ # @raise [NEAR::Error::InvalidBlock] if the block does not exist
59
+ def fetch(block)
60
+ self.fetch_neardata_block(block.to_i)
61
+ end
62
+
63
+ ##
64
+ # Fetches the latest finalized block.
65
+ #
66
+ # The block data is fetched from the neardata.xyz API.
67
+ # The block is guaranteed to exist.
68
+ #
69
+ # @return [NEAR::Block]
70
+ def fetch_latest
71
+ self.fetch_neardata_block(:final)
72
+ end
73
+
74
+ protected
75
+
76
+ ##
77
+ # @param [Integer, Symbol] block_id
78
+ # @return [NEAR::Block]
79
+ def fetch_neardata_block(block_id)
80
+ request_path = case block_id
81
+ when Integer then "/v0/block/#{block_id}"
82
+ when Symbol then "/v0/last_block/#{block_id}"
83
+ else raise ArgumentError
84
+ end
85
+
86
+ begin
87
+ block_data = self.fetch_neardata_url(request_path)
88
+ rescue Faraday::ResourceNotFound => error
89
+ case error.response[:body]['type']
90
+ when "BLOCK_DOES_NOT_EXIST", "BLOCK_HEIGHT_TOO_LOW", "BLOCK_HEIGHT_TOO_HIGH"
91
+ raise NEAR::Error::InvalidBlock, block_id
92
+ else raise NEAR::Error::UnexpectedProblem
93
+ end
94
+ end
95
+
96
+ return nil if block_data.nil? || block_data == 'null'
97
+ block_header = block_data['block']['header']
98
+ block_height = block_header['height'].to_i
99
+ block_hash = block_header['hash'].to_s
100
+ NEAR::Block.new(height: block_height, hash: block_hash, data: block_data)
101
+ end
102
+
103
+ ##
104
+ # @return [Object]
105
+ def fetch_neardata_url(path)
106
+ begin
107
+ retries ||= 0
108
+ self.http_client.get("#{neardata_url}#{path}").body
109
+ rescue Faraday::ConnectionFailed => error
110
+ # `Faraday::Retry` doesn't seem able to handle `ConnectionFailed`,
111
+ # so we'll handle it manually here. Note that in case of a DNS error,
112
+ # there's no point in retrying, so we'll wrap & re-raise the error.
113
+ case error.wrapped_exception
114
+ when Socket::ResolutionError then raise NEAR::Error::TemporaryProblem
115
+ else retry if (retries += 1) < 3
116
+ end
117
+ raise NEAR::Error::UnexpectedProblem
118
+ end
119
+ end
120
+
121
+ ##
122
+ # @return [Faraday::Connection]
123
+ def http_client
124
+ Faraday.new do |faraday|
125
+ faraday.response :raise_error
126
+ faraday.response :follow_redirects
127
+ faraday.response :json
128
+ faraday.request :retry, {
129
+ max: 3,
130
+ exceptions: Faraday::Retry::Middleware::DEFAULT_EXCEPTIONS + [Faraday::ConnectionFailed]
131
+ }.freeze
132
+ end
133
+ end
134
+ end # NEAR::Testnet
@@ -0,0 +1,17 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ require_relative 'network'
4
+
5
+ ##
6
+ # Represents the NEAR Testnet.
7
+ #
8
+ # @see https://docs.near.org/concepts/basics/networks#testnet
9
+ class NEAR::Testnet < NEAR::Network
10
+ NEARDATA_URL = 'https://testnet.neardata.xyz'.freeze
11
+
12
+ ##
13
+ # @return [void]
14
+ def initialize
15
+ super(:testnet)
16
+ end
17
+ end # NEAR::Testnet
@@ -3,10 +3,21 @@
3
3
  ##
4
4
  # Represents a NEAR transaction.
5
5
  class NEAR::Transaction
6
+ ##
7
+ # @param [Hash] json
8
+ # @return [NEAR::Transaction]
9
+ def self.parse(json)
10
+ tx_data = json['transaction']
11
+ NEAR::Transaction.new(tx_data['hash'], data: tx_data)
12
+ end
13
+
6
14
  ##
7
15
  # @param [String, #to_s] hash
8
- def initialize(hash)
16
+ # @param [Hash, #to_h] data
17
+ # @return [void]
18
+ def initialize(hash, data: nil)
9
19
  @hash = hash.to_s
20
+ @data = data.to_h if data
10
21
  end
11
22
 
12
23
  ##
@@ -15,7 +26,108 @@ class NEAR::Transaction
15
26
  # @return [String]
16
27
  attr_reader :hash
17
28
 
29
+ ##
30
+ # The transaction data, if available.
31
+ #
32
+ # @return [Hash]
33
+ attr_reader :data
34
+
35
+ ##
36
+ # The transaction signer account.
37
+ #
38
+ # @return [NEAR::Account]
39
+ attr_reader :signer
40
+ def signer
41
+ @signer ||= NEAR::Account.new(self.signer_id)
42
+ end
43
+
44
+ ##
45
+ # The transaction signer ID.
46
+ #
47
+ # @return [String]
48
+ attr_reader :signer_id
49
+ def signer_id
50
+ self.data['signer_id']
51
+ end
52
+
53
+ ##
54
+ # The transaction receiver account.
55
+ #
56
+ # @return [NEAR::Account]
57
+ attr_reader :receiver
58
+ def receiver
59
+ @receiver ||= NEAR::Account.new(self.receiver_id)
60
+ end
61
+
62
+ ##
63
+ # The transaction receiver ID.
64
+ #
65
+ # @return [String]
66
+ attr_reader :receiver_id
67
+ def receiver_id
68
+ self.data['receiver_id']
69
+ end
70
+
71
+ ##
72
+ # @return [String]
73
+ attr_reader :public_key
74
+ def public_key
75
+ self.data['public_key']
76
+ end
77
+
78
+ ##
79
+ # @return [String]
80
+ attr_reader :signature
81
+ def signature
82
+ self.data['signature']
83
+ end
84
+
85
+ ##
86
+ # @return [Integer]
87
+ attr_reader :nonce
88
+ def nonce
89
+ self.data['nonce']
90
+ end
91
+
92
+ ##
93
+ # @return [Integer]
94
+ attr_reader :priority_fee
95
+ def priority_fee
96
+ self.data['priority_fee']
97
+ end
98
+
99
+ ##
100
+ # The transaction actions.
101
+ #
102
+ # @return [Array<Hash>]
103
+ attr_reader :actions
104
+ def actions
105
+ self.data['actions']
106
+ end
107
+
108
+ ##
109
+ # @yield [NEAR::Action]
110
+ # @yieldparam [NEAR::Action] action
111
+ # @yieldreturn [void]
112
+ # @return [Enumerator] if no block is given
113
+ def each_action(&)
114
+ return enum_for(:each_action) unless block_given?
115
+ self.data['actions'].each do |action|
116
+ yield NEAR::Action.parse(action)
117
+ end
118
+ end
119
+
18
120
  ##
19
121
  # @return [String]
20
122
  def to_s; @hash; end
123
+
124
+ ##
125
+ # @return [Hash]
126
+ def to_h; @data; end
127
+
128
+ ##
129
+ # @return [String]
130
+ def inspect
131
+ "#<#{self.class.name} hash: #{@hash.inspect}, signer: #{self.signer}, receiver: #{self.receiver}, actions: #{self.actions.size}>"
132
+ end
21
133
  end # NEAR::Transaction
data/lib/near.rb CHANGED
@@ -3,8 +3,27 @@
3
3
  module NEAR; end
4
4
 
5
5
  require_relative 'near/account'
6
+ require_relative 'near/action'
6
7
  require_relative 'near/balance'
7
8
  require_relative 'near/block'
8
9
  require_relative 'near/cli'
10
+ require_relative 'near/error'
11
+ require_relative 'near/mainnet'
12
+ require_relative 'near/network'
13
+ require_relative 'near/testnet'
9
14
  require_relative 'near/transaction'
10
15
  require_relative 'near/version'
16
+
17
+ module NEAR
18
+ ##
19
+ # @return [NEAR::Testnet]
20
+ def self.testnet
21
+ @testnet ||= Testnet.new
22
+ end
23
+
24
+ ##
25
+ # @return [NEAR::Mainnet]
26
+ def self.mainnet
27
+ @mainnet ||= Mainnet.new
28
+ end
29
+ end
metadata CHANGED
@@ -1,14 +1,84 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: near
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arto Bendiken
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-01-19 00:00:00.000000000 Z
10
+ date: 2025-02-02 00:00:00.000000000 Z
11
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: bigdecimal
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: faraday
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '2.12'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '2.12'
54
+ - !ruby/object:Gem::Dependency
55
+ name: faraday-follow_redirects
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0.3'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0.3'
68
+ - !ruby/object:Gem::Dependency
69
+ name: faraday-retry
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '2.0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '2.0'
12
82
  - !ruby/object:Gem::Dependency
13
83
  name: rspec
14
84
  requirement: !ruby/object:Gem::Requirement
@@ -50,6 +120,7 @@ files:
50
120
  - VERSION
51
121
  - lib/near.rb
52
122
  - lib/near/account.rb
123
+ - lib/near/action.rb
53
124
  - lib/near/balance.rb
54
125
  - lib/near/block.rb
55
126
  - lib/near/cli.rb
@@ -59,6 +130,10 @@ files:
59
130
  - lib/near/cli/staking.rb
60
131
  - lib/near/cli/tokens.rb
61
132
  - lib/near/cli/transaction.rb
133
+ - lib/near/error.rb
134
+ - lib/near/mainnet.rb
135
+ - lib/near/network.rb
136
+ - lib/near/testnet.rb
62
137
  - lib/near/transaction.rb
63
138
  - lib/near/version.rb
64
139
  homepage: https://github.com/dryruby/near.rb