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.
- data/.gitignore +17 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +265 -0
- data/Rakefile +6 -0
- data/coinbase.gemspec +31 -0
- data/lib/coinbase/ca-coinbase.crt +629 -0
- data/lib/coinbase/client.rb +254 -0
- data/lib/coinbase/money.rb +13 -0
- data/lib/coinbase/version.rb +3 -0
- data/lib/coinbase.rb +5 -18
- data/spec/client_spec.rb +191 -0
- data/supported_currencies.json +163 -0
- metadata +83 -116
- checksums.yaml +0 -7
- data/lib/coinbase/address.rb +0 -127
- data/lib/coinbase/asset.rb +0 -20
- data/lib/coinbase/balance_map.rb +0 -48
- data/lib/coinbase/constants.rb +0 -38
- data/lib/coinbase/network.rb +0 -55
- data/lib/coinbase/transfer.rb +0 -153
- data/lib/coinbase/wallet.rb +0 -160
data/lib/coinbase/transfer.rb
DELETED
@@ -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
|
data/lib/coinbase/wallet.rb
DELETED
@@ -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
|