Skip to content

Commit 3f132b5

Browse files
committed
[FEATURE] implement getFiatExchangeRate on WalletServices
1 parent d00933b commit 3f132b5

File tree

6 files changed

+384
-38
lines changed

6 files changed

+384
-38
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log/slog"
6+
7+
"github.com/bsv-blockchain/go-wallet-toolbox/examples/internal/show"
8+
"github.com/bsv-blockchain/go-wallet-toolbox/pkg/defs"
9+
"github.com/bsv-blockchain/go-wallet-toolbox/pkg/services"
10+
)
11+
12+
func exampleFiatExchangeRate(srv *services.WalletServices) {
13+
examples := []struct {
14+
currency defs.Currency
15+
base *defs.Currency
16+
}{
17+
{currency: defs.EUR, base: ptr(defs.USD)},
18+
{currency: defs.GBP, base: ptr(defs.EUR)},
19+
{currency: defs.GBP, base: nil}, // defaults to USD
20+
{currency: "ABC", base: ptr(defs.USD)}, // invalid case
21+
}
22+
23+
for _, ex := range examples {
24+
base := "<nil>"
25+
if ex.base != nil {
26+
base = string(*ex.base)
27+
}
28+
step := fmt.Sprintf("Getting fiat rate for %s per %s", ex.currency, base)
29+
show.Step("FiatExchangeRate", step)
30+
31+
rate := srv.FiatExchangeRate(ex.currency, ex.base)
32+
if rate == 0 {
33+
show.WalletError("FiatExchangeRate", fmt.Sprintf("%s/%s", ex.currency, base), fmt.Errorf("rate not found"))
34+
} else {
35+
show.WalletSuccess("FiatExchangeRate", fmt.Sprintf("%s/%s", ex.currency, base), rate)
36+
}
37+
}
38+
}
39+
40+
func ptr(c defs.Currency) *defs.Currency {
41+
return &c
42+
}
43+
44+
func main() {
45+
show.ProcessStart("Fiat Exchange Rate Conversion")
46+
47+
cfg := defs.DefaultServicesConfig(defs.NetworkMainnet)
48+
cfg.FiatExchangeRates = defs.FiatExchangeRates{
49+
Rates: map[defs.Currency]float64{
50+
defs.USD: 1.0,
51+
defs.EUR: 0.85,
52+
defs.GBP: 0.65,
53+
},
54+
}
55+
56+
srv := services.New(slog.Default(), cfg)
57+
58+
exampleFiatExchangeRate(srv)
59+
60+
show.ProcessComplete("Fiat Exchange Rate Conversion")
61+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Fiat Exchange Rate
2+
3+
This example demonstrates how to retrieve and convert fiat currency exchange rates using the Go Wallet Toolbox SDK. It supports converting one fiat currency to another based on the latest rates (e.g., EUR to USD, GBP to EUR).
4+
5+
## Overview
6+
7+
The process involves:
8+
1. Setting up the service with a mock configuration including fiat exchange rates.
9+
2. Calling the `FiatExchangeRate()` method to retrieve the exchange rate of one currency relative to another.
10+
3. Processing the returned `float64` result representing the conversion rate.
11+
4. Handling invalid currencies or missing data.
12+
13+
This showcases a simplified currency conversion mechanism used internally within the wallet toolbox.
14+
15+
## Code Walkthrough
16+
17+
### Configuration
18+
19+
The fiat exchange rates are mocked for testing or example purposes using a `map[defs.Currency]float64`:
20+
21+
```go
22+
{
23+
USD: 1.0,
24+
EUR: 0.85,
25+
GBP: 0.65,
26+
}
27+
```
28+
29+
You can replace or update this map with actual rates from a provider or service.
30+
31+
### Method Signature
32+
33+
```go
34+
func (s *WalletServices) FiatExchangeRate(ctx context.Context, currency defs.Currency, base *defs.Currency) (float64, error)
35+
```
36+
37+
- **`currency`**: Target fiat currency (e.g., EUR).
38+
- **`base`**: Base fiat currency to convert against (e.g., USD). Defaults to USD if `nil`.
39+
- **Returns**: The conversion rate from `currency` to `base`.
40+
41+
### Example Scenarios
42+
43+
- `FiatExchangeRate(EUR, USD)` → 0.85 (EUR to USD)
44+
- `FiatExchangeRate(GBP, EUR)` → 0.65 / 0.85 ≈ 0.7647
45+
- `FiatExchangeRate(GBP, nil)` → 0.65 (GBP to default USD)
46+
- `FiatExchangeRate(ABC, USD)` → Error (unknown currency)
47+
48+
## Running the Example
49+
50+
```bash
51+
go run ./examples/services_examples/fiat_exchange_rate/fiat_exchange_rate.go
52+
```
53+
54+
## Expected Output
55+
56+
```text
57+
🚀 STARTING: Fiat Exchange Rate Conversion
58+
============================================================
59+
60+
=== STEP ===
61+
FiatExchangeRate is performing: Getting fiat rate for EUR per USD
62+
--------------------------------------------------
63+
64+
WALLET CALL: FiatExchangeRate
65+
Args: EUR/USD
66+
✅ Result: 0.85
67+
68+
=== STEP ===
69+
FiatExchangeRate is performing: Getting fiat rate for GBP per EUR
70+
--------------------------------------------------
71+
72+
WALLET CALL: FiatExchangeRate
73+
Args: GBP/EUR
74+
✅ Result: 0.7647058823529412
75+
76+
=== STEP ===
77+
FiatExchangeRate is performing: Getting fiat rate for GBP per <nil>
78+
--------------------------------------------------
79+
80+
WALLET CALL: FiatExchangeRate
81+
Args: GBP/<nil>
82+
✅ Result: 0.65
83+
84+
=== STEP ===
85+
FiatExchangeRate is performing: Getting fiat rate for ABC per USD
86+
--------------------------------------------------
87+
88+
WALLET CALL: FiatExchangeRate
89+
Args: ABC/USD
90+
❌ Error: rate not found
91+
============================================================
92+
🎉 COMPLETED: Fiat Exchange Rate Conversion
93+
```
94+
95+
## Integration Steps
96+
97+
1. **Add fiat exchange rates** to your service configuration or implement a live provider.
98+
2. **Invoke `FiatExchangeRate()`** with the desired currency and optional base.
99+
3. **Use the returned value** to display or convert currency values in your app.
100+
4. **Handle errors gracefully** if a currency is missing or unknown.
101+
5. **Cache frequently-used rates** to avoid redundant calls in performance-critical paths.
102+
103+
## Additional Resources
104+
105+
- [FiatExchangeRate Code](./fiat_exchange_rate.go) - Main example implementation
106+
- [BSVExchangeRate](../bsv_exchange_rate/bsv_exchange_rate.go) - Check BSV/USD rate
107+
- [Currency Definitions](../../pkg/defs/currency.go) - Enum of supported fiat currencies

examples/services_examples/nlock_time_is_final/nlock_time_is_final.go

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -78,36 +78,3 @@ func main() {
7878

7979
show.ProcessComplete("Check nLockTime Finality")
8080
}
81-
82-
/* Output:
83-
84-
🚀 STARTING: Check nLockTime Finality
85-
============================================================
86-
87-
=== STEP ===
88-
Wallet-Services is performing: Checking finality for past timestamp locktime: 1754897347
89-
--------------------------------------------------
90-
91-
WALLET CALL: NLockTimeIsFinal
92-
Args: 1754897347
93-
✅ Result: true
94-
95-
=== STEP ===
96-
Wallet-Services is performing: Checking finality for future timestamp locktime: 1754904547
97-
--------------------------------------------------
98-
99-
WALLET CALL: NLockTimeIsFinal
100-
Args: 1754904547
101-
✅ Result: false
102-
103-
=== STEP ===
104-
Wallet-Services is performing: Checking finality for block height locktime: 800000
105-
--------------------------------------------------
106-
107-
WALLET CALL: NLockTimeIsFinal
108-
Args: 800000
109-
✅ Result: true
110-
============================================================
111-
🎉 COMPLETED: Check nLockTime Finality
112-
113-
*/
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# NLockTime Finality
2+
3+
This example demonstrates how to check whether a given `nLockTime` value is considered **final** on the BSV blockchain using the Go Wallet Toolbox SDK. It supports evaluating both timestamp-based and block-height-based locktimes and determines their finality status based on current blockchain state.
4+
5+
## Overview
6+
7+
The `NLockTimeIsFinal` method determines whether a transaction can be accepted for mining based on its `nLockTime` value. A transaction is considered final if:
8+
- It has a locktime of 0
9+
- The locktime is less than the current block height (for block-based locktime)
10+
- The locktime is less than the current UNIX timestamp (for time-based locktime)
11+
- All input sequence numbers are `0xffffffff` (final)
12+
13+
## Code Walkthrough
14+
15+
The example demonstrates three scenarios:
16+
17+
### 1. Timestamp LockTime (Past)
18+
```go
19+
lockTime := uint32(time.Now().Unix() - 3600)
20+
isFinal, err := srv.NLockTimeIsFinal(ctx, lockTime)
21+
// ✅ Result: true
22+
```
23+
24+
### 2. Timestamp LockTime (Future)
25+
```go
26+
lockTime := uint32(time.Now().Unix() + 3600)
27+
isFinal, err := srv.NLockTimeIsFinal(ctx, lockTime)
28+
// ❌ Result: false
29+
```
30+
31+
### 3. Block Height LockTime
32+
```go
33+
lockTime := uint32(800000)
34+
isFinal, err := srv.NLockTimeIsFinal(ctx, lockTime)
35+
// ✅ Result: depends on current blockchain height
36+
```
37+
38+
## Method Signature
39+
40+
```go
41+
func (s *WalletServices) NLockTimeIsFinal(ctx context.Context, txOrLockTime any) (bool, error)
42+
```
43+
44+
- **`txOrLockTime`**: Can be a `uint32`, `int`, transaction hex string, `sdk.Transaction`, etc.
45+
- **Returns**: Whether the locktime is final and the transaction is ready to be accepted into a block.
46+
47+
## Running the Example
48+
49+
```bash
50+
go run ./examples/services_examples/nlocktime_finality/nlocktime_finality.go
51+
```
52+
53+
## Expected Output
54+
55+
```text
56+
🚀 STARTING: Check nLockTime Finality
57+
============================================================
58+
59+
=== STEP ===
60+
Wallet-Services is performing: Checking finality for past timestamp locktime: 1754897347
61+
--------------------------------------------------
62+
63+
WALLET CALL: NLockTimeIsFinal
64+
Args: 1754897347
65+
✅ Result: true
66+
67+
=== STEP ===
68+
Wallet-Services is performing: Checking finality for future timestamp locktime: 1754904547
69+
--------------------------------------------------
70+
71+
WALLET CALL: NLockTimeIsFinal
72+
Args: 1754904547
73+
✅ Result: false
74+
75+
=== STEP ===
76+
Wallet-Services is performing: Checking finality for block height locktime: 800000
77+
--------------------------------------------------
78+
79+
WALLET CALL: NLockTimeIsFinal
80+
Args: 800000
81+
✅ Result: true
82+
============================================================
83+
🎉 COMPLETED: Check nLockTime Finality
84+
```
85+
86+
## Integration Steps
87+
88+
1. **Import Wallet Toolbox** and initialize `WalletServices` with proper network config.
89+
2. **Pass a locktime or transaction** into `NLockTimeIsFinal()`.
90+
3. **Check returned boolean** to determine if the transaction is final.
91+
4. **Handle errors gracefully**, especially with malformed inputs or failed service lookups.
92+
93+
## Additional Resources
94+
95+
- [NLockTimeIsFinal Code](./nlocktime_finality.go) - Full example implementation
96+
- [Go-SDK Transaction Type](https://pkg.go.dev/github.com/bsv-blockchain/go-sdk/transaction) - For parsing raw transactions
97+
- [Bitcoin nLockTime Reference](https://en.bitcoin.it/wiki/NLockTime) - Understanding nLockTime usage

pkg/services/services.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -349,11 +349,6 @@ func (s *WalletServices) BsvExchangeRate(ctx context.Context) (float64, error) {
349349
return bsvExchangeRate, nil
350350
}
351351

352-
// FiatExchangeRate returns approximate exchange rate currency per base.
353-
func (s *WalletServices) FiatExchangeRate(currency defs.Currency, base *defs.Currency) float64 {
354-
panic("Not implemented yet")
355-
}
356-
357352
// MerklePath attempts to obtain the merkle proof associated with a 32 byte transaction hash (txid).
358353
func (s *WalletServices) MerklePath(ctx context.Context, txid string) (*wdk.MerklePathResult, error) {
359354
result, err := s.merklePathServices.OneByOne(ctx, txid)
@@ -579,3 +574,23 @@ func (s *WalletServices) HashOutputScript(scriptHex string) (string, error) {
579574
}
580575
return outputScript, nil
581576
}
577+
578+
// FiatExchangeRate returns approximate exchange rate currency per base.
579+
// Uses config.FiatExchangeRates as the source.
580+
func (s *WalletServices) FiatExchangeRate(currency defs.Currency, base *defs.Currency) float64 {
581+
rates := s.config.FiatExchangeRates.Rates
582+
583+
baseCurrency := defs.USD
584+
if base != nil {
585+
baseCurrency = *base
586+
}
587+
588+
currencyRate, ok1 := rates[currency]
589+
baseRate, ok2 := rates[baseCurrency]
590+
591+
if !ok1 || !ok2 || baseRate == 0 {
592+
return 0
593+
}
594+
595+
return currencyRate / baseRate
596+
}

0 commit comments

Comments
 (0)