diff --git a/Cargo.lock b/Cargo.lock index 9bb5e573b..f273a2929 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "account-for-display" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "derive_more", @@ -49,10 +49,10 @@ dependencies = [ [[package]] name = "addresses" -version = "1.2.54" +version = "1.2.55" dependencies = [ "assert-json", - "bytes 1.2.54", + "bytes 1.2.55", "cap26-models", "core-utils", "derive_more", @@ -252,7 +252,7 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "assert-json" -version = "1.2.54" +version = "1.2.55" dependencies = [ "assert-json-diff", "error", @@ -552,7 +552,7 @@ dependencies = [ [[package]] name = "build-info" -version = "1.2.54" +version = "1.2.55" dependencies = [ "assert-json", "cargo_toml 0.22.3", @@ -579,7 +579,7 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "bytes" -version = "1.2.54" +version = "1.2.55" dependencies = [ "assert-json", "delegate", @@ -613,7 +613,7 @@ dependencies = [ [[package]] name = "cap26-models" -version = "1.2.54" +version = "1.2.55" dependencies = [ "assert-json", "derive_more", @@ -766,7 +766,7 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clients" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "async-trait", @@ -849,7 +849,7 @@ checksum = "0d8a42181e0652c2997ae4d217f25b63c5337a52fd2279736e97b832fa0a3cff" [[package]] name = "core-collections" -version = "1.2.54" +version = "1.2.55" dependencies = [ "has-sample-values", "indexmap 2.13.0", @@ -876,7 +876,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-misc" -version = "1.2.54" +version = "1.2.55" dependencies = [ "assert-json", "core-utils", @@ -896,7 +896,7 @@ dependencies = [ [[package]] name = "core-utils" -version = "1.2.54" +version = "1.2.55" dependencies = [ "error", "iso8601-timestamp", @@ -1167,7 +1167,7 @@ dependencies = [ [[package]] name = "discover" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "clients", @@ -1205,7 +1205,7 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "drivers" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "addresses", @@ -1230,10 +1230,10 @@ dependencies = [ [[package]] name = "ecc" -version = "1.2.54" +version = "1.2.55" dependencies = [ "assert-json", - "bytes 1.2.54", + "bytes 1.2.55", "derive_more", "enum-as-inner", "error", @@ -1332,11 +1332,11 @@ dependencies = [ [[package]] name = "encryption" -version = "1.2.54" +version = "1.2.55" dependencies = [ "aes-gcm", "assert-json", - "bytes 1.2.54", + "bytes 1.2.55", "derive_more", "error", "has-sample-values", @@ -1353,7 +1353,7 @@ dependencies = [ [[package]] name = "entity-by-address" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "error", @@ -1365,7 +1365,7 @@ dependencies = [ [[package]] name = "entity-foundation" -version = "1.2.54" +version = "1.2.55" dependencies = [ "assert-json", "derive_more", @@ -1440,7 +1440,7 @@ dependencies = [ [[package]] name = "error" -version = "1.2.54" +version = "1.2.55" dependencies = [ "derive_more", "log", @@ -1499,7 +1499,7 @@ dependencies = [ [[package]] name = "factor-instances-provider" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "addresses", @@ -1525,9 +1525,9 @@ dependencies = [ [[package]] name = "factors" -version = "1.2.54" +version = "1.2.55" dependencies = [ - "bytes 1.2.54", + "bytes 1.2.55", "cap26-models", "core-collections", "core-misc", @@ -1562,7 +1562,7 @@ dependencies = [ [[package]] name = "factors-supporting-types" -version = "1.2.54" +version = "1.2.55" dependencies = [ "async-trait", "error", @@ -1737,7 +1737,7 @@ dependencies = [ [[package]] name = "gateway-client-and-api" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "addresses", @@ -1759,7 +1759,7 @@ dependencies = [ [[package]] name = "gateway-models" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "assert-json", @@ -1871,7 +1871,7 @@ dependencies = [ [[package]] name = "has-sample-values" -version = "1.2.54" +version = "1.2.55" dependencies = [ "error", "indexmap 2.13.0", @@ -1882,9 +1882,9 @@ dependencies = [ [[package]] name = "hash" -version = "1.2.54" +version = "1.2.55" dependencies = [ - "bytes 1.2.54", + "bytes 1.2.55", "derive_more", "prelude", "radix-common", @@ -1948,11 +1948,11 @@ dependencies = [ [[package]] name = "hierarchical-deterministic" -version = "1.2.54" +version = "1.2.55" dependencies = [ "assert-json", "bip39", - "bytes 1.2.54", + "bytes 1.2.55", "cap26-models", "derive_more", "ecc", @@ -1995,13 +1995,13 @@ dependencies = [ [[package]] name = "home-cards" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "addresses", "async-trait", "base64", - "bytes 1.2.54", + "bytes 1.2.55", "core-utils", "derive_more", "drivers", @@ -2021,7 +2021,7 @@ dependencies = [ [[package]] name = "host-info" -version = "1.2.54" +version = "1.2.55" dependencies = [ "assert-json", "derive_more", @@ -2068,10 +2068,10 @@ dependencies = [ [[package]] name = "http-client" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", - "bytes 1.2.54", + "bytes 1.2.55", "core-utils", "drivers", "error", @@ -2296,7 +2296,7 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "identified-vec-of" -version = "1.2.54" +version = "1.2.55" dependencies = [ "assert-json", "derive_more", @@ -2370,7 +2370,7 @@ dependencies = [ [[package]] name = "interactors" -version = "1.2.54" +version = "1.2.55" dependencies = [ "async-trait", "derive_more", @@ -2508,7 +2508,7 @@ dependencies = [ [[package]] name = "key-derivation-traits" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "async-trait", @@ -2526,7 +2526,7 @@ dependencies = [ [[package]] name = "keys-collector" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "addresses", @@ -2624,7 +2624,7 @@ dependencies = [ [[package]] name = "manifests" -version = "1.2.54" +version = "1.2.55" dependencies = [ "account-for-display", "addresses", @@ -2670,7 +2670,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "metadata" -version = "1.2.54" +version = "1.2.55" dependencies = [ "derive_more", "has-sample-values", @@ -2794,7 +2794,7 @@ dependencies = [ [[package]] name = "network" -version = "1.2.54" +version = "1.2.55" dependencies = [ "assert-json", "enum-iterator", @@ -2810,7 +2810,7 @@ dependencies = [ [[package]] name = "next-derivation-index-ephemeral" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "assert-json", @@ -2886,9 +2886,9 @@ dependencies = [ [[package]] name = "numeric" -version = "1.2.54" +version = "1.2.55" dependencies = [ - "bytes 1.2.54", + "bytes 1.2.55", "delegate", "derive_more", "enum-iterator", @@ -3158,7 +3158,7 @@ dependencies = [ [[package]] name = "prelude" -version = "1.2.54" +version = "1.2.55" dependencies = [ "radix-engine", "radix-engine-toolkit", @@ -3219,7 +3219,7 @@ dependencies = [ [[package]] name = "profile" -version = "1.2.54" +version = "1.2.55" dependencies = [ "account-for-display", "addresses", @@ -3263,7 +3263,7 @@ dependencies = [ [[package]] name = "profile-account" -version = "1.2.54" +version = "1.2.55" dependencies = [ "account-for-display", "addresses", @@ -3283,7 +3283,7 @@ dependencies = [ [[package]] name = "profile-account-or-persona" -version = "1.2.54" +version = "1.2.55" dependencies = [ "cap26-models", "derive_more", @@ -3300,7 +3300,7 @@ dependencies = [ [[package]] name = "profile-app-preferences" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "core-misc", @@ -3323,7 +3323,7 @@ dependencies = [ [[package]] name = "profile-base-entity" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "derive_more", @@ -3348,7 +3348,7 @@ dependencies = [ [[package]] name = "profile-gateway" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "assert-json", @@ -3372,7 +3372,7 @@ dependencies = [ [[package]] name = "profile-logic" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "derive_more", @@ -3394,7 +3394,7 @@ dependencies = [ [[package]] name = "profile-persona" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "cap26-models", @@ -3415,7 +3415,7 @@ dependencies = [ [[package]] name = "profile-persona-data" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "assert-json", @@ -3434,7 +3434,7 @@ dependencies = [ [[package]] name = "profile-security-structures" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "cap26-models", @@ -3462,7 +3462,7 @@ dependencies = [ [[package]] name = "profile-state-holder" -version = "1.2.54" +version = "1.2.55" dependencies = [ "derive_more", "error", @@ -3477,7 +3477,7 @@ dependencies = [ [[package]] name = "profile-supporting-types" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "derive_more", @@ -3566,14 +3566,14 @@ dependencies = [ [[package]] name = "radix-connect" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "addresses", "assert-json", "async-trait", "base64", - "bytes 1.2.54", + "bytes 1.2.55", "core-misc", "core-utils", "derive_more", @@ -3602,10 +3602,10 @@ dependencies = [ [[package]] name = "radix-connect-models" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", - "bytes 1.2.54", + "bytes 1.2.55", "core-misc", "derive_more", "error", @@ -3729,11 +3729,11 @@ dependencies = [ [[package]] name = "radix-name-service" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "addresses", - "bytes 1.2.54", + "bytes 1.2.55", "core-utils", "drivers", "gateway-client-and-api", @@ -4082,7 +4082,7 @@ dependencies = [ [[package]] name = "sargon" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "addresses", @@ -4150,7 +4150,7 @@ dependencies = [ [[package]] name = "sargon-os" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "async-trait", @@ -4181,7 +4181,7 @@ dependencies = [ [[package]] name = "sargon-os-accounts" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "addresses", @@ -4207,7 +4207,7 @@ dependencies = [ [[package]] name = "sargon-os-arculus-card" -version = "1.2.54" +version = "1.2.55" dependencies = [ "async-trait", "ecc", @@ -4222,7 +4222,7 @@ dependencies = [ [[package]] name = "sargon-os-derive-public-keys" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "async-trait", @@ -4241,7 +4241,7 @@ dependencies = [ [[package]] name = "sargon-os-factors" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "addresses", @@ -4276,7 +4276,7 @@ dependencies = [ [[package]] name = "sargon-os-security-center" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "derive_more", @@ -4292,7 +4292,7 @@ dependencies = [ [[package]] name = "sargon-os-signing" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "async-trait", @@ -4324,7 +4324,7 @@ dependencies = [ [[package]] name = "sargon-os-transaction" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "async-std", @@ -4359,7 +4359,7 @@ dependencies = [ [[package]] name = "sargon-uniffi" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "addresses", @@ -4416,7 +4416,7 @@ dependencies = [ [[package]] name = "sargon-uniffi-conversion-macros" -version = "1.2.54" +version = "1.2.55" dependencies = [ "proc-macro2", "quote", @@ -4634,7 +4634,7 @@ dependencies = [ [[package]] name = "security-center" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", "assert-json", @@ -4821,7 +4821,7 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "short-string" -version = "1.2.54" +version = "1.2.55" dependencies = [ "arraystring", "assert-json", @@ -4856,13 +4856,13 @@ dependencies = [ [[package]] name = "signatures-collector" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "addresses", "assert-json", "async-trait", - "bytes 1.2.54", + "bytes 1.2.55", "cap26-models", "core-collections", "core-misc", @@ -4894,11 +4894,11 @@ dependencies = [ [[package]] name = "signing-traits" -version = "1.2.54" +version = "1.2.55" dependencies = [ "actix-rt", "async-trait", - "bytes 1.2.54", + "bytes 1.2.55", "core-collections", "derive_more", "ecc", @@ -5065,7 +5065,7 @@ dependencies = [ [[package]] name = "sub-systems" -version = "1.2.54" +version = "1.2.55" dependencies = [ "derive_more", "drivers", @@ -5230,7 +5230,7 @@ dependencies = [ [[package]] name = "time-utils" -version = "1.2.54" +version = "1.2.55" dependencies = [ "iso8601-timestamp", "prelude", @@ -5452,10 +5452,10 @@ dependencies = [ [[package]] name = "transaction-foundation" -version = "1.2.54" +version = "1.2.55" dependencies = [ "assert-json", - "bytes 1.2.54", + "bytes 1.2.55", "derive_more", "has-sample-values", "paste", @@ -5467,10 +5467,10 @@ dependencies = [ [[package]] name = "transaction-models" -version = "1.2.54" +version = "1.2.55" dependencies = [ "addresses", - "bytes 1.2.54", + "bytes 1.2.55", "cargo_toml 0.22.3", "core-collections", "core-misc", diff --git a/crates/app/discover/Cargo.toml b/crates/app/discover/Cargo.toml index c10325a5f..e626e4e9f 100644 --- a/crates/app/discover/Cargo.toml +++ b/crates/app/discover/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "discover" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/app/home-cards/Cargo.toml b/crates/app/home-cards/Cargo.toml index b020c9ab7..bad5f6ffe 100644 --- a/crates/app/home-cards/Cargo.toml +++ b/crates/app/home-cards/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "home-cards" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/app/key-derivation-traits/Cargo.toml b/crates/app/key-derivation-traits/Cargo.toml index d20c18098..69dd07c44 100644 --- a/crates/app/key-derivation-traits/Cargo.toml +++ b/crates/app/key-derivation-traits/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "key-derivation-traits" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/app/radix-connect-models/Cargo.toml b/crates/app/radix-connect-models/Cargo.toml index e43be199f..88a781b5f 100644 --- a/crates/app/radix-connect-models/Cargo.toml +++ b/crates/app/radix-connect-models/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-connect-models" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/app/radix-connect/Cargo.toml b/crates/app/radix-connect/Cargo.toml index 9afa8398e..fd276aae7 100644 --- a/crates/app/radix-connect/Cargo.toml +++ b/crates/app/radix-connect/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-connect" -version = "1.2.54" +version = "1.2.55" edition = "2021" diff --git a/crates/app/security-center/Cargo.toml b/crates/app/security-center/Cargo.toml index 5847ae536..53c41b69a 100644 --- a/crates/app/security-center/Cargo.toml +++ b/crates/app/security-center/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "security-center" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/app/signing-traits/Cargo.toml b/crates/app/signing-traits/Cargo.toml index f9c32664e..bed894281 100644 --- a/crates/app/signing-traits/Cargo.toml +++ b/crates/app/signing-traits/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "signing-traits" -version = "1.2.54" +version = "1.2.55" edition = "2021" [features] diff --git a/crates/common/build-info/Cargo.toml b/crates/common/build-info/Cargo.toml index 287f8263e..bc8c89f60 100644 --- a/crates/common/build-info/Cargo.toml +++ b/crates/common/build-info/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "build-info" -version = "1.2.54" +version = "1.2.55" edition = "2021" build = "build.rs" diff --git a/crates/common/bytes/Cargo.toml b/crates/common/bytes/Cargo.toml index af5a01b6b..58088ba2e 100644 --- a/crates/common/bytes/Cargo.toml +++ b/crates/common/bytes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bytes" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/common/entity-foundation/Cargo.toml b/crates/common/entity-foundation/Cargo.toml index ebf25c878..4261853ac 100644 --- a/crates/common/entity-foundation/Cargo.toml +++ b/crates/common/entity-foundation/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "entity-foundation" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/common/host-info/Cargo.toml b/crates/common/host-info/Cargo.toml index 1473682ca..a7cca68e0 100644 --- a/crates/common/host-info/Cargo.toml +++ b/crates/common/host-info/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "host-info" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/common/identified-vec-of/Cargo.toml b/crates/common/identified-vec-of/Cargo.toml index 1712c6745..ffab65bbc 100644 --- a/crates/common/identified-vec-of/Cargo.toml +++ b/crates/common/identified-vec-of/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identified-vec-of" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/common/metadata/Cargo.toml b/crates/common/metadata/Cargo.toml index d24ad36a0..a4a419fbb 100644 --- a/crates/common/metadata/Cargo.toml +++ b/crates/common/metadata/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "metadata" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/common/network/Cargo.toml b/crates/common/network/Cargo.toml index 678b4f69c..407f32380 100644 --- a/crates/common/network/Cargo.toml +++ b/crates/common/network/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "network" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/common/numeric/Cargo.toml b/crates/common/numeric/Cargo.toml index e29f84db5..e2c3914cb 100644 --- a/crates/common/numeric/Cargo.toml +++ b/crates/common/numeric/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "numeric" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/common/short-string/Cargo.toml b/crates/common/short-string/Cargo.toml index a0668e6df..f5205f207 100644 --- a/crates/common/short-string/Cargo.toml +++ b/crates/common/short-string/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "short-string" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/core/assert-json/Cargo.toml b/crates/core/assert-json/Cargo.toml index 1b183c794..b8d29cd97 100644 --- a/crates/core/assert-json/Cargo.toml +++ b/crates/core/assert-json/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "assert-json" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/core/collections/Cargo.toml b/crates/core/collections/Cargo.toml index c137c8b1f..9be0dc5a9 100644 --- a/crates/core/collections/Cargo.toml +++ b/crates/core/collections/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "core-collections" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/core/error/Cargo.toml b/crates/core/error/Cargo.toml index 003d7d075..2d5a96369 100644 --- a/crates/core/error/Cargo.toml +++ b/crates/core/error/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "error" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/core/has-sample-values/Cargo.toml b/crates/core/has-sample-values/Cargo.toml index d3c81474f..8e5b9ed28 100644 --- a/crates/core/has-sample-values/Cargo.toml +++ b/crates/core/has-sample-values/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "has-sample-values" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/core/misc/Cargo.toml b/crates/core/misc/Cargo.toml index 27970f7e6..442deeee3 100644 --- a/crates/core/misc/Cargo.toml +++ b/crates/core/misc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "core-misc" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/core/prelude/Cargo.toml b/crates/core/prelude/Cargo.toml index ca72ee309..07a36cf02 100644 --- a/crates/core/prelude/Cargo.toml +++ b/crates/core/prelude/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prelude" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/core/time-utils/Cargo.toml b/crates/core/time-utils/Cargo.toml index b51a8cbdd..ec4a7fc3d 100644 --- a/crates/core/time-utils/Cargo.toml +++ b/crates/core/time-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "time-utils" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/core/utils/Cargo.toml b/crates/core/utils/Cargo.toml index b13e6bffd..3f4b11af2 100644 --- a/crates/core/utils/Cargo.toml +++ b/crates/core/utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "core-utils" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/crypto/addresses/Cargo.toml b/crates/crypto/addresses/Cargo.toml index f428a5435..0ef694317 100644 --- a/crates/crypto/addresses/Cargo.toml +++ b/crates/crypto/addresses/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "addresses" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/crypto/cap26-models/Cargo.toml b/crates/crypto/cap26-models/Cargo.toml index d77d8b961..e45f383de 100644 --- a/crates/crypto/cap26-models/Cargo.toml +++ b/crates/crypto/cap26-models/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cap26-models" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/crypto/ecc/Cargo.toml b/crates/crypto/ecc/Cargo.toml index 85fb2403a..d2314d059 100644 --- a/crates/crypto/ecc/Cargo.toml +++ b/crates/crypto/ecc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ecc" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/crypto/encryption/Cargo.toml b/crates/crypto/encryption/Cargo.toml index c974b3277..c010a24e2 100644 --- a/crates/crypto/encryption/Cargo.toml +++ b/crates/crypto/encryption/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "encryption" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/crypto/hash/Cargo.toml b/crates/crypto/hash/Cargo.toml index 28a2d928a..37da5d14c 100644 --- a/crates/crypto/hash/Cargo.toml +++ b/crates/crypto/hash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hash" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/crypto/hd/Cargo.toml b/crates/crypto/hd/Cargo.toml index 19a21e760..a303caddd 100644 --- a/crates/crypto/hd/Cargo.toml +++ b/crates/crypto/hd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hierarchical-deterministic" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/factors/factors/Cargo.toml b/crates/factors/factors/Cargo.toml index 67175cbec..34ef73328 100644 --- a/crates/factors/factors/Cargo.toml +++ b/crates/factors/factors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "factors" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/factors/instances-provider/Cargo.toml b/crates/factors/instances-provider/Cargo.toml index cea48b0cd..718e9c5d0 100644 --- a/crates/factors/instances-provider/Cargo.toml +++ b/crates/factors/instances-provider/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "factor-instances-provider" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/factors/keys-collector/Cargo.toml b/crates/factors/keys-collector/Cargo.toml index cf143e531..70deb8aae 100644 --- a/crates/factors/keys-collector/Cargo.toml +++ b/crates/factors/keys-collector/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "keys-collector" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/factors/next-derivation-index-ephemeral/Cargo.toml b/crates/factors/next-derivation-index-ephemeral/Cargo.toml index de1d63a85..d04009cab 100644 --- a/crates/factors/next-derivation-index-ephemeral/Cargo.toml +++ b/crates/factors/next-derivation-index-ephemeral/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "next-derivation-index-ephemeral" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/factors/signatures-collector/Cargo.toml b/crates/factors/signatures-collector/Cargo.toml index d6e8b47dd..499f3e7a5 100644 --- a/crates/factors/signatures-collector/Cargo.toml +++ b/crates/factors/signatures-collector/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "signatures-collector" -version = "1.2.54" +version = "1.2.55" edition = "2021" diff --git a/crates/factors/supporting-types/Cargo.toml b/crates/factors/supporting-types/Cargo.toml index ae6b27973..9c3dba976 100644 --- a/crates/factors/supporting-types/Cargo.toml +++ b/crates/factors/supporting-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "factors-supporting-types" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/gateway/client-and-api/Cargo.toml b/crates/gateway/client-and-api/Cargo.toml index a9301bffd..5b1c3f569 100644 --- a/crates/gateway/client-and-api/Cargo.toml +++ b/crates/gateway/client-and-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gateway-client-and-api" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/gateway/models/Cargo.toml b/crates/gateway/models/Cargo.toml index adfacf2a9..31ba02ca4 100644 --- a/crates/gateway/models/Cargo.toml +++ b/crates/gateway/models/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gateway-models" -version = "1.2.54" +version = "1.2.55" edition = "2021" diff --git a/crates/profile/logic/logic_SPLIT_ME/Cargo.toml b/crates/profile/logic/logic_SPLIT_ME/Cargo.toml index 76bdd8a68..1d448d5cc 100644 --- a/crates/profile/logic/logic_SPLIT_ME/Cargo.toml +++ b/crates/profile/logic/logic_SPLIT_ME/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "profile-logic" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/profile/models/account-for-display/Cargo.toml b/crates/profile/models/account-for-display/Cargo.toml index 91ac7d130..7856afb21 100644 --- a/crates/profile/models/account-for-display/Cargo.toml +++ b/crates/profile/models/account-for-display/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "account-for-display" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/profile/models/account-or-persona/Cargo.toml b/crates/profile/models/account-or-persona/Cargo.toml index 36970da86..bdaa592a8 100644 --- a/crates/profile/models/account-or-persona/Cargo.toml +++ b/crates/profile/models/account-or-persona/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "profile-account-or-persona" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/profile/models/account/Cargo.toml b/crates/profile/models/account/Cargo.toml index 3ec7b8820..c96aed7dc 100644 --- a/crates/profile/models/account/Cargo.toml +++ b/crates/profile/models/account/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "profile-account" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/profile/models/app-preferences/Cargo.toml b/crates/profile/models/app-preferences/Cargo.toml index aa3cdec8c..663591480 100644 --- a/crates/profile/models/app-preferences/Cargo.toml +++ b/crates/profile/models/app-preferences/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "profile-app-preferences" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/profile/models/app-preferences/src/p2p_transport_profiles.rs b/crates/profile/models/app-preferences/src/p2p_transport_profiles.rs index 2ae051fa6..cf0a1bf90 100644 --- a/crates/profile/models/app-preferences/src/p2p_transport_profiles.rs +++ b/crates/profile/models/app-preferences/src/p2p_transport_profiles.rs @@ -111,6 +111,17 @@ impl SavedP2PTransportProfiles { all } + pub fn all_available_for_selection(&self) -> Vec { + self.all() + .into_iter() + .filter(|profile| { + !P2PTransportProfile::is_radix_development_signaling_server( + &profile.signaling_server, + ) + }) + .collect() + } + pub fn has_signaling_server(&self, url: impl AsRef) -> bool { let signaling_server = url.as_ref(); self.all() @@ -159,6 +170,14 @@ impl Default for SavedP2PTransportProfiles { } impl P2PTransportProfile { + const RADIX_DEVELOPMENT_SIGNALING_SERVER: &'static str = + "wss://signaling-server-dev.rdx-works-main.extratools.works/"; + + pub fn is_radix_development_signaling_server(url: impl AsRef) -> bool { + url.as_ref().trim_end_matches('/') + == Self::RADIX_DEVELOPMENT_SIGNALING_SERVER.trim_end_matches('/') + } + fn google_stun_servers() -> P2PStunServer { P2PStunServer::new(vec![ "stun:stun.l.google.com:19302".to_string(), @@ -201,7 +220,7 @@ impl P2PTransportProfile { Self::new( "Radix Development", - "wss://signaling-server-dev.rdx-works-main.extratools.works/", + Self::RADIX_DEVELOPMENT_SIGNALING_SERVER, stun, turn, ) @@ -304,4 +323,31 @@ mod tests { assert_eq!(sut.current, development); assert!(sut.other.contains_by_id(&production)); } + + #[test] + fn all_available_for_selection_filters_out_radix_development_profile() { + let sut = SUT::default(); + let all = sut.all_available_for_selection(); + + assert_eq!(all.len(), 1); + assert_eq!(all[0].name, "Radix Production"); + assert_eq!( + all[0].signaling_server, + "wss://signaling-server.radixdlt.com/" + ); + } + + #[test] + fn all_available_for_selection_filters_out_current_radix_development_profile( + ) { + let sut = SUT::sample_other(); + let all = sut.all_available_for_selection(); + + assert_eq!(all.len(), 1); + assert_eq!(all[0].name, "Radix Production"); + assert_eq!( + all[0].signaling_server, + "wss://signaling-server.radixdlt.com/" + ); + } } diff --git a/crates/profile/models/base-entity/Cargo.toml b/crates/profile/models/base-entity/Cargo.toml index e80a7a63d..042a3cf32 100644 --- a/crates/profile/models/base-entity/Cargo.toml +++ b/crates/profile/models/base-entity/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "profile-base-entity" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/profile/models/gateway/Cargo.toml b/crates/profile/models/gateway/Cargo.toml index ae9efa0dc..131141a1d 100644 --- a/crates/profile/models/gateway/Cargo.toml +++ b/crates/profile/models/gateway/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "profile-gateway" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/profile/models/persona-data/Cargo.toml b/crates/profile/models/persona-data/Cargo.toml index b6f4a9dcb..263bbdd35 100644 --- a/crates/profile/models/persona-data/Cargo.toml +++ b/crates/profile/models/persona-data/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "profile-persona-data" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/profile/models/persona/Cargo.toml b/crates/profile/models/persona/Cargo.toml index db41dbce3..9e3322153 100644 --- a/crates/profile/models/persona/Cargo.toml +++ b/crates/profile/models/persona/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "profile-persona" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/profile/models/profile_SPLIT_ME/Cargo.toml b/crates/profile/models/profile_SPLIT_ME/Cargo.toml index d4dabf146..2fad1cf82 100644 --- a/crates/profile/models/profile_SPLIT_ME/Cargo.toml +++ b/crates/profile/models/profile_SPLIT_ME/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "profile" -version = "1.2.54" +version = "1.2.55" edition = "2021" diff --git a/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/mod.rs b/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/mod.rs index b4aac0856..59df82cea 100644 --- a/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/mod.rs +++ b/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/mod.rs @@ -8,6 +8,8 @@ mod mfa_factor_instances; mod personas; mod profile_network; mod resource_preferences; +mod token_price_service; +mod token_price_services; pub use accounts::*; pub use address_book::*; @@ -19,3 +21,5 @@ pub use mfa_factor_instances::*; pub use personas::*; pub use profile_network::*; pub use resource_preferences::*; +pub use token_price_service::*; +pub use token_price_services::*; diff --git a/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/profile_network.rs b/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/profile_network.rs index 20b22a655..6e1f4e613 100644 --- a/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/profile_network.rs +++ b/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/profile_network.rs @@ -41,6 +41,13 @@ pub struct ProfileNetwork { #[serde(default, skip_serializing_if = "AddressBook::is_empty")] pub address_book: AddressBook, + /// Ordered token price service endpoints used for failover. + #[serde( + default = "TokenPriceServices::default", + skip_serializing_if = "TokenPriceServices::is_default" + )] + pub token_price_services: TokenPriceServices, + /// Pre-derived MFA factor instances #[serde(default)] pub mfa_factor_instances: MFAFactorInstances, @@ -62,6 +69,7 @@ impl ProfileNetwork { authorized_dapps: {} resource_preferences: {:?} address_book: {:?} + token_price_services: {:?} mfa_factor_instances: {:?} "#, self.id, @@ -70,6 +78,7 @@ impl ProfileNetwork { self.authorized_dapps, self.resource_preferences, self.address_book, + self.token_price_services, self.mfa_factor_instances, ) } @@ -150,6 +159,7 @@ impl ProfileNetwork { authorized_dapps, resource_preferences, address_book: AddressBook::new(), + token_price_services: TokenPriceServices::default(), mfa_factor_instances, } } @@ -263,6 +273,14 @@ mod tests { assert_eq!(SUT::sample().address_book, AddressBook::new()) } + #[test] + fn get_token_price_services() { + assert_eq!( + SUT::sample().token_price_services, + TokenPriceServices::default() + ) + } + #[test] fn deserialize_legacy_json_without_address_book_defaults_to_empty() { let sut: SUT = serde_json::from_str( @@ -280,6 +298,7 @@ mod tests { .unwrap(); assert!(sut.address_book.is_empty()); + assert_eq!(sut.token_price_services, TokenPriceServices::default()); } #[test] @@ -293,6 +312,15 @@ mod tests { assert_json_roundtrip(&sut); } + #[test] + fn json_roundtrip_with_populated_token_price_services() { + let mut sut = SUT::new_empty_on(NetworkID::Mainnet); + assert!(sut + .token_price_services + .add(TokenPriceService::sample_other())); + assert_json_roundtrip(&sut); + } + #[test] fn duplicate_accounts_are_filtered_out() { assert_eq!( diff --git a/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/token_price_service.rs b/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/token_price_service.rs new file mode 100644 index 000000000..77bcbce36 --- /dev/null +++ b/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/token_price_service.rs @@ -0,0 +1,89 @@ +use crate::prelude::*; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Debug)] +#[serde(rename_all = "camelCase")] +pub struct TokenPriceService { + pub base_url: Url, +} + +impl TokenPriceService { + pub const SCOPED_TOKENS_PATH: &str = "/price/tokens"; + pub const PRODUCTION_BASE_URL: &str = + "https://token-price-service.radixdlt.com"; + + pub fn new(base_url: Url) -> Self { + Self { base_url } + } + + pub fn production() -> Self { + Self::new(Url::parse(Self::PRODUCTION_BASE_URL).expect("valid URL")) + } + + pub fn scoped_tokens_url(&self) -> Result { + self.base_url.join(Self::SCOPED_TOKENS_PATH).map_err(|_| { + CommonError::InvalidURL { + bad_value: self.base_url.to_string(), + } + }) + } +} + +impl Identifiable for TokenPriceService { + type ID = Url; + + fn id(&self) -> Self::ID { + self.base_url.clone() + } +} + +impl HasSampleValues for TokenPriceService { + fn sample() -> Self { + Self::production() + } + + fn sample_other() -> Self { + Self::new( + Url::parse("https://token-price-service-alt.example").unwrap(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = TokenPriceService; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn identifiable() { + let sut = SUT::sample(); + assert_eq!(sut.id(), sut.base_url); + } + + #[test] + fn scoped_tokens_url_is_derived_from_base_url() { + let sut = SUT::sample(); + assert_eq!( + sut.scoped_tokens_url().unwrap().as_str(), + "https://token-price-service.radixdlt.com/price/tokens" + ); + } + + #[test] + fn json_roundtrip() { + let sut = SUT::sample(); + assert_json_roundtrip(&sut); + } +} diff --git a/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/token_price_services.rs b/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/token_price_services.rs new file mode 100644 index 000000000..79986fccd --- /dev/null +++ b/crates/profile/models/profile_SPLIT_ME/src/v100/networks/network/token_price_services.rs @@ -0,0 +1,103 @@ +use crate::prelude::*; + +decl_identified_vec_of!( + /// Ordered token price service endpoints used for failover. + TokenPriceServices, + TokenPriceService +); + +impl TokenPriceServices { + /// "Default" for this collection in profile/network context: + /// one production endpoint. + #[allow(clippy::should_implement_trait)] + pub fn default() -> Self { + Self::just(TokenPriceService::production()) + } + + pub fn is_default(&self) -> bool { + self == &Self::default() + } + + pub fn add(&mut self, service: TokenPriceService) -> bool { + self.try_insert_unique(service).is_ok() + } + + pub fn remove_by_base_url(&mut self, base_url: &Url) -> bool { + if self.len() <= 1 { + return false; + } + self.remove_id(base_url).is_some() + } +} + +impl HasSampleValues for TokenPriceServices { + fn sample() -> Self { + Self::default() + } + + fn sample_other() -> Self { + Self::from_iter([ + TokenPriceService::sample(), + TokenPriceService::sample_other(), + ]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = TokenPriceServices; + + #[test] + fn add_duplicate_returns_false() { + let mut sut = SUT::new(); + let service = TokenPriceService::sample(); + + assert!(sut.add(service.clone())); + assert!(!sut.add(service)); + } + + #[test] + fn insertion_order_is_preserved() { + let mut sut = SUT::new(); + let first = TokenPriceService::sample(); + let second = TokenPriceService::sample_other(); + + assert!(sut.add(first.clone())); + assert!(sut.add(second.clone())); + + let all = sut.get_all().into_iter().cloned().collect_vec(); + assert_eq!(all, vec![first, second]); + } + + #[test] + fn remove_missing_returns_false() { + let mut sut = SUT::sample(); + let missing = Url::parse("https://missing.example").unwrap(); + assert!(!sut.remove_by_base_url(&missing)); + } + + #[test] + fn remove_last_is_disallowed() { + let mut sut = SUT::sample(); + let only = sut.get_all().first().unwrap().base_url.clone(); + assert!(!sut.remove_by_base_url(&only)); + assert_eq!(sut.len(), 1); + } + + #[test] + fn remove_existing_when_more_than_one_returns_true() { + let mut sut = SUT::sample_other(); + let to_remove = TokenPriceService::sample_other().base_url; + assert!(sut.remove_by_base_url(&to_remove)); + assert_eq!(sut.len(), 1); + } + + #[test] + fn json_roundtrip() { + let sut = SUT::sample_other(); + assert_json_roundtrip(&sut); + } +} diff --git a/crates/profile/models/security-structures/Cargo.toml b/crates/profile/models/security-structures/Cargo.toml index 6322ac04b..ccb16cce9 100644 --- a/crates/profile/models/security-structures/Cargo.toml +++ b/crates/profile/models/security-structures/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "profile-security-structures" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/profile/models/supporting-types/Cargo.toml b/crates/profile/models/supporting-types/Cargo.toml index edce37fda..626c07cf8 100644 --- a/crates/profile/models/supporting-types/Cargo.toml +++ b/crates/profile/models/supporting-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "profile-supporting-types" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/profile/traits/entity-by-address/Cargo.toml b/crates/profile/traits/entity-by-address/Cargo.toml index 08847fecb..ad8efeffa 100644 --- a/crates/profile/traits/entity-by-address/Cargo.toml +++ b/crates/profile/traits/entity-by-address/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "entity-by-address" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/radix-name-service/Cargo.toml b/crates/radix-name-service/Cargo.toml index 9a76e88cb..955f3998a 100644 --- a/crates/radix-name-service/Cargo.toml +++ b/crates/radix-name-service/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-name-service" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/sargon/Cargo.toml b/crates/sargon/Cargo.toml index 2c4986443..c897b73c7 100644 --- a/crates/sargon/Cargo.toml +++ b/crates/sargon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sargon" -version = "1.2.54" +version = "1.2.55" edition = "2021" resolver = "2" # features enabled in integration test diff --git a/crates/system/clients/clients/Cargo.toml b/crates/system/clients/clients/Cargo.toml index b54426632..82c0c4551 100644 --- a/crates/system/clients/clients/Cargo.toml +++ b/crates/system/clients/clients/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clients" -version = "1.2.54" +version = "1.2.55" edition = "2021" diff --git a/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/cache.rs b/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/cache.rs index 8902b5b5a..63a5aed0d 100644 --- a/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/cache.rs +++ b/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/cache.rs @@ -1,26 +1,29 @@ use crate::prelude::*; -use std::path::Path; +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + path::Path, +}; -/// File name for the cached token prices snapshot -const ALL_TOKEN_PRICES_PATH: &str = "all_token_prices.json"; +/// Prefix for request-scoped token price cache files. +const SCOPED_TOKEN_PRICES_PATH_PREFIX: &str = "scoped_token_prices"; -/// Cache time-to-live in seconds (5 minutes) -/// -/// Token prices are considered fresh for 5 minutes before requiring a refresh -/// from the remote API. This balances data freshness with API rate limiting. +/// Cache time-to-live in seconds (5 minutes). pub const CACHE_TTL_SECONDS: i64 = 5 * 60; impl FungiblesPricesClient { - pub async fn load_cached_prices(&self) -> Result>> { - let path = self + pub async fn load_cached_prices( + &self, + request: &FungiblePricesRequest, + ) -> Result> { + let path = Self::cache_file_path(request); + let bytes = self .file_system_client - .create_if_needed(ALL_TOKEN_PRICES_PATH) + .load_from_file(Path::new(&path)) .await?; - let bytes = self.file_system_client.load_from_file(path).await?; - if let Some(bytes) = bytes { - let snapshot: AllTokenPricesSnapshot = bytes.deserialize()?; + let snapshot: ScopedTokenPricesSnapshot = bytes.deserialize()?; let now = Timestamp::now_utc(); let age = now.duration_since(snapshot.fetched_at); @@ -32,32 +35,42 @@ impl FungiblesPricesClient { Ok(None) } - pub async fn store_prices(&self, prices: Vec) -> Result<()> { - let snapshot = - AllTokenPricesSnapshot::new(Timestamp::now_utc(), prices); + pub async fn store_prices( + &self, + request: &FungiblePricesRequest, + prices: &PerTokenPrices, + ) -> Result<()> { + let snapshot = ScopedTokenPricesSnapshot::new( + Timestamp::now_utc(), + prices.clone(), + ); let serialized = snapshot.serialize_to_bytes()?; + let path = Self::cache_file_path(request); self.file_system_client - .save_to_file(Path::new(ALL_TOKEN_PRICES_PATH), serialized, true) + .save_to_file(Path::new(&path), serialized, true) .await } + + fn cache_file_path(request: &FungiblePricesRequest) -> String { + let mut hasher = DefaultHasher::new(); + request.hash(&mut hasher); + let hash = hasher.finish(); + + format!("{}_{}.json", SCOPED_TOKEN_PRICES_PATH_PREFIX, hash) + } } -/// A cached snapshot of all token prices with timestamp for TTL validation. -/// -/// This structure is serialized and stored in the file system to provide caching -/// for token price data. The cache is considered valid for 5 minutes. +/// A cached snapshot of scoped prices with timestamp for TTL validation. #[derive(Deserialize, Serialize)] -pub struct AllTokenPricesSnapshot { - /// When this snapshot was created (UTC timestamp) +pub struct ScopedTokenPricesSnapshot { pub fetched_at: Timestamp, - /// The cached token prices across all currencies - pub prices: Vec, + pub prices: PerTokenPrices, } -impl AllTokenPricesSnapshot { - fn new(fetched_at: Timestamp, prices: Vec) -> Self { +impl ScopedTokenPricesSnapshot { + fn new(fetched_at: Timestamp, prices: PerTokenPrices) -> Self { Self { fetched_at, prices } } } diff --git a/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/remote_fetcher.rs b/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/remote_fetcher.rs index a2be96da3..b0154f36e 100644 --- a/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/remote_fetcher.rs +++ b/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/remote_fetcher.rs @@ -1,14 +1,63 @@ use crate::prelude::*; -/// The URL endpoint for the Radix token price service API -pub const FETCH_URL: &str = "https://token-price-service.radixdlt.com/tokens"; - impl FungiblesPricesClient { - pub async fn fetch_remote_token_prices(&self) -> Result> { - let request = - NetworkRequest::new_post(Url::from_str(FETCH_URL).unwrap()); - self.http_client - .execute_request_with_decoding(request) + pub async fn fetch_remote_token_prices( + &self, + request_body: &FungiblePricesRequest, + ) -> Result { + let production_url = + TokenPriceService::production().scoped_tokens_url()?; + self.fetch_remote_token_prices_from_url(request_body, production_url) .await } + + pub async fn fetch_remote_token_prices_using_token_price_services( + &self, + request_body: &FungiblePricesRequest, + token_price_services: TokenPriceServices, + ) -> Result { + if token_price_services.is_empty() { + return Err(CommonError::ExpectedNonEmptyCollection); + } + + let mut last_error: Option = None; + + for service in token_price_services { + let url = match service.scoped_tokens_url() { + Ok(url) => url, + Err(error) => { + last_error = Some(error); + continue; + } + }; + match self + .fetch_remote_token_prices_from_url(request_body, url) + .await + { + Ok(prices) => return Ok(prices), + Err(error) => { + last_error = Some(error); + } + } + } + + Err(last_error.unwrap_or(CommonError::ExpectedNonEmptyCollection)) + } + + async fn fetch_remote_token_prices_from_url( + &self, + request_body: &FungiblePricesRequest, + fetch_url: Url, + ) -> Result { + let request = NetworkRequest::new_post(fetch_url) + .with_serializing_body(request_body.clone())? + .with_gateway_api_headers(); + + let response: ScopedTokenPricesResponse = self + .http_client + .execute_request_with_decoding(request) + .await?; + + Ok(response.into_prices_map()) + } } diff --git a/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/tests.rs b/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/tests.rs index 4f8082d9e..3bcc2375f 100644 --- a/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/tests.rs +++ b/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/tests.rs @@ -1,14 +1,12 @@ -use crate::prelude::*; use crate::clients::client::token_pricing_client::fungibles_prices_service::cache::*; +use crate::prelude::*; use std::path::Path; #[allow(clippy::upper_case_acronyms)] type SUT = FungiblesPricesClient; -// Test helper functions - fn make_http_client_with_responses( - responses: Vec>, + responses: Vec, ) -> Arc { Arc::new(HttpClient::new(Arc::new( MockNetworkingDriver::with_responses(responses), @@ -16,9 +14,9 @@ fn make_http_client_with_responses( } fn make_http_client_with_single_response( - prices: Vec, + response: ScopedTokenPricesResponse, ) -> Arc { - make_http_client_with_responses(vec![prices]) + make_http_client_with_responses(vec![response]) } fn make_http_client_failing() -> Arc { @@ -27,534 +25,394 @@ fn make_http_client_failing() -> Arc { ))) } -fn sample_token_price( - address: &str, - price: f32, - currency: FiatCurrency, -) -> TokenPrice { - TokenPrice { - resource_address: ResourceAddress::from_str(address).unwrap(), - price, - currency, +fn addr(s: &str) -> ResourceAddress { + ResourceAddress::from_str(s).unwrap() +} + +fn token(resource_address: &str, usd_price: f64) -> ScopedTokenPrice { + ScopedTokenPrice { + resource_address: addr(resource_address), + usd_price, } } -fn sample_token_prices_usd() -> Vec { - vec![ - sample_token_price( - "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", - 0.5, - FiatCurrency::USD, - ), - sample_token_price( - "resource_rdx1t5u04cs3u2yxqkcwku7jdvdvv9cu739jsx0rdwu97682lr0rn92qdh", - 1.25, - FiatCurrency::USD, - ), - ] +fn lsu(resource_address: &str, usd_price: f64) -> ScopedLsuPrice { + ScopedLsuPrice { + resource_address: addr(resource_address), + usd_price, + } } -fn sample_token_prices_mixed() -> Vec { - vec![ - sample_token_price( - "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", - 0.5, - FiatCurrency::USD, - ), - sample_token_price( - "resource_rdx1t5u04cs3u2yxqkcwku7jdvdvv9cu739jsx0rdwu97682lr0rn92qdh", - 5.0, - FiatCurrency::SEK, - ), - sample_token_price( +fn sample_scoped_response() -> ScopedTokenPricesResponse { + ScopedTokenPricesResponse { + tokens: vec![ + token( + "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", + 0.5, + ), + token( + "resource_rdx1t5u04cs3u2yxqkcwku7jdvdvv9cu739jsx0rdwu97682lr0rn92qdh", + 1.25, + ), + ], + lsus: vec![lsu( "resource_rdx1t4kc5ljyrwlxvg54s6gnctt7nwwgx89h9r2gvrpm369s23yhzyyzlx", - 1.25, - FiatCurrency::USD, - ), - ] + 2.0, + )], + } } -// Tests for get_prices_for_currency - -#[actix_rt::test] -async fn test_get_prices_for_currency_filters_correctly() { - // Arrange - let prices = sample_token_prices_mixed(); - let http_client = make_http_client_with_single_response(prices); - let file_system = Arc::new(FileSystemClient::in_memory()); - let sut = SUT::new(http_client, file_system); - - // Act - let result = sut.get_prices_for_currency(FiatCurrency::USD).await; - - // Assert - assert!(result.is_ok()); - let per_token_prices = result.unwrap(); - assert_eq!(per_token_prices.len(), 2); // Only USD prices - - // Check that we got the right prices - let addr1 = ResourceAddress::from_str( - "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", - ) - .unwrap(); - let addr3 = ResourceAddress::from_str( - "resource_rdx1t4kc5ljyrwlxvg54s6gnctt7nwwgx89h9r2gvrpm369s23yhzyyzlx", +fn request_usd_with_unsorted_duplicates() -> FungiblePricesRequest { + FungiblePricesRequest::new( + FiatCurrency::USD, + vec![ + addr( + "resource_rdx1t5u04cs3u2yxqkcwku7jdvdvv9cu739jsx0rdwu97682lr0rn92qdh", + ), + addr( + "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", + ), + addr( + "resource_rdx1t5u04cs3u2yxqkcwku7jdvdvv9cu739jsx0rdwu97682lr0rn92qdh", + ), + ], + vec![ + addr( + "resource_rdx1t4kc5ljyrwlxvg54s6gnctt7nwwgx89h9r2gvrpm369s23yhzyyzlx", + ), + addr( + "resource_rdx1t4kc5ljyrwlxvg54s6gnctt7nwwgx89h9r2gvrpm369s23yhzyyzlx", + ), + ], ) - .unwrap(); +} - assert!(per_token_prices.contains_key(&addr1)); - assert!(per_token_prices.contains_key(&addr3)); - assert_eq!( - per_token_prices.get(&addr1).unwrap(), - &Decimal192::from(0.5f32) - ); - assert_eq!( - per_token_prices.get(&addr3).unwrap(), - &Decimal192::from(1.25f32) - ); +fn token_price_services_two_endpoints() -> TokenPriceServices { + TokenPriceServices::from_iter([ + TokenPriceService::new( + Url::parse("https://token-prices-primary.example").unwrap(), + ), + TokenPriceService::new( + Url::parse("https://token-prices-secondary.example").unwrap(), + ), + ]) } -#[actix_rt::test] -async fn test_get_prices_for_currency_no_matches_returns_empty() { - // Arrange - let prices = sample_token_prices_usd(); - let http_client = make_http_client_with_single_response(prices); - let file_system = Arc::new(FileSystemClient::in_memory()); - let sut = SUT::new(http_client, file_system); +fn cache_file_path_for(request: &FungiblePricesRequest) -> String { + use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + }; - // Act - let result = sut.get_prices_for_currency(FiatCurrency::SEK).await; + let mut hasher = DefaultHasher::new(); + request.hash(&mut hasher); + let hash = hasher.finish(); - // Assert - assert!(result.is_ok()); - let per_token_prices = result.unwrap(); - assert_eq!(per_token_prices.len(), 0); // No SEK prices + format!("scoped_token_prices_{}.json", hash) } -#[actix_rt::test] -async fn test_get_prices_for_currency_with_empty_response() { - // Arrange - let http_client = make_http_client_with_single_response(vec![]); - let file_system = Arc::new(FileSystemClient::in_memory()); - let sut = SUT::new(http_client, file_system); +#[test] +fn test_request_normalizes_and_deduplicates() { + let request = request_usd_with_unsorted_duplicates(); - // Act - let result = sut.get_prices_for_currency(FiatCurrency::USD).await; + assert_eq!(request.tokens.len(), 2); + assert_eq!(request.lsus.len(), 1); - // Assert - assert!(result.is_ok()); - let per_token_prices = result.unwrap(); - assert_eq!(per_token_prices.len(), 0); -} + let token_strings: Vec = + request.tokens.iter().map(|a| a.to_string()).collect(); + let lsu_strings: Vec = + request.lsus.iter().map(|a| a.to_string()).collect(); -// Tests for remote fetching + assert!(token_strings.windows(2).all(|w| w[0] <= w[1])); + assert!(lsu_strings.windows(2).all(|w| w[0] <= w[1])); +} #[actix_rt::test] async fn test_fetch_remote_token_prices_success() { - // Arrange - let prices = sample_token_prices_usd(); - let http_client = make_http_client_with_single_response(prices.clone()); + let response = sample_scoped_response(); + let http_client = make_http_client_with_single_response(response); let file_system = Arc::new(FileSystemClient::in_memory()); let sut = SUT::new(http_client, file_system); - // Act - let result = sut.fetch_remote_token_prices().await; + let request = request_usd_with_unsorted_duplicates(); + let result = sut.fetch_remote_token_prices(&request).await; - // Assert assert!(result.is_ok()); - let fetched_prices = result.unwrap(); - assert_eq!(fetched_prices.len(), 2); - assert_eq!(fetched_prices[0].price, 0.5); - assert_eq!(fetched_prices[0].currency, FiatCurrency::USD); - assert_eq!(fetched_prices[1].price, 1.25); + let prices = result.unwrap(); + assert_eq!(prices.len(), 3); } #[actix_rt::test] -async fn test_fetch_remote_token_prices_network_failure() { - // Arrange - let http_client = make_http_client_failing(); +async fn test_fetch_remote_token_prices_uses_requested_currency() { + let captured_requests = + Arc::new(std::sync::Mutex::new(Vec::::new())); + let captured_requests_clone = captured_requests.clone(); + + let response = sample_scoped_response(); + + let driver = Arc::new(MockNetworkingDriver::with_lazy_responses( + move |request, _| { + captured_requests_clone + .lock() + .unwrap() + .push(request.clone()); + let body = serde_json::to_vec(&response).unwrap(); + NetworkResponse::new(200, body) + }, + )); + + let http_client = Arc::new(HttpClient::new(driver)); let file_system = Arc::new(FileSystemClient::in_memory()); let sut = SUT::new(http_client, file_system); - // Act - let result = sut.fetch_remote_token_prices().await; + let request = FungiblePricesRequest::new( + FiatCurrency::SEK, + vec![addr( + "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", + )], + vec![], + ); - // Assert - assert!(result.is_err()); -} + let _ = sut.fetch_remote_token_prices(&request).await.unwrap(); -// Tests for cache loading + let captured = captured_requests.lock().unwrap(); + assert_eq!(captured.len(), 1); + let body: Vec = captured[0].body.to_vec(); + let decoded: FungiblePricesRequest = serde_json::from_slice(&body).unwrap(); + assert_eq!(decoded.currency, FiatCurrency::SEK); +} #[actix_rt::test] -async fn test_load_cached_prices_when_no_cache() { - // Arrange - let http_client = make_http_client_failing(); // Doesn't matter for this test +async fn test_fetch_remote_token_prices_fails_over_to_next_service() { + let captured_requests = + Arc::new(std::sync::Mutex::new(Vec::::new())); + let captured_requests_clone = captured_requests.clone(); + let response = sample_scoped_response(); + + let driver = Arc::new(MockNetworkingDriver::with_lazy_responses( + move |request, _| { + captured_requests_clone + .lock() + .unwrap() + .push(request.clone()); + + if request.url.host_str() == Some("token-prices-primary.example") { + NetworkResponse::new(500, vec![]) + } else { + let body = serde_json::to_vec(&response).unwrap(); + NetworkResponse::new(200, body) + } + }, + )); + + let http_client = Arc::new(HttpClient::new(driver)); let file_system = Arc::new(FileSystemClient::in_memory()); let sut = SUT::new(http_client, file_system); - // Act - let result = sut.load_cached_prices().await; + let request = request_usd_with_unsorted_duplicates(); + let result = sut + .fetch_remote_token_prices_using_token_price_services( + &request, + token_price_services_two_endpoints(), + ) + .await; - // Assert - should return Ok(None) when no cache exists assert!(result.is_ok()); - assert!(result.unwrap().is_none()); + assert_eq!(result.unwrap().len(), 3); + + let captured = captured_requests.lock().unwrap(); + assert_eq!(captured.len(), 2); + assert_eq!( + captured[0].url.as_str(), + "https://token-prices-primary.example/price/tokens" + ); + assert_eq!( + captured[1].url.as_str(), + "https://token-prices-secondary.example/price/tokens" + ); } #[actix_rt::test] -async fn test_load_cached_prices_with_valid_cache() { - // Arrange - let http_client = make_http_client_failing(); // Won't be used +async fn test_fetch_remote_token_prices_all_services_fail() { + let driver = Arc::new(MockNetworkingDriver::new_always_failing()); + let http_client = Arc::new(HttpClient::new(driver)); let file_system = Arc::new(FileSystemClient::in_memory()); - let sut = SUT::new(http_client.clone(), file_system.clone()); + let sut = SUT::new(http_client, file_system); - // Store prices first - let prices = sample_token_prices_usd(); - let result = sut.store_prices(prices.clone()).await; - assert!(result.is_ok()); + let request = request_usd_with_unsorted_duplicates(); + let result = sut + .fetch_remote_token_prices_using_token_price_services( + &request, + token_price_services_two_endpoints(), + ) + .await; - // Act - load cached prices - let loaded_result = sut.load_cached_prices().await; - - // Assert - assert!(loaded_result.is_ok()); - let loaded_prices = loaded_result.unwrap(); - assert!(loaded_prices.is_some()); - let loaded_prices = loaded_prices.unwrap(); - assert_eq!(loaded_prices.len(), 2); - assert_eq!(loaded_prices[0].price, 0.5); - assert_eq!(loaded_prices[1].price, 1.25); + assert!(result.is_err()); } #[actix_rt::test] -async fn test_load_cached_prices_with_expired_cache() { - // Arrange - let http_client = make_http_client_failing(); +async fn test_fetch_remote_token_prices_empty_services_returns_error() { + let response = sample_scoped_response(); + let http_client = make_http_client_with_single_response(response); let file_system = Arc::new(FileSystemClient::in_memory()); - - // Manually create an expired cache (fetched_at in the past) - let expired_snapshot = AllTokenPricesSnapshot { - fetched_at: Timestamp::parse("2020-01-01T00:00:00Z").unwrap(), - prices: sample_token_prices_usd(), - }; - - let serialized = expired_snapshot.serialize_to_bytes().unwrap(); - file_system - .save_to_file(Path::new("all_token_prices.json"), serialized, true) - .await - .unwrap(); - let sut = SUT::new(http_client, file_system); - // Act - let result = sut.load_cached_prices().await; + let request = request_usd_with_unsorted_duplicates(); + let result = sut + .fetch_remote_token_prices_using_token_price_services( + &request, + TokenPriceServices::new(), + ) + .await; - // Assert - expired cache should return None - assert!(result.is_ok()); - assert!(result.unwrap().is_none()); + assert_eq!(result, Err(CommonError::ExpectedNonEmptyCollection)); } -// Tests for cache storage - #[actix_rt::test] -async fn test_store_prices_success() { - // Arrange - let http_client = make_http_client_failing(); +async fn test_get_prices_for_request_fetches_and_caches() { + let response = sample_scoped_response(); + let http_client = make_http_client_with_single_response(response); let file_system = Arc::new(FileSystemClient::in_memory()); - let sut = SUT::new(http_client, file_system.clone()); - - let prices = sample_token_prices_usd(); - - // Act - let result = sut.store_prices(prices.clone()).await; - - // Assert - assert!(result.is_ok()); + let sut = SUT::new(http_client, file_system); - // Verify the file was created - let loaded = file_system - .load_from_file(Path::new("all_token_prices.json")) + let request = request_usd_with_unsorted_duplicates(); + let result = sut + .get_prices_for_request(request.clone(), false) .await .unwrap(); - assert!(loaded.is_some()); + + assert_eq!(result.len(), 3); + let cached = sut.load_cached_prices(&request).await.unwrap(); + assert!(cached.is_some()); } #[actix_rt::test] -async fn test_store_and_load_roundtrip() { - // Arrange +async fn test_get_prices_for_request_uses_cache() { let http_client = make_http_client_failing(); let file_system = Arc::new(FileSystemClient::in_memory()); let sut = SUT::new(http_client, file_system); - let prices = sample_token_prices_mixed(); - - // Act - store and then load - sut.store_prices(prices.clone()).await.unwrap(); - let loaded = sut.load_cached_prices().await.unwrap(); + let request = request_usd_with_unsorted_duplicates(); + let mut cached_prices = PerTokenPrices::new(); + cached_prices.insert( + addr("resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0"), + Decimal192::from(99.0f32), + ); - // Assert - assert!(loaded.is_some()); - let loaded_prices = loaded.unwrap(); - assert_eq!(loaded_prices.len(), prices.len()); + sut.store_prices(&request, &cached_prices).await.unwrap(); - // Verify each price - for (expected, actual) in prices.iter().zip(loaded_prices.iter()) { - assert_eq!(expected.resource_address, actual.resource_address); - assert_eq!(expected.price, actual.price); - assert_eq!(expected.currency, actual.currency); - } + let result = sut.get_prices_for_request(request, false).await.unwrap(); + assert_eq!(result, cached_prices); } -// Integration tests for get_token_prices (internal method testing the cache-first strategy) - #[actix_rt::test] -async fn test_get_token_prices_uses_cache_when_available() { - // Arrange - let http_client = make_http_client_failing(); // Will fail if called +async fn test_force_fetch_bypasses_cache() { + let response = ScopedTokenPricesResponse { + tokens: vec![token( + "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", + 1.0, + )], + lsus: vec![], + }; + let http_client = make_http_client_with_single_response(response); let file_system = Arc::new(FileSystemClient::in_memory()); - let sut = SUT::new(http_client.clone(), file_system.clone()); - - // Pre-populate cache - let cached_prices = sample_token_prices_usd(); - sut.store_prices(cached_prices.clone()).await.unwrap(); - - // Act - this should use cache and NOT call HTTP (which would fail) - let result = sut.get_prices_for_currency(FiatCurrency::USD).await; - - // Assert - should succeed because it uses cache - assert!(result.is_ok()); - let prices = result.unwrap(); - assert_eq!(prices.len(), 2); -} + let sut = SUT::new(http_client, file_system); -#[actix_rt::test] -async fn test_get_token_prices_fetches_remote_on_cache_miss() { - // Arrange - no cache, but HTTP will succeed - let remote_prices = sample_token_prices_usd(); - let http_client = - make_http_client_with_single_response(remote_prices.clone()); - let file_system = Arc::new(FileSystemClient::in_memory()); - let sut = SUT::new(http_client, file_system.clone()); + let request = request_usd_with_unsorted_duplicates(); + let mut cached_prices = PerTokenPrices::new(); + cached_prices.insert( + addr("resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0"), + Decimal192::from(99.0f32), + ); + sut.store_prices(&request, &cached_prices).await.unwrap(); - // Act - let result = sut.get_prices_for_currency(FiatCurrency::USD).await; + let result = sut.get_prices_for_request(request, true).await.unwrap(); - // Assert - should succeed by fetching from remote - assert!(result.is_ok()); - let prices = result.unwrap(); - assert_eq!(prices.len(), 2); - - // Verify cache was populated - let cached = sut.load_cached_prices().await.unwrap(); - assert!(cached.is_some()); + assert_eq!( + result + .get(&addr( + "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", + )) + .unwrap(), + &Decimal192::from(1.0f32) + ); } #[actix_rt::test] -async fn test_get_token_prices_returns_error_when_cache_miss_and_remote_fails() -{ - // Arrange - no cache AND HTTP fails +async fn test_cache_is_request_scoped() { let http_client = make_http_client_failing(); let file_system = Arc::new(FileSystemClient::in_memory()); let sut = SUT::new(http_client, file_system); - // Act - let result = sut.get_prices_for_currency(FiatCurrency::USD).await; - - // Assert - should fail - assert!(result.is_err()); -} - -#[actix_rt::test] -async fn test_get_token_prices_prefers_cache_over_remote() { - // Arrange - cache available with different prices than remote - let cached_prices = vec![sample_token_price( - "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", - 99.99, // Different price in cache + let request_a = request_usd_with_unsorted_duplicates(); + let request_b = FungiblePricesRequest::new( FiatCurrency::USD, - )]; - - let remote_prices = sample_token_prices_usd(); // Different prices - let http_client = make_http_client_with_single_response(remote_prices); - let file_system = Arc::new(FileSystemClient::in_memory()); - let sut = SUT::new(http_client, file_system); + vec![addr( + "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", + )], + vec![], + ); - // Store cache first - sut.store_prices(cached_prices.clone()).await.unwrap(); + let mut prices = PerTokenPrices::new(); + prices.insert( + addr("resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0"), + Decimal192::from(5.0f32), + ); - // Act - let result = sut.get_prices_for_currency(FiatCurrency::USD).await; + sut.store_prices(&request_a, &prices).await.unwrap(); - // Assert - should get cached price (99.99), not remote price (0.5) - assert!(result.is_ok()); - let prices = result.unwrap(); - assert_eq!(prices.len(), 1); + let cached_a = sut.load_cached_prices(&request_a).await.unwrap(); + let cached_b = sut.load_cached_prices(&request_b).await.unwrap(); - let addr = ResourceAddress::from_str( - "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", - ) - .unwrap(); - assert_eq!(prices.get(&addr).unwrap(), &Decimal192::from(99.99f32)); + assert!(cached_a.is_some()); + assert!(cached_b.is_none()); } #[actix_rt::test] async fn test_expired_cache_triggers_remote_fetch() { - // Arrange - expired cache should trigger remote fetch + let request = request_usd_with_unsorted_duplicates(); + let response = ScopedTokenPricesResponse { + tokens: vec![token( + "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", + 1.0, + )], + lsus: vec![], + }; + + let http_client = make_http_client_with_single_response(response); let file_system = Arc::new(FileSystemClient::in_memory()); - // Create expired cache - let expired_snapshot = AllTokenPricesSnapshot { + let expired_snapshot = ScopedTokenPricesSnapshot { fetched_at: Timestamp::parse("2020-01-01T00:00:00Z").unwrap(), - prices: vec![sample_token_price( - "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", - 99.99, - FiatCurrency::USD, - )], + prices: { + let mut map = PerTokenPrices::new(); + map.insert( + addr( + "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", + ), + Decimal192::from(99.0f32), + ); + map + }, }; + let serialized = expired_snapshot.serialize_to_bytes().unwrap(); + let path = cache_file_path_for(&request); file_system - .save_to_file(Path::new("all_token_prices.json"), serialized, true) + .save_to_file(Path::new(&path), serialized, true) .await .unwrap(); - // Remote will return different prices - let remote_prices = sample_token_prices_usd(); - let http_client = make_http_client_with_single_response(remote_prices); let sut = SUT::new(http_client, file_system); - // Act - let result = sut.get_prices_for_currency(FiatCurrency::USD).await; - - // Assert - should get remote prices (not expired cache) - assert!(result.is_ok()); - let prices = result.unwrap(); - assert_eq!(prices.len(), 2); // Remote has 2 prices, cache had 1 + let result = sut.get_prices_for_request(request, false).await.unwrap(); - // First price should be from remote (0.5), not cache (99.99) - let addr = ResourceAddress::from_str( - "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", - ) - .unwrap(); assert_eq!( - prices.get(&addr).unwrap(), - &Decimal192::from(0.5f32) // Remote price + result + .get(&addr( + "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", + )) + .unwrap(), + &Decimal192::from(1.0f32) ); } - -// Tests for data consistency and edge cases - -#[actix_rt::test] -async fn test_price_conversion_from_f32_to_decimal192() { - // Arrange - let prices = vec![ - sample_token_price( - "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", - 0.123456, - FiatCurrency::USD, - ), - sample_token_price( - "resource_rdx1t5u04cs3u2yxqkcwku7jdvdvv9cu739jsx0rdwu97682lr0rn92qdh", - 999999.99, - FiatCurrency::USD, - ), - ]; - - let http_client = make_http_client_with_single_response(prices); - let file_system = Arc::new(FileSystemClient::in_memory()); - let sut = SUT::new(http_client, file_system); - - // Act - let result = sut.get_prices_for_currency(FiatCurrency::USD).await; - - // Assert - verify conversion worked - assert!(result.is_ok()); - let per_token_prices = result.unwrap(); - assert_eq!(per_token_prices.len(), 2); - - // Verify the Decimal192 conversion - for value in per_token_prices.values() { - // All values should be valid Decimal192 - assert!(value.clone() >= Decimal192::zero()); - } -} - -#[actix_rt::test] -async fn test_multiple_calls_use_cache() { - // Arrange - let remote_prices = sample_token_prices_usd(); - let http_client = make_http_client_with_single_response(remote_prices); - let file_system = Arc::new(FileSystemClient::in_memory()); - let sut = SUT::new(http_client, file_system); - - // Act - first call fetches from remote and caches - let result1 = sut.get_prices_for_currency(FiatCurrency::USD).await; - assert!(result1.is_ok()); - - // Second call should use cache (HTTP client only has one response, would fail on second call) - let result2 = sut.get_prices_for_currency(FiatCurrency::USD).await; - assert!(result2.is_ok()); - - // Third call should also use cache - let result3 = sut.get_prices_for_currency(FiatCurrency::SEK).await; - assert!(result3.is_ok()); -} - -#[test] -fn test_token_price_decoding() { - let raw_json = r#" - [ - { - "id": 2, - "resource_address": "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", - "symbol": "$BOBBY", - "name": "Bobby", - "price": 0.04134813463690507, - "currency": "USD" - }, - { - "id": 102, - "resource_address": "resource_rdx1t5u04cs3u2yxqkcwku7jdvdvv9cu739jsx0rdwu97682lr0rn92qdh", - "symbol": "$MRD", - "name": "Memerad", - "price": 0.000004698356561816775, - "currency": "USD" - }, - { - "id": 133, - "resource_address": "resource_rdx1t4kc5ljyrwlxvg54s6gnctt7nwwgx89h9r2gvrpm369s23yhzyyzlx", - "symbol": "$WOWO", - "name": "WOWO", - "price": 0.00007746121946503883, - "currency": "USD" - } - ] - "#; - - let decoded: Vec = serde_json::from_str(raw_json).unwrap(); - let expected_tokens = vec![ - TokenPrice { - resource_address: ResourceAddress::from_str( - "resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0", - ) - .unwrap(), - price: 0.04134813463690507, - currency: FiatCurrency::USD, - }, - TokenPrice { - resource_address: ResourceAddress::from_str( - "resource_rdx1t5u04cs3u2yxqkcwku7jdvdvv9cu739jsx0rdwu97682lr0rn92qdh", - ) - .unwrap(), - price: 0.000004698356561816775, - currency: FiatCurrency::USD, - }, - TokenPrice { - resource_address: ResourceAddress::from_str( - "resource_rdx1t4kc5ljyrwlxvg54s6gnctt7nwwgx89h9r2gvrpm369s23yhzyyzlx", - ) - .unwrap(), - price: 0.00007746121946503883, - currency: FiatCurrency::USD, - }, - ]; - - pretty_assertions::assert_eq!(decoded, expected_tokens); -} diff --git a/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/token_pricing_client.rs b/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/token_pricing_client.rs index 9bd310d3b..e8eba99a0 100644 --- a/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/token_pricing_client.rs +++ b/crates/system/clients/clients/src/clients/client/token_pricing_client/fungibles_prices_service/token_pricing_client.rs @@ -3,44 +3,87 @@ use crate::prelude::*; /// A mapping from resource addresses to their prices in a specific fiat currency. pub type PerTokenPrices = HashMap; -/// Represents a token's price in a specific fiat currency. -/// -/// This structure is returned by the remote token pricing API and used internally -/// for price calculations. The price is stored as f32 from the API but converted -/// to Decimal192 for precise calculations. +/// Request payload for scoped fungible and LSU price lookup. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Debug)] +#[serde(rename_all = "camelCase")] +pub struct FungiblePricesRequest { + pub currency: FiatCurrency, + pub tokens: Vec, + pub lsus: Vec, +} + +impl FungiblePricesRequest { + pub fn new( + currency: FiatCurrency, + tokens: impl IntoIterator, + lsus: impl IntoIterator, + ) -> Self { + Self { + currency, + tokens: Self::normalize_addresses(tokens), + lsus: Self::normalize_addresses(lsus), + } + } + + fn normalize_addresses( + addresses: impl IntoIterator, + ) -> Vec { + let mut unique: Vec<_> = + HashSet::::from_iter(addresses) + .into_iter() + .collect(); + unique.sort_by_key(|address| address.to_string()); + unique + } +} + #[derive(Deserialize, Serialize, Clone, PartialEq, Debug)] -pub struct TokenPrice { - /// The on-ledger address of the token resource +#[serde(rename_all = "camelCase")] +pub struct ScopedTokenPricesResponse { + pub tokens: Vec, + pub lsus: Vec, +} + +#[derive(Deserialize, Serialize, Clone, PartialEq, Debug)] +pub struct ScopedTokenPrice { pub resource_address: ResourceAddress, - /// The price per unit of the token in the specified currency - pub price: f32, - /// The fiat currency this price is denominated in - pub currency: FiatCurrency, + pub usd_price: f64, } -/// Client for fetching and caching fungible token prices. -/// -/// This client provides access to real-time token pricing data from the Radix token price service. -/// It implements a cache-first strategy with a 5-minute TTL to minimize network requests and -/// improve performance. -/// -/// # Caching Strategy -/// -/// 1. When prices are requested, the client first checks the local file system cache -/// 2. If valid cached prices exist (less than 5 minutes old), they are returned immediately -/// 3. If cache is expired or missing, prices are fetched from the remote API -/// 4. Successful remote fetches are automatically cached for future requests -/// -/// # Example -/// -/// ```ignore -/// let http_client = Arc::new(HttpClient::new(driver)); -/// let file_system = Arc::new(FileSystemClient::new(fs_driver)); -/// let client = FungiblesPricesClient::new(http_client, file_system); -/// -/// // Fetch USD prices for all tokens -/// let prices = client.get_prices_for_currency(FiatCurrency::USD).await?; -/// ``` +#[derive(Deserialize, Serialize, Clone, PartialEq, Debug)] +pub struct ScopedLsuPrice { + pub resource_address: ResourceAddress, + pub usd_price: f64, +} + +impl ScopedTokenPricesResponse { + pub fn into_prices_map(self) -> PerTokenPrices { + let mut prices: PerTokenPrices = HashMap::new(); + + for token in self.tokens { + insert_price(&mut prices, token.resource_address, token.usd_price); + } + + // In case a resource appears in both arrays, LSU value wins. + for lsu in self.lsus { + insert_price(&mut prices, lsu.resource_address, lsu.usd_price); + } + + prices + } +} + +fn insert_price( + prices: &mut PerTokenPrices, + resource_address: ResourceAddress, + value: f64, +) { + if let Ok(decimal) = Decimal192::try_from(value) { + prices.insert(resource_address, decimal); + } +} + +/// Client for fetching and caching scoped fungible token prices. pub struct FungiblesPricesClient { pub(crate) http_client: Arc, pub(crate) file_system_client: Arc, @@ -59,68 +102,48 @@ impl FungiblesPricesClient { } impl FungiblesPricesClient { - /// Fetches token prices for a specific fiat currency. - /// - /// This method returns a mapping of resource addresses to their prices in the requested - /// fiat currency. It uses a cache-first strategy with automatic fallback to remote fetching. - /// - /// # Arguments - /// - /// * `currency` - The fiat currency to get prices in (e.g., USD, SEK) - /// - /// # Returns - /// - /// A `HashMap` mapping `ResourceAddress` to `Decimal192` price values in the requested currency. - /// Only tokens with prices in the requested currency are included in the result. - /// - /// # Errors - /// - /// Returns an error if: - /// * The cache is invalid and remote fetch fails - /// * Network request fails - /// * Response cannot be deserialized + /// Fetches token prices for a scoped request. /// - /// # Example - /// - /// ```ignore - /// let prices = client.get_prices_for_currency(FiatCurrency::USD).await?; - /// for (resource_addr, price) in prices { - /// println!("{}: ${}", resource_addr, price); - /// } - /// ``` - pub async fn get_prices_for_currency( + /// Uses a 5-minute cache keyed by (currency, tokens, lsus). + pub async fn get_prices_for_request( &self, - currency: FiatCurrency, + request: FungiblePricesRequest, + force_fetch: bool, ) -> Result { - let prices = self.get_token_prices().await?; - let mut per_token_prices: HashMap = - HashMap::new(); - - for token_price in prices { - if token_price.currency == currency { - per_token_prices.insert( - token_price.resource_address, - token_price.price.into(), - ); - } - } - - Ok(per_token_prices) + self.get_prices_for_request_using_token_price_services( + request, + TokenPriceServices::default(), + force_fetch, + ) + .await } - async fn get_token_prices(&self) -> Result> { - let cached_prices = self.load_cached_prices().await.ok().flatten(); - - if let Some(cached_prices) = cached_prices { - return Ok(cached_prices); - } - - let remote_prices = self.fetch_remote_token_prices().await; - - if let Ok(remote_prices) = remote_prices.clone() { - _ = self.store_prices(remote_prices).await; + /// Fetches token prices for a scoped request using an ordered list of + /// token price services for automatic failover. + pub async fn get_prices_for_request_using_token_price_services( + &self, + request: FungiblePricesRequest, + token_price_services: TokenPriceServices, + force_fetch: bool, + ) -> Result { + if !force_fetch { + let cached_prices = + self.load_cached_prices(&request).await.ok().flatten(); + if let Some(cached_prices) = cached_prices { + return Ok(cached_prices); + } } - remote_prices + let remote_prices = if token_price_services.is_default() { + self.fetch_remote_token_prices(&request).await? + } else { + self.fetch_remote_token_prices_using_token_price_services( + &request, + token_price_services, + ) + .await? + }; + _ = self.store_prices(&request, &remote_prices).await; + Ok(remote_prices) } } diff --git a/crates/system/clients/clients/src/clients/client/token_pricing_client/non_fungibles_prices_service/non_fungibles_prices_client.rs b/crates/system/clients/clients/src/clients/client/token_pricing_client/non_fungibles_prices_service/non_fungibles_prices_client.rs index 4d562956b..7b55298c3 100644 --- a/crates/system/clients/clients/src/clients/client/token_pricing_client/non_fungibles_prices_service/non_fungibles_prices_client.rs +++ b/crates/system/clients/clients/src/clients/client/token_pricing_client/non_fungibles_prices_service/non_fungibles_prices_client.rs @@ -52,6 +52,38 @@ impl NonFungiblePricesClient { } impl NonFungiblePricesClient { + pub async fn fetch_fungible_fiat_values( + &self, + tokens: HashSet, + lsus: HashSet, + currency: FiatCurrency, + force_fetch: bool, + ) -> Result { + self.fungibles_prices_client + .get_prices_for_request( + FungiblePricesRequest::new(currency, tokens, lsus), + force_fetch, + ) + .await + } + + pub async fn fetch_fungible_fiat_values_using_token_price_services( + &self, + tokens: HashSet, + lsus: HashSet, + currency: FiatCurrency, + token_price_services: TokenPriceServices, + force_fetch: bool, + ) -> Result { + self.fungibles_prices_client + .get_prices_for_request_using_token_price_services( + FungiblePricesRequest::new(currency, tokens, lsus), + token_price_services, + force_fetch, + ) + .await + } + /// Fetches and calculates the fiat value of NFTs based on their liquidity receipts. /// /// This method: @@ -99,13 +131,45 @@ impl NonFungiblePricesClient { addresses: HashSet, currency: FiatCurrency, force_fetch: bool, + ) -> Result { + self.fetch_nft_fiat_values_using_token_price_services( + state_version, + addresses, + currency, + TokenPriceServices::default(), + force_fetch, + ) + .await + } + + pub async fn fetch_nft_fiat_values_using_token_price_services( + &self, + state_version: u64, + addresses: HashSet, + currency: FiatCurrency, + token_price_services: TokenPriceServices, + force_fetch: bool, ) -> Result { let liquidity_receipts = self .fetch_liquidity_receipts(state_version, addresses, force_fetch) .await?; + + let backing_tokens: HashSet = liquidity_receipts + .iter() + .flat_map(|receipt| receipt.items.iter()) + .flat_map(|item| { + item.resources.iter().map(|resource| resource.address) + }) + .collect(); + let fungible_prices = self - .fungibles_prices_client - .get_prices_for_currency(currency) + .fetch_fungible_fiat_values_using_token_price_services( + backing_tokens, + HashSet::new(), + currency, + token_price_services, + force_fetch, + ) .await?; let mut value_table = NonFungibleTokenPricesTable::new(); diff --git a/crates/system/clients/clients/src/clients/client/token_pricing_client/non_fungibles_prices_service/remote_fetcher.rs b/crates/system/clients/clients/src/clients/client/token_pricing_client/non_fungibles_prices_service/remote_fetcher.rs index 8d253243a..9171a0a3c 100644 --- a/crates/system/clients/clients/src/clients/client/token_pricing_client/non_fungibles_prices_service/remote_fetcher.rs +++ b/crates/system/clients/clients/src/clients/client/token_pricing_client/non_fungibles_prices_service/remote_fetcher.rs @@ -97,7 +97,6 @@ mod tests { use drivers::{ MockNetworkingDriver, NetworkMethod, NetworkResponse, NetworkingDriver, }; - use serde_json::Value; use std::collections::{HashMap, HashSet}; use std::sync::{Arc, Mutex}; diff --git a/crates/system/clients/clients/src/clients/client/token_pricing_client/non_fungibles_prices_service/tests.rs b/crates/system/clients/clients/src/clients/client/token_pricing_client/non_fungibles_prices_service/tests.rs index 5e0e149b3..61029aa34 100644 --- a/crates/system/clients/clients/src/clients/client/token_pricing_client/non_fungibles_prices_service/tests.rs +++ b/crates/system/clients/clients/src/clients/client/token_pricing_client/non_fungibles_prices_service/tests.rs @@ -56,7 +56,7 @@ fn sample_liquidity_receipt_item_resource( fn make_client_with_responses( liquidity_receipts: Vec, - token_prices: Vec, + token_prices: Vec, ) -> SUT { // Mock HTTP client for both NFT and fungible endpoints let captured_requests = @@ -79,7 +79,11 @@ fn make_client_with_responses( serde_json::to_vec(&liquidity_receipts_clone).unwrap(); NetworkResponse::new(200, body) } else if request.url.as_str().contains("token-price-service") { - let body = serde_json::to_vec(&token_prices_clone).unwrap(); + let body = serde_json::to_vec(&TokenPricesResponse { + tokens: token_prices_clone.clone(), + lsus: vec![], + }) + .unwrap(); NetworkResponse::new(200, body) } else { NetworkResponse::new(404, vec![]) @@ -102,16 +106,83 @@ fn make_client_failing() -> SUT { fn sample_token_price( address: ResourceAddress, - price: f32, - currency: FiatCurrency, -) -> TokenPrice { - TokenPrice { + price: f64, + _currency: FiatCurrency, +) -> TokenPriceResponseItem { + TokenPriceResponseItem { resource_address: address, - price, - currency, + usd_price: price, } } +#[derive(Serialize, Clone)] +struct TokenPricesResponse { + tokens: Vec, + lsus: Vec, +} + +#[derive(Serialize, Clone)] +struct TokenPriceResponseItem { + resource_address: ResourceAddress, + usd_price: f64, +} + +#[derive(Serialize, Clone)] +struct LsuPriceResponseItem { + resource_address: ResourceAddress, + usd_price: f64, +} + +#[actix_rt::test] +async fn test_fetch_fungible_fiat_values_uses_provided_token_price_service() { + let captured_requests = + Arc::new(std::sync::Mutex::new(Vec::::new())); + let captured_requests_clone = captured_requests.clone(); + + let xrd = ResourceAddress::sample_mainnet_xrd(); + let response = TokenPricesResponse { + tokens: vec![sample_token_price(xrd.clone(), 1.0, FiatCurrency::USD)], + lsus: vec![], + }; + let response_bytes = serde_json::to_vec(&response).unwrap(); + + let driver = Arc::new(MockNetworkingDriver::with_lazy_responses( + move |request, _| { + captured_requests_clone + .lock() + .unwrap() + .push(request.clone()); + NetworkResponse::new(200, response_bytes.clone()) + }, + )); + let http_client = Arc::new(HttpClient::new(driver)); + let file_system = Arc::new(FileSystemClient::in_memory()); + let sut = SUT::new(http_client, file_system); + + let token_price_services = + TokenPriceServices::just(TokenPriceService::new( + Url::parse("https://token-prices-custom.example").unwrap(), + )); + + let result = sut + .fetch_fungible_fiat_values_using_token_price_services( + HashSet::from([xrd]), + HashSet::new(), + FiatCurrency::USD, + token_price_services, + false, + ) + .await; + + assert!(result.is_ok()); + let captured = captured_requests.lock().unwrap(); + assert_eq!(captured.len(), 1); + assert_eq!( + captured[0].url.as_str(), + "https://token-prices-custom.example/price/tokens" + ); +} + // Tests for fetch_non_fungibles_prices #[actix_rt::test] @@ -494,7 +565,11 @@ async fn test_uses_cache_when_available() { move |request, _| { // Only succeed for token price requests, fail for liquidity receipts if request.url.as_str().contains("token-price-service") { - let body = serde_json::to_vec(&token_prices).unwrap(); + let body = serde_json::to_vec(&TokenPricesResponse { + tokens: token_prices.clone(), + lsus: vec![], + }) + .unwrap(); NetworkResponse::new(200, body) } else { NetworkResponse::new(500, vec![]) diff --git a/crates/system/clients/http/Cargo.toml b/crates/system/clients/http/Cargo.toml index 2381dd32f..7b6de699e 100644 --- a/crates/system/clients/http/Cargo.toml +++ b/crates/system/clients/http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "http-client" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/system/drivers/Cargo.toml b/crates/system/drivers/Cargo.toml index 7d2ef0491..c2565f02f 100644 --- a/crates/system/drivers/Cargo.toml +++ b/crates/system/drivers/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "drivers" -version = "1.2.54" +version = "1.2.55" edition = "2021" [features] diff --git a/crates/system/interactors/Cargo.toml b/crates/system/interactors/Cargo.toml index 938ae7384..32b569f60 100644 --- a/crates/system/interactors/Cargo.toml +++ b/crates/system/interactors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "interactors" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/system/os/accounts/Cargo.toml b/crates/system/os/accounts/Cargo.toml index 0f9d0cba2..0b3d03519 100644 --- a/crates/system/os/accounts/Cargo.toml +++ b/crates/system/os/accounts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sargon-os-accounts" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/system/os/arculus-card/Cargo.toml b/crates/system/os/arculus-card/Cargo.toml index 6342a2b28..e77555c9d 100644 --- a/crates/system/os/arculus-card/Cargo.toml +++ b/crates/system/os/arculus-card/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sargon-os-arculus-card" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/system/os/derive-public-keys/Cargo.toml b/crates/system/os/derive-public-keys/Cargo.toml index 3c182e37a..df422b72d 100644 --- a/crates/system/os/derive-public-keys/Cargo.toml +++ b/crates/system/os/derive-public-keys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sargon-os-derive-public-keys" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/system/os/factors/Cargo.toml b/crates/system/os/factors/Cargo.toml index 66c69115f..ecc0b656a 100644 --- a/crates/system/os/factors/Cargo.toml +++ b/crates/system/os/factors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sargon-os-factors" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/system/os/os/Cargo.toml b/crates/system/os/os/Cargo.toml index eb2138245..8d41a6b25 100644 --- a/crates/system/os/os/Cargo.toml +++ b/crates/system/os/os/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sargon-os" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/system/os/os/src/lib.rs b/crates/system/os/os/src/lib.rs index 40aa94ae0..71edf1000 100644 --- a/crates/system/os/os/src/lib.rs +++ b/crates/system/os/os/src/lib.rs @@ -11,6 +11,7 @@ mod sargon_os_accounts; mod sargon_os_address_book; mod sargon_os_arculus_card; mod sargon_os_factors; +mod sargon_os_fungible_pricing; mod sargon_os_gateway; mod sargon_os_gateway_client; mod sargon_os_nft_pricing; @@ -19,6 +20,7 @@ mod sargon_os_personas; mod sargon_os_profile; mod sargon_os_radix_connect_mobile; mod sargon_os_relay_service; +mod sargon_os_token_price_services; mod testing_interactors; pub mod prelude { @@ -31,6 +33,7 @@ pub mod prelude { pub use crate::sargon_os_address_book::*; pub use crate::sargon_os_arculus_card::*; pub use crate::sargon_os_factors::*; + pub use crate::sargon_os_fungible_pricing::*; pub use crate::sargon_os_gateway::*; pub use crate::sargon_os_gateway_client::*; pub use crate::sargon_os_nft_pricing::*; @@ -39,6 +42,7 @@ pub mod prelude { pub use crate::sargon_os_profile::*; pub use crate::sargon_os_radix_connect_mobile::*; pub use crate::sargon_os_relay_service::*; + pub use crate::sargon_os_token_price_services::*; pub use crate::testing_interactors::*; pub use clients::prelude::ArculusMinFirmwareVersionRequirement; pub use clients::prelude::NFCTagArculusInteractonPurpose; diff --git a/crates/system/os/os/src/sargon_os_fungible_pricing.rs b/crates/system/os/os/src/sargon_os_fungible_pricing.rs new file mode 100644 index 000000000..23257fca6 --- /dev/null +++ b/crates/system/os/os/src/sargon_os_fungible_pricing.rs @@ -0,0 +1,138 @@ +use crate::prelude::*; + +impl SargonOS { + /// Fetches scoped fiat prices for fungible tokens and LSUs. + /// + /// The request is scoped by the provided sets of resource addresses and + /// cached for 5 minutes. + pub async fn fetch_fungible_fiat_values( + &self, + tokens: HashSet, + lsus: HashSet, + currency: FiatCurrency, + force_fetch: bool, + ) -> Result> { + let token_price_services = self + .profile_state_holder + .token_price_services_on_current_network()?; + self.nft_prices_client + .fetch_fungible_fiat_values_using_token_price_services( + tokens, + lsus, + currency, + token_price_services, + force_fetch, + ) + .await + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = SargonOS; + + #[derive(serde::Serialize)] + #[serde(rename_all = "camelCase")] + struct TokenPriceResponseBody { + tokens: Vec, + lsus: Vec, + } + + #[derive(serde::Serialize)] + struct TokenPriceResponseItem { + resource_address: ResourceAddress, + usd_price: f64, + } + + #[actix_rt::test] + async fn fetch_fungible_fiat_values_uses_current_network_services_with_failover( + ) { + let captured_requests = + Arc::new(std::sync::Mutex::new(Vec::::new())); + let captured_requests_clone = captured_requests.clone(); + let token = ResourceAddress::sample_mainnet_xrd(); + + let success_response = TokenPriceResponseBody { + tokens: vec![TokenPriceResponseItem { + resource_address: token.clone(), + usd_price: 1.0, + }], + lsus: vec![], + }; + let success_body = serde_json::to_vec(&success_response).unwrap(); + + let networking = Arc::new(MockNetworkingDriver::with_lazy_responses( + move |request, _| { + if request.url.path() != "/price/tokens" { + return NetworkResponse::new(500, vec![]); + } + + captured_requests_clone + .lock() + .unwrap() + .push(request.clone()); + + if request.url.host_str() + == Some("token-prices-secondary.example") + { + NetworkResponse::new(200, success_body.clone()) + } else { + NetworkResponse::new(500, vec![]) + } + }, + )); + + let os = SUT::boot_test_with_networking_driver(networking) + .await + .unwrap(); + os.with_timeout(|x| x.change_current_gateway(Gateway::stokenet())) + .await + .unwrap(); + + os.with_timeout(|x| { + x.add_token_price_service_on_current_network( + Url::parse("https://token-prices-primary.example").unwrap(), + ) + }) + .await + .unwrap(); + os.with_timeout(|x| { + x.add_token_price_service_on_current_network( + Url::parse("https://token-prices-secondary.example").unwrap(), + ) + }) + .await + .unwrap(); + + let prices = os + .with_timeout(|x| { + x.fetch_fungible_fiat_values( + HashSet::from([token.clone()]), + HashSet::new(), + FiatCurrency::USD, + true, + ) + }) + .await + .unwrap(); + + assert_eq!(prices.get(&token), Some(&Decimal192::one())); + + let captured = captured_requests.lock().unwrap(); + let attempted_hosts: Vec = captured + .iter() + .filter_map(|r| r.url.host_str().map(|h| h.to_string())) + .collect(); + assert_eq!( + attempted_hosts, + vec![ + "token-price-service.radixdlt.com".to_owned(), + "token-prices-primary.example".to_owned(), + "token-prices-secondary.example".to_owned(), + ] + ); + } +} diff --git a/crates/system/os/os/src/sargon_os_nft_pricing.rs b/crates/system/os/os/src/sargon_os_nft_pricing.rs index 0f9d9a780..908219f0e 100644 --- a/crates/system/os/os/src/sargon_os_nft_pricing.rs +++ b/crates/system/os/os/src/sargon_os_nft_pricing.rs @@ -49,12 +49,16 @@ impl SargonOS { .await? .ledger_state .state_version; + let token_price_services = self + .profile_state_holder + .token_price_services_on_current_network()?; self.nft_prices_client - .fetch_nft_fiat_values( + .fetch_nft_fiat_values_using_token_price_services( state_version, nft_ids, currency, + token_price_services, force_fetch, ) .await diff --git a/crates/system/os/os/src/sargon_os_token_price_services.rs b/crates/system/os/os/src/sargon_os_token_price_services.rs new file mode 100644 index 000000000..81a376330 --- /dev/null +++ b/crates/system/os/os/src/sargon_os_token_price_services.rs @@ -0,0 +1,155 @@ +use crate::prelude::*; + +impl SargonOS { + /// Returns token price services configured on the current network. + pub fn token_price_services_on_current_network( + &self, + ) -> Result { + self.profile_state_holder + .token_price_services_on_current_network() + } +} + +impl SargonOS { + /// Adds a token price service endpoint on the current network. + /// Returns false if endpoint already exists. + pub async fn add_token_price_service_on_current_network( + &self, + base_url: Url, + ) -> Result { + self.update_profile_with(|profile| { + let current_network = profile.current_network_id(); + let mut did_add = false; + profile.networks.update_with(current_network, |network| { + did_add = network + .token_price_services + .add(TokenPriceService::new(base_url.clone())); + }); + Ok(did_add) + }) + .await + } + + /// Deletes a token price service endpoint on the current network. + /// Returns false if endpoint is missing or if this is the last endpoint. + pub async fn delete_token_price_service_on_current_network( + &self, + base_url: Url, + ) -> Result { + self.update_profile_with(|profile| { + let current_network = profile.current_network_id(); + let mut did_delete = false; + profile.networks.update_with(current_network, |network| { + did_delete = + network.token_price_services.remove_by_base_url(&base_url); + }); + Ok(did_delete) + }) + .await + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = SargonOS; + + #[actix_rt::test] + async fn token_price_services_default_on_current_network() { + let os = SUT::fast_boot().await; + let services = os.token_price_services_on_current_network().unwrap(); + assert_eq!(services, TokenPriceServices::default()); + } + + #[actix_rt::test] + async fn add_token_price_service_on_current_network() { + let os = SUT::fast_boot().await; + let url = + Url::parse("https://token-price-service-alt.example").unwrap(); + + let did_add = os + .with_timeout(|x| { + x.add_token_price_service_on_current_network(url.clone()) + }) + .await + .unwrap(); + + assert!(did_add); + let stored = os.token_price_services_on_current_network().unwrap(); + assert!(stored.contains_id(url)); + } + + #[actix_rt::test] + async fn add_duplicate_token_price_service_returns_false() { + let os = SUT::fast_boot().await; + let url = + Url::parse("https://token-price-service-alt.example").unwrap(); + + _ = os + .with_timeout(|x| { + x.add_token_price_service_on_current_network(url.clone()) + }) + .await + .unwrap(); + let did_add_again = os + .with_timeout(|x| { + x.add_token_price_service_on_current_network(url.clone()) + }) + .await + .unwrap(); + + assert!(!did_add_again); + } + + #[actix_rt::test] + async fn delete_missing_or_last_token_price_service_returns_false() { + let os = SUT::fast_boot().await; + let missing = + Url::parse("https://missing-token-price-service.example").unwrap(); + + let missing_deleted = os + .with_timeout(|x| { + x.delete_token_price_service_on_current_network(missing.clone()) + }) + .await + .unwrap(); + assert!(!missing_deleted); + + let only_default = TokenPriceService::production().base_url; + let deleted_last = os + .with_timeout(|x| { + x.delete_token_price_service_on_current_network( + only_default.clone(), + ) + }) + .await + .unwrap(); + assert!(!deleted_last); + } + + #[actix_rt::test] + async fn delete_token_price_service_when_more_than_one_returns_true() { + let os = SUT::fast_boot().await; + let extra = + Url::parse("https://token-price-service-alt.example").unwrap(); + + os.with_timeout(|x| { + x.add_token_price_service_on_current_network(extra.clone()) + }) + .await + .unwrap(); + + let did_delete = os + .with_timeout(|x| { + x.delete_token_price_service_on_current_network(extra.clone()) + }) + .await + .unwrap(); + + assert!(did_delete); + let stored = os.token_price_services_on_current_network().unwrap(); + assert!(!stored.contains_id(extra)); + } +} diff --git a/crates/system/os/security-center/Cargo.toml b/crates/system/os/security-center/Cargo.toml index c4d30ce43..02684f9e0 100644 --- a/crates/system/os/security-center/Cargo.toml +++ b/crates/system/os/security-center/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sargon-os-security-center" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/system/os/signing/Cargo.toml b/crates/system/os/signing/Cargo.toml index dd414fbf0..f99344c51 100644 --- a/crates/system/os/signing/Cargo.toml +++ b/crates/system/os/signing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sargon-os-signing" -version = "1.2.54" +version = "1.2.55" edition = "2021" [features] diff --git a/crates/system/os/transaction/Cargo.toml b/crates/system/os/transaction/Cargo.toml index b71bc1923..242783a28 100644 --- a/crates/system/os/transaction/Cargo.toml +++ b/crates/system/os/transaction/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sargon-os-transaction" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/system/profile-state-holder/Cargo.toml b/crates/system/profile-state-holder/Cargo.toml index e67d7d903..456fe4401 100644 --- a/crates/system/profile-state-holder/Cargo.toml +++ b/crates/system/profile-state-holder/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "profile-state-holder" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/system/profile-state-holder/src/profile_state_holder.rs b/crates/system/profile-state-holder/src/profile_state_holder.rs index fa890810f..4c49ce7d8 100644 --- a/crates/system/profile-state-holder/src/profile_state_holder.rs +++ b/crates/system/profile-state-holder/src/profile_state_holder.rs @@ -52,6 +52,14 @@ impl ProfileStateHolder { }) } + pub fn token_price_services_on_current_network( + &self, + ) -> Result { + self.try_access_profile_with(|p| { + p.current_network().map(|n| n.token_price_services.clone()) + }) + } + pub fn address_book_entry_by_address( &self, address: AccountAddress, diff --git a/crates/system/sub-systems/Cargo.toml b/crates/system/sub-systems/Cargo.toml index 34b14c4b5..e7ee67695 100644 --- a/crates/system/sub-systems/Cargo.toml +++ b/crates/system/sub-systems/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sub-systems" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/transaction/foundation/Cargo.toml b/crates/transaction/foundation/Cargo.toml index 55c7638cf..dcfde34d1 100644 --- a/crates/transaction/foundation/Cargo.toml +++ b/crates/transaction/foundation/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "transaction-foundation" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/transaction/manifests/Cargo.toml b/crates/transaction/manifests/Cargo.toml index a5361b496..e6b261b30 100644 --- a/crates/transaction/manifests/Cargo.toml +++ b/crates/transaction/manifests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "manifests" -version = "1.2.54" +version = "1.2.55" edition = "2021" diff --git a/crates/transaction/models/Cargo.toml b/crates/transaction/models/Cargo.toml index c72b04a96..87582b2d4 100644 --- a/crates/transaction/models/Cargo.toml +++ b/crates/transaction/models/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "transaction-models" -version = "1.2.54" +version = "1.2.55" edition = "2021" diff --git a/crates/uniffi/conversion-macros/Cargo.toml b/crates/uniffi/conversion-macros/Cargo.toml index 350b03a77..06ab1dbb6 100644 --- a/crates/uniffi/conversion-macros/Cargo.toml +++ b/crates/uniffi/conversion-macros/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sargon-uniffi-conversion-macros" -version = "1.2.54" +version = "1.2.55" edition = "2021" [dependencies] diff --git a/crates/uniffi/uniffi_SPLIT_ME/Cargo.toml b/crates/uniffi/uniffi_SPLIT_ME/Cargo.toml index 6b5d36fc2..f3ba6a1a6 100644 --- a/crates/uniffi/uniffi_SPLIT_ME/Cargo.toml +++ b/crates/uniffi/uniffi_SPLIT_ME/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sargon-uniffi" -version = "1.2.54" +version = "1.2.55" edition = "2021" build = "build.rs" diff --git a/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/app_preferences/p2p_transport_profiles/saved_p2p_transport_profiles.rs b/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/app_preferences/p2p_transport_profiles/saved_p2p_transport_profiles.rs index 7b2dd1904..7464efdeb 100644 --- a/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/app_preferences/p2p_transport_profiles/saved_p2p_transport_profiles.rs +++ b/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/app_preferences/p2p_transport_profiles/saved_p2p_transport_profiles.rs @@ -36,5 +36,8 @@ pub fn new_saved_p2p_transport_profiles_sample_other( pub fn saved_p2p_transport_profiles_get_all_elements( profiles: &SavedP2PTransportProfiles, ) -> Vec { - profiles.into_internal().all().into_type() + profiles + .into_internal() + .all_available_for_selection() + .into_type() } diff --git a/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/networks/network/mod.rs b/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/networks/network/mod.rs index 263d3359c..10e49933f 100644 --- a/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/networks/network/mod.rs +++ b/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/networks/network/mod.rs @@ -5,6 +5,7 @@ mod mfa_factor_instances; mod network_id; mod profile_network; mod resource_preferences; +mod token_price_service; pub use address_book_entry::*; pub use authorized_dapp::*; @@ -13,3 +14,4 @@ pub use mfa_factor_instances::*; pub use network_id::*; pub use profile_network::*; pub use resource_preferences::*; +pub use token_price_service::*; diff --git a/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/networks/network/profile_network.rs b/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/networks/network/profile_network.rs index 92e90ce07..14f1ab0e7 100644 --- a/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/networks/network/profile_network.rs +++ b/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/networks/network/profile_network.rs @@ -2,6 +2,7 @@ use crate::prelude::*; use profile_logic::prelude::ProfileNetworkDetailsForAuthorizedDapp as _; use sargon::AddressBook as InternalAddressBook; use sargon::ProfileNetwork as InternalProfileNetwork; +use sargon::TokenPriceServices as InternalTokenPriceServices; decl_vec_samples_for!(ProfileNetworks, ProfileNetwork); @@ -13,6 +14,14 @@ impl IntoInternal, InternalAddressBook> } } +impl IntoInternal, InternalTokenPriceServices> + for Vec +{ + fn into_internal(self) -> InternalTokenPriceServices { + self.into_iter().map(Into::into).collect() + } +} + /// [`Accounts`], [`Personas`] and [`AuthorizedDapps`] for some [`ProfileNetwork`] /// which user has created/interacted with, all on the same [Radix Network][`NetworkDefinition`], /// identified by `id` ([`NetworkID`]). @@ -40,6 +49,9 @@ pub struct ProfileNetwork { /// User-managed external addresses and names. pub address_book: Vec, + /// Ordered token price service endpoints used for failover. + pub token_price_services: Vec, + /// Pre-derived MFA factor instances pub mfa_factor_instances: Vec, } diff --git a/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/networks/network/token_price_service.rs b/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/networks/network/token_price_service.rs new file mode 100644 index 000000000..dd80da19c --- /dev/null +++ b/crates/uniffi/uniffi_SPLIT_ME/src/profile/v100/networks/network/token_price_service.rs @@ -0,0 +1,19 @@ +use crate::prelude::*; +use sargon::TokenPriceService as InternalTokenPriceService; + +#[derive(Clone, PartialEq, Eq, Hash, InternalConversion, uniffi::Record)] +pub struct TokenPriceService { + pub base_url: Url, +} + +delegate_debug_into!(TokenPriceService, InternalTokenPriceService); + +#[uniffi::export] +pub fn new_token_price_service_sample() -> TokenPriceService { + InternalTokenPriceService::sample().into() +} + +#[uniffi::export] +pub fn new_token_price_service_sample_other() -> TokenPriceService { + InternalTokenPriceService::sample_other().into() +} diff --git a/crates/uniffi/uniffi_SPLIT_ME/src/system/sargon_os/mod.rs b/crates/uniffi/uniffi_SPLIT_ME/src/system/sargon_os/mod.rs index dc2e1b722..4e66d270e 100644 --- a/crates/uniffi/uniffi_SPLIT_ME/src/system/sargon_os/mod.rs +++ b/crates/uniffi/uniffi_SPLIT_ME/src/system/sargon_os/mod.rs @@ -12,6 +12,7 @@ mod sargon_os_entities_linked_to_factor_source; mod sargon_os_entities_linked_to_security_structure; mod sargon_os_factor_source_integrity; mod sargon_os_factors; +mod sargon_os_fungible_fiat_values; mod sargon_os_gateway; mod sargon_os_nft_fiat_values; mod sargon_os_p2p_transport_profiles; @@ -23,6 +24,7 @@ mod sargon_os_security_center; mod sargon_os_security_structures; mod sargon_os_signing; mod sargon_os_stop_timed_recovery_interaction; +mod sargon_os_token_price_services; mod transactions; pub use add_factor_source::*; @@ -39,6 +41,7 @@ pub use sargon_os_entities_linked_to_factor_source::*; pub use sargon_os_entities_linked_to_security_structure::*; pub use sargon_os_factor_source_integrity::*; pub use sargon_os_factors::*; +pub use sargon_os_fungible_fiat_values::*; pub use sargon_os_gateway::*; pub use sargon_os_nft_fiat_values::*; pub use sargon_os_p2p_transport_profiles::*; @@ -50,4 +53,5 @@ pub use sargon_os_security_center::*; pub use sargon_os_security_structures::*; pub use sargon_os_signing::*; pub use sargon_os_stop_timed_recovery_interaction::*; +pub use sargon_os_token_price_services::*; pub use transactions::*; diff --git a/crates/uniffi/uniffi_SPLIT_ME/src/system/sargon_os/sargon_os_fungible_fiat_values.rs b/crates/uniffi/uniffi_SPLIT_ME/src/system/sargon_os/sargon_os_fungible_fiat_values.rs new file mode 100644 index 000000000..fa93e6b65 --- /dev/null +++ b/crates/uniffi/uniffi_SPLIT_ME/src/system/sargon_os/sargon_os_fungible_fiat_values.rs @@ -0,0 +1,40 @@ +use crate::prelude::*; +use sargon::SargonOS as InternalSargonOS; + +#[uniffi::export] +impl SargonOS { + /// Fetches scoped fiat prices for fungible tokens and LSUs. + pub async fn fetch_fungible_fiat_values( + &self, + tokens: Vec, + lsus: Vec, + currency: FiatCurrency, + force_fetch: bool, + ) -> Result> { + let tokens_set: HashSet = tokens + .into_iter() + .map(|address| address.into_internal()) + .collect(); + let lsus_set: HashSet = lsus + .into_iter() + .map(|address| address.into_internal()) + .collect(); + + let result = self + .wrapped + .fetch_fungible_fiat_values( + tokens_set, + lsus_set, + currency.into_internal(), + force_fetch, + ) + .await?; + + Ok(result + .into_iter() + .map(|(resource_address, price)| { + (resource_address.into(), price.into()) + }) + .collect()) + } +} diff --git a/crates/uniffi/uniffi_SPLIT_ME/src/system/sargon_os/sargon_os_token_price_services.rs b/crates/uniffi/uniffi_SPLIT_ME/src/system/sargon_os/sargon_os_token_price_services.rs new file mode 100644 index 000000000..ee0ef0138 --- /dev/null +++ b/crates/uniffi/uniffi_SPLIT_ME/src/system/sargon_os/sargon_os_token_price_services.rs @@ -0,0 +1,37 @@ +use crate::prelude::*; + +#[uniffi::export] +impl SargonOS { + pub fn token_price_services_on_current_network( + &self, + ) -> Result> { + let services: sargon::TokenPriceServices = self + .wrapped + .token_price_services_on_current_network() + .into_result()?; + Ok(services.into_iter().map(Into::into).collect()) + } +} + +#[uniffi::export] +impl SargonOS { + pub async fn add_token_price_service_on_current_network( + &self, + base_url: Url, + ) -> Result { + self.wrapped + .add_token_price_service_on_current_network(base_url) + .await + .into_result() + } + + pub async fn delete_token_price_service_on_current_network( + &self, + base_url: Url, + ) -> Result { + self.wrapped + .delete_token_price_service_on_current_network(base_url) + .await + .into_result() + } +} diff --git a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/ProfileNetworkSample.kt b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/ProfileNetworkSample.kt index b25a29815..2c4377625 100644 --- a/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/ProfileNetworkSample.kt +++ b/jvm/sargon-android/src/main/java/com/radixdlt/sargon/samples/ProfileNetworkSample.kt @@ -28,6 +28,7 @@ val ProfileNetwork.Companion.sampleMainnet: Sample ).asList(), resourcePreferences = ResourceAppPreferences.sample().asList(), addressBook = emptyList(), + tokenPriceServices = emptyList(), mfaFactorInstances = MfaFactorInstances.sample().asList() ) @@ -44,6 +45,7 @@ val ProfileNetwork.Companion.sampleMainnet: Sample ).asList(), resourcePreferences = ResourceAppPreferences.sample.other().asList(), addressBook = emptyList(), + tokenPriceServices = emptyList(), mfaFactorInstances = MfaFactorInstances.sample().asList() ) } @@ -64,6 +66,7 @@ val ProfileNetwork.Companion.sampleStokenet: Sample ).asList(), resourcePreferences = ResourceAppPreferences.sample().asList(), addressBook = emptyList(), + tokenPriceServices = emptyList(), mfaFactorInstances = MfaFactorInstances.sample().asList() ) @@ -80,6 +83,7 @@ val ProfileNetwork.Companion.sampleStokenet: Sample ).asList(), resourcePreferences = ResourceAppPreferences.sample.other().asList(), addressBook = emptyList(), + tokenPriceServices = emptyList(), mfaFactorInstances = MfaFactorInstances.sample().asList() ) }