-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add CryptoAmount and FiatAmount types
This adds a CryptoAmount types that provides a wrapper for the underlying CryptoAmount object and can provide helpers for converting from atomic to whole amounts. This provides a FiatAmount type as well to wrap our fiat amount + currency pairings in a consistent manner.
- Loading branch information
1 parent
1e4a621
commit 6dc6256
Showing
4 changed files
with
264 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# frozen_string_literal: true | ||
|
||
module Coinbase | ||
# A representation of a CryptoAmount that includes the amount and asset. | ||
class CryptoAmount | ||
# Converts a Coinbase::Client::CryptoAmount model to a Coinbase::CryptoAmount | ||
# @param amount_model [Coinbase::Client::CryptoAmount] The crypto amount from the API. | ||
# @return [CryptoAmount] The converted CryptoAmount object. | ||
def self.from_model(amount_model) | ||
asset = Coinbase::Asset.from_model(amount_model.asset) | ||
|
||
new(amount: asset.from_atomic_amount(amount_model.amount), asset: asset) | ||
end | ||
|
||
# Converts a Coinbase::Client::CryptoAmount model and asset ID to a Coinbase::CryptoAmount | ||
# This can be used to specify a non-primary denomination that we want the amount | ||
# to be converted to. | ||
# @param amount_model [Coinbase::Client::CryptoAmount] The crypto amount from the API. | ||
# @param asset_id [Symbol] The Asset ID of the denomination we want returned. | ||
# @return [CryptoAmount] The converted CryptoAmount object. | ||
def self.from_model_and_asset_id(amount_model, asset_id) | ||
asset = Coinbase::Asset.from_model(amount_model.asset, asset_id: asset_id) | ||
|
||
new( | ||
amount: asset.from_atomic_amount(amount_model.amount), | ||
asset: asset, | ||
asset_id: asset_id | ||
) | ||
end | ||
|
||
# Returns a new CryptoAmount object. Do not use this method. | ||
# Instead, use CryptoAmount.from_model or CryptoAmount.from_model_and_asset_id. | ||
# @param amount [BigDecimal] The amount of the Asset | ||
# @param asset [Coinbase::Asset] The Asset | ||
# @param asset_id [Symbol] The Asset ID | ||
def initialize(amount:, asset:, asset_id: nil) | ||
@amount = amount | ||
@asset = asset | ||
@asset_id = asset_id || asset.asset_id | ||
end | ||
|
||
attr_reader :amount, :asset, :asset_id | ||
|
||
# Returns the amount in atomic units. | ||
# @return [BigDecimal] the amount in atomic units | ||
def to_atomic_amount | ||
asset.to_atomic_amount(amount) | ||
end | ||
|
||
# Returns a string representation of the CryptoAmount. | ||
# @return [String] a string representation of the CryptoAmount | ||
def to_s | ||
Coinbase.pretty_print_object(self.class, amount: amount.to_i, asset_id: asset_id) | ||
end | ||
|
||
# Same as to_s. | ||
# @return [String] a string representation of the CryptoAmount | ||
def inspect | ||
to_s | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# frozen_string_literal: true | ||
|
||
module Coinbase | ||
# A representation of a FiatAmount that includes the amount and fiat. | ||
class FiatAmount | ||
# Converts a Coinbase::Client::FiatAmount model to a Coinbase::FiatAmount | ||
# @param amount_model [Coinbase::Client::FiatAmount] The crypto amount from the API. | ||
# @return [FiatAmount] The converted FiatAmount object. | ||
def self.from_model(amount_model) | ||
new(amount: amount_model.amount, currency: currency) | ||
end | ||
|
||
# Returns a new CryptoAmount object. Do not use this method. | ||
# Instead, use CryptoAmount.from_model or CryptoAmount.from_model_and_asset_id. | ||
# @param amount [BigDecimal] The amount of the Asset | ||
# @param asset [Coinbase::Asset] The Asset | ||
# @param asset_id [Symbol] The Asset ID | ||
def initialize(amount:, currency:) | ||
@amount = BigDecimal(amount) | ||
@currency = currency | ||
end | ||
|
||
attr_reader :amount, :currency | ||
|
||
# Returns a string representation of the CryptoAmount. | ||
# @return [String] a string representation of the CryptoAmount | ||
def to_s | ||
Coinbase.pretty_print_object(self.class, amount: amount.to_i, currency: currency) | ||
end | ||
|
||
# Same as to_s. | ||
# @return [String] a string representation of the CryptoAmount | ||
def inspect | ||
to_s | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
# frozen_string_literal: true | ||
|
||
describe Coinbase::CryptoAmount do | ||
let(:amount) { BigDecimal('123.0') } | ||
let(:crypto_amount_model) { instance_double(Coinbase::Client::CryptoAmount, asset: asset, amount: amount) } | ||
let(:eth_asset) { build(:asset_model) } | ||
|
||
describe '.from_model' do | ||
subject(:crypto_amount) { described_class.from_model(crypto_amount_model) } | ||
|
||
context 'when the asset is :eth' do | ||
let(:asset) { eth_asset } | ||
|
||
it 'returns a CryptoAmount object' do | ||
expect(crypto_amount).to be_a(described_class) | ||
end | ||
|
||
it 'sets the correct amount' do | ||
expect(crypto_amount.amount).to eq(amount / BigDecimal(10).power(eth_asset.decimals)) | ||
end | ||
|
||
it 'sets the correct asset_id' do | ||
expect(crypto_amount.asset_id).to eq(:eth) | ||
end | ||
end | ||
|
||
context 'when the asset is other' do | ||
let(:decimals) { 9 } | ||
let(:asset) { build(:asset_model, asset_id: 'other', decimals: decimals) } | ||
|
||
it 'returns a CryptoAmount object' do | ||
expect(crypto_amount).to be_a(described_class) | ||
end | ||
|
||
it 'sets the correct amount' do | ||
expect(crypto_amount.amount).to eq(amount / BigDecimal(10).power(decimals)) | ||
end | ||
|
||
it 'sets the correct asset_id' do | ||
expect(crypto_amount.asset_id).to eq(:other) | ||
end | ||
end | ||
end | ||
|
||
describe '.from_model_and_asset_id' do | ||
subject(:crypto_amount) { described_class.from_model_and_asset_id(crypto_amount_model, asset_id) } | ||
|
||
context 'when the crypto_amount model asset is :eth' do | ||
let(:asset) { eth_asset } | ||
|
||
context 'when the specified asset_id is :eth' do | ||
let(:asset_id) { :eth } | ||
|
||
it 'returns a new CryptoAmount object with the correct amount' do | ||
expect(crypto_amount.amount).to eq(amount / BigDecimal(10).power(eth_asset.decimals)) | ||
end | ||
|
||
it 'returns a new CryptoAmount object with the correct asset_id' do | ||
expect(crypto_amount.asset_id).to eq(asset_id) | ||
end | ||
end | ||
|
||
context 'when the specified asset_id is :gwei' do | ||
let(:asset_id) { :gwei } | ||
|
||
it 'returns a new CryptoAmount object with the correct amount' do | ||
expect(crypto_amount.amount).to eq(amount / BigDecimal(10).power(Coinbase::GWEI_DECIMALS)) | ||
end | ||
|
||
it 'returns a new CryptoAmount object with the correct asset_id' do | ||
expect(crypto_amount.asset_id).to eq(asset_id) | ||
end | ||
end | ||
|
||
context 'when the specified asset_id is :wei' do | ||
let(:asset_id) { :wei } | ||
|
||
it 'returns a new CryptoAmount object with the correct amount' do | ||
expect(crypto_amount.amount).to eq(amount) | ||
end | ||
|
||
it 'returns a new CryptoAmount object with the correct asset_id' do | ||
expect(crypto_amount.asset_id).to eq(asset_id) | ||
end | ||
end | ||
|
||
context 'when the specified asset_id is another asset type' do | ||
let(:asset_id) { :other } | ||
|
||
it 'raise an error' do | ||
expect { crypto_amount }.to raise_error(ArgumentError) | ||
end | ||
end | ||
end | ||
|
||
context 'when the asset is not eth' do | ||
let(:decimals) { 9 } | ||
let(:asset_id) { :other } | ||
let(:asset) { build(:asset_model, asset_id: 'other', decimals: decimals) } | ||
|
||
it 'returns a new CryptoAmount object with the correct amount' do | ||
expect(crypto_amount.amount).to eq(amount / BigDecimal(10).power(decimals)) | ||
end | ||
|
||
it 'returns a new CryptoAmount object with the correct asset_id' do | ||
expect(crypto_amount.asset_id).to eq(asset_id) | ||
end | ||
|
||
context 'when the asset ID does not match the asset' do | ||
let(:asset_id) { :different } | ||
|
||
it 'raises an error' do | ||
expect { crypto_amount }.to raise_error(ArgumentError) | ||
end | ||
end | ||
end | ||
end | ||
|
||
describe '#initialize' do | ||
subject(:crypto_amount) { described_class.new(amount: amount, asset: asset) } | ||
|
||
let(:amount) { BigDecimal('123.0') } | ||
let(:asset) { Coinbase::Asset.from_model(eth_asset) } | ||
|
||
it 'sets the amount' do | ||
expect(crypto_amount.amount).to eq(amount) | ||
end | ||
|
||
it 'sets the asset' do | ||
expect(crypto_amount.asset).to eq(asset) | ||
end | ||
|
||
it "sets the asset_id to the asset's ID" do | ||
expect(crypto_amount.asset_id).to eq(:eth) | ||
end | ||
end | ||
|
||
describe '#to_atomic_amount' do | ||
subject(:crypto_amount) { described_class.new(amount: amount, asset: asset) } | ||
|
||
let(:amount) { BigDecimal('123.0') } | ||
let(:asset) { Coinbase::Asset.from_model(build(:asset_model, decimals: 3)) } | ||
|
||
it 'returns the amount in atomic units' do | ||
expect(crypto_amount.to_atomic_amount).to eq(123_000) | ||
end | ||
end | ||
|
||
describe '#inspect' do | ||
subject(:crypto_amount) { described_class.new(amount: amount, asset: asset) } | ||
|
||
let(:amount) { BigDecimal('123.0') } | ||
let(:asset) { Coinbase::Asset.from_model(eth_asset) } | ||
|
||
it 'includes crypto_amount details' do | ||
expect(crypto_amount.inspect).to include('123', 'eth') | ||
end | ||
|
||
it 'returns the same value as to_s' do | ||
expect(crypto_amount.inspect).to eq(crypto_amount.to_s) | ||
end | ||
end | ||
end |