From ae834405f3660fa11dcae687117fe30d13cd8d40 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Thu, 2 Oct 2025 22:03:59 +0400 Subject: [PATCH 01/21] fixed oracleSet tests --- xrpl/transaction/oracle_set_test.go | 68 +++++++++++++++++------------ 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/xrpl/transaction/oracle_set_test.go b/xrpl/transaction/oracle_set_test.go index 94a6da38..c1ea3452 100644 --- a/xrpl/transaction/oracle_set_test.go +++ b/xrpl/transaction/oracle_set_test.go @@ -24,7 +24,7 @@ func TestOracleSet_Flatten(t *testing.T) { name: "pass - empty", tx: &OracleSet{}, expected: map[string]interface{}{ - "TransactionType": OracleSetTx, + "TransactionType": OracleSetTx.String(), "OracleDocumentID": uint32(0), "LastUpdatedTime": uint32(0), }, @@ -43,17 +43,19 @@ func TestOracleSet_Flatten(t *testing.T) { URI: "https://example.com", LastUpdatedTime: 1715702400, AssetClass: "currency", - PriceDataSeries: []ledger.PriceData{ + PriceDataSeries: []ledger.PriceDataWrapper{ { - BaseAsset: "XRP", - QuoteAsset: "USD", - AssetPrice: 740, - Scale: 3, + PriceData: ledger.PriceData{ + BaseAsset: "XRP", + QuoteAsset: "USD", + AssetPrice: 740, + Scale: 3, + }, }, }, }, expected: map[string]interface{}{ - "TransactionType": OracleSetTx, + "TransactionType": OracleSetTx.String(), "Account": "r9cZA1mTh4KVPD5PXPBGVdqw9XRybCz6z", "Fee": "1000000", "Sequence": uint32(1), @@ -63,12 +65,14 @@ func TestOracleSet_Flatten(t *testing.T) { "URI": "https://example.com", "LastUpdatedTime": uint32(1715702400), "AssetClass": "currency", - "PriceDataSeries": []map[string]interface{}{ + "PriceDataSeries": []map[string]any{ { - "BaseAsset": "XRP", - "QuoteAsset": "USD", - "AssetPrice": uint64(740), - "Scale": uint8(3), + "PriceData": ledger.FlatPriceData{ + "AssetPrice": uint64(740), + "BaseAsset": "XRP", + "QuoteAsset": "USD", + "Scale": uint8(3), + }, }, }, }, @@ -118,7 +122,7 @@ func TestOracleSet_Validate(t *testing.T) { Account: "r9cZA1mTh4KVPD5PXPBGVdqw9XRybCz6z", TransactionType: OracleSetTx, }, - PriceDataSeries: make([]ledger.PriceData, 100), + PriceDataSeries: make([]ledger.PriceDataWrapper, 100), }, expected: ErrOraclePriceDataSeriesItems{ Length: 100, @@ -132,9 +136,11 @@ func TestOracleSet_Validate(t *testing.T) { Account: "r9cZA1mTh4KVPD5PXPBGVdqw9XRybCz6z", TransactionType: OracleSetTx, }, - PriceDataSeries: []ledger.PriceData{ + PriceDataSeries: []ledger.PriceDataWrapper{ { - BaseAsset: "XRP", + PriceData: ledger.PriceData{ + BaseAsset: "XRP", + }, }, }, }, @@ -147,11 +153,13 @@ func TestOracleSet_Validate(t *testing.T) { Account: "r9cZA1mTh4KVPD5PXPBGVdqw9XRybCz6z", TransactionType: OracleSetTx, }, - PriceDataSeries: []ledger.PriceData{ + PriceDataSeries: []ledger.PriceDataWrapper{ { - BaseAsset: "XRP", - QuoteAsset: "USD", - Scale: 11, + PriceData: ledger.PriceData{ + BaseAsset: "XRP", + QuoteAsset: "USD", + Scale: 11, + }, }, }, }, @@ -167,11 +175,13 @@ func TestOracleSet_Validate(t *testing.T) { Account: "r9cZA1mTh4KVPD5PXPBGVdqw9XRybCz6z", TransactionType: OracleSetTx, }, - PriceDataSeries: []ledger.PriceData{ + PriceDataSeries: []ledger.PriceDataWrapper{ { - BaseAsset: "XRP", - QuoteAsset: "USD", - Scale: 10, + PriceData: ledger.PriceData{ + BaseAsset: "XRP", + QuoteAsset: "USD", + Scale: 10, + }, }, }, }, @@ -189,12 +199,14 @@ func TestOracleSet_Validate(t *testing.T) { URI: "https://example.com", LastUpdatedTime: 1715702400, AssetClass: "currency", - PriceDataSeries: []ledger.PriceData{ + PriceDataSeries: []ledger.PriceDataWrapper{ { - BaseAsset: "XRP", - QuoteAsset: "USD", - AssetPrice: 740, - Scale: 3, + PriceData: ledger.PriceData{ + BaseAsset: "XRP", + QuoteAsset: "USD", + AssetPrice: 740, + Scale: 3, + }, }, }, }, From e2efafb212dc989f4521c4bf1580f20af51341b4 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Thu, 2 Oct 2025 22:07:17 +0400 Subject: [PATCH 02/21] test with map[string]any --- xrpl/transaction/oracle_set_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xrpl/transaction/oracle_set_test.go b/xrpl/transaction/oracle_set_test.go index c1ea3452..b0bb1f49 100644 --- a/xrpl/transaction/oracle_set_test.go +++ b/xrpl/transaction/oracle_set_test.go @@ -67,7 +67,7 @@ func TestOracleSet_Flatten(t *testing.T) { "AssetClass": "currency", "PriceDataSeries": []map[string]any{ { - "PriceData": ledger.FlatPriceData{ + "PriceData": map[string]any{ "AssetPrice": uint64(740), "BaseAsset": "XRP", "QuoteAsset": "USD", From 19509b39907682c8ba7cb7dbc3a8240a5fa5eab6 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 10:56:46 +0400 Subject: [PATCH 03/21] updated oracle-set txn and tests --- xrpl/transaction/oracle_set.go | 15 ++++++++------- xrpl/transaction/oracle_set_test.go | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/xrpl/transaction/oracle_set.go b/xrpl/transaction/oracle_set.go index 2947efba..a39a96df 100644 --- a/xrpl/transaction/oracle_set.go +++ b/xrpl/transaction/oracle_set.go @@ -56,7 +56,7 @@ type OracleSet struct { // This field is required when creating a new Oracle ledger entry, but is optional for updates. AssetClass string `json:",omitempty"` // An array of up to 10 PriceData objects, each representing the price information for a token pair. More than five PriceData objects require two owner reserves. - PriceDataSeries []ledger.PriceData + PriceDataSeries []ledger.PriceDataWrapper } // TxType returns the TxType for OracleSet transactions. @@ -68,7 +68,7 @@ func (tx *OracleSet) TxType() TxType { func (tx *OracleSet) Flatten() map[string]interface{} { flattened := tx.BaseTx.Flatten() - flattened["TransactionType"] = tx.TxType() + flattened["TransactionType"] = tx.TxType().String() if tx.Account != "" { flattened["Account"] = tx.Account.String() @@ -90,9 +90,10 @@ func (tx *OracleSet) Flatten() map[string]interface{} { } if len(tx.PriceDataSeries) > 0 { - flattenedPriceDataSeries := make([]map[string]interface{}, 0, len(tx.PriceDataSeries)) - for _, priceData := range tx.PriceDataSeries { - flattenedPriceDataSeries = append(flattenedPriceDataSeries, priceData.Flatten()) + flattenedPriceDataSeries := make([]map[string]any, 0, len(tx.PriceDataSeries)) + for _, priceDataWrapper := range tx.PriceDataSeries { + flattenedPriceDataWrapper := priceDataWrapper.Flatten() + flattenedPriceDataSeries = append(flattenedPriceDataSeries, flattenedPriceDataWrapper) } flattened["PriceDataSeries"] = flattenedPriceDataSeries } @@ -120,8 +121,8 @@ func (tx *OracleSet) Validate() (bool, error) { } } - for _, priceData := range tx.PriceDataSeries { - if err := priceData.Validate(); err != nil { + for _, priceDataWrapper := range tx.PriceDataSeries { + if err := priceDataWrapper.PriceData.Validate(); err != nil { return false, err } } diff --git a/xrpl/transaction/oracle_set_test.go b/xrpl/transaction/oracle_set_test.go index b0bb1f49..bd39dc63 100644 --- a/xrpl/transaction/oracle_set_test.go +++ b/xrpl/transaction/oracle_set_test.go @@ -68,7 +68,7 @@ func TestOracleSet_Flatten(t *testing.T) { "PriceDataSeries": []map[string]any{ { "PriceData": map[string]any{ - "AssetPrice": uint64(740), + "AssetPrice": "740", "BaseAsset": "XRP", "QuoteAsset": "USD", "Scale": uint8(3), From 2994b0be8351249a2f641c676b925676bdbc66c1 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 10:57:15 +0400 Subject: [PATCH 04/21] updated definitions.json --- binary-codec/definitions/definitions.json | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/binary-codec/definitions/definitions.json b/binary-codec/definitions/definitions.json index acfc7aa4..aa802379 100644 --- a/binary-codec/definitions/definitions.json +++ b/binary-codec/definitions/definitions.json @@ -13,6 +13,7 @@ "LedgerEntry": 10002, "Metadata": 10004, "NotPresent": 0, + "Number": 9, "PathSet": 18, "STArray": 15, "STObject": 14, @@ -236,6 +237,16 @@ "type": "UInt8" } ], + [ + "AssetScale", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 5, + "type": "UInt8" + } + ], [ "TickSize", { @@ -527,7 +538,7 @@ } ], [ - "LastUpdateTime", + "LastUpdatedTime", { "nth": 15, "isVLEncoded": false, @@ -2416,6 +2427,16 @@ "type": "AccountID" } ], + [ + "Number", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 1, + "type": "Number" + } + ], [ "TransactionMetaData", { From 0af38a8624477443f0620e8ccf9bbc3a4de5bd80 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 10:57:44 +0400 Subject: [PATCH 05/21] fixed currency.ToJSON with default 20 bytes --- binary-codec/types/currency.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/binary-codec/types/currency.go b/binary-codec/types/currency.go index 13ef084b..2aa7c333 100644 --- a/binary-codec/types/currency.go +++ b/binary-codec/types/currency.go @@ -29,11 +29,13 @@ func (c *Currency) FromJSON(json any) ([]byte, error) { // ToJSON serializes a binary currency value into a JSON-compatible format. // It requires a length option specifying the byte length to read. func (c *Currency) ToJSON(p interfaces.BinaryParser, opts ...int) (any, error) { - if len(opts) == 0 { - return nil, ErrMissingCurrencyLengthOption + // default to 20 bytes, https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/oracle#currency-internal-format + length := 20 + if len(opts) > 0 && opts[0] > 0 { + length = opts[0] } - currencyBytes, err := p.ReadBytes(opts[0]) + currencyBytes, err := p.ReadBytes(length) if err != nil { return nil, err } From 4d21a26ab3c6364f2015ba39fa89efa6c19a7286 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 10:58:26 +0400 Subject: [PATCH 06/21] updated Oracle with a wrapper for PriceDate --- xrpl/ledger-entry-types/oracle.go | 32 ++++++++++++++++++++------ xrpl/ledger-entry-types/oracle_test.go | 8 +++---- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/xrpl/ledger-entry-types/oracle.go b/xrpl/ledger-entry-types/oracle.go index 0d9e2218..7c29fc3d 100644 --- a/xrpl/ledger-entry-types/oracle.go +++ b/xrpl/ledger-entry-types/oracle.go @@ -1,6 +1,8 @@ package ledger import ( + "strconv" + "github.com/Peersyst/xrpl-go/xrpl/transaction/types" ) @@ -9,6 +11,10 @@ const ( PriceDataScaleMax uint8 = 10 ) +type PriceDataWrapper struct { + PriceData PriceData +} + // A PriceData object represents the price information for a token pair. type PriceData struct { // The primary asset in a trading pair. Any valid identifier, such as a stock symbol, @@ -52,28 +58,40 @@ func (priceData *PriceData) Validate() error { return nil } +// Flatten returns a map containing the PriceData if it is set, or nil otherwise. +func (mw *PriceDataWrapper) Flatten() map[string]any { + if mw.PriceData != (PriceData{}) { + flattened := make(map[string]any) + flattened["PriceData"] = mw.PriceData.Flatten() + return flattened + } + return nil +} + // FlatPriceData represents a flattened map of PriceData fields for JSON serialization. -type FlatPriceData map[string]interface{} +// type FlatPriceData map[string]interface{} // Flatten flattens the price data. -func (priceData *PriceData) Flatten() FlatPriceData { +func (priceData *PriceData) Flatten() map[string]any { mapKeys := 2 if priceData.Scale != 0 && priceData.AssetPrice != 0 { mapKeys = 4 } - flattened := make(FlatPriceData, mapKeys) + flattened := make(map[string]any, mapKeys) + if priceData.AssetPrice != 0 { + // AssetPrice needs to be a string + flatAssetPrice := strconv.FormatUint(priceData.AssetPrice, 10) + flattened["AssetPrice"] = flatAssetPrice + } if priceData.BaseAsset != "" { flattened["BaseAsset"] = priceData.BaseAsset } if priceData.QuoteAsset != "" { flattened["QuoteAsset"] = priceData.QuoteAsset } - if priceData.AssetPrice != 0 { - flattened["AssetPrice"] = priceData.AssetPrice - } flattened["Scale"] = priceData.Scale @@ -119,7 +137,7 @@ type Oracle struct { Provider string // An array of up to 10 PriceData objects, each representing the price information for a token pair. // More than five PriceData objects require two owner reserves. - PriceDataSeries []PriceData + PriceDataSeries []PriceDataWrapper // The time the data was last updated, represented in Unix time. LastUpdateTime uint32 // An optional Universal Resource Identifier to reference price data off-chain. diff --git a/xrpl/ledger-entry-types/oracle_test.go b/xrpl/ledger-entry-types/oracle_test.go index 996478d4..0df33386 100644 --- a/xrpl/ledger-entry-types/oracle_test.go +++ b/xrpl/ledger-entry-types/oracle_test.go @@ -15,12 +15,12 @@ func TestPriceData_Flatten(t *testing.T) { testcases := []struct { name string priceData *PriceData - expected FlatPriceData + expected map[string]any }{ { name: "pass - empty", priceData: &PriceData{}, - expected: FlatPriceData{ + expected: map[string]any{ "Scale": uint8(0), }, }, @@ -32,10 +32,10 @@ func TestPriceData_Flatten(t *testing.T) { AssetPrice: 740, Scale: 3, }, - expected: FlatPriceData{ + expected: map[string]any{ "BaseAsset": "XRP", "QuoteAsset": "USD", - "AssetPrice": uint64(740), + "AssetPrice": 740, "Scale": uint8(3), }, }, From 5da50675de15d216b729440ed8afd588c74e56de Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 11:08:08 +0400 Subject: [PATCH 07/21] oracle set rpc example --- examples/oracle/rpc/main.go | 109 ++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 examples/oracle/rpc/main.go diff --git a/examples/oracle/rpc/main.go b/examples/oracle/rpc/main.go new file mode 100644 index 00000000..a6780cf8 --- /dev/null +++ b/examples/oracle/rpc/main.go @@ -0,0 +1,109 @@ +package main + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "time" + + "github.com/Peersyst/xrpl-go/pkg/crypto" + "github.com/Peersyst/xrpl-go/xrpl/currency" + "github.com/Peersyst/xrpl-go/xrpl/faucet" + "github.com/Peersyst/xrpl-go/xrpl/ledger-entry-types" + "github.com/Peersyst/xrpl-go/xrpl/rpc" + "github.com/Peersyst/xrpl-go/xrpl/rpc/types" + "github.com/Peersyst/xrpl-go/xrpl/transaction" + "github.com/Peersyst/xrpl-go/xrpl/wallet" +) + +func printJSON(data interface{}) { + jsonBytes, err := json.MarshalIndent(data, "", " ") + if err != nil { + fmt.Printf("❌ Error marshaling to JSON: %s\n", err) + return + } + fmt.Println(string(jsonBytes)) +} + +func main() { + // + // Configure client + // + fmt.Println("⏳ Setting up client...") + cfg, err := rpc.NewClientConfig( + "https://s.altnet.rippletest.net:51234/", + rpc.WithFaucetProvider(faucet.NewTestnetFaucetProvider()), + ) + if err != nil { + panic(err) + } + + client := rpc.NewClient(cfg) + fmt.Println("✅ Client configured!") + fmt.Println() + + // + // Configure wallets + // + fmt.Println("⏳ Setting up wallets...") + oracleIssuer, err := wallet.New(crypto.ED25519()) + if err != nil { + fmt.Printf("❌ Error creating oracle issuer wallet: %s\n", err) + return + } + err = client.FundWallet(&oracleIssuer) + if err != nil { + fmt.Printf("❌ Error funding oracle issuer wallet: %s\n", err) + return + } + fmt.Println("💸 Oracle issuer wallet funded!") + fmt.Println() + + // + // Create oracle set transaction + // + fmt.Println("⏳ Creating oracle set transaction...") + + // 1 minute ago + lastUpdatedTime := time.Now().Add(-time.Second).Unix() + + oracleSet := transaction.OracleSet{ + BaseTx: transaction.BaseTx{ + Account: oracleIssuer.ClassicAddress, + }, + OracleDocumentID: 1, + LastUpdatedTime: uint32(lastUpdatedTime), + URI: hex.EncodeToString([]byte("https://example.com")), + Provider: hex.EncodeToString([]byte("Chainlink")), + AssetClass: hex.EncodeToString([]byte("currency")), + PriceDataSeries: []ledger.PriceDataWrapper{ + { + PriceData: ledger.PriceData{ + BaseAsset: currency.ConvertStringToHex("ACGB"), + QuoteAsset: "USD", + AssetPrice: 123, + Scale: 3, + }, + }, + }, + } + + flatOracleSet := oracleSet.Flatten() + + fmt.Println("📄 Oracle Set Transaction JSON:") + printJSON(flatOracleSet) + fmt.Println() + + response, err := client.SubmitTxAndWait(flatOracleSet, &types.SubmitOptions{ + Wallet: &oracleIssuer, + Autofill: true, + }) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println("✅ Oracle set transaction submitted") + fmt.Printf("🌐 Hash: %s\n", response.Hash.String()) + fmt.Printf("🌐 Validated: %t\n", response.Validated) +} From 7ea73683e64dfe82c67892fd63d16be3478453cf Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 11:12:19 +0400 Subject: [PATCH 08/21] added OracleDelete in rpc example --- examples/oracle/rpc/main.go | 38 ++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/examples/oracle/rpc/main.go b/examples/oracle/rpc/main.go index a6780cf8..c03ff6ed 100644 --- a/examples/oracle/rpc/main.go +++ b/examples/oracle/rpc/main.go @@ -66,12 +66,13 @@ func main() { // 1 minute ago lastUpdatedTime := time.Now().Add(-time.Second).Unix() + oracleDocumentID := uint32(1) oracleSet := transaction.OracleSet{ BaseTx: transaction.BaseTx{ Account: oracleIssuer.ClassicAddress, }, - OracleDocumentID: 1, + OracleDocumentID: oracleDocumentID, LastUpdatedTime: uint32(lastUpdatedTime), URI: hex.EncodeToString([]byte("https://example.com")), Provider: hex.EncodeToString([]byte("Chainlink")), @@ -106,4 +107,39 @@ func main() { fmt.Println("✅ Oracle set transaction submitted") fmt.Printf("🌐 Hash: %s\n", response.Hash.String()) fmt.Printf("🌐 Validated: %t\n", response.Validated) + + if !response.Validated { + fmt.Println("❌ Oracle set transaction failed") + return + } + fmt.Println() + + // Delete oracle + fmt.Println("⏳ Deleting oracle...") + + oracleDelete := transaction.OracleDelete{ + BaseTx: transaction.BaseTx{ + Account: oracleIssuer.ClassicAddress, + }, + OracleDocumentID: oracleDocumentID, + } + + flatOracleDelete := oracleDelete.Flatten() + + fmt.Println("📄 Oracle Delete Transaction JSON:") + printJSON(flatOracleDelete) + fmt.Println() + + responseDelete, err := client.SubmitTxAndWait(flatOracleDelete, &types.SubmitOptions{ + Wallet: &oracleIssuer, + Autofill: true, + }) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println("✅ Oracle deleted") + fmt.Printf("🌐 Hash: %s\n", responseDelete.Hash.String()) + fmt.Printf("🌐 Validated: %t\n", responseDelete.Validated) } From 5bbe3fbb429099bcdbb156c825480c3c75cbeb74 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 11:19:07 +0400 Subject: [PATCH 09/21] oracle websocket example --- examples/oracle/ws/main.go | 150 +++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 examples/oracle/ws/main.go diff --git a/examples/oracle/ws/main.go b/examples/oracle/ws/main.go new file mode 100644 index 00000000..90fb4d88 --- /dev/null +++ b/examples/oracle/ws/main.go @@ -0,0 +1,150 @@ +package main + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "time" + + "github.com/Peersyst/xrpl-go/examples/clients" + "github.com/Peersyst/xrpl-go/pkg/crypto" + "github.com/Peersyst/xrpl-go/xrpl/currency" + "github.com/Peersyst/xrpl-go/xrpl/ledger-entry-types" + "github.com/Peersyst/xrpl-go/xrpl/transaction" + "github.com/Peersyst/xrpl-go/xrpl/wallet" + wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types" +) + +func printJSON(data interface{}) { + jsonBytes, err := json.MarshalIndent(data, "", " ") + if err != nil { + fmt.Printf("❌ Error marshaling to JSON: %s\n", err) + return + } + fmt.Println(string(jsonBytes)) +} + +func main() { + // Setup client + fmt.Println("⏳ Setting up testnet WebSocket client...") + client := clients.GetTestnetWebsocketClient() + defer func() { + if err := client.Disconnect(); err != nil { + fmt.Printf("Error disconnecting: %s\n", err) + } + }() + + if err := client.Connect(); err != nil { + fmt.Printf("❌ Error connecting to testnet: %s\n", err) + return + } + + if !client.IsConnected() { + fmt.Println("❌ Failed to connect to testnet") + return + } + + fmt.Println("✅ Connected to testnet") + fmt.Println() + + // + // Configure wallets + // + fmt.Println("⏳ Setting up wallets...") + oracleIssuer, err := wallet.New(crypto.ED25519()) + if err != nil { + fmt.Printf("❌ Error creating oracle issuer wallet: %s\n", err) + return + } + err = client.FundWallet(&oracleIssuer) + if err != nil { + fmt.Printf("❌ Error funding oracle issuer wallet: %s\n", err) + return + } + fmt.Println("💸 Oracle issuer wallet funded!") + fmt.Println() + + // + // Create oracle set transaction + // + fmt.Println("⏳ Creating oracle set transaction...") + + // 1 minute ago + lastUpdatedTime := time.Now().Add(-time.Second).Unix() + oracleDocumentID := uint32(1) + + oracleSet := transaction.OracleSet{ + BaseTx: transaction.BaseTx{ + Account: oracleIssuer.ClassicAddress, + }, + OracleDocumentID: oracleDocumentID, + LastUpdatedTime: uint32(lastUpdatedTime), + URI: hex.EncodeToString([]byte("https://example.com")), + Provider: hex.EncodeToString([]byte("Chainlink")), + AssetClass: hex.EncodeToString([]byte("currency")), + PriceDataSeries: []ledger.PriceDataWrapper{ + { + PriceData: ledger.PriceData{ + BaseAsset: currency.ConvertStringToHex("ACGB"), + QuoteAsset: "USD", + AssetPrice: 123, + Scale: 3, + }, + }, + }, + } + + flatOracleSet := oracleSet.Flatten() + + fmt.Println("📄 Oracle Set Transaction JSON:") + printJSON(flatOracleSet) + fmt.Println() + + response, err := client.SubmitTxAndWait(flatOracleSet, &wstypes.SubmitOptions{ + Wallet: &oracleIssuer, + Autofill: true, + }) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println("✅ Oracle set transaction submitted") + fmt.Printf("🌐 Hash: %s\n", response.Hash.String()) + fmt.Printf("🌐 Validated: %t\n", response.Validated) + + if !response.Validated { + fmt.Println("❌ Oracle set transaction failed") + return + } + fmt.Println() + + // Delete oracle + fmt.Println("⏳ Deleting oracle...") + + oracleDelete := transaction.OracleDelete{ + BaseTx: transaction.BaseTx{ + Account: oracleIssuer.ClassicAddress, + }, + OracleDocumentID: oracleDocumentID, + } + + flatOracleDelete := oracleDelete.Flatten() + + fmt.Println("📄 Oracle Delete Transaction JSON:") + printJSON(flatOracleDelete) + fmt.Println() + + responseDelete, err := client.SubmitTxAndWait(flatOracleDelete, &wstypes.SubmitOptions{ + Wallet: &oracleIssuer, + Autofill: true, + }) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println("✅ Oracle deleted") + fmt.Printf("🌐 Hash: %s\n", responseDelete.Hash.String()) + fmt.Printf("🌐 Validated: %t\n", responseDelete.Validated) +} From 11ab1a44fe8509879e749d407c3279ecadd15b7e Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 11:20:24 +0400 Subject: [PATCH 10/21] refactor get client in rpc --- examples/oracle/rpc/main.go | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/examples/oracle/rpc/main.go b/examples/oracle/rpc/main.go index c03ff6ed..e7fb6c3f 100644 --- a/examples/oracle/rpc/main.go +++ b/examples/oracle/rpc/main.go @@ -6,11 +6,10 @@ import ( "fmt" "time" + "github.com/Peersyst/xrpl-go/examples/clients" "github.com/Peersyst/xrpl-go/pkg/crypto" "github.com/Peersyst/xrpl-go/xrpl/currency" - "github.com/Peersyst/xrpl-go/xrpl/faucet" "github.com/Peersyst/xrpl-go/xrpl/ledger-entry-types" - "github.com/Peersyst/xrpl-go/xrpl/rpc" "github.com/Peersyst/xrpl-go/xrpl/rpc/types" "github.com/Peersyst/xrpl-go/xrpl/transaction" "github.com/Peersyst/xrpl-go/xrpl/wallet" @@ -29,18 +28,8 @@ func main() { // // Configure client // - fmt.Println("⏳ Setting up client...") - cfg, err := rpc.NewClientConfig( - "https://s.altnet.rippletest.net:51234/", - rpc.WithFaucetProvider(faucet.NewTestnetFaucetProvider()), - ) - if err != nil { - panic(err) - } - - client := rpc.NewClient(cfg) - fmt.Println("✅ Client configured!") - fmt.Println() + fmt.Println("⏳ Setting up testnet RPC client...") + client := clients.GetTestnetRPCClient() // // Configure wallets From 1d9e542577a6814d248ae1161e45acc3846ce38b Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 11:26:20 +0400 Subject: [PATCH 11/21] fix Oracle tests --- xrpl/ledger-entry-types/oracle_test.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/xrpl/ledger-entry-types/oracle_test.go b/xrpl/ledger-entry-types/oracle_test.go index 0df33386..7c1b22b7 100644 --- a/xrpl/ledger-entry-types/oracle_test.go +++ b/xrpl/ledger-entry-types/oracle_test.go @@ -35,7 +35,22 @@ func TestPriceData_Flatten(t *testing.T) { expected: map[string]any{ "BaseAsset": "XRP", "QuoteAsset": "USD", - "AssetPrice": 740, + "AssetPrice": "740", + "Scale": uint8(3), + }, + }, + { + name: "pass - complete with currency more than 3 characters", + priceData: &PriceData{ + BaseAsset: "XRP", + QuoteAsset: "ACGBD", + AssetPrice: 740, + Scale: 3, + }, + expected: map[string]any{ + "BaseAsset": "XRP", + "QuoteAsset": "ACGBD", + "AssetPrice": "740", "Scale": uint8(3), }, }, From 43687bb40e69c3d8f2b25d02c9d90cde9f5c3a3e Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 11:30:10 +0400 Subject: [PATCH 12/21] changelog --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d27abef7..0d18d73e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Adds `PermissionedDomain` ledger entry type (XLS-80d). +#### binary-codec + +- `Number` and `AssetPrice` fields to `definitions.json` + +### Fixed + +#### xrpl + +- `OracleSet` transaction to Flatten correctly and `Oracle` PriceDataSeries array. + +#### binary-codec + +- `definitions.json` where `LastUpdatedTime` had a typo issue. + ## [v0.1.11] ### BREAKING CHANGES From cfa2a987b7f6e8eb213109ababa8ca12bfa3d0fb Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 11:40:27 +0400 Subject: [PATCH 13/21] PriceDataWrapper tests --- xrpl/ledger-entry-types/oracle_test.go | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/xrpl/ledger-entry-types/oracle_test.go b/xrpl/ledger-entry-types/oracle_test.go index 7c1b22b7..10497e58 100644 --- a/xrpl/ledger-entry-types/oracle_test.go +++ b/xrpl/ledger-entry-types/oracle_test.go @@ -63,6 +63,64 @@ func TestPriceData_Flatten(t *testing.T) { } } +func TestPriceDataWrapper_Flatten(t *testing.T) { + testcases := []struct { + name string + priceData *PriceDataWrapper + expected map[string]any + }{ + { + name: "pass - empty", + priceData: &PriceDataWrapper{}, + expected: nil, + }, + { + name: "pass - complete", + priceData: &PriceDataWrapper{ + PriceData: PriceData{ + BaseAsset: "XRP", + QuoteAsset: "USD", + AssetPrice: 740, + Scale: 3, + }, + }, + expected: map[string]any{ + "PriceData": map[string]any{ + "BaseAsset": "XRP", + "QuoteAsset": "USD", + "AssetPrice": "740", + "Scale": uint8(3), + }, + }, + }, + { + name: "pass - complete with currency more than 3 characters", + priceData: &PriceDataWrapper{ + PriceData: PriceData{ + BaseAsset: "XRP", + QuoteAsset: "ACGBD", + AssetPrice: 740, + Scale: 3, + }, + }, + expected: map[string]any{ + "PriceData": map[string]any{ + "BaseAsset": "XRP", + "QuoteAsset": "ACGBD", + "AssetPrice": "740", + "Scale": uint8(3), + }, + }, + }, + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + assert.Equal(t, testcase.priceData.Flatten(), testcase.expected) + }) + } +} + func TestPriceData_Validate(t *testing.T) { testcases := []struct { name string From 4b10b6a86afeb17b1af57485848c2fcd8aacc7f6 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 11:41:23 +0400 Subject: [PATCH 14/21] AssetScale instead of AssetPrice in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d18d73e..2dc4a5cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### binary-codec -- `Number` and `AssetPrice` fields to `definitions.json` +- `Number` and `AssetScale` fields to `definitions.json` ### Fixed From c434dab0efdbeb787526400b4ae93c952abf1440 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 11:43:31 +0400 Subject: [PATCH 15/21] removed comments in oracle.go --- xrpl/ledger-entry-types/oracle.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/xrpl/ledger-entry-types/oracle.go b/xrpl/ledger-entry-types/oracle.go index 7c29fc3d..106c56e2 100644 --- a/xrpl/ledger-entry-types/oracle.go +++ b/xrpl/ledger-entry-types/oracle.go @@ -68,9 +68,6 @@ func (mw *PriceDataWrapper) Flatten() map[string]any { return nil } -// FlatPriceData represents a flattened map of PriceData fields for JSON serialization. -// type FlatPriceData map[string]interface{} - // Flatten flattens the price data. func (priceData *PriceData) Flatten() map[string]any { mapKeys := 2 From f6a918d8c46bbdb5dbfd5af33168bd8dc76e500f Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 11:52:37 +0400 Subject: [PATCH 16/21] fixed linting --- examples/oracle/rpc/main.go | 14 ++++++++++++-- examples/oracle/ws/main.go | 14 ++++++++++++-- xrpl/ledger-entry-types/oracle.go | 1 + xrpl/transaction/types/authorize_credential.go | 1 + xrpl/transaction/types/currency_amount.go | 1 + 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/examples/oracle/rpc/main.go b/examples/oracle/rpc/main.go index e7fb6c3f..b4e80e98 100644 --- a/examples/oracle/rpc/main.go +++ b/examples/oracle/rpc/main.go @@ -15,6 +15,16 @@ import ( "github.com/Peersyst/xrpl-go/xrpl/wallet" ) +func safeInt64ToUint32(value int64) uint32 { + if value < 0 { + return 0 + } + if value > int64(^uint32(0)) { + return ^uint32(0) // max uint32 value + } + return uint32(value) +} + func printJSON(data interface{}) { jsonBytes, err := json.MarshalIndent(data, "", " ") if err != nil { @@ -54,7 +64,7 @@ func main() { fmt.Println("⏳ Creating oracle set transaction...") // 1 minute ago - lastUpdatedTime := time.Now().Add(-time.Second).Unix() + lastUpdatedTime := safeInt64ToUint32(time.Now().Add(-time.Second).Unix()) oracleDocumentID := uint32(1) oracleSet := transaction.OracleSet{ @@ -62,7 +72,7 @@ func main() { Account: oracleIssuer.ClassicAddress, }, OracleDocumentID: oracleDocumentID, - LastUpdatedTime: uint32(lastUpdatedTime), + LastUpdatedTime: lastUpdatedTime, URI: hex.EncodeToString([]byte("https://example.com")), Provider: hex.EncodeToString([]byte("Chainlink")), AssetClass: hex.EncodeToString([]byte("currency")), diff --git a/examples/oracle/ws/main.go b/examples/oracle/ws/main.go index 90fb4d88..669a26e4 100644 --- a/examples/oracle/ws/main.go +++ b/examples/oracle/ws/main.go @@ -15,6 +15,16 @@ import ( wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types" ) +func safeInt64ToUint32(value int64) uint32 { + if value < 0 { + return 0 + } + if value > int64(^uint32(0)) { + return ^uint32(0) // max uint32 value + } + return uint32(value) +} + func printJSON(data interface{}) { jsonBytes, err := json.MarshalIndent(data, "", " ") if err != nil { @@ -70,7 +80,7 @@ func main() { fmt.Println("⏳ Creating oracle set transaction...") // 1 minute ago - lastUpdatedTime := time.Now().Add(-time.Second).Unix() + lastUpdatedTime := safeInt64ToUint32(time.Now().Add(-time.Second).Unix()) oracleDocumentID := uint32(1) oracleSet := transaction.OracleSet{ @@ -78,7 +88,7 @@ func main() { Account: oracleIssuer.ClassicAddress, }, OracleDocumentID: oracleDocumentID, - LastUpdatedTime: uint32(lastUpdatedTime), + LastUpdatedTime: lastUpdatedTime, URI: hex.EncodeToString([]byte("https://example.com")), Provider: hex.EncodeToString([]byte("Chainlink")), AssetClass: hex.EncodeToString([]byte("currency")), diff --git a/xrpl/ledger-entry-types/oracle.go b/xrpl/ledger-entry-types/oracle.go index 106c56e2..83ece0bc 100644 --- a/xrpl/ledger-entry-types/oracle.go +++ b/xrpl/ledger-entry-types/oracle.go @@ -11,6 +11,7 @@ const ( PriceDataScaleMax uint8 = 10 ) +// PriceDataWrapper represents a wrapper for the PriceData struct. type PriceDataWrapper struct { PriceData PriceData } diff --git a/xrpl/transaction/types/authorize_credential.go b/xrpl/transaction/types/authorize_credential.go index b640b3f9..031039fc 100644 --- a/xrpl/transaction/types/authorize_credential.go +++ b/xrpl/transaction/types/authorize_credential.go @@ -1,3 +1,4 @@ +//revive:disable:var-naming package types // MaxAcceptedCredentials is the maximum number of accepted credentials. diff --git a/xrpl/transaction/types/currency_amount.go b/xrpl/transaction/types/currency_amount.go index 6fe606ff..bc18c867 100644 --- a/xrpl/transaction/types/currency_amount.go +++ b/xrpl/transaction/types/currency_amount.go @@ -1,3 +1,4 @@ +//revive:disable:var-naming package types import ( From bb081bc28f1433d5f8cabe4fac55e57609210b60 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 11:54:55 +0400 Subject: [PATCH 17/21] domain_id.go disable lint issue --- xrpl/transaction/types/domain_id.go | 1 + 1 file changed, 1 insertion(+) diff --git a/xrpl/transaction/types/domain_id.go b/xrpl/transaction/types/domain_id.go index b4ea5d73..38266e02 100644 --- a/xrpl/transaction/types/domain_id.go +++ b/xrpl/transaction/types/domain_id.go @@ -1,3 +1,4 @@ +//revive:disable:var-naming package types // DomainID returns the domain ID as a string. From e17516135b3566dca9efa67f1be13f524ab30fa5 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 12:00:13 +0400 Subject: [PATCH 18/21] more linting package disabling... --- xrpl/transaction/types/authorize_credential_list.go | 1 + xrpl/transaction/types/authorize_credentials.go | 1 + 2 files changed, 2 insertions(+) diff --git a/xrpl/transaction/types/authorize_credential_list.go b/xrpl/transaction/types/authorize_credential_list.go index b6d5a911..c914e323 100644 --- a/xrpl/transaction/types/authorize_credential_list.go +++ b/xrpl/transaction/types/authorize_credential_list.go @@ -1,3 +1,4 @@ +//revive:disable:var-naming package types // AuthorizeCredentialList represents a list of AuthorizeCredential entries with validation and flattening. diff --git a/xrpl/transaction/types/authorize_credentials.go b/xrpl/transaction/types/authorize_credentials.go index 1d544f57..d22a00be 100644 --- a/xrpl/transaction/types/authorize_credentials.go +++ b/xrpl/transaction/types/authorize_credentials.go @@ -1,3 +1,4 @@ +//revive:disable:var-naming package types import ( From 68f6ca7239eae28fc93f8955d635486b751cf408 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 13:14:17 +0400 Subject: [PATCH 19/21] more //revive:disable:var-naming --- xrpl/transaction/types/domain.go | 1 + xrpl/transaction/types/errors.go | 1 + xrpl/transaction/types/issued_currency.go | 1 + 3 files changed, 3 insertions(+) diff --git a/xrpl/transaction/types/domain.go b/xrpl/transaction/types/domain.go index 523a505e..ddd44dc4 100644 --- a/xrpl/transaction/types/domain.go +++ b/xrpl/transaction/types/domain.go @@ -1,3 +1,4 @@ +//revive:disable:var-naming package types // Domain returns the domain that owns this account, as a string of hex representing the. diff --git a/xrpl/transaction/types/errors.go b/xrpl/transaction/types/errors.go index e6876849..733729f1 100644 --- a/xrpl/transaction/types/errors.go +++ b/xrpl/transaction/types/errors.go @@ -1,3 +1,4 @@ +//revive:disable:var-naming package types import "errors" diff --git a/xrpl/transaction/types/issued_currency.go b/xrpl/transaction/types/issued_currency.go index 7fd1f2a1..5879ac4c 100644 --- a/xrpl/transaction/types/issued_currency.go +++ b/xrpl/transaction/types/issued_currency.go @@ -1,3 +1,4 @@ +//revive:disable:var-naming package types // IssuedCurrency represents an amount of a non-XRP currency issued by an account. From 2b4f54702e26d09bd8133c5c9ff618f45f866d86 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 13:20:26 +0400 Subject: [PATCH 20/21] credential and wallet locator //revive:disable:var-naming --- xrpl/transaction/types/credential.go | 1 + xrpl/transaction/types/wallet_locator.go | 1 + 2 files changed, 2 insertions(+) diff --git a/xrpl/transaction/types/credential.go b/xrpl/transaction/types/credential.go index 5b85bce4..3afc2e32 100644 --- a/xrpl/transaction/types/credential.go +++ b/xrpl/transaction/types/credential.go @@ -1,3 +1,4 @@ +//revive:disable:var-naming package types // Credential represents an XRPL credential, including the issuer address and credential type. diff --git a/xrpl/transaction/types/wallet_locator.go b/xrpl/transaction/types/wallet_locator.go index b472cef4..d3479a9d 100644 --- a/xrpl/transaction/types/wallet_locator.go +++ b/xrpl/transaction/types/wallet_locator.go @@ -1,3 +1,4 @@ +//revive:disable:var-naming package types // WalletLocator returns a pointer to a Hash256 representing an arbitrary 256-bit value (optional). From b4762ef9487c8617c15c9887ee853fa645ab1ba1 Mon Sep 17 00:00:00 2001 From: Florent Uzio Date: Fri, 3 Oct 2025 15:38:19 +0400 Subject: [PATCH 21/21] oracle_set perf optimized --- xrpl/transaction/oracle_set.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/xrpl/transaction/oracle_set.go b/xrpl/transaction/oracle_set.go index a39a96df..62dbf8d9 100644 --- a/xrpl/transaction/oracle_set.go +++ b/xrpl/transaction/oracle_set.go @@ -90,10 +90,9 @@ func (tx *OracleSet) Flatten() map[string]interface{} { } if len(tx.PriceDataSeries) > 0 { - flattenedPriceDataSeries := make([]map[string]any, 0, len(tx.PriceDataSeries)) - for _, priceDataWrapper := range tx.PriceDataSeries { - flattenedPriceDataWrapper := priceDataWrapper.Flatten() - flattenedPriceDataSeries = append(flattenedPriceDataSeries, flattenedPriceDataWrapper) + flattenedPriceDataSeries := make([]map[string]any, len(tx.PriceDataSeries)) + for i, priceDataWrapper := range tx.PriceDataSeries { + flattenedPriceDataSeries[i] = priceDataWrapper.Flatten() } flattened["PriceDataSeries"] = flattenedPriceDataSeries }