Skip to content

Commit

Permalink
Support requesting faucet funds for address
Browse files Browse the repository at this point in the history
This adds support for requesting faucet funds for a given address.
This will be rate limited by the address via the smart contract
that backs the faucet and via the API key (1 request every 24 hours).

This may be able to be loosened for base-sepolia via the Platform API,
but those are the current rate limit parameters.
  • Loading branch information
alex-stone committed May 2, 2024
1 parent d6ab2e5 commit e80053d
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- Faucet
- Trade
- Individual private key export
- Allow disabling debug tracing
Expand All @@ -18,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Coinbase.configure_from_file
- Faucet

## [0.0.2] - 2024-05-01

Expand Down
2 changes: 2 additions & 0 deletions lib/coinbase.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require_relative 'coinbase/client'
require_relative 'coinbase/constants'
require_relative 'coinbase/errors'
require_relative 'coinbase/faucet_transaction'
require_relative 'coinbase/middleware'
require_relative 'coinbase/network'
require_relative 'coinbase/transfer'
Expand All @@ -17,6 +18,7 @@
# The Coinbase SDK.
module Coinbase
class InvalidConfiguration < StandardError; end
class FaucetLimitReached < StandardError; end

# Returns the configuration object.
# @return [Configuration] the configuration object
Expand Down
11 changes: 11 additions & 0 deletions lib/coinbase/address.rb
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ def to_s
address_id
end

# Requests funds for the address from the faucet and returns the faucet transaction.
# This is only supported on testnet networks.
# @return [Coinbase::FaucetTransaction] The successful faucet transaction
# @raise [Coinbase::FaucetLimitReached] If the faucet limit has been reached for the address or user.
# @raise [Coinbase::Client::ApiError] If an unexpected error occurs while requesting faucet funds.
def faucet
Coinbase.call_api do
Coinbase::FaucetTransaction.new(addresses_api.request_faucet_funds(wallet_id, address_id))
end
end

private

# Normalizes the amount of the Asset to send to the atomic unit.
Expand Down
21 changes: 21 additions & 0 deletions lib/coinbase/faucet_transaction.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module Coinbase
# A representation of a transaction from a faucet.
# a user-controlled Wallet to another address. The fee is assumed to be paid
# in the native Asset of the Network. Transfers should be created through Wallet#transfer or
# Address#transfer.
class FaucetTransaction
def initialize(model)
@model = model
end

attr_reader :model

# Returns the transaction hash.
# @return [String] The onchain transaction hash
def transaction_hash
model.transaction_hash
end
end
end
65 changes: 65 additions & 0 deletions spec/unit/coinbase/address_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,71 @@
end
end

describe '#faucet' do
let(:request) { double('Request', transaction: transaction) }
let(:tx_hash) { '0xdeadbeef' }
let(:faucet_tx) do
instance_double('Coinbase::Client::FaucetTransaction', transaction_hash: tx_hash)
end

context 'when the request is successful' do
subject(:faucet_response) { address.faucet }

before do
expect(addresses_api)
.to receive(:request_faucet_funds)
.with(wallet_id, address_id)
.and_return(faucet_tx)
end

it 'requests funds from the faucet and returns the faucet transaction' do
expect(faucet_response).to be_a(Coinbase::FaucetTransaction)
expect(faucet_response.transaction_hash).to eq(tx_hash)
end
end

context 'when the request is unsuccesful' do
before do
expect(addresses_api)
.to receive(:request_faucet_funds)
.with(wallet_id, address_id)
.and_raise(api_error)
end

context 'when the faucet limit is reached' do
let(:api_error) do
Coinbase::Client::ApiError.new(
code: 429,
response_body: {
'code' => 'faucet_limit_reached',
'message' => 'failed to claim funds - address likely has already claimed in the past 24 hours'
}.to_json,
)
end

it 'raises a FaucetLimitReachedError' do
expect { address.faucet }.to raise_error(::Coinbase::FaucetLimitReachedError)
end
end

context 'when the request fails unexpectedly' do
let(:api_error) do
Coinbase::Client::ApiError.new(
code: 500,
response_body: {
'code' => 'internal',
'message' => 'unexpected error occurred while requesting faucet funds'
}.to_json,
)
end

it 'raises an internal error' do
expect { address.faucet }.to raise_error(::Coinbase::InternalError)
end
end
end
end

describe '#to_s' do
it 'returns the address as a string' do
expect(address.to_s).to eq(address_id)
Expand Down

0 comments on commit e80053d

Please sign in to comment.