coinbase 0.0.1 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of coinbase might be problematic. Click here for more details.

@@ -1,153 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'constants'
4
- require 'bigdecimal'
5
- require 'eth'
6
-
7
- module Coinbase
8
- # A representation of a Transfer, which moves an amount of an Asset from
9
- # a user-controlled Wallet to another address. The fee is assumed to be paid
10
- # in the native Asset of the Network. Currently only ETH transfers are supported. Transfers
11
- # should be created through {link:Wallet#transfer} or {link:Address#transfer}.
12
- class Transfer
13
- attr_reader :network_id, :wallet_id, :from_address_id, :amount, :asset_id, :to_address_id
14
-
15
- # A representation of a Transfer status.
16
- module Status
17
- # The Transfer is awaiting being broadcast to the Network. At this point, transaction
18
- # hashes may not yet be assigned.
19
- PENDING = :pending
20
-
21
- # The Transfer has been broadcast to the Network. At this point, at least the transaction hash
22
- # should be assigned.
23
- BROADCAST = :broadcast
24
-
25
- # The Transfer is complete, and has confirmed on the Network.
26
- COMPLETE = :complete
27
-
28
- # The Transfer has failed for some reason.
29
- FAILED = :failed
30
- end
31
-
32
- # Returns a new Transfer object.
33
- # @param network_id [Symbol] The ID of the Network on which the Transfer originated
34
- # @param wallet_id [String] The ID of the Wallet from which the Transfer originated
35
- # @param from_address_id [String] The ID of the address from which the Transfer originated
36
- # @param amount [Integer, Float, BigDecimal] The amount of the Asset to send. Integers are interpreted as
37
- # the smallest denomination of the Asset (e.g. Wei for Ether). Floats and BigDecimals are interpreted as the Asset
38
- # itself (e.g. Ether).
39
- # @param asset_id [Symbol] The ID of the Asset being transferred. Currently only ETH is supported.
40
- # @param to_address_id [String] The address to which the Transfer is being sent
41
- # @param client [Jimson::Client] (Optional) The JSON RPC client to use for interacting with the Network
42
- def initialize(network_id, wallet_id, from_address_id, amount, asset_id, to_address_id,
43
- client: Jimson::Client.new(ENV.fetch('BASE_SEPOLIA_RPC_URL', nil)))
44
-
45
- raise ArgumentError, "Unsupported asset: #{asset_id}" if asset_id != :eth
46
-
47
- @network_id = network_id
48
- @wallet_id = wallet_id
49
- @from_address_id = from_address_id
50
- @amount = normalize_eth_amount(amount)
51
- @asset_id = asset_id
52
- @to_address_id = to_address_id
53
- @client = client
54
- end
55
-
56
- # Returns the underlying Transfer transaction, creating it if it has not been yet.
57
- # @return [Eth::Tx::Eip1559] The Transfer transaction
58
- def transaction
59
- return @transaction unless @transaction.nil?
60
-
61
- nonce = @client.eth_getTransactionCount(@from_address_id.to_s, 'latest').to_i(16)
62
- gas_price = @client.eth_gasPrice.to_i(16)
63
-
64
- params = {
65
- chain_id: BASE_SEPOLIA.chain_id, # TODO: Don't hardcode Base Sepolia.
66
- nonce: nonce,
67
- priority_fee: gas_price, # TODO: Optimize this.
68
- max_gas_fee: gas_price,
69
- gas_limit: 21_000, # TODO: Handle multiple currencies.
70
- from: Eth::Address.new(@from_address_id),
71
- to: Eth::Address.new(@to_address_id),
72
- value: (@amount * Coinbase::WEI_PER_ETHER).to_i
73
- }
74
-
75
- @transaction = Eth::Tx::Eip1559.new(Eth::Tx.validate_eip1559_params(params))
76
- @transaction
77
- end
78
-
79
- # Returns the status of the Transfer.
80
- # @return [Symbol] The status
81
- def status
82
- begin
83
- # Create the transaction, and attempt to get the hash to see if it has been signed.
84
- transaction.hash
85
- rescue Eth::Signature::SignatureError
86
- # If the transaction has not been signed, it is still pending.
87
- return Status::PENDING
88
- end
89
-
90
- onchain_transaction = @client.eth_getTransactionByHash(transaction_hash)
91
-
92
- if onchain_transaction.nil?
93
- # If the transaction has not been broadcast, it is still pending.
94
- Status::PENDING
95
- elsif onchain_transaction['blockHash'].nil?
96
- # If the transaction has been broadcast but hasn't been included in a block, it is
97
- # broadcast.
98
- Status::BROADCAST
99
- else
100
- transaction_receipt = @client.eth_getTransactionReceipt(transaction_hash)
101
-
102
- if transaction_receipt['status'].to_i(16) == 1
103
- Status::COMPLETE
104
- else
105
- Status::FAILED
106
- end
107
- end
108
- end
109
-
110
- # Waits until the Transfer is completed or failed by polling the Network at the given interval. Raises a
111
- # Timeout::Error if the Transfer takes longer than the given timeout.
112
- # @param interval_seconds [Integer] The interval at which to poll the Network, in seconds
113
- # @param timeout_seconds [Integer] The maximum amount of time to wait for the Transfer to complete, in seconds
114
- # @return [Transfer] The completed Transfer object
115
- def wait!(interval_seconds = 0.2, timeout_seconds = 10)
116
- start_time = Time.now
117
-
118
- loop do
119
- return self if status == Status::COMPLETE || status == Status::FAILED
120
-
121
- raise Timeout::Error, 'Transfer timed out' if Time.now - start_time > timeout_seconds
122
-
123
- self.sleep interval_seconds
124
- end
125
-
126
- self
127
- end
128
-
129
- # Returns the transaction hash of the Transfer, or nil if not yet available.
130
- # @return [String] The transaction hash
131
- def transaction_hash
132
- "0x#{transaction.hash}"
133
- rescue Eth::Signature::SignatureError
134
- nil
135
- end
136
-
137
- private
138
-
139
- # Normalizes the given Ether amount into a BigDecimal.
140
- # @param amount [Integer, Float, BigDecimal] The amount to normalize
141
- # @return [BigDecimal] The normalized amount
142
- def normalize_eth_amount(amount)
143
- case amount
144
- when BigDecimal
145
- amount
146
- when Integer, Float
147
- BigDecimal(amount.to_s)
148
- else
149
- raise ArgumentError, "Invalid amount: #{amount}"
150
- end
151
- end
152
- end
153
- end
@@ -1,160 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'jimson'
4
- require 'money-tree'
5
- require 'securerandom'
6
-
7
- module Coinbase
8
- # A representation of a Wallet. Wallets come with a single default Address, but can expand to have a set of Addresses,
9
- # each of which can hold a balance of one or more Assets. Wallets can create new Addresses, list their addresses,
10
- # list their balances, and transfer Assets to other Addresses.
11
- class Wallet
12
- attr_reader :wallet_id, :network_id
13
-
14
- # Returns a new Wallet object.
15
- # @param seed [Integer] (Optional) The seed to use for the Wallet. Expects a 32-byte hexadecimal. If not provided,
16
- # a new seed will be generated.
17
- # @param address_count [Integer] (Optional) The number of addresses to generate for the Wallet. If not provided,
18
- # a single address will be generated.
19
- # @param client [Jimson::Client] (Optional) The JSON RPC client to use for interacting with the Network
20
- def initialize(seed: nil, address_count: 1, client: Jimson::Client.new(ENV.fetch('BASE_SEPOLIA_RPC_URL', nil)))
21
- raise ArgumentError, 'Seed must be 32 bytes' if !seed.nil? && seed.length != 64
22
- raise ArgumentError, 'Address count must be positive' if address_count < 1
23
-
24
- @master = seed.nil? ? MoneyTree::Master.new : MoneyTree::Master.new(seed_hex: seed)
25
-
26
- @wallet_id = SecureRandom.uuid
27
- # TODO: Make Network an argument to the constructor.
28
- @network_id = :base_sepolia
29
- @addresses = []
30
-
31
- # TODO: Adjust derivation path prefix based on network protocol.
32
- @address_path_prefix = "m/44'/60'/0'/0"
33
- @address_index = 0
34
-
35
- @client = client
36
-
37
- address_count.times { create_address }
38
- end
39
-
40
- # Creates a new Address in the Wallet.
41
- # @return [Address] The new Address
42
- def create_address
43
- # TODO: Register with server.
44
- path = "#{@address_path_prefix}/#{@address_index}"
45
- private_key = @master.node_for_path(path).private_key.to_hex
46
- key = Eth::Key.new(priv: private_key)
47
- address = Address.new(@network_id, key.address.address, @wallet_id, key, client: @client)
48
- @addresses << address
49
- @address_index += 1
50
- address
51
- end
52
-
53
- # Returns the default address of the Wallet.
54
- # @return [Address] The default address
55
- def default_address
56
- @addresses.first
57
- end
58
-
59
- # Returns the Address with the given ID.
60
- # @param address_id [String] The ID of the Address to retrieve
61
- # @return [Address] The Address
62
- def get_address(address_id)
63
- @addresses.find { |address| address.address_id == address_id }
64
- end
65
-
66
- # Returns the list of Addresses in the Wallet.
67
- # @return [Array<Address>] The list of Addresses
68
- def list_addresses
69
- # TODO: Register with server.
70
- @addresses
71
- end
72
-
73
- # Returns the list of balances of this Wallet. Balances are aggregated across all Addresses in the Wallet.
74
- # @return [BalanceMap] The list of balances. The key is the Asset ID, and the value is the balance.
75
- def list_balances
76
- balance_map = BalanceMap.new
77
-
78
- @addresses.each do |address|
79
- address.list_balances.each do |asset_id, balance|
80
- balance_map[asset_id] ||= BigDecimal(0)
81
- current_balance = balance_map[asset_id]
82
- new_balance = balance + current_balance
83
- balance_map[asset_id] = new_balance
84
- end
85
- end
86
-
87
- balance_map
88
- end
89
-
90
- # Returns the balance of the provided Asset. Balances are aggregated across all Addresses in the Wallet.
91
- # @param asset_id [Symbol] The ID of the Asset to retrieve the balance for
92
- # @return [BigDecimal] The balance of the Asset
93
- def get_balance(asset_id)
94
- normalized_asset_id = if %i[wei gwei].include?(asset_id)
95
- :eth
96
- else
97
- asset_id
98
- end
99
-
100
- eth_balance = list_balances[normalized_asset_id] || BigDecimal(0)
101
-
102
- case asset_id
103
- when :eth
104
- eth_balance
105
- when :gwei
106
- eth_balance * Coinbase::GWEI_PER_ETHER
107
- when :wei
108
- eth_balance * Coinbase::WEI_PER_ETHER
109
- else
110
- BigDecimal(0)
111
- end
112
- end
113
-
114
- # Transfers the given amount of the given Asset to the given address. Only same-Network Transfers are supported.
115
- # Currently only the default_address is used to source the Transfer.
116
- # @param amount [Integer, Float, BigDecimal] The amount of the Asset to send
117
- # @param asset_id [Symbol] The ID of the Asset to send
118
- # @param destination [Wallet | Address | String] The destination of the transfer. If a Wallet, sends to the Wallet's
119
- # default address. If a String, interprets it as the address ID.
120
- # @return [Transfer] The hash of the Transfer transaction.
121
- def transfer(amount, asset_id, destination)
122
- if destination.is_a?(Wallet)
123
- raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id != @network_id
124
-
125
- destination = destination.default_address.address_id
126
- elsif destination.is_a?(Address)
127
- raise ArgumentError, 'Transfer must be on the same Network' if destination.network_id != @network_id
128
-
129
- destination = destination.address_id
130
- end
131
-
132
- default_address.transfer(amount, asset_id, destination)
133
- end
134
-
135
- # Exports the Wallet's data to a WalletData object.
136
- # @return [WalletData] The Wallet data
137
- def export
138
- WalletData.new(@master.seed_hex, @addresses.length)
139
- end
140
-
141
- # The data required to recreate a Wallet.
142
- class WalletData
143
- attr_reader :seed, :address_count
144
-
145
- # Returns a new WalletData object.
146
- # @param seed [String] The seed of the Wallet
147
- # @param address_count [Integer] The number of addresses in the Wallet
148
- def initialize(seed, address_count)
149
- @seed = seed
150
- @address_count = address_count
151
- end
152
- end
153
-
154
- # Returns the data required to recreate the Wallet.
155
- # @return [WalletData] The Wallet data
156
- def to_data
157
- WalletData.new(@master.seed_hex, @addresses.length)
158
- end
159
- end
160
- end