diff --git a/Cargo.lock b/Cargo.lock index 1b18e458..b930db2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,12 +158,34 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + [[package]] name = "bitcoin-private" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -195,8 +217,7 @@ dependencies = [ [[package]] name = "bp-consensus" version = "0.11.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b50dbcec1e75163cdd1ba1cc265f1bb67ae4319b00469c36b3972a2eb49b879" +source = "git+https://github.com/BP-WG/bp-core?branch=doubleanchors#2d3806295804fc3d50b4259f1d5da41f8cf88356" dependencies = [ "amplify", "chrono", @@ -210,8 +231,7 @@ dependencies = [ [[package]] name = "bp-core" version = "0.11.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece2f2e6f91bdbf5684f26a105c078e50394f98e37dc173c20b23eba53e7be09" +source = "git+https://github.com/BP-WG/bp-core?branch=doubleanchors#2d3806295804fc3d50b4259f1d5da41f8cf88356" dependencies = [ "amplify", "bp-consensus", @@ -227,8 +247,7 @@ dependencies = [ [[package]] name = "bp-dbc" version = "0.11.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0d9d7bbc4dc2debd9c30469752963410b44adee55af807378589e88c5fd913a" +source = "git+https://github.com/BP-WG/bp-core?branch=doubleanchors#2d3806295804fc3d50b4259f1d5da41f8cf88356" dependencies = [ "amplify", "base85", @@ -239,11 +258,21 @@ dependencies = [ "strict_encoding", ] +[[package]] +name = "bp-invoice" +version = "0.11.0-beta.2" +source = "git+https://github.com/BP-WG/bp-std?branch=v0.11#04dc07f0c93359d5ceef26efc28365a7a507866f" +dependencies = [ + "amplify", + "bech32", + "bitcoin_hashes", + "bp-consensus", +] + [[package]] name = "bp-seals" version = "0.11.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3661c2c7bddda0b6fd37dafa1a56c863ba0ff9787ebc06d05eb8ac676f1d469e" +source = "git+https://github.com/BP-WG/bp-core?branch=doubleanchors#2d3806295804fc3d50b4259f1d5da41f8cf88356" dependencies = [ "amplify", "baid58", @@ -344,9 +373,9 @@ checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" @@ -389,6 +418,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -423,9 +461,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -433,6 +471,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hex-conservative" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" + [[package]] name = "iana-time-zone" version = "0.1.58" @@ -468,15 +512,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -489,9 +533,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "log" @@ -532,9 +576,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "paste" @@ -542,6 +586,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -550,9 +600,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -599,8 +649,7 @@ dependencies = [ [[package]] name = "rgb-core" version = "0.11.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8572f1484191ad41598bc3c9b2a5aa3ed0a323ce1a56fc3916c8c4f7fa81d8eb" +source = "git+https://github.com/RGB-WG/rgb-core?branch=validation#234a16f7ada64aa942ec83d6cbc2fb4db74329e8" dependencies = [ "aluvm", "amplify", @@ -617,6 +666,23 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "rgb-invoice" +version = "0.11.0-beta.2" +dependencies = [ + "amplify", + "baid58", + "bp-core", + "bp-invoice", + "fluent-uri", + "indexmap", + "percent-encoding", + "rgb-core", + "serde", + "strict_encoding", + "strict_types", +] + [[package]] name = "rgb-std" version = "0.11.0-beta.2" @@ -631,6 +697,7 @@ dependencies = [ "indexmap", "rand", "rgb-core", + "rgb-invoice", "serde", "strict_encoding", "strict_types", @@ -657,9 +724,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "scoped-tls" @@ -685,7 +752,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acea373acb8c21ecb5a23741452acd2593ed44ee3d343e72baaa143bc89d0d5" dependencies = [ "rand", - "secp256k1-sys 0.9.0", + "secp256k1-sys 0.9.1", "serde", ] @@ -700,9 +767,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e67c467c38fd24bd5499dc9a18183b31575c12ee549197e3e20d57aa4fe3b7" +checksum = "4dd97a086ec737e30053fd5c46f097465d25bb81dd3608825f65298c4c98be83" dependencies = [ "cc", ] @@ -732,22 +799,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -880,9 +947,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -906,7 +973,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -957,9 +1024,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unsafe-libyaml" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" [[package]] name = "version_check" @@ -975,9 +1042,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -985,24 +1052,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -1012,9 +1079,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1022,28 +1089,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wasm-bindgen-test" -version = "0.3.38" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6433b7c56db97397842c46b67e11873eda263170afeb3a2dc74a7cb370fee0d" +checksum = "2cf9242c0d27999b831eae4767b2a146feb0b27d332d553e605864acd2afd403" dependencies = [ "console_error_panic_hook", "js-sys", @@ -1055,20 +1122,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.38" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "493fcbab756bb764fa37e6bee8cec2dd709eb4273d06d0c282a5e74275ded735" +checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", @@ -1142,9 +1209,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.19" +version = "0.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 38319151..5d9e2774 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,12 @@ [workspace] members = [ ".", + "invoice", "stl" ] default-members = [ ".", + "invoice" ] resolver = "2" @@ -13,6 +15,8 @@ version = "0.11.0-beta.2" authors = ["Dr Maxim Orlovsky "] homepage = "https://github.com/RGB-WG" repository = "https://github.com/RGB-WG/rgb-wallet" +keywords = ["bitcoin", "lightning", "rgb", "smart-contracts", "lnp-bp"] +categories = ["cryptography::cryptocurrencies"] rust-version = "1.67" # Due to strict encoding library edition = "2021" license = "Apache-2.0" @@ -24,15 +28,17 @@ strict_encoding = "2.6.1" strict_types = "1.6.3" commit_verify = { version = "0.11.0-beta.1", features = ["stl"] } bp-core = { version = "0.11.0-beta.1", features = ["stl"] } +bp-invoice = { version = "0.11.0-beta.2" } rgb-core = { version = "0.11.0-beta.2", features = ["stl"] } +indexmap = "2.0.2" serde_crate = { package = "serde", version = "1", features = ["derive"] } [package] name = "rgb-std" version = { workspace = true } description = "RGB standard library for working with smart contracts on Bitcoin & Lightning" -keywords = ["bitcoin", "lightning", "rgb", "smart-contracts", "lnp-bp"] -categories = ["cryptography::cryptocurrencies"] +keywords = { workspace = true } +categories = { workspace = true } authors = { workspace = true } repository = { workspace = true } homepage = { workspace = true } @@ -52,10 +58,11 @@ strict_types = { workspace = true } commit_verify = { workspace = true } bp-core = { workspace = true } rgb-core = { workspace = true } +rgb-invoice = { version = "0.11.0-beta.2", path = "invoice" } baid58 = { workspace = true } base85 = "=2.0.0" chrono = "0.4.31" -indexmap = "2.0.2" +indexmap = { workspace = true } serde_crate = { workspace = true, optional = true } [features] @@ -69,6 +76,7 @@ serde = [ "commit_verify/serde", "bp-core/serde", "rgb-core/serde", + "rgb-invoice/serde" ] fs = [] @@ -82,3 +90,11 @@ wasm-bindgen-test = "0.3" [package.metadata.docs.rs] features = [ "all" ] + +[patch.crates-io] +bp-consensus = { git = "https://github.com/BP-WG/bp-core", branch = "doubleanchors" } +bp-dbc = { git = "https://github.com/BP-WG/bp-core", branch = "doubleanchors" } +bp-seals = { git = "https://github.com/BP-WG/bp-core", branch = "doubleanchors" } +bp-core = { git = "https://github.com/BP-WG/bp-core", branch = "doubleanchors" } +bp-invoice = { git = "https://github.com/BP-WG/bp-std", branch = "v0.11" } +rgb-core = { git = "https://github.com/RGB-WG/rgb-core", branch = "validation" } diff --git a/invoice/Cargo.toml b/invoice/Cargo.toml new file mode 100644 index 00000000..181595f6 --- /dev/null +++ b/invoice/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "rgb-invoice" +version = { workspace = true } +description = "Invoicing library for RGB smart contracts" +keywords = { workspace = true } +categories = { workspace = true } +readme = "../README.md" +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +rust-version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } + +[lib] +name = "invoice" + +[dependencies] +amplify = { workspace = true } +baid58 = { workspace = true } +strict_encoding = { workspace = true } +strict_types = { workspace = true } +bp-core = { workspace = true } +bp-invoice = { workspace = true } +rgb-core = { workspace = true } +indexmap = { workspace = true } +fluent-uri = "0.1.4" +percent-encoding = "2.3.0" +serde_crate = { workspace = true, optional = true } + +[features] +default = [] +serde = ["serde_crate"] +# TODO: Separate URL with a feature gate diff --git a/invoice/src/amount.rs b/invoice/src/amount.rs new file mode 100644 index 00000000..f5785c9c --- /dev/null +++ b/invoice/src/amount.rs @@ -0,0 +1,303 @@ +// RGB wallet library for smart contracts on Bitcoin & Lightning network +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt; +use std::fmt::{Display, Formatter, Write}; +use std::iter::Sum; + +use bp::Sats; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use strict_encoding::{StrictDeserialize, StrictSerialize}; +use strict_types::StrictVal; + +use crate::LIB_NAME_RGB_CONTRACT; + +#[derive( + Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From +)] +#[wrapper(Display, FromStr, Add, Sub, Mul, Div, Rem)] +#[wrapper_mut(AddAssign, SubAssign, MulAssign, DivAssign, RemAssign)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_CONTRACT)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +pub struct Amount( + #[from] + #[from(u32)] + #[from(u16)] + #[from(u8)] + #[from(Sats)] + u64, +); + +impl StrictSerialize for Amount {} +impl StrictDeserialize for Amount {} + +impl Amount { + pub const ZERO: Self = Amount(0); + + pub fn from_strict_val_unchecked(value: &StrictVal) -> Self { + value.unwrap_uint::().into() + } + + pub fn with_precision(amount: u64, precision: impl Into) -> Self { + precision.into().unchecked_convert(amount) + } + + pub fn with_precision_checked(amount: u64, precision: impl Into) -> Option { + precision.into().checked_convert(amount) + } + + pub fn value(self) -> u64 { self.0 } + + pub fn split(self, precision: impl Into) -> (u64, u64) { + let precision = precision.into(); + let int = self.floor(precision); + let fract = self.rem(precision); + (int, fract) + } + + pub fn round(&self, precision: impl Into) -> u64 { + let precision = precision.into(); + let mul = precision.multiplier(); + if self.0 == 0 { + return 0; + } + let inc = 2 * self.rem(precision) / mul; + self.0 / mul + inc + } + + pub fn ceil(&self, precision: impl Into) -> u64 { + let precision = precision.into(); + if self.0 == 0 { + return 0; + } + let inc = if self.rem(precision) > 0 { 1 } else { 0 }; + self.0 / precision.multiplier() + inc + } + + pub fn floor(&self, precision: impl Into) -> u64 { + if self.0 == 0 { + return 0; + } + self.0 / precision.into().multiplier() + } + + pub fn rem(&self, precision: impl Into) -> u64 { + self.0 % precision.into().multiplier() + } + + pub fn saturating_add(&self, other: impl Into) -> Self { + self.0.saturating_add(other.into().0).into() + } + pub fn saturating_sub(&self, other: impl Into) -> Self { + self.0.saturating_sub(other.into().0).into() + } + + pub fn saturating_add_assign(&mut self, other: impl Into) { + *self = self.0.saturating_add(other.into().0).into(); + } + pub fn saturating_sub_assign(&mut self, other: impl Into) { + *self = self.0.saturating_sub(other.into().0).into(); + } +} + +impl Sum for Amount { + fn sum>(iter: I) -> Self { + iter.fold(Amount::ZERO, |sum, value| sum.saturating_add(value)) + } +} + +impl Sum for Amount { + fn sum>(iter: I) -> Self { + iter.fold(Amount::ZERO, |sum, value| sum.saturating_add(value)) + } +} + +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default)] +#[repr(u8)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_CONTRACT, tags = repr, into_u8, try_from_u8)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub enum Precision { + Indivisible = 0, + Deci = 1, + Centi = 2, + Milli = 3, + DeciMilli = 4, + CentiMilli = 5, + Micro = 6, + DeciMicro = 7, + #[default] + CentiMicro = 8, + Nano = 9, + DeciNano = 10, + CentiNano = 11, + Pico = 12, + DeciPico = 13, + CentiPico = 14, + Femto = 15, + DeciFemto = 16, + CentiFemto = 17, + Atto = 18, +} +impl StrictSerialize for Precision {} +impl StrictDeserialize for Precision {} + +impl Precision { + pub fn from_strict_val_unchecked(value: &StrictVal) -> Self { value.unwrap_enum() } + pub const fn decimals(self) -> u8 { self as u8 } + pub const fn decimals_u32(self) -> u32 { self as u8 as u32 } + + pub const fn multiplier(self) -> u64 { + match self { + Precision::Indivisible => 1, + Precision::Deci => 10, + Precision::Centi => 100, + Precision::Milli => 1000, + Precision::DeciMilli => 10_000, + Precision::CentiMilli => 100_000, + Precision::Micro => 1_000_000, + Precision::DeciMicro => 10_000_000, + Precision::CentiMicro => 100_000_000, + Precision::Nano => 1_000_000_000, + Precision::DeciNano => 10_000_000_000, + Precision::CentiNano => 100_000_000_000, + Precision::Pico => 1_000_000_000_000, + Precision::DeciPico => 10_000_000_000_000, + Precision::CentiPico => 100_000_000_000_000, + Precision::Femto => 1_000_000_000_000_000, + Precision::DeciFemto => 10_000_000_000_000_000, + Precision::CentiFemto => 100_000_000_000_000_000, + Precision::Atto => 1_000_000_000_000_000_000, + } + } + + pub fn unchecked_convert(self, amount: impl Into) -> Amount { + (amount.into() * self.multiplier()).into() + } + + pub fn checked_convert(self, amount: impl Into) -> Option { + amount + .into() + .checked_mul(self.multiplier()) + .map(Amount::from) + } + pub fn saturating_convert(self, amount: impl Into) -> Amount { + amount.into().saturating_mul(self.multiplier()).into() + } +} + +impl From for u16 { + fn from(value: Precision) -> Self { value as u8 as u16 } +} + +impl From for u32 { + fn from(value: Precision) -> Self { value as u8 as u32 } +} + +impl From for u64 { + fn from(value: Precision) -> Self { value as u8 as u64 } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct CoinAmount { + pub int: u64, + pub fract: u64, + pub precision: Precision, +} + +impl CoinAmount { + pub fn with(value: impl Into, precision: impl Into) -> Self { + let precision = precision.into(); + let value = value.into(); + let (int, fract) = value.split(precision); + CoinAmount { + int, + fract, + precision, + } + } +} + +impl Display for CoinAmount { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut int = self.int.to_string(); + if f.alternate() { + int = int + .chars() + .rev() + .collect::() + .as_bytes() + .chunks(3) + .map(<[u8]>::to_owned) + .map(|mut chunk| unsafe { + chunk.reverse(); + String::from_utf8_unchecked(chunk) + }) + .rev() + .collect::>() + .join("`"); + } + f.write_str(&int)?; + if self.fract > 0 { + f.write_char('.')?; + let mut float = self.fract.to_string(); + let len = float.len(); + if let Some(decimals) = f.precision() { + float.push_str(&"0".repeat(decimals - len)); + } + if f.alternate() { + float = float + .as_bytes() + .chunks(3) + .map(|chunk| unsafe { String::from_utf8_unchecked(chunk.to_owned()) }) + .collect::>() + .join("`"); + } + f.write_str(&float)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[allow(clippy::inconsistent_digit_grouping)] + fn coin_amount() { + let amount = CoinAmount::with(10_000_436_081_95u64, Precision::default()); + assert_eq!(amount.int, 10_000); + assert_eq!(amount.fract, 436_081_95); + assert_eq!(format!("{amount}"), "10000.43608195"); + assert_eq!(format!("{amount:#.10}"), "10`000.436`081`950`0"); + } +} diff --git a/invoice/src/builder.rs b/invoice/src/builder.rs new file mode 100644 index 00000000..b786635c --- /dev/null +++ b/invoice/src/builder.rs @@ -0,0 +1,169 @@ +// RGB wallet library for smart contracts on Bitcoin & Lightning network +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::str::FromStr; + +use invoice::Network; +use rgb::ContractId; + +use super::{Beneficiary, InvoiceState, Precision, RgbInvoice, RgbTransport, TransportParseError}; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct RgbInvoiceBuilder(RgbInvoice); + +#[allow(clippy::result_large_err)] +impl RgbInvoiceBuilder { + pub fn new(beneficiary: impl Into) -> Self { + Self(RgbInvoice { + transports: vec![RgbTransport::UnspecifiedMeans], + contract: None, + iface: None, + operation: None, + assignment: None, + beneficiary: beneficiary.into(), + owned_state: InvoiceState::Void, + network: None, + expiry: None, + unknown_query: none!(), + }) + } + + pub fn with(contract_id: ContractId, beneficiary: impl Into) -> Self { + Self::new(beneficiary).set_contract(contract_id) + } + + pub fn rgb20(contract_id: ContractId, beneficiary: impl Into) -> Self { + Self::with(contract_id, beneficiary).set_interface("RGB20") + } + + pub fn rgb20_anything(beneficiary: impl Into) -> Self { + Self::new(beneficiary).set_interface("RGB20") + } + + pub fn set_contract(mut self, contract_id: ContractId) -> Self { + self.0.contract = Some(contract_id); + self + } + + pub fn set_interface(mut self, name: &'static str) -> Self { + self.0.iface = Some(tn!(name)); + self + } + + pub fn set_operation(mut self, name: &'static str) -> Self { + self.0.operation = Some(tn!(name)); + self + } + + pub fn set_assignment(mut self, name: &'static str) -> Self { + self.0.assignment = Some(fname!(name)); + self + } + + pub fn set_amount_raw(mut self, amount: u64) -> Self { + self.0.owned_state = InvoiceState::Amount(amount); + self + } + + pub fn set_amount( + self, + integer: u64, + decimals: u64, + precision: Precision, + ) -> Result { + // 2^64 ~ 10^19 < 10^18 (18 is max value for Precision enum) + let pow = 10u64.pow(precision as u32); + // number of decimals can't be larger than the smallest possible integer + if decimals >= pow { + return Err(self); + } + let Some(mut amount) = integer.checked_mul(pow) else { + return Err(self); + }; + amount = amount.checked_add(decimals).expect( + "integer has at least the same number of zeros in the lowest digits as much as \ + decimals has digits at most, so overflow is not possible", + ); + Ok(self.set_amount_raw(amount)) + } + + /// # Safety + /// + /// The function may cause the loss of the information about the precise + /// amout of the asset, since f64 type doesn't provide full precision + /// required for that. + pub unsafe fn set_amount_approx(self, amount: f64, precision: Precision) -> Result { + if amount <= 0.0 { + return Err(self); + } + let coins = amount.floor(); + let cents = amount - coins; + self.set_amount(coins as u64, cents as u64, precision) + } + + pub fn set_network(mut self, network: impl Into) -> Self { + self.0.network = Some(network.into()); + self + } + + pub fn set_expiry_timestamp(mut self, expiry: i64) -> Self { + self.0.expiry = Some(expiry); + self + } + + pub fn add_transport(self, transport: &str) -> Result { + let transport = match RgbTransport::from_str(transport) { + Err(err) => return Err((self, err)), + Ok(transport) => transport, + }; + Ok(self.add_transport_raw(transport)) + } + + pub fn add_transport_raw(mut self, transport: RgbTransport) -> Self { + self.0.transports.push(transport); + self + } + + pub fn add_transports<'a>( + self, + transports: impl IntoIterator, + ) -> Result { + let res = transports + .into_iter() + .map(RgbTransport::from_str) + .collect::, TransportParseError>>(); + let transports = match res { + Err(err) => return Err((self, err)), + Ok(transports) => transports, + }; + Ok(self.add_transports_raw(transports)) + } + + pub fn add_transports_raw( + mut self, + transports: impl IntoIterator, + ) -> Self { + self.0.transports.extend(transports); + self + } + + pub fn finish(self) -> RgbInvoice { self.0 } +} diff --git a/invoice/src/invoice.rs b/invoice/src/invoice.rs new file mode 100644 index 00000000..14d15684 --- /dev/null +++ b/invoice/src/invoice.rs @@ -0,0 +1,94 @@ +// RGB wallet library for smart contracts on Bitcoin & Lightning network +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use indexmap::IndexMap; +use invoice::{Address, Network}; +use rgb::{AttachId, ContractId, Layer1, SecretSeal}; +use strict_encoding::{FieldName, TypeName}; + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub enum RgbTransport { + JsonRpc { tls: bool, host: String }, + RestHttp { tls: bool, host: String }, + WebSockets { tls: bool, host: String }, + Storm {/* todo */}, + UnspecifiedMeans, +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug, Display)] +pub enum InvoiceState { + #[display("")] + Void, + #[display("{0}")] + Amount(u64), + #[display("...")] // TODO + Data(Vec /* StrictVal */), + #[display(inner)] + Attach(AttachId), +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, From)] +#[display(inner)] +pub enum Beneficiary { + // TODO: Create wrapping type for SecretSeal to cover/commit to a specific layer1. + // Move Baid58 encoding from BP seals to here. Use utxob1 for bitcoin, and use + // utxol1 for liquid. + #[from] + BlindedSeal(SecretSeal), + #[from] + WitnessVoutBitcoin(Address), + // TODO: Add support for Liquid beneficiaries + //#[from] + //WitnessVoutLiquid(Address), +} + +impl Beneficiary { + pub fn layer1(&self) -> Layer1 { + match self { + // TODO: Fix supporting liquid + Beneficiary::BlindedSeal(_) => Layer1::Bitcoin, + Beneficiary::WitnessVoutBitcoin(_) => Layer1::Bitcoin, + } + } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct RgbInvoice { + pub transports: Vec, + pub contract: Option, + pub iface: Option, + pub operation: Option, + pub assignment: Option, + pub beneficiary: Beneficiary, + pub owned_state: InvoiceState, + pub network: Option, + /// UTC unix timestamp + pub expiry: Option, + pub unknown_query: IndexMap, +} + +impl RgbInvoice { + pub fn layer1(&self) -> Layer1 { + match self.beneficiary { + Beneficiary::BlindedSeal(_) | Beneficiary::WitnessVoutBitcoin(_) => Layer1::Bitcoin, + } + } +} diff --git a/invoice/src/lib.rs b/invoice/src/lib.rs new file mode 100644 index 00000000..20a79a10 --- /dev/null +++ b/invoice/src/lib.rs @@ -0,0 +1,44 @@ +// RGB wallet library for smart contracts on Bitcoin & Lightning network +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[macro_use] +extern crate amplify; +#[macro_use] +extern crate strict_encoding; +#[cfg(feature = "serde")] +extern crate serde_crate as serde; + +/// Re-exporting BP invoice data types. +pub use ::invoice::*; + +#[allow(clippy::module_inception)] +mod invoice; +mod parse; +mod builder; +mod amount; + +pub use amount::{Amount, CoinAmount, Precision}; +pub use builder::RgbInvoiceBuilder; +pub use parse::{InvoiceParseError, TransportParseError}; + +pub use crate::invoice::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; + +pub const LIB_NAME_RGB_CONTRACT: &str = "RGBContract"; diff --git a/invoice/src/parse.rs b/invoice/src/parse.rs new file mode 100644 index 00000000..02a3e48b --- /dev/null +++ b/invoice/src/parse.rs @@ -0,0 +1,660 @@ +// RGB wallet library for smart contracts on Bitcoin & Lightning network +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::{self, Debug, Display, Formatter}; +use std::num::ParseIntError; +use std::str::FromStr; + +use fluent_uri::enc::EStr; +use fluent_uri::Uri; +use indexmap::IndexMap; +use invoice::{Address, AddressNetwork, Network, UnknownNetwork}; +use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; +use rgb::{ContractId, SecretSeal}; +use strict_encoding::{InvalidIdent, TypeName}; + +use super::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; + +const OMITTED: &str = "~"; +const EXPIRY: &str = "expiry"; +const NETWORK: &str = "network"; +const ENDPOINTS: &str = "endpoints"; +const TRANSPORT_SEP: char = ','; +const TRANSPORT_HOST_SEP: &str = "://"; +const QUERY_ENCODE: &AsciiSet = &CONTROLS + .add(b' ') + .add(b'"') + .add(b'#') + .add(b'<') + .add(b'>') + .add(b'[') + .add(b']') + .add(b'&') + .add(b'='); + +#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] +#[display(inner)] +pub enum TransportParseError { + #[display(doc_comments)] + /// invalid transport {0}. + InvalidTransport(String), + + #[display(doc_comments)] + /// invalid transport host {0}. + InvalidTransportHost(String), +} + +#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum InvoiceParseError { + #[from] + #[display(inner)] + Uri(fluent_uri::ParseError), + + /// invalid invoice. + Invalid, + + /// contract id is missed from the invoice. + ContractMissed, + + /// interface information is missed from the invoice. + IfaceMissed, + + /// assignment data is missed from the invoice. + AssignmentMissed, + + /// invalid invoice scheme {0}. + InvalidScheme(String), + + /// no invoice transport has been provided. + NoTransport, + + /// invalid invoice: contract ID present but no contract interface provided. + ContractIdNoIface, + + /// invalid contract ID. + InvalidContractId(String), + + /// invalid interface {0}. + InvalidIface(String), + + /// invalid expiration timestamp {0}. + InvalidExpiration(String), + + #[display(inner)] + #[from] + InvalidNetwork(UnknownNetwork), + + /// address network `{0:#?}` doesn't match network `{1}` specified in the + /// invoice. + NetworkMismatch(AddressNetwork, Network), + + /// invalid query parameter {0}. + InvalidQueryParam(String), + + #[from] + #[display(inner)] + Id(baid58::Baid58ParseError), + + /// can't recognize beneficiary "": it should be either a bitcoin address or + /// a blinded UTXO seal. + Beneficiary(String), + + #[from] + #[display(inner)] + Num(ParseIntError), + + #[from] + /// invalid interface name. + IfaceName(InvalidIdent), +} + +impl RgbInvoice { + fn has_params(&self) -> bool { + self.expiry.is_some() || + self.transports != vec![RgbTransport::UnspecifiedMeans] || + !self.unknown_query.is_empty() + } + + fn query_params(&self) -> IndexMap { + let mut query_params: IndexMap = IndexMap::new(); + if let Some(expiry) = self.expiry { + query_params.insert(EXPIRY.to_string(), expiry.to_string()); + } + if self.transports != vec![RgbTransport::UnspecifiedMeans] { + let mut transports: Vec = vec![]; + for transport in self.transports.clone() { + transports.push(transport.to_string()); + } + query_params.insert(ENDPOINTS.to_string(), transports.join(&TRANSPORT_SEP.to_string())); + } + query_params.extend(self.unknown_query.clone()); + query_params + } +} + +impl Display for RgbTransport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RgbTransport::JsonRpc { tls, host } => { + let s = if *tls { "s" } else { "" }; + write!(f, "rpc{s}{TRANSPORT_HOST_SEP}{}", host)?; + } + RgbTransport::RestHttp { tls, host } => { + let s = if *tls { "s" } else { "" }; + write!(f, "http{s}{TRANSPORT_HOST_SEP}{}", host)?; + } + RgbTransport::WebSockets { tls, host } => { + let s = if *tls { "s" } else { "" }; + write!(f, "ws{s}{TRANSPORT_HOST_SEP}{}", host)?; + } + RgbTransport::Storm {} => { + write!(f, "storm{TRANSPORT_HOST_SEP}_/")?; + } + RgbTransport::UnspecifiedMeans => {} + }; + Ok(()) + } +} + +impl FromStr for RgbTransport { + type Err = TransportParseError; + + fn from_str(s: &str) -> Result { + let tokens = s.split_once(TRANSPORT_HOST_SEP); + if tokens.is_none() { + return Err(TransportParseError::InvalidTransport(s.to_string())); + } + let (trans_type, host) = tokens.unwrap(); + if host.is_empty() { + return Err(TransportParseError::InvalidTransportHost(host.to_string())); + } + let host = host.to_string(); + let transport = match trans_type { + "rpc" => RgbTransport::JsonRpc { tls: false, host }, + "rpcs" => RgbTransport::JsonRpc { tls: true, host }, + "http" => RgbTransport::RestHttp { tls: false, host }, + "https" => RgbTransport::RestHttp { tls: true, host }, + "ws" => RgbTransport::WebSockets { tls: false, host }, + "wss" => RgbTransport::WebSockets { tls: true, host }, + "storm" => RgbTransport::Storm {}, + _ => return Err(TransportParseError::InvalidTransport(s.to_string())), + }; + Ok(transport) + } +} + +impl Display for RgbInvoice { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let amt = self.owned_state.to_string(); + if let Some(contract) = self.contract { + Display::fmt(&contract, f)?; + f.write_str("/")?; + } else { + write!(f, "rgb:{OMITTED}/")?; + } + if let Some(iface) = self.iface.clone() { + write!(f, "{iface}/")?; + } else { + write!(f, "{OMITTED}/")?; + } + if let Some(ref op) = self.operation { + write!(f, "{op}/")?; + } + if let Some(ref assignment_name) = self.assignment { + write!(f, "{assignment_name}/")?; + } + if !amt.is_empty() { + write!(f, "{amt}+")?; + } + Display::fmt(&self.beneficiary, f)?; + if self.has_params() { + f.write_str("?")?; + } + let query_params = self.query_params(); + for (key, val) in query_params.iter().take(1) { + write!( + f, + "{}={}", + utf8_percent_encode(key, QUERY_ENCODE), + utf8_percent_encode(val, QUERY_ENCODE) + )?; + } + for (key, val) in query_params.iter().skip(1) { + write!( + f, + "&{}={}", + utf8_percent_encode(key, QUERY_ENCODE), + utf8_percent_encode(val, QUERY_ENCODE) + )?; + } + Ok(()) + } +} + +impl FromStr for RgbInvoice { + type Err = InvoiceParseError; + + fn from_str(s: &str) -> Result { + let uri = Uri::parse(s)?; + + let scheme = uri.scheme().ok_or(InvoiceParseError::Invalid)?; + if scheme.as_str() != "rgb" { + return Err(InvoiceParseError::InvalidScheme(scheme.to_string())); + } + + let mut path = uri.path().segments(); + + let mut network = None; + let mut address_network = None; + + let Some(contract_id_str) = path.next() else { + return Err(InvoiceParseError::ContractMissed); + }; + let contract = match ContractId::from_str(contract_id_str.as_str()) { + Ok(cid) => Some(cid), + Err(_) if contract_id_str.as_str() == OMITTED => None, + Err(_) => { + return Err(InvoiceParseError::InvalidContractId(contract_id_str.to_string())); + } + }; + + let Some(iface_str) = path.next() else { + return Err(InvoiceParseError::IfaceMissed); + }; + let iface = match TypeName::try_from(iface_str.to_string()) { + Ok(i) => Some(i), + Err(_) if iface_str.as_str() == OMITTED => None, + Err(_) => return Err(InvoiceParseError::InvalidIface(iface_str.to_string())), + }; + if contract.is_some() && iface.is_none() { + return Err(InvoiceParseError::ContractIdNoIface); + } + + let Some(assignment) = path.next() else { + return Err(InvoiceParseError::AssignmentMissed); + }; + let (amount, beneficiary) = assignment + .as_str() + .split_once('+') + .map(|(a, b)| (Some(a), Some(b))) + .unwrap_or((Some(assignment.as_str()), None)); + // TODO: support other state types + let (beneficiary_str, value) = match (amount, beneficiary) { + (Some(a), Some(b)) => (b, InvoiceState::Amount(a.parse::()?)), + (Some(b), None) => (b, InvoiceState::Void), + _ => unreachable!(), + }; + + let beneficiary = + match (SecretSeal::from_str(beneficiary_str), Address::from_str(beneficiary_str)) { + (Ok(seal), Err(_)) => Beneficiary::BlindedSeal(seal), + (Err(_), Ok(addr)) => { + address_network = Some(addr.network); + Beneficiary::WitnessVoutBitcoin(addr) + } + (Err(_), Err(_)) => { + return Err(InvoiceParseError::Beneficiary(beneficiary_str.to_owned())); + } + (Ok(_), Ok(_)) => { + panic!("found a string which is both valid bitcoin address and UTXO blind seal") + } + }; + + let mut query_params = map_query_params(&uri)?; + + let transports = if let Some(endpoints) = query_params.remove(ENDPOINTS) { + let tokens = endpoints.split(TRANSPORT_SEP); + let mut transport_vec: Vec = vec![]; + for token in tokens { + transport_vec.push( + RgbTransport::from_str(token) + .map_err(|e| InvoiceParseError::InvalidQueryParam(e.to_string()))?, + ); + } + transport_vec + } else { + vec![RgbTransport::UnspecifiedMeans] + }; + + let mut expiry = None; + if let Some(exp) = query_params.remove(EXPIRY) { + let timestamp = exp + .parse::() + .map_err(|e| InvoiceParseError::InvalidExpiration(e.to_string()))?; + expiry = Some(timestamp); + } + + if let Some(nw) = query_params.remove(NETWORK) { + let nw = Network::from_str(&nw)?; + if let Some(an) = address_network { + if an.is_testnet() != nw.is_testnet() { + return Err(InvoiceParseError::NetworkMismatch(an, nw)); + } + } + } else if let Some(an) = address_network { + network = Some(match an { + AddressNetwork::Mainnet => Network::Mainnet, + AddressNetwork::Testnet => Network::Testnet3, + AddressNetwork::Regtest => Network::Regtest, + }) + } + + Ok(RgbInvoice { + transports, + contract, + iface, + operation: None, + assignment: None, + beneficiary, + owned_state: value, + network, + expiry, + unknown_query: query_params, + }) + } +} + +fn percent_decode(estr: &EStr) -> Result { + Ok(estr + .decode() + .into_string() + .map_err(|e| InvoiceParseError::InvalidQueryParam(e.to_string()))? + .to_string()) +} + +fn map_query_params(uri: &Uri<&str>) -> Result, InvoiceParseError> { + let mut map: IndexMap = IndexMap::new(); + if let Some(q) = uri.query() { + let params = q.split('&'); + for p in params { + if let Some((k, v)) = p.split_once('=') { + map.insert(percent_decode(k)?, percent_decode(v)?); + } else { + return Err(InvoiceParseError::InvalidQueryParam(p.to_string())); + } + } + } + Ok(map) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse() { + // all path parameters + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + assert_eq!(format!("{invoice:#}"), invoice_str.replace('-', "")); + + // no amount + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // no contract ID + let invoice_str = + "rgb:~/RGB20/utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // no contract ID nor iface + let invoice_str = "rgb:~/~/utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // contract ID provided but no iface + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/~/utxob:\ + egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::ContractIdNoIface))); + + // invalid contract ID + let invalid_contract_id = "invalid"; + let invoice_str = format!( + "rgb:{invalid_contract_id}/RGB20/utxob:\ + egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb" + ); + let result = RgbInvoice::from_str(&invoice_str); + assert!(matches!(result, + Err(InvoiceParseError::InvalidContractId(c)) if c == invalid_contract_id)); + + // with expiration + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + expiry=1682086371"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // bad expiration + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + expiry=six"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidExpiration(_)))); + + // with bad query parameter + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?expiry"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); + + // with an unknown query parameter + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + unknown=new"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // with two unknown query parameters + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + unknown=new&another=new"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // with expiration and an unknown query parameter + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + expiry=1682086371&unknown=new"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.to_string(), invoice_str); + + // with an unknown query parameter containing percent-encoded text + let invoice_base = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?"; + let query_key_encoded = ":@-%20%23"; + let query_key_decoded = ":@- #"; + let query_val_encoded = "?/.%26%3D"; + let query_val_decoded = "?/.&="; + let invoice = + RgbInvoice::from_str(&format!("{invoice_base}{query_key_encoded}={query_val_encoded}")) + .unwrap(); + let query_params = invoice.query_params(); + assert_eq!(query_params[query_key_decoded], query_val_decoded); + assert_eq!( + invoice.to_string(), + format!("{invoice_base}{query_key_encoded}={query_val_encoded}") + ); + + // no scheme + let invoice_str = "2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/~/utxob:\ + egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::Invalid))); + + // invalid scheme + let invoice_str = "bad:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/~/utxob:\ + egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidScheme(_)))); + + // empty transport endpoint specification + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints="; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); + + // invalid transport endpoint specification + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=bad"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); + + // invalid transport variant + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpca://host.example.com"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); + + // rgb-rpc variant + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpc://host.example.com"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.transports, vec![RgbTransport::JsonRpc { + tls: false, + host: "host.example.com".to_string() + }]); + assert_eq!(invoice.to_string(), invoice_str); + + // rgb-rpc variant, host containing authentication, "-" characters and port + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpcs://user:pass@host-1.ex-ample.com:1234"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.transports, vec![RgbTransport::JsonRpc { + tls: true, + host: "user:pass@host-1.ex-ample.com:1234".to_string() + }]); + assert_eq!(invoice.to_string(), invoice_str); + + // rgb-rpc variant, IPv6 host + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpcs://%5B2001:db8::1%5D:1234"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + assert_eq!(invoice.transports, vec![RgbTransport::JsonRpc { + tls: true, + host: "[2001:db8::1]:1234".to_string() + }]); + assert_eq!(invoice.to_string(), invoice_str); + + // rgb-rpc variant with missing host + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpc://"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); + + // rgb-rpc variant with invalid separator + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpc/host.example.com"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); + + // rgb-rpc variant with invalid transport host specification + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=rpc://ho]t"; + let result = RgbInvoice::from_str(invoice_str); + assert!(matches!(result, Err(InvoiceParseError::Uri(_)))); + + // rgb+http variant + let invoice_str = "rgb:\ + 2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?endpoints=https://\ + host.example.com"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + let transports = vec![RgbTransport::RestHttp { + tls: true, + host: "host.example.com".to_string(), + }]; + assert_eq!(invoice.transports, transports); + assert_eq!(invoice.to_string(), invoice_str); + + // rgb+ws variant + let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?\ + endpoints=wss://host.example.com"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + let transports = vec![RgbTransport::WebSockets { + tls: true, + host: "host.example.com".to_string(), + }]; + assert_eq!(invoice.transports, transports); + assert_eq!(invoice.to_string(), invoice_str); + + // TODO: rgb+storm variant + + // multiple transports + let invoice_str = "rgb:\ + 2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\ + 100+utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb?endpoints=rpcs://\ + host1.example.com,http://host2.example.com,ws://host3.example.com"; + let invoice = RgbInvoice::from_str(invoice_str).unwrap(); + let transports = vec![ + RgbTransport::JsonRpc { + tls: true, + host: "host1.example.com".to_string(), + }, + RgbTransport::RestHttp { + tls: false, + host: "host2.example.com".to_string(), + }, + RgbTransport::WebSockets { + tls: false, + host: "host3.example.com".to_string(), + }, + ]; + assert_eq!(invoice.transports, transports); + assert_eq!(invoice.to_string(), invoice_str); + + // empty transport parse error + let result = RgbTransport::from_str(""); + assert!(matches!(result, Err(TransportParseError::InvalidTransport(_)))); + + // invalid transport parse error + let result = RgbTransport::from_str("bad"); + assert!(matches!(result, Err(TransportParseError::InvalidTransport(_)))); + + // invalid transport variant parse error + let result = RgbTransport::from_str("rpca://host.example.com"); + assert!(matches!(result, Err(TransportParseError::InvalidTransport(_)))); + + // rgb-rpc variant with missing host parse error + let result = RgbTransport::from_str("rpc://"); + assert!(matches!(result, Err(TransportParseError::InvalidTransportHost(_)))); + + // rgb-rpc variant with invalid separator parse error + let result = RgbTransport::from_str("rpc/host.example.com"); + assert!(matches!(result, Err(TransportParseError::InvalidTransport(_)))); + } +} diff --git a/src/accessors/assignments.rs b/src/accessors/assignments.rs index 70981052..08ca488b 100644 --- a/src/accessors/assignments.rs +++ b/src/accessors/assignments.rs @@ -23,20 +23,20 @@ use amplify::confinement::SmallVec; use commit_verify::Conceal; use rgb::{ Assign, AssignAttach, AssignData, AssignFungible, AssignRights, ExposedSeal, ExposedState, - SealDefinition, TypedAssigns, + TypedAssigns, XSeal, }; pub trait TypedAssignsExt { - fn reveal_seal(&mut self, seal: SealDefinition); + fn reveal_seal(&mut self, seal: XSeal); - fn filter_revealed_seals(&self) -> Vec>; + fn filter_revealed_seals(&self) -> Vec>; } impl TypedAssignsExt for TypedAssigns { - fn reveal_seal(&mut self, seal: SealDefinition) { + fn reveal_seal(&mut self, seal: XSeal) { fn reveal( vec: &mut SmallVec>, - revealed: SealDefinition, + revealed: XSeal, ) { for assign in vec.iter_mut() { match assign { @@ -65,7 +65,7 @@ impl TypedAssignsExt for TypedAssigns { } } - fn filter_revealed_seals(&self) -> Vec> { + fn filter_revealed_seals(&self) -> Vec> { match self { TypedAssigns::Declarative(s) => { s.iter().filter_map(AssignRights::revealed_seal).collect() diff --git a/src/accessors/bundle.rs b/src/accessors/bundle.rs index 190d40da..aba5d9d1 100644 --- a/src/accessors/bundle.rs +++ b/src/accessors/bundle.rs @@ -19,7 +19,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use rgb::{GraphSeal, OpId, Operation, SealDefinition, Transition, TransitionBundle}; +use rgb::{GraphSeal, OpId, Operation, Transition, TransitionBundle, XSeal}; use crate::accessors::TypedAssignsExt; @@ -32,7 +32,7 @@ pub enum RevealError { pub trait BundleExt { /// Ensures that the seal is revealed inside the bundle. - fn reveal_seal(&mut self, seal: SealDefinition); + fn reveal_seal(&mut self, seal: XSeal); /// Ensures that the transition is revealed inside the bundle. /// @@ -40,31 +40,30 @@ pub trait BundleExt { /// /// `true` if the transition was previously concealed; `false` if it was /// already revealed; error if the transition is unrelated to the bundle. - fn reveal_transition(&mut self, transition: &Transition) -> Result; + fn reveal_transition(&mut self, transition: Transition) -> Result; } impl BundleExt for TransitionBundle { - fn reveal_seal(&mut self, seal: SealDefinition) { - for (_, item) in self.keyed_values_mut() { - if let Some(transition) = &mut item.transition { - for (_, assign) in transition.assignments.keyed_values_mut() { - assign.reveal_seal(seal) - } + fn reveal_seal(&mut self, seal: XSeal) { + for (_, transition) in self.known_transitions.keyed_values_mut() { + for (_, assign) in transition.assignments.keyed_values_mut() { + assign.reveal_seal(seal) } } } - fn reveal_transition(&mut self, transition: &Transition) -> Result { - let id = transition.id(); - let item = self - .get_mut(&id) - .ok_or(RevealError::UnrelatedTransition(id))?; - match item.transition { - None => { - item.transition = Some(transition.clone()); - Ok(true) - } - Some(_) => Ok(false), + fn reveal_transition(&mut self, transition: Transition) -> Result { + let opid = transition.id(); + self.input_map + .values() + .find(|id| *id == &opid) + .ok_or(RevealError::UnrelatedTransition(opid))?; + if self.known_transitions.contains_key(&opid) { + return Ok(false); } + self.known_transitions + .insert(opid, transition.clone()) + .expect("same size as input map"); + Ok(true) } } diff --git a/src/accessors/merge_reveal.rs b/src/accessors/merge_reveal.rs index 6cdcb4d2..7a951df3 100644 --- a/src/accessors/merge_reveal.rs +++ b/src/accessors/merge_reveal.rs @@ -26,8 +26,8 @@ use amplify::Wrapper; use bp::dbc::anchor::MergeError; use commit_verify::{mpc, CommitmentId}; use rgb::{ - Anchor, AnchoredBundle, Assign, Assignments, BundleItem, ExposedSeal, ExposedState, Extension, - Genesis, OpId, Transition, TransitionBundle, TypedAssigns, + AnchorSet, Assign, Assignments, ExposedSeal, ExposedState, Extension, Genesis, OpId, + Transition, TransitionBundle, TypedAssigns, XAnchor, }; #[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error, From)] @@ -41,6 +41,15 @@ pub enum MergeRevealError { #[from] #[display(inner)] AnchorMismatch(MergeError), + + /// the merged bundles contain excessive transactions. + ExcessiveTransactions, + + /// contract id provided for the merge-reveal operation doesn't matches + /// multi-protocol commitment. + #[from(mpc::InvalidProof)] + #[from(mpc::LeafNotKnown)] + ContractMismatch, } /// A trait to merge two structures modifying the revealed status @@ -64,11 +73,15 @@ pub trait MergeReveal: Sized { fn merge_reveal(self, other: Self) -> Result; } -impl MergeReveal for Anchor { - fn merge_reveal(self, other: Self) -> Result { - Anchor::merge_reveal(self, other).map_err(MergeRevealError::from) - } +/* +pub trait MergeRevealContract: Sized { + fn merge_reveal_contract( + self, + other: Self, + contract_id: ContractId, + ) -> Result; } + */ impl MergeReveal for Assign { fn merge_reveal(self, other: Self) -> Result { @@ -171,42 +184,74 @@ impl MergeReveal for Assignments { } } -impl MergeReveal for BundleItem { +impl MergeReveal for TransitionBundle { fn merge_reveal(mut self, other: Self) -> Result { - debug_assert_eq!(self.inputs, other.inputs); - match (self.transition, other.transition) { - (Some(op1), Some(op2)) => self.transition = Some(op1.merge_reveal(op2)?), - (None, Some(op)) => self.transition = Some(op), - (me, None) => self.transition = me, + debug_assert_eq!(self.commitment_id(), other.commitment_id()); + if self.known_transitions.len() + other.known_transitions.len() > self.input_map.len() || + self.known_transitions + .extend(other.known_transitions) + .is_err() + { + return Err(MergeRevealError::ExcessiveTransactions); } Ok(self) } } -impl MergeReveal for TransitionBundle { +/* +impl MergeRevealContract for AnchoredBundle { + fn merge_reveal_contract( + self, + other: Self, + contract_id: ContractId, + ) -> Result { + let bundle_id = self.bundle_id(); + let anchor1 = self.anchor.into_merkle_block(contract_id, bundle_id)?; + let anchor2 = other.anchor.into_merkle_block(contract_id, bundle_id)?; + Ok(AnchoredBundle { + anchor: anchor1 + .merge_reveal(anchor2)? + .into_merkle_proof(contract_id)?, + bundle: self.bundle.merge_reveal(other.bundle)?, + }) + } +} + */ + +impl MergeReveal for XAnchor { fn merge_reveal(self, other: Self) -> Result { - debug_assert_eq!(self.commitment_id(), other.commitment_id()); - let mut result = BTreeMap::new(); - for (first, second) in self - .into_inner() - .into_iter() - .zip(other.into_inner().into_iter()) - { - debug_assert_eq!(first.0, second.0); - result.insert(first.0, first.1.merge_reveal(second.1)?); + match (self, other) { + (XAnchor::Bitcoin(anchor), XAnchor::Bitcoin(other)) => { + anchor.merge_reveal(other).map(XAnchor::Bitcoin) + } + (XAnchor::Liquid(anchor), XAnchor::Liquid(other)) => { + anchor.merge_reveal(other).map(XAnchor::Liquid) + } + _ => Err(MergeError::TxidMismatch.into()), } - Ok(TransitionBundle::from_inner( - Confined::try_from(result).expect("collection of the same size"), - )) } } -impl MergeReveal for AnchoredBundle { +impl MergeReveal for AnchorSet { fn merge_reveal(self, other: Self) -> Result { - Ok(AnchoredBundle { - // TODO: uncomment - anchor: self.anchor, //.merge_reveal(other.anchor)?, - bundle: self.bundle.merge_reveal(other.bundle)?, + let (tapret1, opret1) = self.into_split(); + let (tapret2, opret2) = other.into_split(); + + let tapret = match (tapret1, tapret2) { + (Some(tr), None) | (None, Some(tr)) => Some(tr), + (Some(tapret1), Some(tapret2)) => Some(tapret1.merge_reveal(tapret2)?), + (None, None) => None, + }; + let opret = match (opret1, opret2) { + (Some(or), None) | (None, Some(or)) => Some(or), + (Some(opret1), Some(opret2)) => Some(opret1.merge_reveal(opret2)?), + (None, None) => None, + }; + Ok(match (tapret, opret) { + (Some(tapret), None) => Self::Tapret(tapret), + (None, Some(opret)) => Self::Opret(opret), + (Some(tapret), Some(opret)) => Self::Dual { tapret, opret }, + _ => unreachable!(), }) } } diff --git a/src/containers/bindle.rs b/src/containers/bindle.rs index 5a97b42e..e9874ee4 100644 --- a/src/containers/bindle.rs +++ b/src/containers/bindle.rs @@ -26,7 +26,7 @@ use std::collections::BTreeMap; use std::fmt::{Debug, Display}; use std::io::{self, Read}; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use std::str::FromStr; use amplify::confinement::{self, Confined, TinyVec, U24}; @@ -155,6 +155,10 @@ impl Deref for Bindle { fn deref(&self) -> &Self::Target { &self.data } } +impl DerefMut for Bindle { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.data } +} + impl From for Bindle { fn from(data: C) -> Self { Bindle::new(data) } } @@ -291,7 +295,8 @@ impl Bindle { } } -#[derive(Clone, Debug, From)] +#[derive(Clone, Debug, Display, From)] +#[display(inner)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), diff --git a/src/containers/consignment.rs b/src/containers/consignment.rs index 809eb520..3456be98 100644 --- a/src/containers/consignment.rs +++ b/src/containers/consignment.rs @@ -20,15 +20,16 @@ // limitations under the License. use std::collections::{BTreeMap, BTreeSet}; -use std::{iter, slice}; +use std::rc::Rc; +use std::{iter, vec}; use amplify::confinement::{LargeVec, MediumBlob, SmallOrdMap, TinyOrdMap, TinyOrdSet}; use commit_verify::Conceal; use rgb::validation::{self, ConsignmentApi}; use rgb::{ AnchoredBundle, AssetTag, AssignmentType, AttachId, BundleId, ContractHistory, ContractId, - Extension, Genesis, GraphSeal, OpId, OpRef, Operation, Schema, SchemaId, SealDefinition, - SecretSeal, SubSchema, Transition, TransitionBundle, + Extension, Genesis, GraphSeal, OpId, OpRef, Operation, Schema, SchemaId, SecretSeal, SubSchema, + Transition, XSeal, }; use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize}; @@ -48,10 +49,7 @@ pub type Contract = Consignment; /// /// All consignments-related procedures, including validation or merging /// consignments data into stash or schema-specific data storage, must start -/// with `endpoints` and process up to the genesis. If any of the nodes within -/// the consignments are not part of the paths connecting endpoints with the -/// genesis, consignments validation will return -/// [`validation::Warning::ExcessiveNode`] warning. +/// with `endpoints` and process up to the genesis. #[derive(Clone, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] @@ -159,6 +157,18 @@ impl Consignment { .find(|anchored_bundle| anchored_bundle.bundle.bundle_id() == bundle_id) } + fn transition(&self, opid: OpId) -> Option<&Transition> { + self.bundles + .iter() + .find_map(|ab| ab.bundle.known_transitions.get(&opid)) + } + + fn extension(&self, opid: OpId) -> Option<&Extension> { + self.extensions + .iter() + .find(|&extension| extension.id() == opid) + } + pub fn validation_status(&self) -> Option<&validation::Status> { self.validation_status.as_ref() } @@ -187,25 +197,23 @@ impl Consignment { .collect::>(); let mut ordered_extensions = BTreeMap::new(); for anchored_bundle in &self.bundles { - for item in anchored_bundle.bundle.values() { - if let Some(transition) = &item.transition { - let witness_anchor = resolver.resolve_anchor(&anchored_bundle.anchor)?; - - history.add_transition(transition, witness_anchor); - for (id, used) in &mut extension_idx { - if *used { - continue; - } - for input in &transition.inputs { - if input.prev_out.op == *id { - *used = true; - if let Some(ord) = ordered_extensions.get_mut(id) { - if *ord > witness_anchor { - *ord = witness_anchor; - } - } else { - ordered_extensions.insert(*id, witness_anchor); + for transition in anchored_bundle.bundle.known_transitions.values() { + let witness_anchor = resolver.resolve_anchor(&anchored_bundle.anchor)?; + + history.add_transition(transition, witness_anchor); + for (id, used) in &mut extension_idx { + if *used { + continue; + } + for input in &transition.inputs { + if input.prev_out.op == *id { + *used = true; + if let Some(ord) = ordered_extensions.get_mut(id) { + if *ord > witness_anchor { + *ord = witness_anchor; } + } else { + ordered_extensions.insert(*id, witness_anchor); } } } @@ -221,7 +229,7 @@ impl Consignment { Ok(history) } - pub fn reveal_bundle_seal(&mut self, bundle_id: BundleId, revealed: SealDefinition) { + pub fn reveal_bundle_seal(&mut self, bundle_id: BundleId, revealed: XSeal) { for anchored_bundle in &mut self.bundles { if anchored_bundle.bundle.bundle_id() == bundle_id { anchored_bundle.bundle.reveal_seal(revealed); @@ -248,9 +256,19 @@ impl Consignment { } } +#[derive(Debug)] +pub struct BundleIdIter(vec::IntoIter); + +impl Iterator for BundleIdIter { + type Item = BundleId; + + fn next(&mut self) -> Option { + self.0.next().as_ref().map(AnchoredBundle::bundle_id) + } +} + impl ConsignmentApi for Consignment { - type BundleIter<'container> - = slice::Iter<'container, AnchoredBundle> where Self: 'container; + type Iter<'a> = BundleIdIter; fn schema(&self) -> &SubSchema { &self.schema } @@ -268,23 +286,6 @@ impl ConsignmentApi for Consignment { fn genesis(&self) -> &Genesis { &self.genesis } - fn transition(&self, opid: OpId) -> Option<&Transition> { - for anchored_bundle in &self.bundles { - for (id, item) in anchored_bundle.bundle.iter() { - if *id == opid { - return item.transition.as_ref(); - } - } - } - None - } - - fn extension(&self, opid: OpId) -> Option<&Extension> { - self.extensions - .iter() - .find(|&extension| extension.id() == opid) - } - fn terminals(&self) -> BTreeSet<(BundleId, SecretSeal)> { self.terminals .iter() @@ -297,34 +298,10 @@ impl ConsignmentApi for Consignment { .collect() } - fn anchored_bundles(&self) -> Self::BundleIter<'_> { self.bundles.iter() } - - fn bundle_by_id(&self, bundle_id: BundleId) -> Option<&TransitionBundle> { - self.anchored_bundle(bundle_id).map(|ab| &ab.bundle) - } - - fn op_ids_except(&self, ids: &BTreeSet) -> BTreeSet { - let mut exceptions = BTreeSet::new(); - for anchored_bundle in &self.bundles { - for item in anchored_bundle.bundle.values() { - if let Some(id) = item.transition.as_ref().map(Transition::id) { - if !ids.contains(&id) { - exceptions.insert(id); - } - } - } - } - exceptions - } - - fn has_operation(&self, opid: OpId) -> bool { self.operation(opid).is_some() } + fn bundle_ids<'a>(&self) -> Self::Iter<'a> { BundleIdIter(self.bundles.clone().into_iter()) } - fn known_transitions_by_bundle_id(&self, bundle_id: BundleId) -> Option> { - self.bundle_by_id(bundle_id).map(|bundle| { - bundle - .values() - .filter_map(|item| item.transition.as_ref()) - .collect() - }) + fn anchored_bundle(&self, bundle_id: BundleId) -> Option> { + self.anchored_bundle(bundle_id) + .map(|ab| Rc::new(ab.clone())) } } diff --git a/src/containers/mod.rs b/src/containers/mod.rs index 5bc0a9dd..43dc5250 100644 --- a/src/containers/mod.rs +++ b/src/containers/mod.rs @@ -36,10 +36,12 @@ mod seal; mod util; mod validate; mod certs; +mod partials; pub use bindle::{Bindle, BindleContent, BindleParseError, LoadError, UniversalBindle}; pub use certs::{Cert, ContentId, ContentSigs, Identity}; pub use consignment::{Consignment, Contract, Transfer}; pub use disclosure::Disclosure; +pub use partials::{Batch, CloseMethodSet, Fascia, TransitionInfo}; pub use seal::{BuilderSeal, TerminalSeal, VoutSeal}; -pub use util::{ContainerVer, Terminal}; +pub use util::{ContainerVer, Terminal, XchainOutpoint}; diff --git a/src/containers/partials.rs b/src/containers/partials.rs new file mode 100644 index 00000000..354e2726 --- /dev/null +++ b/src/containers/partials.rs @@ -0,0 +1,240 @@ +// RGB standard library for working with smart contracts on Bitcoin & Lightning +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::cmp::Ordering; +use std::collections::BTreeMap; +use std::hash::{Hash, Hasher}; +use std::ops::{BitOr, BitOrAssign}; +use std::vec; + +use amplify::confinement; +use amplify::confinement::{Confined, U24}; +use bp::seals::txout::CloseMethod; +use commit_verify::mpc; +use rgb::{ContractId, OpId, Operation, OutputSeal, Transition, TransitionBundle, XAnchor}; +use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize}; + +use crate::containers::XchainOutpoint; +use crate::LIB_NAME_RGB_STD; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_STD, tags = repr, into_u8, try_from_u8)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +#[repr(u8)] +pub enum CloseMethodSet { + #[strict_type(dumb)] + TapretFirst = 0x01, + OpretFirst = 0x02, + Both = 0x03, +} + +impl BitOr> for CloseMethodSet { + type Output = Self; + fn bitor(mut self, rhs: Option) -> Self::Output { + if let Some(m) = rhs { + self |= m + }; + self + } +} + +impl BitOrAssign> for CloseMethodSet { + fn bitor_assign(&mut self, rhs: Option) { + if let Some(m) = rhs { + *self |= m + }; + } +} + +impl BitOr for Option { + type Output = CloseMethodSet; + fn bitor(self, mut rhs: CloseMethodSet) -> Self::Output { + if let Some(m) = self { + rhs |= m + }; + rhs + } +} + +impl BitOrAssign for Option { + fn bitor_assign(&mut self, rhs: CloseMethodSet) { *self = Some(rhs | *self) } +} + +impl BitOr for CloseMethodSet { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { if self == rhs { self } else { Self::Both } } +} + +impl BitOrAssign for CloseMethodSet { + fn bitor_assign(&mut self, rhs: Self) { *self = self.bitor(rhs); } +} + +impl From for CloseMethodSet { + fn from(seal: OutputSeal) -> Self { seal.method().into() } +} + +impl From for CloseMethodSet { + fn from(method: CloseMethod) -> Self { + match method { + CloseMethod::OpretFirst => CloseMethodSet::OpretFirst, + CloseMethod::TapretFirst => CloseMethodSet::TapretFirst, + } + } +} + +impl CloseMethodSet { + pub fn has_tapret_first(self) -> bool { matches!(self, Self::TapretFirst | Self::Both) } + pub fn has_opret_first(self) -> bool { matches!(self, Self::OpretFirst | Self::Both) } +} + +#[derive(Clone, Eq, Debug)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_STD)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct TransitionInfo { + pub id: OpId, + pub inputs: Confined, 1, U24>, + pub transition: Transition, + pub methods: CloseMethodSet, +} + +impl StrictDumb for TransitionInfo { + fn strict_dumb() -> Self { Self::new(strict_dumb!(), [strict_dumb!()]).unwrap() } +} + +impl PartialEq for TransitionInfo { + fn eq(&self, other: &Self) -> bool { self.id.eq(&other.id) } +} + +impl Ord for TransitionInfo { + fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } +} + +impl PartialOrd for TransitionInfo { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } +} + +impl Hash for TransitionInfo { + fn hash(&self, state: &mut H) { state.write(self.id.as_slice()) } +} + +impl TransitionInfo { + pub fn new( + transition: Transition, + seals: impl AsRef<[OutputSeal]>, + ) -> Result { + let inputs = Confined::, 1, U24>::try_from_iter( + seals.as_ref().iter().copied().map(XchainOutpoint::from), + )?; + let methods = seals + .as_ref() + .iter() + .map(|seal| seal.method()) + .map(CloseMethodSet::from) + .fold(None, |acc, i| { + Some(match acc { + None => i, + Some(a) => a | i, + }) + }) + .expect("confinement guarantees at least one item"); + Ok(TransitionInfo { + id: transition.id(), + inputs, + transition, + methods, + }) + } +} + +/// A batch of state transitions under different contracts which are associated +/// with some specific transfer and will be anchored within a single layer 1 +/// transaction. +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_STD)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct Batch { + pub main: TransitionInfo, + pub blanks: Confined, 0, { U24 - 1 }>, +} + +impl StrictSerialize for Batch {} +impl StrictDeserialize for Batch {} + +impl IntoIterator for Batch { + type Item = TransitionInfo; + type IntoIter = vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + let mut vec = self.blanks.into_inner(); + vec.push(self.main); + vec.into_iter() + } +} + +impl Batch { + pub fn close_method_set(&self) -> CloseMethodSet { + let mut methods = self.main.methods; + self.blanks.iter().for_each(|i| methods |= i.methods); + methods + } +} + +/// Structure exported from a PSBT for merging into the stash. It contains a set +/// of finalized state transitions (under multiple contracts), packed into +/// bundles, and anchored to a single layer 1 transaction. +#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_STD)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct Fascia { + pub anchor: XAnchor, + pub bundles: Confined, 1, U24>, +} + +impl StrictDumb for Fascia { + fn strict_dumb() -> Self { + Fascia { + anchor: strict_dumb!(), + bundles: confined_bmap![strict_dumb!() => strict_dumb!()], + } + } +} +impl StrictSerialize for Fascia {} +impl StrictDeserialize for Fascia {} diff --git a/src/containers/seal.rs b/src/containers/seal.rs index 0975e269..ee7f5edd 100644 --- a/src/containers/seal.rs +++ b/src/containers/seal.rs @@ -27,9 +27,9 @@ use std::str::FromStr; use bp::seals::txout::blind::ParseError; use bp::seals::txout::{CloseMethod, TxPtr}; use bp::secp256k1::rand::{thread_rng, RngCore}; -use bp::Vout; +use bp::{Outpoint, Vout}; use commit_verify::Conceal; -use rgb::{ExposedSeal, GraphSeal, SealDefinition, SecretSeal}; +use rgb::{ExposedSeal, GenesisSeal, GraphSeal, SecretSeal, XSeal}; use crate::LIB_NAME_RGB_STD; @@ -156,10 +156,10 @@ impl From for TerminalSeal { } } -impl From> for TerminalSeal { - fn from(seal: SealDefinition) -> Self { +impl From> for TerminalSeal { + fn from(seal: XSeal) -> Self { match seal { - SealDefinition::Bitcoin( + XSeal::Bitcoin( seal @ GraphSeal { txid: TxPtr::WitnessTx, .. @@ -169,7 +169,7 @@ impl From> for TerminalSeal { seal.vout, seal.blinding, )), - SealDefinition::Liquid( + XSeal::Liquid( seal @ GraphSeal { txid: TxPtr::WitnessTx, .. @@ -206,11 +206,9 @@ impl Conceal for TerminalSeal { match *self { TerminalSeal::ConcealedUtxo(hash) => hash, TerminalSeal::BitcoinWitnessVout(seal) => { - SealDefinition::Bitcoin(GraphSeal::from(seal)).conceal() - } - TerminalSeal::LiquidWitnessVout(seal) => { - SealDefinition::Liquid(GraphSeal::from(seal)).conceal() + XSeal::Bitcoin(GraphSeal::from(seal)).conceal() } + TerminalSeal::LiquidWitnessVout(seal) => XSeal::Liquid(GraphSeal::from(seal)).conceal(), } } } @@ -239,7 +237,15 @@ impl FromStr for TerminalSeal { #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, From)] pub enum BuilderSeal { #[from] - Revealed(SealDefinition), + Revealed(XSeal), #[from] Concealed(SecretSeal), } + +impl From for BuilderSeal { + fn from(outpoint: Outpoint) -> Self { BuilderSeal::Revealed(XSeal::Bitcoin(outpoint.into())) } +} + +impl From for BuilderSeal { + fn from(outpoint: Outpoint) -> Self { BuilderSeal::Revealed(XSeal::Bitcoin(outpoint.into())) } +} diff --git a/src/containers/util.rs b/src/containers/util.rs index c0063130..b6f0bc37 100644 --- a/src/containers/util.rs +++ b/src/containers/util.rs @@ -20,7 +20,8 @@ // limitations under the License. use amplify::confinement::SmallOrdSet; -use bp::Tx; +use bp::{Outpoint, Tx}; +use rgb::OutputSeal; use super::TerminalSeal; use crate::LIB_NAME_RGB_STD; @@ -69,3 +70,37 @@ pub enum ContainerVer { #[default] V2 = 2, } + +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_STD, tags = custom, dumb = Self::Bitcoin(strict_dumb!()))] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +#[non_exhaustive] +pub enum XchainOutpoint { + #[strict_type(tag = 0x00)] + #[display("bitcoin:{0}", alt = "{0}")] + Bitcoin(Outpoint), + + #[strict_type(tag = 0x01)] + #[display("liquid:{0}")] + Liquid(Outpoint), + /* + #[strict_type(tag = 0x10)] + Abraxas, + #[strict_type(tag = 0x11)] + Prime, + */ +} + +impl From for XchainOutpoint { + fn from(seal: OutputSeal) -> Self { + match seal { + OutputSeal::Bitcoin(s) => XchainOutpoint::Bitcoin(s.into()), + OutputSeal::Liquid(s) => XchainOutpoint::Liquid(s.into()), + } + } +} diff --git a/src/interface/builder.rs b/src/interface/builder.rs index a9b3a40d..a843efc3 100644 --- a/src/interface/builder.rs +++ b/src/interface/builder.rs @@ -19,20 +19,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; -use amplify::confinement::{Confined, TinyOrdMap, TinyOrdSet, U16, U8}; +use amplify::confinement::{Confined, TinyOrdMap, TinyOrdSet, U16}; use amplify::{confinement, Wrapper}; use rgb::{ AltLayer1, AltLayer1Set, AssetTag, Assign, AssignmentType, Assignments, BlindingFactor, ContractId, ExposedSeal, FungibleType, Genesis, GenesisSeal, GlobalState, GraphSeal, Input, - Opout, RevealedData, RevealedValue, SealDefinition, StateSchema, SubSchema, Transition, - TransitionType, TypedAssigns, + Opout, RevealedData, RevealedValue, StateSchema, SubSchema, Transition, TransitionType, + TypedAssigns, }; use strict_encoding::{FieldName, SerializeError, StrictSerialize, TypeName}; use strict_types::decode; use crate::containers::{BuilderSeal, Contract}; +use crate::interface::contract::AttachedState; use crate::interface::{Iface, IfaceImpl, IfacePair, TransitionIface, TypedState}; #[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)] @@ -64,9 +65,17 @@ pub enum BuilderError { /// state `{0}` provided to the builder has invalid name. InvalidState(AssignmentType), - /// asset tag for the state `{0}` must be added before any fungible state of + /// asset tag for state `{0}` must be added before any fungible state of /// the same type. - AssetTagSet(AssignmentType), + AssetTagMissed(AssignmentType), + + /// asset tag for state `{0}` was already automatically created. Please call + /// `add_asset_tag` before adding any fungible state to the builder. + AssetTagAutomatic(AssignmentType), + + /// state data for state type `{0}` are invalid: asset tag doesn't match the + /// tag defined by the contract. + AssetTagInvalid(AssignmentType), /// interface doesn't specifies default operation name, thus an explicit /// operation type must be provided with `set_operation_type` method. @@ -160,23 +169,75 @@ impl ContractBuilder { Ok(self) } + pub fn add_owned_state_raw( + mut self, + type_id: AssignmentType, + seal: impl Into>, + state: TypedState, + ) -> Result { + self.builder = self.builder.add_owned_state_raw(type_id, seal, state)?; + Ok(self) + } + + pub fn add_rights( + mut self, + name: impl Into, + seal: impl Into>, + ) -> Result { + self.builder = self.builder.add_rights(name, seal, None)?; + Ok(self) + } + pub fn add_fungible_state( mut self, name: impl Into, - seal: impl Into, + seal: impl Into>, value: u64, ) -> Result { - self.builder = self.builder.add_fungible_state(name, seal, value, None)?; + let name = name.into(); + let type_id = self + .builder + .assignments_type(&name, None) + .ok_or(BuilderError::AssignmentNotFound(name.clone()))?; + let tag = match self.builder.asset_tags.get(&type_id) { + Some(asset_tag) => *asset_tag, + None => { + let asset_tag = AssetTag::new_random( + format!( + "{}/{}", + self.builder.schema.schema_id(), + self.builder.iface.iface_id() + ), + type_id, + ); + self.builder.asset_tags.insert(type_id, asset_tag)?; + asset_tag + } + }; + + self.builder = self + .builder + .add_fungible_state(name, seal, value, tag, None)?; Ok(self) } - pub fn add_data_state( + pub fn add_data( mut self, name: impl Into, - seal: impl Into, + seal: impl Into>, value: impl StrictSerialize, ) -> Result { - self.builder = self.builder.add_data_state(name, seal, value, None)?; + self.builder = self.builder.add_data(name, seal, value, None)?; + Ok(self) + } + + pub fn add_attachment( + mut self, + name: impl Into, + seal: impl Into>, + attachment: AttachedState, + ) -> Result { + self.builder = self.builder.add_attachment(name, seal, attachment, None)?; Ok(self) } @@ -270,6 +331,16 @@ impl TransitionBuilder { Ok(self) } + #[inline] + pub fn add_asset_tag_raw( + mut self, + type_id: AssignmentType, + asset_tag: AssetTag, + ) -> Result { + self.builder = self.builder.add_asset_tag_raw(type_id, asset_tag)?; + Ok(self) + } + #[inline] pub fn add_global_state( mut self, @@ -293,36 +364,108 @@ impl TransitionBuilder { .ok_or(BuilderError::NoDefaultAssignment) } + #[inline] + pub fn assignments_type(&self, name: &FieldName) -> Option { + self.builder + .assignments_type(name, Some(self.transition_type)) + } + + pub fn add_owned_state_raw( + mut self, + type_id: AssignmentType, + seal: impl Into>, + state: TypedState, + ) -> Result { + if matches!(state, TypedState::Amount(_, _, tag) if self.builder.asset_tag(type_id)? != tag) + { + return Err(BuilderError::AssetTagInvalid(type_id)); + } + self.builder = self.builder.add_owned_state_raw(type_id, seal, state)?; + Ok(self) + } + + pub fn add_rights( + mut self, + name: impl Into, + seal: impl Into>, + ) -> Result { + self.builder = self + .builder + .add_rights(name, seal, Some(self.transition_type))?; + Ok(self) + } + pub fn add_fungible_default_state( self, - seal: impl Into, + seal: impl Into>, value: u64, ) -> Result { let assignment_name = self.default_assignment()?.clone(); self.add_fungible_state(assignment_name, seal.into(), value) } + pub fn add_fungible_state_raw( + mut self, + type_id: AssignmentType, + seal: impl Into>, + value: u64, + blinding: BlindingFactor, + ) -> Result { + let tag = self.builder.asset_tag(type_id)?; + let state = RevealedValue::with_blinding(value, blinding, tag); + self.builder = self.builder.add_fungible_state_raw(type_id, seal, state)?; + Ok(self) + } + pub fn add_fungible_state( mut self, name: impl Into, - seal: impl Into, + seal: impl Into>, value: u64, ) -> Result { + let name = name.into(); + let type_id = self + .builder + .assignments_type(&name, None) + .ok_or(BuilderError::AssignmentNotFound(name.clone()))?; + let tag = self.builder.asset_tag(type_id)?; + self.builder = self.builder - .add_fungible_state(name, seal, value, Some(self.transition_type))?; + .add_fungible_state(name, seal, value, tag, Some(self.transition_type))?; Ok(self) } - pub fn add_data_state( + pub fn add_data( mut self, name: impl Into, - seal: impl Into, + seal: impl Into>, value: impl StrictSerialize, + ) -> Result { + self.builder = self + .builder + .add_data(name, seal, value, Some(self.transition_type))?; + Ok(self) + } + + pub fn add_data_default( + self, + seal: impl Into>, + value: impl StrictSerialize, + ) -> Result { + let assignment_name = self.default_assignment()?.clone(); + self.add_data(assignment_name, seal.into(), value) + } + + pub fn add_attachment( + mut self, + name: impl Into, + seal: impl Into>, + attachment: AttachedState, ) -> Result { self.builder = self.builder - .add_data_state(name, seal, value, Some(self.transition_type))?; + .add_attachment(name, seal, attachment, Some(self.transition_type))?; Ok(self) } @@ -357,11 +500,12 @@ pub struct OperationBuilder { asset_tags: TinyOrdMap, global: GlobalState, - // rights: TinyOrdMap>, 1, U16>>, + rights: TinyOrdMap>, 1, U16>>, fungible: TinyOrdMap, RevealedValue>, 1, U16>>, data: TinyOrdMap, RevealedData>, 1, U16>>, - // TODO: add attachments + attachments: + TinyOrdMap, AttachedState>, 1, U16>>, // TODO: add valencies } @@ -385,7 +529,9 @@ impl OperationBuilder { asset_tags: none!(), global: none!(), + rights: none!(), fungible: none!(), + attachments: none!(), data: none!(), }) } @@ -419,9 +565,17 @@ impl OperationBuilder { .expect("schema should match interface: must be checked by the constructor") } + #[inline] + pub fn asset_tag(&self, type_id: AssignmentType) -> Result { + self.asset_tags + .get(&type_id) + .ok_or(BuilderError::AssetTagMissed(type_id)) + .copied() + } + #[inline] pub fn add_asset_tag( - mut self, + self, name: impl Into, asset_tag: AssetTag, ty: Option, @@ -431,8 +585,17 @@ impl OperationBuilder { .assignments_type(&name, ty) .ok_or(BuilderError::AssignmentNotFound(name))?; + self.add_asset_tag_raw(type_id, asset_tag) + } + + #[inline] + pub fn add_asset_tag_raw( + mut self, + type_id: AssignmentType, + asset_tag: AssetTag, + ) -> Result { if self.fungible.contains_key(&type_id) { - return Err(BuilderError::AssetTagSet(type_id)); + return Err(BuilderError::AssetTagAutomatic(type_id)); } self.asset_tags.insert(type_id, asset_tag)?; @@ -472,11 +635,51 @@ impl OperationBuilder { Ok(self) } - fn add_fungible_state( + fn add_owned_state_raw( + self, + type_id: AssignmentType, + seal: impl Into>, + state: TypedState, + ) -> Result { + match state { + TypedState::Void => self.add_rights_raw(type_id, seal), + TypedState::Amount(value, blinding, tag) => self.add_fungible_state_raw( + type_id, + seal, + RevealedValue::with_blinding(value, blinding, tag), + ), + TypedState::Data(data) => self.add_data_raw(type_id, seal, data), + TypedState::Attachment(attach) => self.add_attachment_raw(type_id, seal, attach), + } + } + + fn add_rights_raw( mut self, + type_id: AssignmentType, + seal: impl Into>, + ) -> Result { + let state_schema = self.state_schema(type_id); + if *state_schema != StateSchema::Fungible(FungibleType::Unsigned64Bit) { + return Err(BuilderError::InvalidState(type_id)); + } + + let seal = seal.into(); + match self.rights.get_mut(&type_id) { + Some(assignments) => { + assignments.push(seal)?; + } + None => { + self.rights.insert(type_id, Confined::with(seal))?; + } + } + + Ok(self) + } + + fn add_rights( + self, name: impl Into, - seal: impl Into, - value: u64, + seal: impl Into>, ty: Option, ) -> Result { let name = name.into(); @@ -485,26 +688,21 @@ impl OperationBuilder { .assignments_type(&name, ty) .ok_or(BuilderError::AssignmentNotFound(name))?; - let tag = match self.asset_tags.get(&type_id) { - Some(asset_tag) => *asset_tag, - None => { - let asset_tag = AssetTag::new_random( - format!("{}/{}", self.schema.schema_id(), self.iface.iface_id()), - type_id, - ); - self.asset_tags.insert(type_id, asset_tag)?; - asset_tag - } - }; - - let state = RevealedValue::new_random_blinding(value, tag); + self.add_rights_raw(type_id, seal) + } + fn add_fungible_state_raw( + mut self, + type_id: AssignmentType, + seal: impl Into>, + state: RevealedValue, + ) -> Result { let state_schema = self.state_schema(type_id); if *state_schema != StateSchema::Fungible(FungibleType::Unsigned64Bit) { return Err(BuilderError::InvalidState(type_id)); } - let seal = BuilderSeal::::from(SealDefinition::Bitcoin(seal.into())); + let seal = seal.into(); match self.fungible.get_mut(&type_id) { Some(assignments) => { assignments.insert(seal, state)?; @@ -518,10 +716,51 @@ impl OperationBuilder { Ok(self) } - fn add_data_state( + fn add_fungible_state( + self, + name: impl Into, + seal: impl Into>, + value: u64, + tag: AssetTag, + ty: Option, + ) -> Result { + let name = name.into(); + + let type_id = self + .assignments_type(&name, ty) + .ok_or(BuilderError::AssignmentNotFound(name))?; + + let state = RevealedValue::new_random_blinding(value, tag); + self.add_fungible_state_raw(type_id, seal, state) + } + + fn add_data_raw( mut self, + type_id: AssignmentType, + seal: impl Into>, + state: RevealedData, + ) -> Result { + let state_schema = self.state_schema(type_id); + if let StateSchema::Structured(_) = *state_schema { + let seal = seal.into(); + match self.data.get_mut(&type_id) { + Some(assignments) => { + assignments.insert(seal, state)?; + } + None => { + self.data.insert(type_id, Confined::with((seal, state)))?; + } + } + } else { + return Err(BuilderError::InvalidState(type_id)); + } + Ok(self) + } + + fn add_data( + self, name: impl Into, - seal: impl Into, + seal: impl Into>, value: impl StrictSerialize, ty: Option, ) -> Result { @@ -533,15 +772,25 @@ impl OperationBuilder { .assignments_type(&name, ty) .ok_or(BuilderError::AssignmentNotFound(name))?; + self.add_data_raw(type_id, seal, state) + } + + fn add_attachment_raw( + mut self, + type_id: AssignmentType, + seal: impl Into>, + state: AttachedState, + ) -> Result { let state_schema = self.state_schema(type_id); - let seal = BuilderSeal::::from(SealDefinition::Bitcoin(seal.into())); if let StateSchema::Structured(_) = *state_schema { - match self.data.get_mut(&type_id) { + let seal = seal.into(); + match self.attachments.get_mut(&type_id) { Some(assignments) => { assignments.insert(seal, state)?; } None => { - self.data.insert(type_id, Confined::with((seal, state)))?; + self.attachments + .insert(type_id, Confined::with((seal, state)))?; } } } else { @@ -550,6 +799,22 @@ impl OperationBuilder { Ok(self) } + fn add_attachment( + self, + name: impl Into, + seal: impl Into>, + state: AttachedState, + ty: Option, + ) -> Result { + let name = name.into(); + + let type_id = self + .assignments_type(&name, ty) + .ok_or(BuilderError::AssignmentNotFound(name))?; + + self.add_attachment_raw(type_id, seal, state) + } + fn complete( self, inputs: Option<&TinyOrdMap>, @@ -579,7 +844,7 @@ impl OperationBuilder { i.iter() .filter(|(out, _)| out.prev_out.ty == id) .map(|(_, ts)| match ts { - TypedState::Amount(_, blinding) => *blinding, + TypedState::Amount(_, blinding, _) => *blinding, _ => panic!("previous state has invalid type"), }) .collect::>() diff --git a/src/interface/contract.rs b/src/interface/contract.rs index dc1c986f..d1113bb5 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -25,13 +25,14 @@ use std::ops::Deref; use amplify::confinement::{LargeOrdMap, LargeVec, SmallVec}; use bp::Outpoint; use rgb::{ - AssignmentType, AttachId, BlindingFactor, ContractId, ContractState, FungibleOutput, MediaType, - Output, RevealedAttach, RevealedData, WitnessId, + AssetTag, AssignmentType, AttachId, BlindingFactor, ContractId, ContractState, FungibleOutput, + MediaType, OutputSeal, RevealedAttach, RevealedData, WitnessId, }; use strict_encoding::FieldName; use strict_types::typify::TypedVal; use strict_types::{decode, StrictVal}; +use crate::containers::XchainOutpoint; use crate::interface::{IfaceId, IfaceImpl}; #[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)] @@ -50,13 +51,24 @@ pub enum ContractError { pub enum TypedState { #[display("")] Void, - Amount(u64, BlindingFactor), + Amount(u64, BlindingFactor, AssetTag), #[from] Data(RevealedData), #[from] Attachment(AttachedState), } +impl TypedState { + pub fn update_blinding(&mut self, blinding: BlindingFactor) { + match self { + TypedState::Void => {} + TypedState::Amount(_, b, _) => *b = blinding, + TypedState::Data(_) => {} + TypedState::Attachment(_) => {} + } + } +} + #[derive(Clone, Eq, PartialEq, Hash, Debug, Display)] #[display("{id}:{media_type}")] pub struct AttachedState { @@ -93,7 +105,7 @@ impl From> for AllocationWitness { // TODO: Consider removing type in favour of `FungibleOutput` #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub struct FungibleAllocation { - pub owner: Output, + pub owner: OutputSeal, pub witness: AllocationWitness, pub value: u64, } @@ -105,7 +117,7 @@ impl From for FungibleAllocation { impl From<&FungibleOutput> for FungibleAllocation { fn from(out: &FungibleOutput) -> Self { FungibleAllocation { - owner: out.output, + owner: out.seal, witness: out.witness.into(), value: out.state.value.as_u64(), } @@ -113,22 +125,26 @@ impl From<&FungibleOutput> for FungibleAllocation { } pub trait OutpointFilter { - fn include_output(&self, output: Output) -> bool; + fn include_output(&self, output: impl Into) -> bool; } pub struct FilterIncludeAll; pub struct FilterExclude(pub T); impl OutpointFilter for &T { - fn include_output(&self, output: Output) -> bool { (*self).include_output(output) } + fn include_output(&self, output: impl Into) -> bool { + (*self).include_output(output) + } } impl OutpointFilter for &mut T { - fn include_output(&self, output: Output) -> bool { self.deref().include_output(output) } + fn include_output(&self, output: impl Into) -> bool { + self.deref().include_output(output) + } } impl OutpointFilter for Option { - fn include_output(&self, output: Output) -> bool { + fn include_output(&self, output: impl Into) -> bool { self.as_ref() .map(|filter| filter.include_output(output)) .unwrap_or(true) @@ -136,27 +152,37 @@ impl OutpointFilter for Option { } impl OutpointFilter for FilterIncludeAll { - fn include_output(&self, _: Output) -> bool { true } + fn include_output(&self, _: impl Into) -> bool { true } } impl OutpointFilter for FilterExclude { - fn include_output(&self, output: Output) -> bool { !self.0.include_output(output) } + fn include_output(&self, output: impl Into) -> bool { + !self.0.include_output(output.into()) + } } -impl OutpointFilter for &[Output] { - fn include_output(&self, output: Output) -> bool { self.contains(&output) } +impl OutpointFilter for &[XchainOutpoint] { + fn include_output(&self, output: impl Into) -> bool { + self.contains(&output.into()) + } } -impl OutpointFilter for Vec { - fn include_output(&self, output: Output) -> bool { self.contains(&output) } +impl OutpointFilter for Vec { + fn include_output(&self, output: impl Into) -> bool { + self.contains(&output.into()) + } } -impl OutpointFilter for HashSet { - fn include_output(&self, output: Output) -> bool { self.contains(&output) } +impl OutpointFilter for HashSet { + fn include_output(&self, output: impl Into) -> bool { + self.contains(&output.into()) + } } -impl OutpointFilter for BTreeSet { - fn include_output(&self, output: Output) -> bool { self.contains(&output) } +impl OutpointFilter for BTreeSet { + fn include_output(&self, output: impl Into) -> bool { + self.contains(&output.into()) + } } /// Contract state is an in-memory structure providing API to read structured @@ -215,7 +241,7 @@ impl ContractIface { .fungibles() .iter() .filter(|outp| outp.opout.ty == type_id) - .filter(|outp| filter.include_output(outp.output)) + .filter(|outp| filter.include_output(outp.seal)) .map(FungibleAllocation::from); Ok(LargeVec::try_from_iter(state).expect("same or smaller collection size")) } diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 8ec4554a..a9f46e3d 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -34,8 +34,8 @@ mod suppl; pub use builder::{BuilderError, ContractBuilder, TransitionBuilder}; pub use contract::{ - AllocationWitness, ContractIface, FilterExclude, FilterIncludeAll, FungibleAllocation, - IfaceWrapper, OutpointFilter, TypedState, + AllocationWitness, ContractError, ContractIface, FilterExclude, FilterIncludeAll, + FungibleAllocation, IfaceWrapper, OutpointFilter, TypedState, }; pub use iface::{ ArgMap, ArgSpec, AssignIface, ExtensionIface, GenesisIface, GlobalIface, Iface, IfaceId, diff --git a/src/interface/rgb20.rs b/src/interface/rgb20.rs index bd31db70..9bafecaf 100644 --- a/src/interface/rgb20.rs +++ b/src/interface/rgb20.rs @@ -21,6 +21,7 @@ use amplify::confinement::LargeVec; use bp::bc::stl::bp_tx_stl; +use invoice::Amount; use strict_types::{CompileError, LibBuilder, TypeLib}; use super::{ @@ -29,9 +30,7 @@ use super::{ use crate::interface::{ ArgSpec, ContractIface, FungibleAllocation, IfaceId, IfaceWrapper, OutpointFilter, }; -use crate::stl::{ - rgb_contract_stl, Amount, ContractData, DivisibleAssetSpec, StandardTypes, Timestamp, -}; +use crate::stl::{rgb_contract_stl, ContractData, DivisibleAssetSpec, StandardTypes, Timestamp}; pub const LIB_NAME_RGB20: &str = "RGB20"; /// Strict types id for the library providing data types for RGB20 interface. diff --git a/src/interface/rgb25.rs b/src/interface/rgb25.rs index 219a8d83..65bf3ff0 100644 --- a/src/interface/rgb25.rs +++ b/src/interface/rgb25.rs @@ -24,6 +24,7 @@ use std::fmt::Debug; use bp::bc::stl::bp_tx_stl; +use invoice::{Amount, Precision}; use strict_encoding::{StrictDumb, StrictEncode}; use strict_types::stl::std_stl; use strict_types::{CompileError, LibBuilder, TypeLib}; @@ -32,7 +33,7 @@ use super::{ AssignIface, GenesisIface, GlobalIface, Iface, OwnedIface, Req, TransitionIface, VerNo, }; use crate::interface::{ArgSpec, ContractIface, IfaceId, IfaceWrapper}; -use crate::stl::{rgb_contract_stl, Amount, ContractData, Details, Name, Precision, StandardTypes}; +use crate::stl::{rgb_contract_stl, ContractData, Details, Name, StandardTypes}; pub const LIB_NAME_RGB25: &str = "RGB25"; /// Strict types id for the library providing data types for RGB25 interface. diff --git a/src/lib.rs b/src/lib.rs index 598a8adc..6840fdcd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,7 +76,8 @@ extern crate strict_encoding; #[macro_use] extern crate serde_crate as serde; -pub use rgb::{contract, schema, validation, vm}; +/// Re-exporting all invoice data types (RGB and BP). +pub extern crate invoice; pub mod stl; pub mod interface; diff --git a/src/persistence/hoard.rs b/src/persistence/hoard.rs index 481477b7..827012d0 100644 --- a/src/persistence/hoard.rs +++ b/src/persistence/hoard.rs @@ -27,8 +27,8 @@ use amplify::confinement::{Confined, LargeOrdMap, SmallOrdMap, TinyOrdMap, TinyO use bp::dbc::anchor::MergeError; use commit_verify::mpc; use rgb::{ - Anchor, AnchorId, AnchoredBundle, AssetTag, AssignmentType, BundleId, ContractId, Extension, - Genesis, OpId, Operation, SchemaId, TransitionBundle, + AnchoredBundle, AssetTag, AssignmentType, BundleId, ContractId, Extension, Genesis, OpId, + Operation, SchemaId, TransitionBundle, WitnessId, XAnchor, }; use strict_encoding::TypeName; @@ -52,6 +52,10 @@ pub enum ConsumeError { #[from] MergeReveal(MergeRevealError), + + /// bundle {1} for contract {0} contains invalid transitioon input map + #[display(doc_comments)] + InvalidBundle(ContractId, BundleId), } impl From for InventoryError { @@ -70,7 +74,7 @@ pub struct Hoard { pub(super) asset_tags: TinyOrdMap>, pub(super) bundles: LargeOrdMap, pub(super) extensions: LargeOrdMap, - pub(super) anchors: LargeOrdMap>, + pub(super) anchors: LargeOrdMap>, pub(super) sigs: SmallOrdMap, } @@ -174,7 +178,7 @@ impl Hoard { for AnchoredBundle { anchor, bundle } in consignment.bundles { let bundle_id = bundle.bundle_id(); - let anchor = anchor.map(|a| a.into_merkle_block(contract_id, bundle_id.into()))?; + let anchor = anchor.into_merkle_block(contract_id, bundle_id)?; self.consume_anchor(anchor)?; self.consume_bundle(bundle)?; } @@ -204,12 +208,15 @@ impl Hoard { } // TODO: Move into Stash trait and re-implement using trait accessor methods - pub fn consume_anchor(&mut self, anchor: Anchor) -> Result<(), ConsumeError> { - let anchor_id = anchor.anchor_id(); - match self.anchors.get_mut(&anchor_id) { + pub fn consume_anchor( + &mut self, + anchor: XAnchor, + ) -> Result<(), ConsumeError> { + let witness_id = anchor.witness_id(); + match self.anchors.get_mut(&witness_id) { Some(a) => *a = a.clone().merge_reveal(anchor)?, None => { - self.anchors.insert(anchor_id, anchor)?; + self.anchors.insert(witness_id, anchor)?; } } Ok(()) @@ -272,7 +279,13 @@ impl Stash for Hoard { Ok(self.geneses.keys().copied().collect()) } - fn contract_suppl(&self, contract_id: ContractId) -> Option<&TinyOrdSet> { + fn contract_suppl(&self, contract_id: ContractId) -> Option<&ContractSuppl> { + // TODO: select supplement basing on the signer trust level + self.contract_suppl_all(contract_id) + .and_then(|set| set.first()) + } + + fn contract_suppl_all(&self, contract_id: ContractId) -> Option<&TinyOrdSet> { self.suppl.get(&contract_id) } @@ -282,6 +295,10 @@ impl Stash for Hoard { .ok_or(StashInconsistency::ContractAbsent(contract_id).into()) } + fn witness_ids(&self) -> Result, Self::Error> { + Ok(self.anchors.keys().copied().collect()) + } + fn bundle_ids(&self) -> Result, Self::Error> { Ok(self.bundles.keys().copied().collect()) } @@ -302,17 +319,13 @@ impl Stash for Hoard { .ok_or(StashInconsistency::OperationAbsent(op_id).into()) } - fn anchor_ids(&self) -> Result, Self::Error> { - Ok(self.anchors.keys().copied().collect()) - } - fn anchor( &self, - anchor_id: AnchorId, - ) -> Result<&Anchor, StashError> { + witness_id: WitnessId, + ) -> Result<&XAnchor, StashError> { self.anchors - .get(&anchor_id) - .ok_or(StashInconsistency::AnchorAbsent(anchor_id).into()) + .get(&witness_id) + .ok_or(StashInconsistency::AnchorAbsent(witness_id).into()) } fn contract_asset_tags( diff --git a/src/persistence/inventory.rs b/src/persistence/inventory.rs index 83995e17..17a501f8 100644 --- a/src/persistence/inventory.rs +++ b/src/persistence/inventory.rs @@ -19,28 +19,33 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeMap, BTreeSet}; +use std::cmp::Ordering; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::error::Error; use std::ops::Deref; -use amplify::confinement::{self, Confined}; +use amplify::confinement::{self, Confined, MediumVec, U24}; use bp::seals::txout::blind::SingleBlindSeal; -use bp::Txid; +use bp::seals::txout::CloseMethod; +use bp::{Txid, Vout}; +use chrono::Utc; use commit_verify::{mpc, Conceal}; +use invoice::{Beneficiary, InvoiceState, RgbInvoice}; use rgb::{ - validation, Anchor, AnchoredBundle, BundleId, ContractId, ExposedSeal, GraphSeal, OpId, - Operation, Opout, Output, SchemaId, SealDefinition, SecretSeal, SubSchema, Transition, - TransitionBundle, WitnessId, + validation, AnchoredBundle, AssignmentType, BlindingFactor, BundleId, ContractId, ExposedSeal, + GraphSeal, OpId, Operation, Opout, OutputSeal, SchemaId, SecretSeal, SubSchema, Transition, + TransitionBundle, WitnessId, XAnchor, XSeal, }; use strict_encoding::TypeName; use crate::accessors::{BundleExt, MergeRevealError, RevealError}; use crate::containers::{ - Bindle, BuilderSeal, Cert, Consignment, ContentId, Contract, Terminal, Transfer, + Batch, Bindle, BuilderSeal, Cert, Consignment, ContentId, Contract, Fascia, Terminal, Transfer, + TransitionInfo, }; use crate::interface::{ - ContractIface, Iface, IfaceId, IfaceImpl, IfacePair, IfaceWrapper, TransitionBuilder, - TypedState, + BuilderError, ContractIface, Iface, IfaceId, IfaceImpl, IfacePair, IfaceWrapper, + TransitionBuilder, TypedState, VelocityHint, }; use crate::persistence::hoard::ConsumeError; use crate::persistence::stash::StashInconsistency; @@ -60,18 +65,59 @@ pub enum ConsignerError { /// public state at operation output {0} is concealed. ConcealedPublicState(Opout), - #[display(inner)] #[from] + #[display(inner)] Reveal(RevealError), - #[display(inner)] #[from] #[from(InventoryInconsistency)] + #[display(inner)] InventoryError(InventoryError), - #[display(inner)] #[from] #[from(StashInconsistency)] + #[display(inner)] + StashError(StashError), +} + +#[derive(Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum ComposeError { + /// no outputs available to store state of type {1} with velocity class + /// '{0}'. + NoBlankOrChange(VelocityHint, AssignmentType), + + /// the provided PSBT doesn't pay any sats to the RGB beneficiary address. + NoBeneficiaryOutput, + + /// expired invoice. + InvoiceExpired, + + /// the invoice contains no contract information. + NoContract, + + /// the invoice contains no interface information. + NoIface, + + /// the invoice requirements can't be fulfilled using available assets or + /// smart contract state. + InsufficientState, + + /// the operation produces too many state transitions which can't fit the + /// container requirements. + #[from] + Confinement(confinement::Error), + + #[from] + #[display(inner)] + Builder(BuilderError), + + #[from] + #[display(inner)] + InventoryError(InventoryError), + + #[from] + #[display(inner)] StashError(StashError), } @@ -140,7 +186,7 @@ impl From> for InventoryError { } #[derive(Debug, Display, Error, From)] -#[display(inner)] +#[display(doc_comments)] pub enum DataError { /// the consignment was not validated by the local host and thus can't be /// imported. @@ -162,8 +208,8 @@ pub enum DataError { /// mismatch between witness seal chain and anchor chain. ChainMismatch, - #[display(inner)] #[from] + #[display(inner)] Reveal(RevealError), #[from] @@ -171,18 +217,21 @@ pub enum DataError { Merge(MergeRevealError), /// outpoint {0} is not part of the contract {1}. - OutpointUnknown(Output, ContractId), + OutpointUnknown(OutputSeal, ContractId), #[from] + #[display(inner)] Confinement(confinement::Error), #[from] + #[display(inner)] IfaceImpl(IfaceImplError), /// schema {0} doesn't implement interface {1}. NoIfaceImpl(SchemaId, IfaceId), #[from] + #[display(inner)] HeightResolver(Box), /// Information is concealed. @@ -301,22 +350,40 @@ pub trait Inventory: Deref { where R::Error: 'static; - /// # Safety + /// Imports fascia into the stash, index and inventory. + /// + /// Part of the transfer workflow. Called once PSBT is completed and an RGB + /// fascia containing anchor and all state transitions is exported from + /// it. /// - /// Assumes that the bundle belongs to a non-mined witness transaction. Must - /// be used only to consume locally-produced bundles before witness - /// transactions are mined. - fn consume_anchor( + /// Must be called before the consignment is created, when witness + /// transaction is not yet mined. + fn consume(&mut self, fascia: Fascia) -> Result<(), InventoryError> { + let witness_id = fascia.anchor.witness_id(); + unsafe { self.consume_anchor(fascia.anchor)? }; + for (contract_id, bundle) in fascia.bundles { + let ids1 = bundle + .known_transitions + .keys() + .copied() + .collect::>(); + let ids2 = bundle.input_map.values().copied().collect::>(); + if !ids1.is_subset(&ids2) { + return Err(ConsumeError::InvalidBundle(contract_id, bundle.bundle_id()).into()); + } + unsafe { self.consume_bundle(contract_id, bundle, witness_id)? }; + } + Ok(()) + } + + #[doc(hidden)] + unsafe fn consume_anchor( &mut self, - anchor: Anchor, + anchor: XAnchor, ) -> Result<(), InventoryError>; - /// # Safety - /// - /// Assumes that the bundle belongs to a non-mined witness transaction. Must - /// be used only to consume locally-produced bundles before witness - /// transactions are mined. - fn consume_bundle( + #[doc(hidden)] + unsafe fn consume_bundle( &mut self, contract_id: ContractId, bundle: TransitionBundle, @@ -335,7 +402,7 @@ pub trait Inventory: Deref { where R::Error: 'static; - fn contracts_by_iface(&mut self) -> Result, InventoryError> + fn contracts_by_iface(&self) -> Result, InventoryError> where Self::Error: From<::Error>, InventoryError: From<::Error>, @@ -347,7 +414,7 @@ pub trait Inventory: Deref { } fn contracts_by_iface_name( - &mut self, + &self, iface: impl Into, ) -> Result, InventoryError> where @@ -363,7 +430,7 @@ pub trait Inventory: Deref { } fn contract_iface_named( - &mut self, + &self, contract_id: ContractId, iface: impl Into, ) -> Result> @@ -377,7 +444,7 @@ pub trait Inventory: Deref { } fn contract_iface_wrapped( - &mut self, + &self, contract_id: ContractId, ) -> Result> { self.contract_iface_id(contract_id, W::IFACE_ID) @@ -385,7 +452,7 @@ pub trait Inventory: Deref { } fn contract_iface_id( - &mut self, + &self, contract_id: ContractId, iface_id: IfaceId, ) -> Result>; @@ -393,7 +460,7 @@ pub trait Inventory: Deref { fn anchored_bundle(&self, opid: OpId) -> Result>; fn transition_builder( - &mut self, + &self, contract_id: ContractId, iface: impl Into, transition_name: Option>, @@ -408,7 +475,7 @@ pub trait Inventory: Deref { .iimpls .get(&iface.iface_id()) .ok_or(DataError::NoIfaceImpl(schema.schema_id(), iface.iface_id()))?; - let builder = if let Some(transition_name) = transition_name { + let mut builder = if let Some(transition_name) = transition_name { TransitionBuilder::named_transition( iface.clone(), schema.clone(), @@ -419,11 +486,17 @@ pub trait Inventory: Deref { TransitionBuilder::default_transition(iface.clone(), schema.clone(), iimpl.clone()) } .expect("internal inconsistency"); + let tags = self.contract_asset_tags(contract_id)?; + for (assignment_type, asset_tag) in tags { + builder = builder + .add_asset_tag_raw(*assignment_type, *asset_tag) + .expect("tags are in bset and must not repeat"); + } Ok(builder) } fn blank_builder( - &mut self, + &self, contract_id: ContractId, iface: impl Into, ) -> Result> @@ -446,50 +519,50 @@ pub trait Inventory: Deref { fn transition(&self, opid: OpId) -> Result<&Transition, InventoryError>; fn contracts_by_outputs( - &mut self, - outputs: impl IntoIterator>, + &self, + outputs: impl IntoIterator>, ) -> Result, InventoryError>; fn public_opouts( - &mut self, + &self, contract_id: ContractId, ) -> Result, InventoryError>; fn opouts_by_outputs( - &mut self, + &self, contract_id: ContractId, - outputs: impl IntoIterator>, + outputs: impl IntoIterator>, ) -> Result, InventoryError>; fn opouts_by_terminals( - &mut self, + &self, terminals: impl IntoIterator, ) -> Result, InventoryError>; + #[allow(clippy::type_complexity)] fn state_for_outputs( - &mut self, + &self, contract_id: ContractId, - outputs: impl IntoIterator>, - ) -> Result, InventoryError>; + outputs: impl IntoIterator>, + ) -> Result, InventoryError>; fn store_seal_secret( &mut self, - seal: SealDefinition, + seal: XSeal, ) -> Result<(), InventoryError>; - fn seal_secrets( - &mut self, - ) -> Result>, InventoryError>; + + fn seal_secrets(&self) -> Result>, InventoryError>; #[allow(clippy::type_complexity)] fn export_contract( - &mut self, + &self, contract_id: ContractId, ) -> Result< Bindle, ConsignerError::Target as Stash>::Error>, > { let mut consignment = - self.consign::(contract_id, [] as [SealDefinition; 0])?; + self.consign::(contract_id, [] as [XSeal; 0])?; consignment.transfer = false; Ok(consignment.into()) // TODO: Add known sigs to the bindle @@ -497,7 +570,7 @@ pub trait Inventory: Deref { #[allow(clippy::type_complexity)] fn transfer( - &mut self, + &self, contract_id: ContractId, seals: impl IntoIterator>>, ) -> Result< @@ -511,7 +584,7 @@ pub trait Inventory: Deref { } fn consign( - &mut self, + &self, contract_id: ContractId, seals: impl IntoIterator>>, ) -> Result< @@ -523,7 +596,7 @@ pub trait Inventory: Deref { let (outpoint_seals, terminal_seals) = seals .into_iter() .map(|seal| match seal.into() { - BuilderSeal::Revealed(seal) => (seal.output(), seal.conceal()), + BuilderSeal::Revealed(seal) => (seal.to_output_seal(), seal.conceal()), BuilderSeal::Concealed(seal) => (None, seal), }) .unzip::<_, _, Vec<_>, Vec<_>>(); @@ -584,7 +657,7 @@ pub trait Inventory: Deref { .entry(id) .or_insert(self.anchored_bundle(id)?.clone()) .bundle - .reveal_transition(transition)?; + .reveal_transition(transition.clone())?; } let genesis = self.genesis(contract_id)?; @@ -609,4 +682,179 @@ pub trait Inventory: Deref { Ok(consignment) } + + /// Composes a batch of state transitions updating state for the provided + /// set of previous outputs, satisfying requirements of the invoice, paying + /// the change back and including the necessary blank state transitions. + fn compose( + &self, + invoice: &RgbInvoice, + prev_outputs: impl IntoIterator>, + method: CloseMethod, + change_vout: Option>, + allocator: impl Fn(ContractId, AssignmentType, VelocityHint) -> Option, + ) -> Result::Target as Stash>::Error>> + where + Self::Error: From<::Error>, + { + self.compose_deterministic(invoice, prev_outputs, method, change_vout, allocator, |_, _| { + BlindingFactor::random() + }) + } + + /// Composes a batch of state transitions updating state for the provided + /// set of previous outputs, satisfying requirements of the invoice, paying + /// the change back and including the necessary blank state transitions. + fn compose_deterministic( + &self, + invoice: &RgbInvoice, + prev_outputs: impl IntoIterator>, + method: CloseMethod, + beneficiary_vout: Option>, + allocator: impl Fn(ContractId, AssignmentType, VelocityHint) -> Option, + blinder: impl Fn(ContractId, AssignmentType) -> BlindingFactor, + ) -> Result::Target as Stash>::Error>> + where + Self::Error: From<::Error>, + { + let layer1 = invoice.layer1(); + let prev_outputs = prev_outputs + .into_iter() + .map(|o| o.into()) + .collect::>(); + + #[allow(clippy::type_complexity)] + let output_for_assignment = |id: ContractId, + assignment_type: AssignmentType| + -> Result< + BuilderSeal, + ComposeError::Target as Stash>::Error>, + > { + let suppl = self.contract_suppl(id); + let velocity = suppl + .and_then(|suppl| suppl.owned_state.get(&assignment_type)) + .map(|s| s.velocity) + .unwrap_or_default(); + let vout = allocator(id, assignment_type, velocity) + .ok_or(ComposeError::NoBlankOrChange(velocity, assignment_type))?; + let seal = GraphSeal::new_vout(method, vout); + Ok(BuilderSeal::Revealed(XSeal::with(layer1, seal))) + }; + + // 1. Prepare the data + if let Some(expiry) = invoice.expiry { + if expiry < Utc::now().timestamp() { + return Err(ComposeError::InvoiceExpired); + } + } + let contract_id = invoice.contract.ok_or(ComposeError::NoContract)?; + let iface = invoice.iface.as_ref().ok_or(ComposeError::NoIface)?; + let mut main_builder = + self.transition_builder(contract_id, iface.clone(), invoice.operation.clone())?; + + let beneficiary = match (invoice.beneficiary, beneficiary_vout) { + (Beneficiary::BlindedSeal(seal), _) => BuilderSeal::Concealed(seal), + (Beneficiary::WitnessVoutBitcoin(_), Some(vout)) => { + BuilderSeal::Revealed(XSeal::Bitcoin(GraphSeal::new_vout(method, vout.into()))) + } + (Beneficiary::WitnessVoutBitcoin(_), None) => { + return Err(ComposeError::NoBeneficiaryOutput); + } + }; + + // 2. Prepare transition + let mut main_inputs = MediumVec::::new(); + let assignment_name = invoice + .assignment + .as_ref() + .or_else(|| main_builder.default_assignment().ok()) + .ok_or(BuilderError::NoDefaultAssignment)? + .clone(); + let assignment_id = main_builder + .assignments_type(&assignment_name) + .ok_or(BuilderError::InvalidStateField(assignment_name.clone()))?; + let mut sum_inputs = 0u64; + for ((opout, output), mut state) in + self.state_for_outputs(contract_id, prev_outputs.iter().cloned())? + { + main_builder = main_builder.add_input(opout, state.clone())?; + main_inputs.push(output)?; + if opout.ty != assignment_id { + let seal = output_for_assignment(contract_id, opout.ty)?; + state.update_blinding(blinder(contract_id, assignment_id)); + main_builder = main_builder.add_owned_state_raw(opout.ty, seal, state)?; + } else if let TypedState::Amount(value, _, _) = state { + sum_inputs += value; + } + } + // Add change + let main_transition = match invoice.owned_state { + InvoiceState::Amount(amt) => { + match sum_inputs.cmp(&amt) { + Ordering::Greater => { + let seal = output_for_assignment(contract_id, assignment_id)?; + main_builder = main_builder.add_fungible_state_raw( + assignment_id, + seal, + sum_inputs - amt, + blinder(contract_id, assignment_id), + )?; + } + Ordering::Less => return Err(ComposeError::InsufficientState), + Ordering::Equal => {} + } + main_builder + .add_fungible_state_raw( + assignment_id, + beneficiary, + amt, + blinder(contract_id, assignment_id), + )? + .complete_transition(contract_id)? + } + _ => { + todo!("only TypedState::Amount is currently supported") + } + }; + + // 3. Prepare other transitions + // Enumerate state + let mut spent_state = + HashMap::>::new(); + for output in prev_outputs { + for id in self.contracts_by_outputs([output])? { + if id == contract_id { + continue; + } + spent_state + .entry(id) + .or_default() + .extend(self.state_for_outputs(id, [output])?); + } + } + // Construct blank transitions + let mut blanks = Confined::, 0, { U24 - 1 }>::with_capacity(spent_state.len()); + for (id, opouts) in spent_state { + let mut blank_builder = self.blank_builder(id, iface.clone())?; + let mut outputs = Vec::with_capacity(opouts.len()); + for ((opout, output), mut state) in opouts { + let seal = output_for_assignment(id, opout.ty)?; + outputs.push(output); + if let TypedState::Amount(_, ref mut blinding, _) = state { + *blinding = blinder(id, opout.ty); + } + blank_builder = blank_builder + .add_input(opout, state.clone())? + .add_owned_state_raw(opout.ty, seal, state)?; + } + + let transition = blank_builder.complete_transition(contract_id)?; + blanks.push(TransitionInfo::new(transition, outputs)?)?; + } + + Ok(Batch { + main: TransitionInfo::new(main_transition, main_inputs)?, + blanks, + }) + } } diff --git a/src/persistence/mod.rs b/src/persistence/mod.rs index 62ecc32a..b9a35adb 100644 --- a/src/persistence/mod.rs +++ b/src/persistence/mod.rs @@ -39,7 +39,8 @@ pub mod hoard; pub use hoard::Hoard; pub use inventory::{ - ConsignerError, Inventory, InventoryDataError, InventoryError, InventoryInconsistency, + ComposeError, ConsignerError, Inventory, InventoryDataError, InventoryError, + InventoryInconsistency, }; pub use stash::{Stash, StashError, StashInconsistency}; pub use stock::Stock; diff --git a/src/persistence/stash.rs b/src/persistence/stash.rs index 2224e3f5..241c8ec7 100644 --- a/src/persistence/stash.rs +++ b/src/persistence/stash.rs @@ -27,8 +27,8 @@ use std::error::Error; use amplify::confinement::{TinyOrdMap, TinyOrdSet}; use commit_verify::mpc; use rgb::{ - Anchor, AnchorId, AssetTag, AssignmentType, BundleId, ContractId, Extension, Genesis, OpId, - SchemaId, TransitionBundle, + AssetTag, AssignmentType, BundleId, ContractId, Extension, Genesis, OpId, SchemaId, + TransitionBundle, WitnessId, XAnchor, }; use strict_encoding::TypeName; @@ -77,7 +77,7 @@ pub enum StashInconsistency { /// /// It may happen due to RGB standard library bug, or indicate internal /// stash inconsistency and compromised stash data storage. - AnchorAbsent(AnchorId), + AnchorAbsent(WitnessId), /// bundle {0} is absent. /// @@ -112,7 +112,8 @@ pub trait Stash { self.schema(genesis.schema_id) } - fn contract_suppl(&self, contract_id: ContractId) -> Option<&TinyOrdSet>; + fn contract_suppl(&self, contract_id: ContractId) -> Option<&ContractSuppl>; + fn contract_suppl_all(&self, contract_id: ContractId) -> Option<&TinyOrdSet>; fn contract_asset_tags( &self, @@ -121,6 +122,8 @@ pub trait Stash { fn genesis(&self, contract_id: ContractId) -> Result<&Genesis, StashError>; + fn witness_ids(&self) -> Result, Self::Error>; + fn bundle_ids(&self) -> Result, Self::Error>; fn bundle(&self, bundle_id: BundleId) -> Result<&TransitionBundle, StashError>; @@ -129,10 +132,8 @@ pub trait Stash { fn extension(&self, op_id: OpId) -> Result<&Extension, StashError>; - fn anchor_ids(&self) -> Result, Self::Error>; - fn anchor( &self, - anchor_id: AnchorId, - ) -> Result<&Anchor, StashError>; + witness_id: WitnessId, + ) -> Result<&XAnchor, StashError>; } diff --git a/src/persistence/stock.rs b/src/persistence/stock.rs index 51d1550c..b48d95e0 100644 --- a/src/persistence/stock.rs +++ b/src/persistence/stock.rs @@ -24,14 +24,13 @@ use std::convert::Infallible; use std::ops::{Deref, DerefMut}; use amplify::confinement::{MediumOrdMap, MediumOrdSet, TinyOrdMap}; -use amplify::ByteArray; use commit_verify::{mpc, Conceal}; use rgb::validation::{Status, Validity, Warning}; use rgb::{ - validation, Anchor, AnchorId, AnchoredBundle, Assign, AssignmentType, BundleId, - ContractHistory, ContractId, ContractState, ExposedState, Extension, Genesis, GenesisSeal, - GraphSeal, OpId, Operation, Opout, Output, SealDefinition, SecretSeal, SubSchema, Transition, - TransitionBundle, TypedAssigns, WitnessAnchor, WitnessId, + validation, AnchoredBundle, Assign, AssignmentType, BundleId, ContractHistory, ContractId, + ContractState, ExposedState, Extension, Genesis, GenesisSeal, GraphSeal, OpId, Operation, + Opout, OutputSeal, SecretSeal, SubSchema, Transition, TransitionBundle, TypedAssigns, + WitnessAnchor, WitnessId, XAnchor, XSeal, }; use strict_encoding::{StrictDeserialize, StrictSerialize}; @@ -56,7 +55,7 @@ pub struct IndexedBundle(ContractId, BundleId); #[strict_type(lib = LIB_NAME_RGB_STD)] pub struct ContractIndex { public_opouts: MediumOrdSet, - outpoint_opouts: MediumOrdMap>, + outpoint_opouts: MediumOrdMap>, } /// Stock is an in-memory inventory (stash, index, contract state) useful for @@ -74,11 +73,11 @@ pub struct Stock { history: TinyOrdMap, // index bundle_op_index: MediumOrdMap, - anchor_bundle_index: MediumOrdMap, + anchor_bundle_index: MediumOrdMap, contract_index: TinyOrdMap, terminal_index: MediumOrdMap, // secrets - seal_secrets: MediumOrdSet>, + seal_secrets: MediumOrdSet>, } impl Default for Stock { @@ -176,9 +175,9 @@ impl Stock { } for AnchoredBundle { anchor, bundle } in &mut consignment.bundles { let bundle_id = bundle.bundle_id(); - let anchor_id = anchor.anchor_id(contract_id, bundle_id.into())?; - self.anchor_bundle_index.insert(bundle_id, anchor_id)?; - self.index_bundle(contract_id, bundle, anchor.witness_id())?; + let witness_id = anchor.witness_id(); + self.anchor_bundle_index.insert(bundle_id, witness_id)?; + self.index_bundle(contract_id, bundle, witness_id)?; } self.hoard.consume_consignment(consignment)?; @@ -243,32 +242,22 @@ impl Stock { witness_id: WitnessId, ) -> Result<(), InventoryError<::Error>> { let bundle_id = bundle.bundle_id(); - for (opid, item) in bundle.iter() { - if let Some(transition) = &item.transition { - self.bundle_op_index - .insert(*opid, IndexedBundle(id, bundle_id))?; - for (type_id, assign) in transition.assignments.iter() { - match assign { - TypedAssigns::Declarative(vec) => { - self.index_transition_assignments( - id, vec, *opid, *type_id, witness_id, - )?; - } - TypedAssigns::Fungible(vec) => { - self.index_transition_assignments( - id, vec, *opid, *type_id, witness_id, - )?; - } - TypedAssigns::Structured(vec) => { - self.index_transition_assignments( - id, vec, *opid, *type_id, witness_id, - )?; - } - TypedAssigns::Attachment(vec) => { - self.index_transition_assignments( - id, vec, *opid, *type_id, witness_id, - )?; - } + for (opid, transition) in &bundle.known_transitions { + self.bundle_op_index + .insert(*opid, IndexedBundle(id, bundle_id))?; + for (type_id, assign) in transition.assignments.iter() { + match assign { + TypedAssigns::Declarative(vec) => { + self.index_transition_assignments(id, vec, *opid, *type_id, witness_id)?; + } + TypedAssigns::Fungible(vec) => { + self.index_transition_assignments(id, vec, *opid, *type_id, witness_id)?; + } + TypedAssigns::Structured(vec) => { + self.index_transition_assignments(id, vec, *opid, *type_id, witness_id)?; + } + TypedAssigns::Attachment(vec) => { + self.index_transition_assignments(id, vec, *opid, *type_id, witness_id)?; } } } @@ -292,7 +281,9 @@ impl Stock { for (no, a) in vec.iter().enumerate() { let opout = Opout::new(opid, type_id, no as u16); if let Assign::ConfidentialState { seal, .. } | Assign::Revealed { seal, .. } = a { - let output = seal.output().expect("genesis seals always have outpoint"); + let output = seal + .to_output_seal() + .expect("genesis seals always have outpoint"); match index.outpoint_opouts.get_mut(&output) { Some(opouts) => { opouts.push(opout)?; @@ -328,7 +319,7 @@ impl Stock { let opout = Opout::new(opid, type_id, no as u16); if let Assign::ConfidentialState { seal, .. } | Assign::Revealed { seal, .. } = a { let output = seal - .output_or_witness(witness_id) + .try_to_output_seal(witness_id) .map_err(|_| DataError::ChainMismatch)?; match index.outpoint_opouts.get_mut(&output) { Some(opouts) => { @@ -471,20 +462,19 @@ impl Inventory for Stock { self.consume_consignment(transfer, resolver, force) } - fn consume_anchor( + unsafe fn consume_anchor( &mut self, - anchor: Anchor, + anchor: XAnchor, ) -> Result<(), InventoryError> { - let anchor_id = anchor.anchor_id(); - for (_, bundle_id) in anchor.mpc_proof.to_known_message_map() { - self.anchor_bundle_index - .insert(bundle_id.to_byte_array().into(), anchor_id)?; + let witness_id = anchor.witness_id(); + for (bundle_id, _) in anchor.known_bundle_ids() { + self.anchor_bundle_index.insert(bundle_id, witness_id)?; } self.hoard.consume_anchor(anchor)?; Ok(()) } - fn consume_bundle( + unsafe fn consume_bundle( &mut self, contract_id: ContractId, bundle: TransitionBundle, @@ -495,11 +485,9 @@ impl Inventory for Stock { .history .get_mut(&contract_id) .ok_or(InventoryInconsistency::StateAbsent(contract_id))?; - for item in bundle.values() { - if let Some(transition) = &item.transition { - let witness_anchor = WitnessAnchor::from_mempool(witness_id); - history.add_transition(transition, witness_anchor); - } + for transition in bundle.known_transitions.values() { + let witness_anchor = WitnessAnchor::from_mempool(witness_id); + history.add_transition(transition, witness_anchor); } self.hoard.consume_bundle(bundle)?; Ok(()) @@ -517,7 +505,7 @@ impl Inventory for Stock { } fn contract_iface_id( - &mut self, + &self, contract_id: ContractId, iface_id: IfaceId, ) -> Result> { @@ -552,8 +540,10 @@ impl Inventory for Stock { .get(&opid) .ok_or(InventoryInconsistency::BundleAbsent(opid))?; let bundle = self.bundle(*bundle_id)?; - let item = bundle.get(&opid).ok_or(DataError::Concealed)?; - let transition = item.transition.as_ref().ok_or(DataError::Concealed)?; + let transition = bundle + .known_transitions + .get(&opid) + .ok_or(DataError::Concealed)?; Ok(transition) } @@ -570,15 +560,15 @@ impl Inventory for Stock { let bundle = self.bundle(*bundle_id)?.clone(); let anchor = self.anchor(*anchor_id)?; - let anchor = anchor.clone().map(|a| a.into_merkle_proof(*contract_id))?; + let anchor = anchor.to_merkle_proof(*contract_id)?; // TODO: Conceal all transitions except the one we need Ok(AnchoredBundle { anchor, bundle }) } fn contracts_by_outputs( - &mut self, - outputs: impl IntoIterator>, + &self, + outputs: impl IntoIterator>, ) -> Result, InventoryError> { let outputs = outputs .into_iter() @@ -596,7 +586,7 @@ impl Inventory for Stock { } fn public_opouts( - &mut self, + &self, contract_id: ContractId, ) -> Result, InventoryError> { let index = self @@ -607,9 +597,9 @@ impl Inventory for Stock { } fn opouts_by_outputs( - &mut self, + &self, contract_id: ContractId, - outputs: impl IntoIterator>, + outputs: impl IntoIterator>, ) -> Result, InventoryError> { let index = self .contract_index @@ -627,7 +617,7 @@ impl Inventory for Stock { } fn opouts_by_terminals( - &mut self, + &self, terminals: impl IntoIterator, ) -> Result, InventoryError> { let terminals = terminals.into_iter().collect::>(); @@ -640,10 +630,10 @@ impl Inventory for Stock { } fn state_for_outputs( - &mut self, + &self, contract_id: ContractId, - outputs: impl IntoIterator>, - ) -> Result, InventoryError> { + outputs: impl IntoIterator>, + ) -> Result, InventoryError> { let outputs = outputs .into_iter() .map(|o| o.into()) @@ -656,30 +646,37 @@ impl Inventory for Stock { let mut res = BTreeMap::new(); - for output in history.fungibles() { - if outputs.contains(&output.output) { + for item in history.fungibles() { + if outputs.contains(&item.seal) { res.insert( - output.opout, - TypedState::Amount(output.state.value.as_u64(), output.state.blinding), + (item.opout, item.seal), + TypedState::Amount( + item.state.value.as_u64(), + item.state.blinding, + item.state.tag, + ), ); } } - for output in history.data() { - if outputs.contains(&output.output) { - res.insert(output.opout, TypedState::Data(output.state.clone())); + for item in history.data() { + if outputs.contains(&item.seal) { + res.insert((item.opout, item.seal), TypedState::Data(item.state.clone())); } } - for output in history.rights() { - if outputs.contains(&output.output) { - res.insert(output.opout, TypedState::Void); + for item in history.rights() { + if outputs.contains(&item.seal) { + res.insert((item.opout, item.seal), TypedState::Void); } } - for output in history.attach() { - if outputs.contains(&output.output) { - res.insert(output.opout, TypedState::Attachment(output.state.clone().into())); + for item in history.attach() { + if outputs.contains(&item.seal) { + res.insert( + (item.opout, item.seal), + TypedState::Attachment(item.state.clone().into()), + ); } } @@ -688,15 +685,13 @@ impl Inventory for Stock { fn store_seal_secret( &mut self, - seal: SealDefinition, + seal: XSeal, ) -> Result<(), InventoryError> { self.seal_secrets.push(seal)?; Ok(()) } - fn seal_secrets( - &mut self, - ) -> Result>, InventoryError> { + fn seal_secrets(&self) -> Result>, InventoryError> { Ok(self.seal_secrets.to_inner()) } } diff --git a/src/resolvers.rs b/src/resolvers.rs index 20de0b22..84577b07 100644 --- a/src/resolvers.rs +++ b/src/resolvers.rs @@ -19,10 +19,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use rgb::{Anchor, WitnessAnchor}; +use rgb::{WitnessAnchor, XAnchor}; pub trait ResolveHeight { type Error: std::error::Error; - fn resolve_anchor(&mut self, anchor: &Anchor) -> Result; + fn resolve_anchor(&mut self, anchor: &XAnchor) -> Result; } diff --git a/src/stl/mod.rs b/src/stl/mod.rs index f5940828..87ae154f 100644 --- a/src/stl/mod.rs +++ b/src/stl/mod.rs @@ -28,14 +28,13 @@ mod chain; pub use chain::ProofOfReserves; use error::Error; +pub use invoice::LIB_NAME_RGB_CONTRACT; pub use mime::{MediaRegName, MediaType}; pub use specs::{ - Amount, AssetNaming, Attachment, BurnMeta, CoinAmount, ContractData, Details, - DivisibleAssetSpec, IssueMeta, Name, Precision, RicardianContract, Ticker, Timestamp, + AssetNaming, Attachment, BurnMeta, ContractData, Details, DivisibleAssetSpec, IssueMeta, Name, + RicardianContract, Ticker, Timestamp, }; pub use stl::{ rgb_contract_stl, rgb_std_stl, StandardTypes, LIB_ID_RGB, LIB_ID_RGB_CONTRACT, LIB_ID_RGB_STD, }; - -pub const LIB_NAME_RGB_CONTRACT: &str = "RGBContract"; pub const LIB_NAME_RGB_STD: &str = "RGBStd"; diff --git a/src/stl/specs.rs b/src/stl/specs.rs index fc33656a..55755d5f 100644 --- a/src/stl/specs.rs +++ b/src/stl/specs.rs @@ -21,15 +21,13 @@ #![allow(unused_braces)] // caused by rustc unable to understand strict_dumb -use std::fmt; -use std::fmt::{Debug, Display, Formatter, Write}; -use std::iter::Sum; +use std::fmt::{self, Debug, Formatter}; use std::str::FromStr; use amplify::ascii::AsciiString; use amplify::confinement::{Confined, NonEmptyString, NonEmptyVec, SmallOrdSet, SmallString, U8}; -use bp::Sats; use chrono::{DateTime, Local, NaiveDateTime, Utc}; +use invoice::Precision; use strict_encoding::stl::{AlphaCapsNum, AsciiPrintable}; use strict_encoding::{ InvalidIdent, StrictDeserialize, StrictDumb, StrictEncode, StrictSerialize, StrictType, @@ -68,262 +66,6 @@ pub struct IssueMeta { impl StrictSerialize for IssueMeta {} impl StrictDeserialize for IssueMeta {} -#[derive( - Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From -)] -#[wrapper(Display, FromStr, Add, Sub, Mul, Div, Rem)] -#[wrapper_mut(AddAssign, SubAssign, MulAssign, DivAssign, RemAssign)] -#[derive(StrictType, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_RGB_CONTRACT)] -#[cfg_attr( - feature = "serde", - derive(Serialize, Deserialize), - serde(crate = "serde_crate", transparent) -)] -pub struct Amount( - #[from] - #[from(u32)] - #[from(u16)] - #[from(u8)] - #[from(Sats)] - u64, -); - -impl StrictSerialize for Amount {} -impl StrictDeserialize for Amount {} - -impl Amount { - pub const ZERO: Self = Amount(0); - - pub fn from_strict_val_unchecked(value: &StrictVal) -> Self { - value.unwrap_uint::().into() - } - - pub fn with_precision(amount: u64, precision: impl Into) -> Self { - precision.into().unchecked_convert(amount) - } - - pub fn with_precision_checked(amount: u64, precision: impl Into) -> Option { - precision.into().checked_convert(amount) - } - - pub fn value(self) -> u64 { self.0 } - - pub fn split(self, precision: impl Into) -> (u64, u64) { - let precision = precision.into(); - let int = self.floor(precision); - let fract = self.rem(precision); - (int, fract) - } - - pub fn round(&self, precision: impl Into) -> u64 { - let precision = precision.into(); - let mul = precision.multiplier(); - if self.0 == 0 { - return 0; - } - let inc = 2 * self.rem(precision) / mul; - self.0 / mul + inc - } - - pub fn ceil(&self, precision: impl Into) -> u64 { - let precision = precision.into(); - if self.0 == 0 { - return 0; - } - let inc = if self.rem(precision) > 0 { 1 } else { 0 }; - self.0 / precision.multiplier() + inc - } - - pub fn floor(&self, precision: impl Into) -> u64 { - if self.0 == 0 { - return 0; - } - self.0 / precision.into().multiplier() - } - - pub fn rem(&self, precision: impl Into) -> u64 { - self.0 % precision.into().multiplier() - } - - pub fn saturating_add(&self, other: impl Into) -> Self { - self.0.saturating_add(other.into().0).into() - } - pub fn saturating_sub(&self, other: impl Into) -> Self { - self.0.saturating_sub(other.into().0).into() - } - - pub fn saturating_add_assign(&mut self, other: impl Into) { - *self = self.0.saturating_add(other.into().0).into(); - } - pub fn saturating_sub_assign(&mut self, other: impl Into) { - *self = self.0.saturating_sub(other.into().0).into(); - } -} - -impl Sum for Amount { - fn sum>(iter: I) -> Self { - iter.fold(Amount::ZERO, |sum, value| sum.saturating_add(value)) - } -} - -impl Sum for Amount { - fn sum>(iter: I) -> Self { - iter.fold(Amount::ZERO, |sum, value| sum.saturating_add(value)) - } -} - -#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default)] -#[repr(u8)] -#[derive(StrictType, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_RGB_CONTRACT, tags = repr, into_u8, try_from_u8)] -#[cfg_attr( - feature = "serde", - derive(Serialize, Deserialize), - serde(crate = "serde_crate", rename_all = "camelCase") -)] -pub enum Precision { - Indivisible = 0, - Deci = 1, - Centi = 2, - Milli = 3, - DeciMilli = 4, - CentiMilli = 5, - Micro = 6, - DeciMicro = 7, - #[default] - CentiMicro = 8, - Nano = 9, - DeciNano = 10, - CentiNano = 11, - Pico = 12, - DeciPico = 13, - CentiPico = 14, - Femto = 15, - DeciFemto = 16, - CentiFemto = 17, - Atto = 18, -} -impl StrictSerialize for Precision {} -impl StrictDeserialize for Precision {} - -impl Precision { - pub fn from_strict_val_unchecked(value: &StrictVal) -> Self { value.unwrap_enum() } - pub const fn decimals(self) -> u8 { self as u8 } - pub const fn decimals_u32(self) -> u32 { self as u8 as u32 } - - pub const fn multiplier(self) -> u64 { - match self { - Precision::Indivisible => 1, - Precision::Deci => 10, - Precision::Centi => 100, - Precision::Milli => 1000, - Precision::DeciMilli => 10_000, - Precision::CentiMilli => 100_000, - Precision::Micro => 1_000_000, - Precision::DeciMicro => 10_000_000, - Precision::CentiMicro => 100_000_000, - Precision::Nano => 1_000_000_000, - Precision::DeciNano => 10_000_000_000, - Precision::CentiNano => 100_000_000_000, - Precision::Pico => 1_000_000_000_000, - Precision::DeciPico => 10_000_000_000_000, - Precision::CentiPico => 100_000_000_000_000, - Precision::Femto => 1_000_000_000_000_000, - Precision::DeciFemto => 10_000_000_000_000_000, - Precision::CentiFemto => 100_000_000_000_000_000, - Precision::Atto => 1_000_000_000_000_000_000, - } - } - - pub fn unchecked_convert(self, amount: impl Into) -> Amount { - (amount.into() * self.multiplier()).into() - } - - pub fn checked_convert(self, amount: impl Into) -> Option { - amount - .into() - .checked_mul(self.multiplier()) - .map(Amount::from) - } - pub fn saturating_convert(self, amount: impl Into) -> Amount { - amount.into().saturating_mul(self.multiplier()).into() - } -} - -impl From for u16 { - fn from(value: Precision) -> Self { value as u8 as u16 } -} - -impl From for u32 { - fn from(value: Precision) -> Self { value as u8 as u32 } -} - -impl From for u64 { - fn from(value: Precision) -> Self { value as u8 as u64 } -} - -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] -pub struct CoinAmount { - pub int: u64, - pub fract: u64, - pub precision: Precision, -} - -impl CoinAmount { - pub fn with(value: impl Into, precision: impl Into) -> Self { - let precision = precision.into(); - let value = value.into(); - let (int, fract) = value.split(precision); - CoinAmount { - int, - fract, - precision, - } - } -} - -impl Display for CoinAmount { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut int = self.int.to_string(); - if f.alternate() { - int = int - .chars() - .rev() - .collect::() - .as_bytes() - .chunks(3) - .map(<[u8]>::to_owned) - .map(|mut chunk| unsafe { - chunk.reverse(); - String::from_utf8_unchecked(chunk) - }) - .rev() - .collect::>() - .join("`"); - } - f.write_str(&int)?; - if self.fract > 0 { - f.write_char('.')?; - let mut float = self.fract.to_string(); - let len = float.len(); - if let Some(decimals) = f.precision() { - float.push_str(&"0".repeat(decimals - len)); - } - if f.alternate() { - float = float - .as_bytes() - .chunks(3) - .map(|chunk| unsafe { String::from_utf8_unchecked(chunk.to_owned()) }) - .collect::>() - .join("`"); - } - f.write_str(&float)?; - } - Ok(()) - } -} - #[derive(Wrapper, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, From)] #[wrapper(Deref, Display)] #[derive(StrictDumb, StrictType, StrictDecode)] @@ -774,18 +516,3 @@ impl ContractData { Self { terms, media } } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - #[allow(clippy::inconsistent_digit_grouping)] - fn coin_amount() { - let amount = CoinAmount::with(10_000_436_081_95u64, Precision::default()); - assert_eq!(amount.int, 10_000); - assert_eq!(amount.fract, 436_081_95); - assert_eq!(format!("{amount}"), "10000.43608195"); - assert_eq!(format!("{amount:#.10}"), "10`000.436`081`950`0"); - } -} diff --git a/src/stl/stl.rs b/src/stl/stl.rs index 8fbc5200..b5b98c89 100644 --- a/src/stl/stl.rs +++ b/src/stl/stl.rs @@ -22,14 +22,15 @@ use bp::bc::stl::bp_tx_stl; use bp::stl::bp_core_stl; use commit_verify::stl::commit_verify_stl; +use invoice::Amount; pub use rgb::stl::{aluvm_stl, rgb_core_stl, LIB_ID_RGB}; use strict_types::stl::{std_stl, strict_types_stl}; use strict_types::typesys::SystemBuilder; use strict_types::{CompileError, LibBuilder, SemId, SymbolicSys, TypeLib, TypeSystem}; use super::{ - Amount, BurnMeta, ContractData, DivisibleAssetSpec, Error, IssueMeta, MediaType, - RicardianContract, Timestamp, LIB_NAME_RGB_CONTRACT, + BurnMeta, ContractData, DivisibleAssetSpec, Error, IssueMeta, MediaType, RicardianContract, + Timestamp, LIB_NAME_RGB_CONTRACT, }; use crate::containers::{Contract, Transfer}; use crate::persistence::Stock; @@ -43,7 +44,7 @@ pub const LIB_ID_RGB_CONTRACT: &str = /// Strict types id for the library representing of RGB StdLib data types. pub const LIB_ID_RGB_STD: &str = - "urn:ubideco:stl:5BWvsBkteVgD3yiGy8gHHuzhHFLQTz4mZU7kJob3JkHW#lazarus-ranger-neutral"; + "urn:ubideco:stl:2N7iehth1MBA1yQaJebjMxw45x9fGYAMNqrmGU3pxwvU#tribal-modem-andy"; fn _rgb_std_stl() -> Result { LibBuilder::new(libname!(LIB_NAME_RGB_STD), tiny_bset! { diff --git a/stl/RGBStd@0.1.0.sta b/stl/RGBStd@0.1.0.sta index bb2ca86e..80aae0ca 100644 --- a/stl/RGBStd@0.1.0.sta +++ b/stl/RGBStd@0.1.0.sta @@ -1,428 +1,432 @@ -----BEGIN STRICT TYPE LIB----- -Id: urn:ubideco:stl:5BWvsBkteVgD3yiGy8gHHuzhHFLQTz4mZU7kJob3JkHW +Id: urn:ubideco:stl:E3LrF7ryeC9J8rABKrs1ceRg2skzsjaWVFk9xbi1x56 Name: RGBStd Dependencies: - urn:ubideco:stl:5dLgsvZEbhQekV5j7ghFCTyu3JPZ1B99SqhqPEA4Fdc, urn:ubideco:stl:ZtHaBzu9ojbDahaGKEXe5v9DfSDxLERbLkEB23R6Q6V, + urn:ubideco:stl:5Nyd3BhfDAEfc5th2httogefZGzMftfcJNh5Lo9guuTx, urn:ubideco:stl:5XLKQ1sNryZm9bdFKU2kBY3MPYdZXhchVdQKBbHA3gby, urn:ubideco:stl:9KALDYR8Nyjq4FdMW6kYoL7vdkWnqPqNuFnmE9qHpNjZ, + urn:ubideco:stl:CjHnUSMpAUVxemMGtj8hJtcCssguGxDghjBzfLrUfiMW, urn:ubideco:stl:DVtm25LRKU4TjbyZmVxPhvCmctZ6vKkPKqfpU2QsDNUo, - urn:ubideco:stl:E4xYrMV4cHgYhTPzrjG3DxzAyLEMnbvnsrBsGhQP4anV, urn:ubideco:stl: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 -WU8aGVR5cGVkQXNzaWduc0JsaW5kU2VhbFR4aWRFKqVffdYBSouhbcRmMrYP8bVs +u0QljDtn9MzCfv785Ce3z14P/YGPL3572HwPVGFwcmV0UGF0aFByb29mFR3JjQEh +eOeO8fTFXOuL6OYzNINEWeLCo1zuLu29MU0cQW5jaG9yTWVya2xlUHJvb2ZUYXBy +ZXRQcm9vZijHiXbarlBwL837e7JgaZjrSMSsJrQq/Ldm9F65/tmGG0FuY2hvck1l +cmtsZUJsb2NrT3ByZXRQcm9vZjg/Yi5xU9LIIZE8y3cdnz1k33byKFVZLfhGQ5QW +TW0FEVRhcHJldFJpZ2h0QnJhbmNoR07PXNDoTD546vs8PljsuFnNdzezZ2QEah4T +Sps4O5cKT3ByZXRQcm9vZliFjn0FklftBQ1ItWb7COWKDt2qsw4Woe8YrmOZBE+y +DEV4cGxpY2l0U2VhbGSc57Qkj6v2AGTDqFknAKSA/5t+BvfUzoxBZGDxF449G0Fu +Y2hvck1lcmtsZVByb29mT3ByZXRQcm9vZmgZ67zVsxirl7OYpUs2Zd3apwZv6Okk +5wNgqZSzvQZOClNlY3JldFNlYWx+tfgzfJGqb7i9lbu7y/XhxSWJRdIRdtoe1NyM +xTElZQ5CbGluZFNlYWxUeFB0crHlODkUCji+8G8az74cYKVv4eH0fXgIKHm/0frT +ECHdBVR4UHRy0lIwfH1xkDX3MH7oKCXsG4EroYfdnZhJi0qNFvpu1UMLQ2xvc2VN +ZXRob2T6K/hG4yCelnDaFH8ciIXsc/7rPweb6v/QYnVP9Sx1PhxBbmNob3JNZXJr +bGVCbG9ja1RhcHJldFByb29mB0JpdGNvaW4UAAEZbYUpyPbaRE0VTo2cjL2eD42J +Alo7sJEhxj1SsB6HBVNlcU5vChmQ11PyCpVmHxM79SNKJY/+S8jdCSekG+QEvY6i +RqoEVHhJbiHjPkPFqlzyKSdTozjBZ+07Y5xN2c69qY80aRe6yUN1BFZvdXQlq/W5 +EhQXuqM51F97FXWncUvhenUYXdAxNXrUyS3phgtTY3JpcHRCeXRlczG7ruiEiXfF +sAcxcjHNkvYwtiktzYl/3LrexbrE6X/ZC1RhcE5vZGVIYXNoNdodFTkgbrvd3KTD +PYcx6vKbp9p03z3IgiAcTha1uRwITG9ja1RpbWU4dQSxS3wORm1HnhdHfSR0JH/4 +A2TsPUuq9zog90F0awlTaWdTY3JpcHRfrNlt5T9OxRTZsY1ngNoKq+stJgv9Ny2j +wHqkXTmfTQpMZWFmU2NyaXB0kDtkcHmEjxsmUyrkzsamiUSgU1i48IHLJrO7+C2e +O/MFVHhPdXSX9dcGQq4qM5IlO6hOHc7Ek+O/PtNxsYdzhjVOzIiCawRTYXRznVWT +rLr5zeCbOEb4gwlx0zjJL251vHzjIEWeqzgZph4HV2l0bmVzc6OCQvPL19HQoRLa +jeFgL1bU+G8OxMR2xcBoWUxLBGVWBFR4aWSofMZ5ZGflV9dE5cliOuAMy5XZGg0v +A990wHUST77HngVUeFZlcqmFhHdTnmj7sL/ux5ohxFdDwVT4+r0/mODTkT3ntzd4 +CkludGVybmFsUGu2MwmpGL+2kg1uyFikJIcjRlppP3JDB7//Hdc6c4FOyQdMZWFm +VmVyvvwe/GalGf1kUmo6E4dA5/EQLQu+/zrBRk7z90B2dFwMU2NyaXB0UHVia2V5 +xHJrj48o0XrBl8gdjg2hm2JEnObQZF3+x+l4PLWH8aMHQnl0ZVN0csV7IZq/9zlu +ckaArUVWL3zsoWxuPybd3P/8fmwD5txEAlR46GpAzVwl+b3ihP9ppREyp0ErIVW9 +DlYSwb/jhJVgxQYIT3V0cG9pbnT8oqcqvpH+zYKosZiQYyLCTnaqqjXyJNFzBWOg +GC6IWQdYT25seVBrDENvbW1pdFZlcmlmeQYAL+7PHkTSoSm5ihQ4R0s5cZUrKByi +AZLVEyb4sjZgXmcLTWVya2xlUHJvb2YwlX7mHWE0WbsFCOThrz1qIb/W+/sdMYKn +eeVFdOBWsQpQcm90b2NvbElkNTepURXI6oXcUvergfJxZVEFJlOUQleviRPoe9Sb +sdMHTWVzc2FnZVNDlQ9EMyoj/+iAz1DnzwgGoQTN4EhrE+EM/BiDQ7DXCFRyZWVO +b2RlVY03B/hFhlOA7sxBVSTopJlgUdOUgkPxlPfxkVcj6eYKTWVya2xlTm9kZcSj +ey0sUm61SVrV2YoViLxzSewBWsX1CXanVe6cuw9UC01lcmtsZUJsb2NrA1JHQlAA +APuWr//RTDiPJjNA6aCvmWzkE3240iW/UCHWIiLtb7gGQW5jaG9yA3uTtT0ahS4H +WQEld822QfAIrU7nAFhhg7XASr3gAlwIQnVuZGxlSWQHi/pv0wJGc5Kt0sDgjzjA +RWXkeLjk1LWUd2NriZyNBhJYY2hhaW5FeHBsaWNpdFNlYWwOL75mFCKjObsFQvYi +k9Cifpwx8YL+trq2rNsVdPBiVhxBc3NpZ25Wb2lkU3RhdGVCbGluZFNlYWxUeGlk +Dr+47ThibqSDujTzFPlUdelW2Uc1E9wnGBY8Y7bhibEKV2l0bmVzc1BvcxBBgnI1 +FxzZgpWSppkX/gNxP61v4tMhTojB/hyjpwqdDFNjaGVtYVNjaGVtYRLQH8MrONyi +51CWKddlonO4k/c2DpdBhPJhuFHeAlDnGlR5cGVkQXNzaWduc0JsaW5kU2VhbFR4 +UHRyGRt4iNydXxqK7p30bML6JXBBoLxpNlGnkO1IBrtpHi8JRXh0ZW5zaW9uGeEu +t1S5LtEpSl8f9bXdzccAzPP243s8SaHE2ntZ9N0OQW5jaG9yZWRCdW5kbGUedhfx +J33bPrvhag9yEbdt7VXfb0MNVRFfA3gnpUJXJwlOb2lzZUR1bWIg8lBWIo9mzvyR ++upnvF/G8GlcPUd5c1k/rNE3ynJIZQxSZXZlYWxlZERhdGEkV1LYZYDwnMpqIzdU +EkQgYHyjLH/uzPEUEABvCgkqjAxBbHRMYXllcjFTZXQkvLMZiKNHP+ioRwLJfnX8 +PwOt6aqth5QAMFBriWteyhRYY2hhaW5CbGluZFNlYWxUeFB0cimWfZ6uFySumdvw +v6f63vlGYuk9lo2f/N6/OX2V7Z0BIkFzc2lnblJldmVhbGVkQXR0YWNoQmxpbmRT +ZWFsVHhQdHIusqX+F8AwRDCY4FybG1ps1h7tdg1h3suAJtNk8xbhuwlWb2lkU3Rh +dGUwgEw6iTEH29grmqM+pjbEpOsNCUKUYovL9AfXtL+NjQlBbmNob3JTZXQ0Ug+u +E5YaXr0p/BEjP4VO8hA4BH/UBL7foUbZFqUyaA5UcmFuc2l0aW9uVHlwZTSA6BeX +QJfNJZ/lR9VvfV/nu0mPz7Y8Ai8GDAEvWZmaB0dlbmVzaXM2wTSh+qCBE6fdMIA8 +XrDywv3aFLDVo9aTw26eDzKqeQtPY2N1cnJlbmNlczjKFOCFIsfjOYJGlLLDmVh1 +U6boygwO4eiVibqJdxvzC1N0YXRlU2NoZW1hPFLnrq+zlEL19wGQ1AoMvppMckAB +/YH3oYJqXfsOYJ0PQ29udHJhY3RIaXN0b3J5Pb9W0u6NtDDqL2vzu2puscG5UUq8 +OWWndY+AWAecj7oGU2NoZW1hP6Xk/XbnoyVMKK58A3Bboboij3LYxcT6MOGKHPKw +SRogQXNzaWduUmV2ZWFsZWRWYWx1ZUJsaW5kU2VhbFR4aWRCMGGFiMjUqxQmQMf9 +yRcszdD/EP8Nk4AARHyImt3MeQlNZWRpYVR5cGVFKqVffdYBSouhbcRmMrYP8bVs 3DpTLs+9a5PVZxmeiQxSZXNlcnZlZEJ5dGVGNH2lHu1oDF77by+mxG/p2cNS74mO KbKURqaNxqBepgxHbG9iYWxWYWx1ZXNG7ebDCBz9uOZXpCpc4MYIhH/8H75edrlx dKnK9YlZzgtWYWxlbmN5VHlwZUi9Gm4X+4Y7Fnx+JV41Z9uCQ+8qXrrrosUKzQmu -nlEaElBlZGVyc2VuQ29tbWl0bWVudEwOZhJWYVmGKwGopFLSjxSghbpucBuoQD3M -Yg/idq3oHFNlYWxEZWZpbml0aW9uQmxpbmRTZWFsVHhQdHJRcFb/JTa0M+NTgQaX -gPTuDQo5vskjWNzPydduxPGH2QZPdXRwdXRZolHNjebLiSRE7c91W7GqpdwcP9rB -NM1sJTbV9hOKwiBBc3NpZ25SZXZlYWxlZERhdGFCbGluZFNlYWxUeFB0clngYcQu -Y9cDm6Ud134roaFso8WFSGSXMKERWZKgIcxUDkFuY2hvcmVkQnVuZGxlZHUeQqkV -oTxDEYLV/4bVHNNEcKOQ4UrsoFDMOlNvSN4NRXh0ZW5zaW9uVHlwZWa0l4SPxHk5 -YN80kut2EpCzDqwQ0T03VC1SZBEIlFBxD0V4dGVuc2lvblNjaGVtYWhTNCAM3FPG -TXbiti6qZi/aOtmRvwarKQ680PZ6A0rMDlJldmVhbGVkQXR0YWNobUTG9C9qBTpD -FQ+m5sIsxOh65SyU+AbUDKXch/Z1jaAQUmV2ZWFsZWRGdW5naWJsZXANZRCygoFv -H7c95RJjkwNXCKVSYa0C4NS+WsXPp+oJDUNvbmNlYWxlZERhdGF52xr8CB6hdXgQ -w8hr7HE9twtbmdr9g/hGMltC0aMcWR5PdXRwdXRBc3NpZ25tZW50UmV2ZWFsZWRB -dHRhY2h69XkNjG4gtH3wH/XNhWn1y7zxEMGvZWy1kKKWtKv/AQlHbG9iYWxPcmSD -+kzgrluOjxrgLhTubmJilfVuAJtCDQ3jB/UybgLsOgdHZW5lc2lzhHENkyxO9MO3 -CEtpi7CHcCl+OWQkf0WR2NqDbdF9ujgIQXR0YWNoSWSFuPgru/Skpg2zvz9FuA+U -bniDw61SbZP0b6MBqG5H2g5CbGluZGluZ0ZhY3Rvcof+4mVYiGzoHL6GhLN5YycT -ZYPFtmgBXosUFjaxRIe5DkFzc2lnbm1lbnRUeXBlkxC8gLE0Wosvw1hS7g9NaNAd -t/o1y5tkkqtWCZr0mpcFT3BvdXSUUtPbA6urqFGfp/Y+0BTr1E19MT/8/gD6XSR6 -VASQEAhTY2hlbWFJZJXI5noedWJf1JZVQmqR635CkKFvWpjxvlD3tookEvfFBE9w -SWSY5gjVKbw/MM5PTKoArCznR/7POkLscrIKgsC+AhMCiRxBc3NpZ25Wb2lkU3Rh -dGVCbGluZFNlYWxUeGlkmZSlir0dPFRFLym65UnNAJfnIJSUv2dHX0hqyiz4Vhch -QXNzaWduUmV2ZWFsZWRWYWx1ZUJsaW5kU2VhbFR4UHRynwgsSTrIAqK6xd3cCyJ8 -IK+U1GjESM8aWiHgvcL1OjIKQ29udHJhY3RJZKIzyegoTsx1mPwGOec00MsCjEss -3ISRPpnZqkY+JNZSC0dsb2JhbFN0YXRlovrqnnBcnJHM291G7Y9w5Y71FIM+yD5c -ZLVqW8NTrbAJQWx1U2NyaXB0pabUe56ieBkXbm3T8ilmk3bA/g7JVbTE642QUrgU -LJ8KQnVuZGxlSXRlbaaMMJFHS8o6wmKMx5VEjSzdqsUUnwUzlav2PFVhBxcmDUZ1 -bmdpYmxlU3RhdGWoWGv4kWXawiMQbb2FxIbJN+awZusMZkH/Fi9oqHelmApSYW5n -ZVByb29mq0L/CsSQakUQ+FRfBiQqTQmMkFVYs9PbNyxwjFngTEMNR2VuZXNpc1Nj -aGVtYbGE2FqNX4O/S8m9j3uvw5TT6slgbWmmash4pOYVT7MiEUFuY2hvck1lcmts -ZUJsb2NrtRWURiX16DrteJt7itR6DTKoivrLIO0ESp8iRPogQ70YQXNzaWdubWVu -dHNCbGluZFNlYWxUeGlkvcdveMo95d9+PKqU9FGUnC0VNYeAXXvK6KRai0nxAX8Q -VHJhbnNpdGlvblNjaGVtYcIe7NwA077i648Cm3I6+7EQwDaX6c8DaBmUFaYEB2nK -CldpdG5lc3NPcmTCUa1l6XUNrJoSWczAhSRc1fexb1LYcjZGLPF1jJ9OXRFDb25j -ZWFsZWRGdW5naWJsZcSgCp7hCQITdyIBFVk7g8NT4mD4gRDkszbK42hGQScbCVdp -dG5lc3NJZMYYY3tnTQy0vKnAQ11/MmKDmHhzdCdD0TflRPu6EtBMBlNjcmlwdMeY -pthjNnhEHtpRbiw+i78OqLBKgMG3HbnpcuY/ceYkEUdsb2JhbFN0YXRlU2NoZW1h -yGuCewtafcaRBCRy5SInj9DPxRpXaKBLP6oxQBs8fiMJQWx0TGF5ZXIxyUJCIu0C -vkdp/U8jHbNFTqcovEOoEQ7bM8uPLwqeSEYFSW5wdXTJj5qpwwZLGv39ZxuXvCr8 -/kxojx9zyC3rcW/naZsirwhBc3NldFRhZ9Dt3q+bZlQ2vSmW24KeCQCmg7JWb0Ky -ZfKw+0WI+q8RGU91dHB1dEFzc2lnbm1lbnRWb2lkU3RhdGXV7pIOSYizafFqU9EH -Svu1I/jHUnEe+zY9VlkF4eQVyw9HbG9iYWxTdGF0ZVR5cGXYY93FeLsPNcGN8j2e -uhtJkH0Sl+eDrKaLkdAx6PyXYxxPdXRwdXRBc3NpZ25tZW50UmV2ZWFsZWREYXRh -2ptRE1gWVnaQh/uZ5VaUcjaA1zkMBqHMJJgsBWT3zNUDRmZ23YVmAG9hZBEU7o7x -16r4CbMaJLCqJ6mbsjDoqs8pR00JVmFsZW5jaWVz38+pkfWH5U1EtwEVXAAp/JVr -m/HNqUGOYziWqsAqg+gIUmVkZWVtZWTi8tuzmTSI9GNgcXARevZlWteS9+UM2b3V -yT4M4euxIBlBc3NpZ25tZW50c0JsaW5kU2VhbFR4UHRy6ta3unkK4FGpfiwZ+Pwh -nMW+Kp3v3/3VY6uveGsbggsNV2l0bmVzc0FuY2hvcvl4TaC2Q945fB7ZV40zjDfR -HMviSsHop5pM5NX8GCerBklucHV0c/n0rAhmrkF3ZtT9DBF9BLHZVP0OZ14SO2IE -63FP6eVGDEZ1bmdpYmxlVHlwZfvMF3YVORemNKbhvuAe3SvcYr+viVrldTly/ypB -l9iAIkFzc2lnblJldmVhbGVkQXR0YWNoQmxpbmRTZWFsVHhQdHL8NEXdX88NC/+s -FaR6ugUi4FuLKxswZVKHg497LeuOPQ9Db25jZWFsZWRBdHRhY2gDU3RkBAAUTa7I -MubEN5J7JV65m0KKh9kNUKjM0E1yEbW3vF9mzgVBc2NpaSLk4JbpvX1chvXh3113 -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 -ZVR5cGUGAwJpZAIBL2mRQiac2OigKTkCtQVKSqM22KVvhTyl/rTVh83K69Xukg5J -iLNp8WpT0QdK+7Uj+MdScR77Nj1WWQXh5BXLBG5hbWUCQzQDlNgbMOJSKJAmHvNv -+fioOVGR9QtpXiMqHrO3QcioFOwosO1V7e6uUXmk5+WfQLP5VRYQbpvBLnIav35W -HAhyZXNlcnZlZAE/lzoyi5jGAv8mGZ9GrdG1kGcEEmztSL2E2VyoCQ3RzBVOYW1l -ZEZpZWxkVmFsZW5jeVR5cGUGAwJpZAIBL2mRQiac2OigKTkCtQVKSqM22KVvhTyl -/rTVh83K60bt5sMIHP245lekKlzgxgiEf/wfvl52uXF0qcr1iVnOBG5hbWUCQzQD -lNgbMOJSKJAmHvNv+fioOVGR9QtpXiMqHrO3QcioFOwosO1V7e6uUXmk5+WfQLP5 -VRYQbpvBLnIav35WHAhyZXNlcnZlZAE/lzoyi5jGAv8mGZ9GrdG1kGcEEmztSL2E -2VyoCQ3RzBdOYW1lZFR5cGVUcmFuc2l0aW9uVHlwZQYDAmlkAgEvaZFCJpzY6KAp -OQK1BUpKozbYpW+FPKX+tNWHzcrrNFIPrhOWGl69KfwRIz+FTvIQOAR/1AS+36FG -2RalMmgEbmFtZQJDNAOU2Bsw4lIokCYe82/5+Kg5UZH1C2leIyoes7dByA0UI2Xy -pK/GG1Nt1GdVcmYrsHKjkA/JiwBmitgxgOKwCHJlc2VydmVkAT+XOjKLmMYC/yYZ -n0at0bWQZwQSbO1IvYTZXKgJDdHMCk93bmVkSWZhY2UEBgADYW55AAAAAQZyaWdo -dHMAAAACBmFtb3VudAAAAAMHYW55RGF0YQAAAAQJYW55QXR0YWNoAAAABQRkYXRh +nlEaElBlZGVyc2VuQ29tbWl0bWVudFK/rBG0XaXChTXqDcbShj4Hjo1oQYhGUhOn +eUb9oDn2GUFzc2lnbm1lbnRzQmxpbmRTZWFsVHhQdHJfVlljb8tZ19U4VCAUF1/4 +byMGQCaxJ9JdslGChgurrBhBc3NpZ25tZW50c0JsaW5kU2VhbFR4aWRkdR5CqRWh +PEMRgtX/htUc00Rwo5DhSuygUMw6U29I3g1FeHRlbnNpb25UeXBlZrSXhI/EeTlg +3zSS63YSkLMOrBDRPTdULVJkEQiUUHEPRXh0ZW5zaW9uU2NoZW1haFM0IAzcU8ZN +duK2LqpmL9o62ZG/BqspDrzQ9noDSswOUmV2ZWFsZWRBdHRhY2htRMb0L2oFOkMV +D6bmwizE6HrlLJT4BtQMpdyH9nWNoBBSZXZlYWxlZEZ1bmdpYmxlcA1lELKCgW8f +tz3lEmOTA1cIpVJhrQLg1L5axc+n6gkNQ29uY2VhbGVkRGF0YXr1eQ2MbiC0ffAf +9c2FafXLvPEQwa9lbLWQopa0q/8BCUdsb2JhbE9yZIRxDZMsTvTDtwhLaYuwh3Ap +fjlkJH9Fkdjag23Rfbo4CEF0dGFjaElkhZvtqANQXFYRvbbnJfZzRFupMPkndqJn +j7pb75O6IPEUQW5jaG9yU2V0TWVya2xlQmxvY2uFuPgru/Skpg2zvz9FuA+UbniD +w61SbZP0b6MBqG5H2g5CbGluZGluZ0ZhY3Rvcof+4mVYiGzoHL6GhLN5YycTZYPF +tmgBXosUFjaxRIe5DkFzc2lnbm1lbnRUeXBljMHTahxhpKx5E26j32H/KIqvL6Cx +lU3Fb9Sp8fdZP1sZVHlwZWRBc3NpZ25zQmxpbmRTZWFsVHhpZJMQvICxNFqLL8NY +Uu4PTWjQHbf6NcubZJKrVgma9JqXBU9wb3V0lFLT2wOrq6hRn6f2PtAU69RNfTE/ +/P4A+l0kelQEkBAIU2NoZW1hSWSVyOZ6HnViX9SWVUJqket+QpChb1qY8b5Q97aK +JBL3xQRPcElknwgsSTrIAqK6xd3cCyJ8IK+U1GjESM8aWiHgvcL1OjIKQ29udHJh +Y3RJZKIzyegoTsx1mPwGOec00MsCjEss3ISRPpnZqkY+JNZSC0dsb2JhbFN0YXRl +ovrqnnBcnJHM291G7Y9w5Y71FIM+yD5cZLVqW8NTrbAJQWx1U2NyaXB0powwkUdL +yjrCYozHlUSNLN2qxRSfBTOVq/Y8VWEHFyYNRnVuZ2libGVTdGF0ZahYa/iRZdrC +IxBtvYXEhsk35rBm6wxmQf8WL2iod6WYClJhbmdlUHJvb2arQv8KxJBqRRD4VF8G +JCpNCYyQVViz09s3LHCMWeBMQw1HZW5lc2lzU2NoZW1huG3OmybYc+Hqt2pqvyux +lcm1PtVUD9Go2wNHNQT3ZWgdT3V0cHV0QXNzaWdubWVudFJldmVhbGVkVmFsdWW9 +x294yj3l3348qpT0UZScLRU1h4Bde8ropFqLSfEBfxBUcmFuc2l0aW9uU2NoZW1h +vrXFo5ePt+xKFxmo8wlgn/amZxJo/dVI5RMfKFB9yFcKVHJhbnNpdGlvbsDiCi/T +yDEa1of6l+qO6dsgkyFvnL2jXCc6qC4zxhzPE1hjaGFpbkJsaW5kU2VhbFR4aWTC +HuzcANO+4uuPAptyOvuxEMA2l+nPA2gZlBWmBAdpygpXaXRuZXNzT3JkwlGtZel1 +DayaElnMwIUkXNX3sW9S2HI2RizxdYyfTl0RQ29uY2VhbGVkRnVuZ2libGXEoAqe +4QkCE3ciARVZO4PDU+Jg+IEQ5LM2yuNoRkEnGwlXaXRuZXNzSWTGGGN7Z00MtLyp +wENdfzJig5h4c3QnQ9E35UT7uhLQTAZTY3JpcHTHmKbYYzZ4RB7aUW4sPou/Dqiw +SoDBtx256XLmP3HmJBFHbG9iYWxTdGF0ZVNjaGVtYchrgnsLWn3GkQQkcuUiJ4/Q +z8UaV2igSz+qMUAbPH4jCUFsdExheWVyMclCQiLtAr5Haf1PIx2zRU6nKLxDqBEO +2zPLjy8KnkhGBUlucHV0yY+aqcMGSxr9/Wcbl7wq/P5MaI8fc8gt63Fv52mbIq8I +QXNzZXRUYWfTDkQGOE+PEd0dAVwNyEeSFs6ElJS07hLnI4wm1s2X0hxPdXRwdXRB +c3NpZ25tZW50UmV2ZWFsZWREYXRh1a9+uW/fOXBOxLEZSIH6ZzopccDAQWLXok70 +CqaQn/cQVHJhbnNpdGlvbkJ1bmRsZdXukg5JiLNp8WpT0QdK+7Uj+MdScR77Nj1W +WQXh5BXLD0dsb2JhbFN0YXRlVHlwZdqbURNYFlZ2kIf7meVWlHI2gNc5DAahzCSY +LAVk98zVA0Zmdt2FZgBvYWQRFO6O8deq+AmzGiSwqiepm7Iw6KrPKUdNCVZhbGVu +Y2llc9/PqZH1h+VNRLcBFVwAKfyVa5vxzalBjmM4lqrAKoPoCFJlZGVlbWVk4VJZ +7SCn5tO9rYWViW+y78lyU2N4jndHTzX4yr9LTgsZT3V0cHV0QXNzaWdubWVudFZv +aWRTdGF0ZeLEXUw5ZnoaK3B8KU2kyaZqt4FgMJavA3pDbbik/KhLIEFzc2lnblJl +dmVhbGVkRGF0YUJsaW5kU2VhbFR4UHRy6ta3unkK4FGpfiwZ+PwhnMW+Kp3v3/3V +Y6uveGsbggsNV2l0bmVzc0FuY2hvcusK1Q6G0rhauKzgdMGSqgLjllvz2Y/z+RPc +mPa0aCMvEUFuY2hvck1lcmtsZUJsb2Nr68+ws5pDFpcjx6rZxoXkIZKoOmYrA+RZ +XbO75vJsClofQXNzaWduUmV2ZWFsZWREYXRhQmxpbmRTZWFsVHhpZOw477864Hht +6UnVUwn/0qR0gubXEp2RHywnW4fZdZ1HIUFzc2lnblJldmVhbGVkVmFsdWVCbGlu +ZFNlYWxUeFB0cu4M9u9zC7cjHsoUn8tDfBBY1axXJI2oUIa4kQ2ST7vYHUFzc2ln +blZvaWRTdGF0ZUJsaW5kU2VhbFR4UHRy9AtS0yw99TbesWzdVnQ/GEXS+93I8jsV +uf5QkWuebMEeT3V0cHV0QXNzaWdubWVudFJldmVhbGVkQXR0YWNo+XhNoLZD3jl8 +HtlXjTOMN9Ecy+JKweinmkzk1fwYJ6sGSW5wdXRz+fSsCGauQXdm1P0MEX0EsdlU +/Q5nXhI7YgTrcU/p5UYMRnVuZ2libGVUeXBl/BFPWxxNmu5c38QFiv/3oKNdkudg +7RgZmD9xLwolBXwhQXNzaWduUmV2ZWFsZWRBdHRhY2hCbGluZFNlYWxUeGlk/DRF +3V/PDQv/rBWkeroFIuBbiysbMGVSh4OPey3rjj0PQ29uY2VhbGVkQXR0YWNoA1N0 +ZAQAFE2uyDLmxDeSeyVeuZtCiofZDVCozNBNchG1t7xfZs4FQXNjaWki5OCW6b19 +XIb14d9ddwFq/jnHPNk0hVCUQImMqKN5XgJVNWGGItF7rvBmAt/ndcmA4LNrbrro +CQ2AdfdRO+xLk/ZNBEJvb2xyjqaKl950IPYqWWmwS4cmBL9F1t84lZx+JuenJJDk +rQ5BbHBoYU51bUxvZGFzaAtTdHJpY3RUeXBlcxIADRQjZfKkr8YbU23UZ1VyZiuw +cqOQD8mLAGaK2DGA4rAIVHlwZU5hbWUkY9q/fErx6pEg77+AwseJoVeS1PH7oyxY +mCOxD2kh+glQcmltaXRpdmUo1blYUNwtVYzQKCXTkW7dbtzyFz3KQHmB1yG2/wQH +WwdWYXJpYW50LkdbPnPJ4CJtjiwnuhSzqiW+bSj3R34s/YxNKF4Y7FYKVHlwZVN5 +c3RlbTGfVIaRN3Rj/O5fZHuG+8x7OeOMsSSRNp1PwyPORVOUEFZhcmlhbnRJbmZv +U2VtSWQ9/s716l6MmUhz2/wjcUiNlzREfgaORQGYjIrieWsJ4RJVbmlvblZhcmlh +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 -5oKlL64K10VGfM6thHnZWoTFo76MzQdoaXN0b3J5AAoCAS9pkUImnNjooCk5ArUF -SkqjNtilb4U8pf601YfNyuufCCxJOsgCorrF3dwLInwgr5TUaMRIzxpaIeC9wvU6 -MgIBL2mRQiac2OigKTkCtQVKSqM22KVvhTyl/rTVh83K6w33LL6KL2JDud+var7z -SUU5W4Xj1HG4kj2s3RQW/2+8AAAAAAAAAAD/AAAAAAAAAA1idW5kbGVPcEluZGV4 -AAoCAS9pkUImnNjooCk5ArUFSkqjNtilb4U8pf601YfNyuuVyOZ6HnViX9SWVUJq -ket+QpChb1qY8b5Q97aKJBL3xQHg4829hvZVZ6Z0tj+BcPH41Jc5akEWEqLcqmFS -t+/EJQAAAAAAAAAA////AAAAAAARYW5jaG9yQnVuZGxlSW5kZXgACgIBL2mRQiac -2OigKTkCtQVKSqM22KVvhTyl/rTVh83K6wN7k7U9GoUuB1kBJXfNtkHwCK1O5wBY -YYO1wEq94AJcAsIre2rJPE6lpvv6/FqJcOpuDjlXp/gPPUemc5UoBqyq9aKMORS3 -m+lXdDRSUor88iwX2yNTTVAzwhUGWTBduFcAAAAAAAAAAP///wAAAAAADWNvbnRy -YWN0SW5kZXgACgIBL2mRQiac2OigKTkCtQVKSqM22KVvhTyl/rTVh83K658ILEk6 -yAKiusXd3AsifCCvlNRoxEjPGloh4L3C9ToyAaqlJbnC3NLVb/EtivqaGABnCXAd -o1XE/CclpYW32VAPAAAAAAAAAAD/AAAAAAAAAA10ZXJtaW5hbEluZGV4AAoCwit7 -ask8TqWm+/r8Wolw6m4OOVen+A89R6ZzlSgGrKpoGeu81bMYq5ezmKVLNmXd2qcG -b+jpJOcDYKmUs70GTgIBL2mRQiac2OigKTkCtQVKSqM22KVvhTyl/rTVh83K65MQ -vICxNFqLL8NYUu4PTWjQHbf6NcubZJKrVgma9JqXAAAAAAAAAAD///8AAAAAAAtz -ZWFsU2VjcmV0cwAJAgEvaZFCJpzY6KApOQK1BUpKozbYpW+FPKX+tNWHzcrrTA5m -ElZhWYYrAaikUtKPFKCFum5wG6hAPcxiD+J2regAAAAAAAAAAP///wAAAAAAB1N1 -cHBsSWQFAQAHAABAIAAIVGVybWluYWwGAgVzZWFscwAJAfKBrNHOWmzs0Ji0/qFl -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 -AQJDNAOU2Bsw4lIokCYe82/5+Kg5UZH1C2leIyoes7dByKgU7Ciw7VXt7q5ReaTn -5Z9As/lVFhBum8Euchq/flYcDFZhbGVuY3lJZmFjZQYCCHJlcXVpcmVkAnuEgDye -+uIRJad8LDm8cNL96PlDrg39nPTmgu3HZspwYYYi0Xuu8GYC3+d1yYDgs2tuuugJ -DYB191E77EuT9k0IbXVsdGlwbGUCe4SAPJ764hElp3wsObxw0v3o+UOuDf2c9OaC -7cdmynBhhiLRe67wZgLf53XJgOCza2666AkNgHX3UTvsS5P2TQxWZWxvY2l0eUhp -bnQDBgt1bnNwZWNpZmllZAAGc2VsZG9tDwhlcGlzb2RpYx8HcmVndWxhcj8IZnJl -cXVlbnR/DWhpZ2hGcmVxdWVuY3n/BVZlck5vAwECdjEACFZvdXRTZWFsBgMGbWV0 -aG9kAsIre2rJPE6lpvv6/FqJcOpuDjlXp/gPPUemc5UoBqyq0lIwfH1xkDX3MH7o -KCXsG4EroYfdnZhJi0qNFvpu1UMEdm91dAL1bBNiI/Y5p0oJk9xHRsn5iqu4g1hd -tdkWPxh+xCgaCiHjPkPFqlzyKSdTozjBZ+07Y5xN2c69qY80aRe6yUN1CGJsaW5k -aW5nAAAI +i0S1vsaJnIwSRgAlLBsWcK95KHCTAAAAAAAAAAD/AAAAAAAAAAZlcnJvcnMACQAA +AQAAAAAAAAAA/wAAAAAAAAARZGVmYXVsdEFzc2lnbm1lbnQABAIABG5vbmUAAAAB +BHNvbWUABQECQzQDlNgbMOJSKJAmHvNv+fioOVGR9QtpXiMqHrO3QcioFOwosO1V +7e6uUXmk5+WfQLP5VRYQbpvBLnIav35WHAxWYWxlbmN5SWZhY2UGAghyZXF1aXJl +ZAJ7hIA8nvriESWnfCw5vHDS/ej5Q64N/Zz05oLtx2bKcGGGItF7rvBmAt/ndcmA +4LNrbrroCQ2AdfdRO+xLk/ZNCG11bHRpcGxlAnuEgDye+uIRJad8LDm8cNL96PlD +rg39nPTmgu3HZspwYYYi0Xuu8GYC3+d1yYDgs2tuuugJDYB191E77EuT9k0MVmVs +b2NpdHlIaW50AwYLdW5zcGVjaWZpZWQABnNlbGRvbQ8IZXBpc29kaWMfB3JlZ3Vs +YXI/CGZyZXF1ZW50fw1oaWdoRnJlcXVlbmN5/wVWZXJObwMBAnYxAAhWb3V0U2Vh +bAYDBm1ldGhvZAJBD/d2QIYSU77AjC52Xwu6XWojk0H9GfxAPSJfiKVy+9JSMHx9 +cZA19zB+6Cgl7BuBK6GH3Z2YSYtKjRb6btVDBHZvdXQC9WwTYiP2OadKCZPcR0bJ ++YqruINYXbXZFj8YfsQoGgoh4z5Dxapc8iknU6M4wWftO2OcTdnOvamPNGkXuslD +dQhibGluZGluZwAACA== -----END STRICT TYPE LIB----- diff --git a/stl/RGBStd@0.1.0.stl b/stl/RGBStd@0.1.0.stl index a5be6974..4a37044b 100644 Binary files a/stl/RGBStd@0.1.0.stl and b/stl/RGBStd@0.1.0.stl differ diff --git a/stl/RGBStd@0.1.0.sty b/stl/RGBStd@0.1.0.sty index 9c8959f0..82f49fb2 100644 --- a/stl/RGBStd@0.1.0.sty +++ b/stl/RGBStd@0.1.0.sty @@ -1,5 +1,5 @@ {- - Id: urn:ubideco:stl:5BWvsBkteVgD3yiGy8gHHuzhHFLQTz4mZU7kJob3JkHW#lazarus-ranger-neutral + Id: urn:ubideco:stl:E3LrF7ryeC9J8rABKrs1ceRg2skzsjaWVFk9xbi1x56#nobel-origami-tempo Name: RGBStd Version: 0.1.0 Description: RGB standard library @@ -10,67 +10,117 @@ typelib RGBStd -import urn:ubideco:stl:5dLgsvZEbhQekV5j7ghFCTyu3JPZ1B99SqhqPEA4Fdc#pigment-evita-clone as RGB +import urn:ubideco:stl:ZtHaBzu9ojbDahaGKEXe5v9DfSDxLERbLkEB23R6Q6V#rhino-cover-frog as CommitVerify +-- Imports: +-- MerkleProof := urn:ubideco:semid:4E7NDL8Nm1EXtcenS9idAx1LAXvTu2wRdYsxT8Q2hgRC#carol-alamo-denver +-- ProtocolId := urn:ubideco:semid:4GenVCt5Xq6xtnJDjT98FehgCS8rTmwEzbjwGkaUVjHz#gamma-banjo-corona +-- Message := urn:ubideco:semid:4ajqScXjJ6wQ5af2zgBFzzP7k1qzD6DXXU28taQidCcA#shampoo-bishop-morgan +-- TreeNode := urn:ubideco:semid:6c2cKiQFpcyVntm5aKHV3hoi2eBd8Ts9dtDzU3ccryw4#buenos-galaxy-editor +-- MerkleNode := urn:ubideco:semid:6kxYeCatpncbA9UiTdsFbxbxJdU56x6MdmTRkEeGAv6R#iceberg-rocket-velvet +-- MerkleBlock := urn:ubideco:semid:EEbVZBjaYQWCQA7uRBe8hFkxV6U1uvpH2dT4PafmJ1ko#proxy-catalog-byte + +import urn:ubideco:stl:5Nyd3BhfDAEfc5th2httogefZGzMftfcJNh5Lo9guuTx#father-soviet-mercy as BPCore +-- Imports: +-- TapretNodePartner := urn:ubideco:semid:6o6mGBNbDXJCcNgk5ohP6wgXcdXZvYd1ZWy1GMBy5q2#iceberg-poker-active +-- BlindSealTxid := urn:ubideco:semid:q529pAPHhD1aFgueAHy8QtfjUayszR85WgEg7s2a3KE#raymond-reply-phrase +-- TapretProof := urn:ubideco:semid:24LBor5ZVKjGk4uBbEb28w6D7rroV3mpVAntNAWQsYaP#forum-paint-tunnel +-- TapretPathProof := urn:ubideco:semid:2LANtvWZDRes61SHKFxtFPzSuTzaKQGCUvYsNowNf3n3#stage-element-update +-- AnchorMerkleProofTapretProof := urn:ubideco:semid:2RRuXmkGM51gpruJzNCL6mSFm7nQFrP6zXm6CmNr77uE#arcade-modest-fossil +-- AnchorMerkleBlockOpretProof := urn:ubideco:semid:3kBnCxpgfcoZ9sUfZRgFsjxcwUsCb3hVvydCMjr4jpY5#panel-urban-liquid +-- TapretRightBranch := urn:ubideco:semid:4nZtVVw7QJaMDHYffkHBWhxXSkLXLcJ89qTLZH4Z3xck#basket-prelude-bridge +-- OpretProof := urn:ubideco:semid:5oMeVsXXeicrdvrCCEZQiKWcrDHy4nQPbaQGhtnwM3ug#segment-ambient-totem +-- ExplicitSeal := urn:ubideco:semid:6xYzjyQ958BPRsJgBXL4N2DhswvgHWLp769Uzo4ybbUV#turtle-phoenix-panda +-- AnchorMerkleProofOpretProof := urn:ubideco:semid:7mkYAw62Vi8BuyQ16qGg3bgFw8WVtTXUkJoYBv1qoNg4#tape-mental-legacy +-- SecretSeal := urn:ubideco:semid:81NKrdc9pBoBjsKaGBVN9wXLG4tKjkK4f8DLj7TNMZxh#santana-domingo-needle +-- BlindSealTxPtr := urn:ubideco:semid:9XdJg1BFMpMXPfaiw4Te79W2qYgArsEye6XPJUtj31L8#metro-chris-olympic +-- TxPtr := urn:ubideco:semid:CyRtMpPJkKLX3AdhgY7ZyA7PnYAzCo7yFTeYwwGsUBhn#strange-source-father +-- CloseMethod := urn:ubideco:semid:FA1JhsEFKi2LLpuAjuvLA3qiBuEJrwpKyypB9J2aPicr#july-salmon-contact +-- AnchorMerkleBlockTapretProof := urn:ubideco:semid:HqZp5AT9Lz7eC3VE4CykrYmUZ5ZBxDwebTq8tvLGtwhF#mimosa-belgium-eric + +import urn:ubideco:stl:5XLKQ1sNryZm9bdFKU2kBY3MPYdZXhchVdQKBbHA3gby#south-strong-welcome as StrictTypes +-- Imports: +-- TypeName := urn:ubideco:semid:t47Qbd4ggmas4GmrE6oxCSyKBsosvGNtiXc2B2tuvUo#jamaica-capsule-chance +-- Primitive := urn:ubideco:semid:3T3zMmQxuir7TsdjhBLaETJfLH4mr5amAseXDePnzhMT#hobby-cable-puzzle +-- Variant := urn:ubideco:semid:3kQKwMdjA637pgAS3nF6SBooJrEbSNemqYtfZUYWinS6#tractor-sofia-company +-- TypeSystem := urn:ubideco:semid:47es12nMYaA5M7zMejQHAydsRPW6juta8ensLigXMBDP#linda-yellow-rival +-- VariantInfoSemId := urn:ubideco:semid:4Lhtbe9Fk5Xi5w1rYmY6VcNHKiPzvbBE9Z7ox8JYLfGF#star-consul-english +-- UnionVariantsSemId := urn:ubideco:semid:5B1DxehrbHq1bwC671nskfn2wVqDjMLkuaS7bpC3VxJk#studio-modern-nuclear +-- EnumVariants := urn:ubideco:semid:6Zt75MUKawbZuckCDFLEtanNvFbsoGZo7hdwdy6TJytE#list-sherman-viking +-- UnnamedFieldsSemId := urn:ubideco:semid:7mWKWSN4oLP5RHpJp2ofSR2DmPLnoGWnSdaT7XkLjJDL#hippie-world-sandra +-- NamedFieldsSemId := urn:ubideco:semid:7t4xRL7UV1zB1pm8nT6mM7LQTKDXrhJtCpXXeY1DdWTi#jordan-kiwi-nickel +-- FieldSemId := urn:ubideco:semid:7xPYsYdNDXSyVVY1FFRMkwYzn6ggw8PXV45A8vtdGGrK#speed-patriot-gallery +-- SemId := urn:ubideco:semid:8Ckj2p3GLKina636pSKJkj7GB6ft8XeoP4jfGkRUNwtp#cargo-plasma-catalog +-- TySemId := urn:ubideco:semid:9CmodzHmcjTizoBRGs6QuGgeBugHz1HinSdWyuXiTKea#vista-edgar-dominic +-- Ident := urn:ubideco:semid:9SkVki7nQ7WRGN2ayWjySv7QSh6ftrakdNRbE2sSbUYw#connect-fragile-exile +-- Sizing := urn:ubideco:semid:9jnMbAs5A91zjK9KDrLuFH42WtinmB8GY6JE6BMY31hw#canoe-gordon-amazon +-- FieldName := urn:ubideco:semid:CK7zqXpkDGwatYDy1bVHoARAq1xTi85DFueWrZdFE7du#orient-spell-talent +-- KeyStep := urn:ubideco:semid:EEZoF2pqf4ZmH7BJVhNyMuT5xtAgBbsWuxmfwSGsHpKb#frozen-margo-cannon +-- Path := urn:ubideco:semid:FiLcRn8DtH8UBS4ByqmSWX6XbCaDShkRU3Wf6i9yzDFV#exotic-western-courage +-- Step := urn:ubideco:semid:Fm1c9Y5SAxBw7jv3Qxwz6AUz3D755y9NxBYM4vAqw1aD#wheel-patriot-sandra + +import urn:ubideco:stl:9KALDYR8Nyjq4FdMW6kYoL7vdkWnqPqNuFnmE9qHpNjZ#justice-rocket-type as Std +-- Imports: +-- Ascii := urn:ubideco:semid:2NFrhqQqGNDA4HujyTW2pmcjtrN5sbtFfpPFXPPYcGER#aloha-lunar-felix +-- U5 := urn:ubideco:semid:3MDHMYsJt8d1gUiyx5vGCWcNLQ7biek6UTjHg3ksW4Bf#ground-volume-singer +-- Bool := urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell +-- AlphaNumLodash := urn:ubideco:semid:8iBe2dh8beD1KUairdqCacEcxAr4h55XfUQN2PspWXjz#north-sound-salsa + +import urn:ubideco:stl:CjHnUSMpAUVxemMGtj8hJtcCssguGxDghjBzfLrUfiMW#action-ribbon-diana as RGB -- Imports: --- AssignRevealedValueBlindSealTxid := urn:ubideco:semid:54H3Wc4UWv8EgE8Kp4oYe8Chj1XrRP1vk3YsSg25fma#fantasy-almond-drama +-- Anchor := urn:ubideco:semid:14qWN39y8UWuqcf23S365d4fWNo9BaKBkjA6VoGzNWud#radical-respond-escape -- BundleId := urn:ubideco:semid:EbWt9bmnjLpAu1LCN78snx734kHLNVUxyb5YxNr8tjd#desert-divide-visible --- ContractHistory := urn:ubideco:semid:wWuMgtgRtxV1sYctSZe4RQ5G9zdWqk913Sw9UBoeEZ1#flame-reverse-deliver +-- XchainExplicitSeal := urn:ubideco:semid:WTenE41RzjcDoZ6DUhdrVV9yYoiVRrG72prS64ePDqP#prefix-halt-service +-- AssignVoidStateBlindSealTxid := urn:ubideco:semid:xNw6CKrHd4mLq7UzJuua3Rwt1JZeCEWzVybkRqtKzDT#carol-data-copy -- WitnessPos := urn:ubideco:semid:zaGYZruf2pxiZqk1bjmoivxK8DkFycoyjQB52uHYhGL#robert-average-artist --- AssignRevealedAttachBlindSealTxid := urn:ubideco:semid:25Mu1o3QKWkwZHDHNahWxMqDHJPJBaqYU9Zkjha5Lg67#info-twist-boston -- SchemaSchema := urn:ubideco:semid:26TTgwB87FyCG5CJMPNpdoUdwdcEHd1STgCMiNmGqe8c#antonio-octopus-dexter --- Anchor := urn:ubideco:semid:2kbCRxs1hpVo6C9XpXBysmxSyx4HhE6ounqggmvWA1wW#spring-felix-model +-- TypedAssignsBlindSealTxPtr := urn:ubideco:semid:2GSQTtMuGzoe3q3k4pmatte6ydhu7zonyNQoqseQR5Ga#quasi-natasha-dollar +-- Extension := urn:ubideco:semid:2h1VHwDQXaVLxXJkcmCT17FNjYEV6qnxepTvYaedfZ3k#sponsor-type-jimmy +-- AnchoredBundle := urn:ubideco:semid:2k2M1VohDqprfbcomFN4v2SkuzkgcuMaF4HG3dCrQNdz#cartel-crimson-appear -- NoiseDumb := urn:ubideco:semid:33ug4TwTBFQxz7D3YdFmwpKET415dv5zQRh5CkavC5fL#deal-orca-aztec --- Extension := urn:ubideco:semid:38W6V9XrEWRP4CQBcKVTQxVwMgZGBKYGJJ2DThX73dDq#pioneer-modest-angel --- TransitionBundle := urn:ubideco:semid:3Cg6aD1To23ZakSmMDxiJYDHkb34cimto3qPLJonMePt#palma-philips-enigma -- RevealedData := urn:ubideco:semid:3DcMJ3YRokNwKN8Cce1ZqsYpiTEuto7EY1szCMEDH97A#velvet-david-manual --- SealDefinitionBlindSealTxid := urn:ubideco:semid:3JLMjcf79wu2VCRFwZqBZyGaZWLDexaUHzRn1XYLL5jA#ricardo-memphis-bagel --- Transition := urn:ubideco:semid:3MenucEisU4BKjD1DBPtP9oDxemcpmmSyJNMqWNGYX2E#shelter-torso-provide -- AltLayer1Set := urn:ubideco:semid:3Sruah3S3s7c8XRpD8bN7c8rnSKemwvnxhocQzHy5D9m#manual-cycle-circus --- TypedAssignsBlindSealTxPtr := urn:ubideco:semid:3gnX4GFgNtKQLNhrCEWksURoauyMcFWqhgZNec3cWMou#bishop-panel-alpine --- AssignVoidStateBlindSealTxPtr := urn:ubideco:semid:3nc63tpiLW8uBcCyutm3PXFWQ9coiADPeeuCou3tU3TT#system-promise-samuel +-- XchainBlindSealTxPtr := urn:ubideco:semid:3UQZiL3kChuaakzDdyt8Y2sFAVcxFVaJyTtjNC49D4Df#diamond-order-martin +-- AssignRevealedAttachBlindSealTxPtr := urn:ubideco:semid:3oLp11hYVBVhVCgj7zVQRtoa7efF6jXhYqXAiPv1ceKa#brigade-rider-albino -- VoidState := urn:ubideco:semid:49HkbZvGaJE3phHjLBMQCR3NK1sGA462HJr5BkqQ6YQr#nectar-ceramic-driver +-- AnchorSet := urn:ubideco:semid:4GL38RVDhs4JkL5phqyF6pguwL6Av8qTUWCVNfGeWdUg#history-joel-ivory -- TransitionType := urn:ubideco:semid:4XEmzMLZTXc4XB3njvemMq5qdMmx5EKJPAXpJaBPrqCb#puma-joshua-evita +-- Genesis := urn:ubideco:semid:4XxCwnwCDc436z3KwMjbEYejaFd5MvBit7d6BKLw1sSh#laser-agatha-kansas -- Occurrences := urn:ubideco:semid:4gjtVBchJQ5f1aAzoyxYWeGp6qZi9dPudJCbWKYKhw1a#unicorn-empire-mama -- StateSchema := urn:ubideco:semid:4pgZ5NMvRK6Jf2ua7H3TCF8bMNHhZR7PuUJawqq1X4uG#yoga-arizona-flex --- OutputAssignmentRevealedValue := urn:ubideco:semid:4udts1jX9QopZY9EVwZEUKUijXfPfmH7yMm8umA45scH#nobel-garlic-blast +-- ContractHistory := urn:ubideco:semid:54UnScbYJR9wEZ7RxfuUtGMtjmJAxQwWE8s1EnkbyiQc#depend-mercury-concept -- Schema := urn:ubideco:semid:5A36GjNTCDMJUcgu7iKtcHGceusD5xrvHyRfPPR2Kecy#dexter-bank-miami --- AssignRevealedDataBlindSealTxid := urn:ubideco:semid:5EPPduFdYTRmiyWNEciq3VS8rtdqjAw5jSr4E657t24z#eclipse-accent-siren +-- AssignRevealedValueBlindSealTxid := urn:ubideco:semid:5HTQNKWb3YKxQ4WhW8Nz6YBEWocEFc8EFwbrxq7G496q#noise-comrade-vampire -- MediaType := urn:ubideco:semid:5TNhPhpZzE6iDWXaUpdsTAwVsCZj5e9Z8AqFU1ii6HHa#light-nelson-brain --- TypedAssignsBlindSealTxid := urn:ubideco:semid:5YomkkMBJoTUo3AvS1vsEjhx4S7DbJVkGb7g29UeS8oT#profit-evening-pelican -- ReservedByte := urn:ubideco:semid:5ezr9uJrSdzez89fTCRWvKwGv6coMmQeXizK371oYdZ2#balloon-justin-place -- GlobalValues := urn:ubideco:semid:5j3xo5bTKFzcKayBQELdAVzUEnuPPAVd8etsBPG1EgZ3#volcano-expand-paper -- ValencyType := urn:ubideco:semid:5mswXMrudHpJEnuoLA86YY2VHN5iL56hmKcmh5k1h3e5#palma-exit-pupil -- PedersenCommitment := urn:ubideco:semid:5twbh2U5hyaowidwum1iRNCqebBLxTuZTuNPt3SaRT13#nepal-delta-earth --- SealDefinitionBlindSealTxPtr := urn:ubideco:semid:67tiipcLpwkwMZTZj2rUbqZPTYWVFeCFs3U6rqc3UEdu#delphi-october-gregory --- Output := urn:ubideco:semid:6UuNtgzG3HDCodyMVSNFS5C9k3bb9hPM7WFZUxpvgkWt#plume-credit-spider --- AssignRevealedDataBlindSealTxPtr := urn:ubideco:semid:72tqoE3ChdTTTKHFurSfkiYshAQFXoqqvYJnQvN9ADrq#diploma-airport-bottle --- AnchoredBundle := urn:ubideco:semid:73qjKLqMAqfv8qbNVyXjo4y2CwGWBgLUAWGamQW17ytP#eagle-jamaica-corner +-- AssignmentsBlindSealTxPtr := urn:ubideco:semid:6a1wvpZ1TWeAW6fEUAiDEFxmgum7UdCXg4y8pGCew5j3#gamma-crack-tina +-- AssignmentsBlindSealTxid := urn:ubideco:semid:7RA6jpEfnmpn4Y2PSPPxzSe4epXHXN2QZ3gteGAKYjby#stop-solar-explain -- ExtensionType := urn:ubideco:semid:7m9MHRdHSXnhYiheDeXybxnHAxPRgs84USnVELFH98Cd#mission-salsa-parole -- ExtensionSchema := urn:ubideco:semid:7uvJVMscXBA6G3cRwnJNeyJpbPPZ5LjR9RheoFjdSrxp#salmon-plato-contour -- RevealedAttach := urn:ubideco:semid:82EzCoBU6LaGX9bVA6WDXYNHmb2tCRZ8HgvxvNKWSGE7#antenna-balloon-manager -- RevealedFungible := urn:ubideco:semid:8MYGRakEEQtank63F1LQPuCW169TwTqBXAexRW7oiLp7#light-sonar-civil -- ConcealedData := urn:ubideco:semid:8YQWVpKJBaYsAwrXvuLDNEDEKRZxfoQJpu1G7X2ZN1tL#mirage-invite-newton --- OutputAssignmentRevealedAttach := urn:ubideco:semid:9Cg7Lu1GAjLmDCsrwjHr1iYWqdc8R8skvtdh6LfdWTNg#mobile-dinner-nadia -- GlobalOrd := urn:ubideco:semid:9GyqXE6fmewGdUwxz8xiSa8N4ZR6ZaGySoJVFxq5vuVJ#disney-belgium-sunset --- Genesis := urn:ubideco:semid:9tBmxzHdxPM1Hn5g9GgBFHnSoydLtYzgJvuSDy11tSMw#brain-adam-oliver -- AttachId := urn:ubideco:semid:9uzoSojhudYvNZYgTmJ5sMwwxzLtdLnfUeudT6Ro8i23#delta-member-agenda +-- AnchorSetMerkleBlock := urn:ubideco:semid:9zZ8QKP3o4u5qKz9akLVJo2mG2UGTg7EW1gnGUSdP6tY#tina-forest-radio -- BlindingFactor := urn:ubideco:semid:9zzp5XyDaLvZSGhCEWtey1Y7xdD1soEYdGaimjyZexyf#agenda-ivory-blast -- AssignmentType := urn:ubideco:semid:A9sThAqgwKPfuJcR4GDfTQHUAbbS5sbEXG5XVk7FZHEg#hunter-hello-retro +-- TypedAssignsBlindSealTxid := urn:ubideco:semid:AUTVu5drWPTHdN5sDioR2xKCBvCJ3gBgVw57b6nhRxXt#tahiti-adios-ivan -- Opout := urn:ubideco:semid:Au5jXjVgXjeE2n7dFQnPQwjLRQJ3eoGygRvt8ppzXrfx#india-joshua-adam -- SchemaId := urn:ubideco:semid:AyzbMn4ux89LLU8ho1L4pQa5TXsmRdHd79oh6SXdrCmd#garcia-smoke-ozone -- OpId := urn:ubideco:semid:B5hRc3ekuQsCAgsk8dmPVfytVDbvECJ3g2ANqrwSLE2p#slow-samuel-ceramic --- AssignVoidStateBlindSealTxid := urn:ubideco:semid:BHrQzpdVPUrZXApPQr696mafdfT5yV9oKE2evZmQrWdv#major-basket-balsa --- AssignRevealedValueBlindSealTxPtr := urn:ubideco:semid:BLWqpAa3FXEaamdWWy4WXA4pgKjSkcK3hiZZwAr2DcwY#region-bikini-cactus -- ContractId := urn:ubideco:semid:Bho42Xw8wPy2nWxgz6H51rNdBBusaPyrVQT8VypvpZ3w#alarm-danube-vampire -- GlobalState := urn:ubideco:semid:BvArFnz7G2BLLVqrk6ryqSS1cpvT5nmvAAStfVmVfcvu#freddie-optimal-germany -- AluScript := urn:ubideco:semid:ByCxcZ2hYTTJ8yoUhpUuHaxceQoAjqsxSF9zJkED3JuM#soviet-arsenal-complex --- BundleItem := urn:ubideco:semid:C9dpJ8oQD6qFYfbpZZKJ5nbYDLCdJK31cfVzqDz7vTjk#maze-escort-small -- FungibleState := urn:ubideco:semid:CD8fR4UCdn4ZE6Y6bNPFkDpXdMCMH1Y8nVPx7NGqqYHB#natasha-profit-winter -- RangeProof := urn:ubideco:semid:CL9hFCwcjTpaybwDxfj2GzdWkapbgjLkm3fjHEErcFju#teacher-telecom-tuna -- GenesisSchema := urn:ubideco:semid:CXXy9rBURF1Kt8wL698oT6zSaS8WiFXHv2SPCjT7STAA#distant-bravo-athlete --- AnchorMerkleBlock := urn:ubideco:semid:CwxenMEcD21WWNvFXXw9YvYexCNdiSHVKEe4jn3a6QxR#analog-textile-wave --- AssignmentsBlindSealTxid := urn:ubideco:semid:DBssvk2swpRDz5EKiDANToiKcEj9KfCunXGSdbtb5JFE#blue-verbal-quota +-- OutputAssignmentRevealedValue := urn:ubideco:semid:DQw8ZLtqqxYjS3MRbKKfS5dyE7jvJZFo4XkPoHpmyyp3#viking-taxi-mission -- TransitionSchema := urn:ubideco:semid:DmpSKgcAYf3cyTdcaHRQCf92p3hzzcd3dzGSQRFxffLz#minus-bravo-frame +-- Transition := urn:ubideco:semid:DqTDv5zeE9gGeFeeCrFrhJs3dFCmyj5pm1MEVGMHQdsU#saddle-cactus-baggage +-- XchainBlindSealTxid := urn:ubideco:semid:DywBwpGAdBpDeSvLqEP2Pvt5mb84r5baE1xDKkcEujBL#portal-kimono-jungle -- WitnessOrd := urn:ubideco:semid:E4mSjwEzEUUcq7i4rKuUFRo4Fpy4ckjqMudRa5y89TgV#rachel-convert-stella -- ConcealedFungible := urn:ubideco:semid:E5YL6Uz7gLb8DHtWEZpRxQaAXBF3ETJmqQeygd7j6f72#chicken-quebec-olivia -- WitnessId := urn:ubideco:semid:EEYT7goTNgX2nNFoKosg6FKx1CDSyFWHKNK1TRySs6gr#axiom-gyro-album @@ -79,77 +129,30 @@ import urn:ubideco:stl:5dLgsvZEbhQekV5j7ghFCTyu3JPZ1B99SqhqPEA4Fdc#pigment-evita -- AltLayer1 := urn:ubideco:semid:EVMd4HiFWvuPQv5JaVPecRY8oyqRNR92gxYUaCFFWqXp#banana-mouse-plaster -- Input := urn:ubideco:semid:EYdYhKC2ZuJsdZLsjbLcBJLfD8VabLQLgTuycV3b3TeD#carbon-dilemma-fragile -- AssetTag := urn:ubideco:semid:EZoxBpGenvb9UVze1zuwuEHqQJAqw2m3T8za5gbX1JZk#buzzer-pattern-craft --- OutputAssignmentVoidState := urn:ubideco:semid:F4aB7rQepbcwpDehQoRvhZD2RnP8Bjdpy5Q5gX4pLBNY#minimum-neuron-float +-- OutputAssignmentRevealedData := urn:ubideco:semid:FCseAzo3QYzXaPnA2Zp5wUWEPjYmkvmtXczb5MvN5DTo#lima-picnic-protect +-- TransitionBundle := urn:ubideco:semid:FP93oW4C5A4gkE17nD4haiUcYscHpJ1zkCh4GW2uA3H8#quality-gemini-susan -- GlobalStateType := urn:ubideco:semid:FQ6qHu9gQzjZu3i7dasU7T1PGi4qZi1a4goxJbHxHkbU#seminar-major-tape --- OutputAssignmentRevealedData := urn:ubideco:semid:FZhPL3ws73FQA7Z171cQNz2ktub4nfS2MoFe7CEAz5Ln#tarzan-domain-rodent -- Ffv := urn:ubideco:semid:FiMEyh3t5FKEsUqVTgQFYJ5XfJF9m2RwKMN9NckympSG#silence-motel-toronto -- Valencies := urn:ubideco:semid:Fuj5cKfnMZ3wbn1bPT3vTa3A5kyj3Rof5ceyyJAZeazQ#lady-paper-anvil -- Redeemed := urn:ubideco:semid:G4faJq4hv5wwmBUnkprf6BWnhDALbD1g93tQw8uzztTZ#money-campus-annual --- AssignmentsBlindSealTxPtr := urn:ubideco:semid:GGuvdn2L8kvpE4GBndncxTmBtVVkzNEzgygyLyxWmnYw#genetic-campus-senator +-- OutputAssignmentVoidState := urn:ubideco:semid:GAZZhtwTL4d97Z9FMDAKsLmquuMcJfZ92UbpG92AsRPp#monica-declare-pump +-- AssignRevealedDataBlindSealTxPtr := urn:ubideco:semid:GGCoiNzqtrHTpWdUhJQcFXVNVYTFFJpG5PZ8SuAtuiTt#protect-trident-life -- WitnessAnchor := urn:ubideco:semid:GoiJKnnFwMEfPauHo3j59bKoryXasRmpSVEkfeVsd6yg#canoe-sonar-money +-- AnchorMerkleBlock := urn:ubideco:semid:GpWPZqsCuQeew8qBAAAL6YAFvGsP4B51ubTjZtcjyBfk#opus-ticket-ariel +-- AssignRevealedDataBlindSealTxid := urn:ubideco:semid:GsWVVejsUo7AAz8sATamNC6EyPhUSBkvCysEvGH5Defj#donald-genetic-dollar +-- AssignRevealedValueBlindSealTxPtr := urn:ubideco:semid:Gu7a9uD8jQzSR3Vz3LhxGfXZLvPZFLUgjNWEu6JUyt8v#garbo-craft-william +-- AssignVoidStateBlindSealTxPtr := urn:ubideco:semid:H2FVtLAWGiN4gwiTuSrTMrX7YJ5u5A2RmAGHWUxhpE3R#alert-place-button +-- OutputAssignmentRevealedAttach := urn:ubideco:semid:HReVaw2ZgtuoRziFRpaZXu9YF6YDe9219tF9y61XDYe4#floor-split-image -- Inputs := urn:ubideco:semid:Hnpuz85o56FtwEPQ6Nia675JLD1nTwApEyowRAKxYvBk#reflex-philips-opera -- FungibleType := urn:ubideco:semid:HpiuYTT7BuhCmoNs2GrwNrHNUx3i3yf6GjDiFphLKeQV#profit-bazooka-present --- AssignRevealedAttachBlindSealTxPtr := urn:ubideco:semid:HwuqFFrAnBePMLfNdbDeSQ1fiRBZUdJ7TRAfwwcoFsEP#famous-general-italian +-- AssignRevealedAttachBlindSealTxid := urn:ubideco:semid:Hxy3qQnuCnc1GhDJp1uCJth3vQKXVtPPLpXZfphDChRu#quality-latin-level -- ConcealedAttach := urn:ubideco:semid:HyVyGxhRswAZ3BHJqx6PKmcEGCUSHaL1Rc7qxxi811qE#pizza-natural-cyclone -import urn:ubideco:stl:ZtHaBzu9ojbDahaGKEXe5v9DfSDxLERbLkEB23R6Q6V#rhino-cover-frog as CommitVerify --- Imports: --- MerkleProof := urn:ubideco:semid:4E7NDL8Nm1EXtcenS9idAx1LAXvTu2wRdYsxT8Q2hgRC#carol-alamo-denver --- ProtocolId := urn:ubideco:semid:4GenVCt5Xq6xtnJDjT98FehgCS8rTmwEzbjwGkaUVjHz#gamma-banjo-corona --- Message := urn:ubideco:semid:4ajqScXjJ6wQ5af2zgBFzzP7k1qzD6DXXU28taQidCcA#shampoo-bishop-morgan --- TreeNode := urn:ubideco:semid:6c2cKiQFpcyVntm5aKHV3hoi2eBd8Ts9dtDzU3ccryw4#buenos-galaxy-editor --- MerkleNode := urn:ubideco:semid:6kxYeCatpncbA9UiTdsFbxbxJdU56x6MdmTRkEeGAv6R#iceberg-rocket-velvet --- MerkleBlock := urn:ubideco:semid:EEbVZBjaYQWCQA7uRBe8hFkxV6U1uvpH2dT4PafmJ1ko#proxy-catalog-byte - -import urn:ubideco:stl:5XLKQ1sNryZm9bdFKU2kBY3MPYdZXhchVdQKBbHA3gby#south-strong-welcome as StrictTypes --- Imports: --- TypeName := urn:ubideco:semid:t47Qbd4ggmas4GmrE6oxCSyKBsosvGNtiXc2B2tuvUo#jamaica-capsule-chance --- Primitive := urn:ubideco:semid:3T3zMmQxuir7TsdjhBLaETJfLH4mr5amAseXDePnzhMT#hobby-cable-puzzle --- Variant := urn:ubideco:semid:3kQKwMdjA637pgAS3nF6SBooJrEbSNemqYtfZUYWinS6#tractor-sofia-company --- TypeSystem := urn:ubideco:semid:47es12nMYaA5M7zMejQHAydsRPW6juta8ensLigXMBDP#linda-yellow-rival --- VariantInfoSemId := urn:ubideco:semid:4Lhtbe9Fk5Xi5w1rYmY6VcNHKiPzvbBE9Z7ox8JYLfGF#star-consul-english --- UnionVariantsSemId := urn:ubideco:semid:5B1DxehrbHq1bwC671nskfn2wVqDjMLkuaS7bpC3VxJk#studio-modern-nuclear --- EnumVariants := urn:ubideco:semid:6Zt75MUKawbZuckCDFLEtanNvFbsoGZo7hdwdy6TJytE#list-sherman-viking --- UnnamedFieldsSemId := urn:ubideco:semid:7mWKWSN4oLP5RHpJp2ofSR2DmPLnoGWnSdaT7XkLjJDL#hippie-world-sandra --- NamedFieldsSemId := urn:ubideco:semid:7t4xRL7UV1zB1pm8nT6mM7LQTKDXrhJtCpXXeY1DdWTi#jordan-kiwi-nickel --- FieldSemId := urn:ubideco:semid:7xPYsYdNDXSyVVY1FFRMkwYzn6ggw8PXV45A8vtdGGrK#speed-patriot-gallery --- SemId := urn:ubideco:semid:8Ckj2p3GLKina636pSKJkj7GB6ft8XeoP4jfGkRUNwtp#cargo-plasma-catalog --- TySemId := urn:ubideco:semid:9CmodzHmcjTizoBRGs6QuGgeBugHz1HinSdWyuXiTKea#vista-edgar-dominic --- Ident := urn:ubideco:semid:9SkVki7nQ7WRGN2ayWjySv7QSh6ftrakdNRbE2sSbUYw#connect-fragile-exile --- Sizing := urn:ubideco:semid:9jnMbAs5A91zjK9KDrLuFH42WtinmB8GY6JE6BMY31hw#canoe-gordon-amazon --- FieldName := urn:ubideco:semid:CK7zqXpkDGwatYDy1bVHoARAq1xTi85DFueWrZdFE7du#orient-spell-talent --- KeyStep := urn:ubideco:semid:EEZoF2pqf4ZmH7BJVhNyMuT5xtAgBbsWuxmfwSGsHpKb#frozen-margo-cannon --- Path := urn:ubideco:semid:FiLcRn8DtH8UBS4ByqmSWX6XbCaDShkRU3Wf6i9yzDFV#exotic-western-courage --- Step := urn:ubideco:semid:Fm1c9Y5SAxBw7jv3Qxwz6AUz3D755y9NxBYM4vAqw1aD#wheel-patriot-sandra - -import urn:ubideco:stl:9KALDYR8Nyjq4FdMW6kYoL7vdkWnqPqNuFnmE9qHpNjZ#justice-rocket-type as Std --- Imports: --- Ascii := urn:ubideco:semid:2NFrhqQqGNDA4HujyTW2pmcjtrN5sbtFfpPFXPPYcGER#aloha-lunar-felix --- U5 := urn:ubideco:semid:3MDHMYsJt8d1gUiyx5vGCWcNLQ7biek6UTjHg3ksW4Bf#ground-volume-singer --- Bool := urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell --- AlphaNumLodash := urn:ubideco:semid:8iBe2dh8beD1KUairdqCacEcxAr4h55XfUQN2PspWXjz#north-sound-salsa - import urn:ubideco:stl:DVtm25LRKU4TjbyZmVxPhvCmctZ6vKkPKqfpU2QsDNUo#exodus-axiom-tommy as AluVM -- Imports: -- LibSite := urn:ubideco:semid:8Q9NNyK2PCcjZ7U7rDGUJBhk8q37hAnWLgSizGLmr56g#mission-papa-mercy -- LibId := urn:ubideco:semid:CFdqBJb1FVEwSwuQp47acj2bCFJgJn9J9NKVdoZuG625#rebel-factor-rodeo -import urn:ubideco:stl:E4xYrMV4cHgYhTPzrjG3DxzAyLEMnbvnsrBsGhQP4anV#quest-harlem-mirror as BPCore --- Imports: --- TapretNodePartner := urn:ubideco:semid:6o6mGBNbDXJCcNgk5ohP6wgXcdXZvYd1ZWy1GMBy5q2#iceberg-poker-active --- BlindSealTxid := urn:ubideco:semid:q529pAPHhD1aFgueAHy8QtfjUayszR85WgEg7s2a3KE#raymond-reply-phrase --- TapretProof := urn:ubideco:semid:24LBor5ZVKjGk4uBbEb28w6D7rroV3mpVAntNAWQsYaP#forum-paint-tunnel --- TapretPathProof := urn:ubideco:semid:2LANtvWZDRes61SHKFxtFPzSuTzaKQGCUvYsNowNf3n3#stage-element-update --- TapretRightBranch := urn:ubideco:semid:4nZtVVw7QJaMDHYffkHBWhxXSkLXLcJ89qTLZH4Z3xck#basket-prelude-bridge --- AnchorMerkleProof := urn:ubideco:semid:5YGZTLPUCHos5Wg8Gm5cLgMPZijqHQLp8CGTMoXTNPf1#coral-vienna-horizon --- SecretSeal := urn:ubideco:semid:81NKrdc9pBoBjsKaGBVN9wXLG4tKjkK4f8DLj7TNMZxh#santana-domingo-needle --- BlindSealTxPtr := urn:ubideco:semid:9XdJg1BFMpMXPfaiw4Te79W2qYgArsEye6XPJUtj31L8#metro-chris-olympic --- TxPtr := urn:ubideco:semid:CyRtMpPJkKLX3AdhgY7ZyA7PnYAzCo7yFTeYwwGsUBhn#strange-source-father --- CloseMethod := urn:ubideco:semid:FA1JhsEFKi2LLpuAjuvLA3qiBuEJrwpKyypB9J2aPicr#july-salmon-contact --- Proof := urn:ubideco:semid:FJrSpHdBSVvF5B8YAq3vY15Qh2k3NUXvdkt8ESb1hLW7#hello-america-origin --- AnchorMerkleBlock := urn:ubideco:semid:GvQcwTUd1mjGZmPypNsvhjhjc6WcJs63nVwdF8YeBvUj#trade-brother-liquid --- AnchorId := urn:ubideco:semid:HXreMRXsXhE6goE2JsF8g9jy4rZ7p7AEeYmxYgfPF2tN#dinner-single-alarm - import urn:ubideco:stl:HX2UBak8vPsTokug1DGMDvTpzns3xUdwZ7QJdyt4qBA9#speed-atlanta-trilogy as Bitcoin -- Imports: -- SeqNo := urn:ubideco:semid:5HtymNhYBhjqPkLLw9QVWZ62cLm57cZxgQTDUBBXtmL#rhino-time-rodent @@ -184,30 +187,30 @@ data AssignIface :: ownedState OwnedIface , multiple Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} -- urn:ubideco:semid:86jgudF9LyMs3LUmADKSdsoXJ8XC4EinnwSvaGqZPRCK#cipher-cafe-koala data Cert :: signer Identity, signature [Byte ^ ..0xff] --- urn:ubideco:semid:E9W8YFGPSwD68NhKvg9uGhTxbw1TbnCQpRvA6SMGDhps#square-solo-micro +-- urn:ubideco:semid:6bbrnuw1M34XCKYN1KFh69GKJafHnDUb4MgURg8Y5zJi#burma-planet-elvis data Consignmentfalse :: version ContainerVer , transfer Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} , schema RGB.SchemaSchema {- urn:ubideco:semid:26TTgwB87FyCG5CJMPNpdoUdwdcEHd1STgCMiNmGqe8c#antonio-octopus-dexter -} , ifaces {IfaceId -> ^ ..0xff IfacePair} , supplements {ContractSuppl ^ ..0xff} , assetTags {RGB.AssignmentType -> ^ ..0xff RGB.AssetTag {- urn:ubideco:semid:EZoxBpGenvb9UVze1zuwuEHqQJAqw2m3T8za5gbX1JZk#buzzer-pattern-craft -}} - , genesis RGB.Genesis {- urn:ubideco:semid:9tBmxzHdxPM1Hn5g9GgBFHnSoydLtYzgJvuSDy11tSMw#brain-adam-oliver -} + , genesis RGB.Genesis {- urn:ubideco:semid:4XxCwnwCDc436z3KwMjbEYejaFd5MvBit7d6BKLw1sSh#laser-agatha-kansas -} , terminals {RGB.BundleId -> Terminal} - , bundles [RGB.AnchoredBundle {- urn:ubideco:semid:73qjKLqMAqfv8qbNVyXjo4y2CwGWBgLUAWGamQW17ytP#eagle-jamaica-corner -} ^ ..0xffffffff] - , extensions [RGB.Extension {- urn:ubideco:semid:38W6V9XrEWRP4CQBcKVTQxVwMgZGBKYGJJ2DThX73dDq#pioneer-modest-angel -} ^ ..0xffffffff] + , bundles [RGB.AnchoredBundle {- urn:ubideco:semid:2k2M1VohDqprfbcomFN4v2SkuzkgcuMaF4HG3dCrQNdz#cartel-crimson-appear -} ^ ..0xffffffff] + , extensions [RGB.Extension {- urn:ubideco:semid:2h1VHwDQXaVLxXJkcmCT17FNjYEV6qnxepTvYaedfZ3k#sponsor-type-jimmy -} ^ ..0xffffffff] , attachments {RGB.AttachId -> [Byte ^ ..0xffffff]} , signatures {ContentId -> ^ ..0xff ContentSigs} --- urn:ubideco:semid:9oDRVvmicJwtf2JY4FaUqMxJFUek4EJDRAvfG18qhng9#joel-byte-nuclear +-- urn:ubideco:semid:25SpzTv8YMS9SXCuRFgqBL5BDHfS8LiG6zQb1tivpope#lake-venice-poker data Consignmenttrue :: version ContainerVer , transfer Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} , schema RGB.SchemaSchema {- urn:ubideco:semid:26TTgwB87FyCG5CJMPNpdoUdwdcEHd1STgCMiNmGqe8c#antonio-octopus-dexter -} , ifaces {IfaceId -> ^ ..0xff IfacePair} , supplements {ContractSuppl ^ ..0xff} , assetTags {RGB.AssignmentType -> ^ ..0xff RGB.AssetTag {- urn:ubideco:semid:EZoxBpGenvb9UVze1zuwuEHqQJAqw2m3T8za5gbX1JZk#buzzer-pattern-craft -}} - , genesis RGB.Genesis {- urn:ubideco:semid:9tBmxzHdxPM1Hn5g9GgBFHnSoydLtYzgJvuSDy11tSMw#brain-adam-oliver -} + , genesis RGB.Genesis {- urn:ubideco:semid:4XxCwnwCDc436z3KwMjbEYejaFd5MvBit7d6BKLw1sSh#laser-agatha-kansas -} , terminals {RGB.BundleId -> Terminal} - , bundles [RGB.AnchoredBundle {- urn:ubideco:semid:73qjKLqMAqfv8qbNVyXjo4y2CwGWBgLUAWGamQW17ytP#eagle-jamaica-corner -} ^ ..0xffffffff] - , extensions [RGB.Extension {- urn:ubideco:semid:38W6V9XrEWRP4CQBcKVTQxVwMgZGBKYGJJ2DThX73dDq#pioneer-modest-angel -} ^ ..0xffffffff] + , bundles [RGB.AnchoredBundle {- urn:ubideco:semid:2k2M1VohDqprfbcomFN4v2SkuzkgcuMaF4HG3dCrQNdz#cartel-crimson-appear -} ^ ..0xffffffff] + , extensions [RGB.Extension {- urn:ubideco:semid:2h1VHwDQXaVLxXJkcmCT17FNjYEV6qnxepTvYaedfZ3k#sponsor-type-jimmy -} ^ ..0xffffffff] , attachments {RGB.AttachId -> [Byte ^ ..0xffffff]} , signatures {ContentId -> ^ ..0xff ContentSigs} -- urn:ubideco:semid:B286kifNGNcJXNxiRq9mSoCerJfsMF5UThZBrBVyYsg2#felix-burger-matrix @@ -221,8 +224,8 @@ data ContentId :: schema RGB.SchemaId {- urn:ubideco:semid:AyzbMn4ux89LLU | suppl SupplId -- urn:ubideco:semid:72v5XvfiTB7HJinscrxy5ZTa4PwubG9YCtkK8JQt7F5B#denver-almanac-cobalt data ContentSigs :: {Cert ^ 1..0xa} --- urn:ubideco:semid:CV8NDWQdTY4hy5edNiHSfNpd2M4RjXuGPjWPv58z8DSn#nurse-chris-song -data ContractIndex :: publicOpouts {RGB.Opout {- urn:ubideco:semid:Au5jXjVgXjeE2n7dFQnPQwjLRQJ3eoGygRvt8ppzXrfx#india-joshua-adam -} ^ ..0xffffff}, outpointOpouts {RGB.Output -> ^ ..0xffffff {RGB.Opout {- urn:ubideco:semid:Au5jXjVgXjeE2n7dFQnPQwjLRQJ3eoGygRvt8ppzXrfx#india-joshua-adam -} ^ ..0xffffff}} +-- urn:ubideco:semid:3qoy3xqtwHPREPRqZNYTLmA229jEZvsajfdUH6exRufk#jargon-road-world +data ContractIndex :: publicOpouts {RGB.Opout {- urn:ubideco:semid:Au5jXjVgXjeE2n7dFQnPQwjLRQJ3eoGygRvt8ppzXrfx#india-joshua-adam -} ^ ..0xffffff}, outpointOpouts {RGB.XchainExplicitSeal -> ^ ..0xffffff {RGB.Opout {- urn:ubideco:semid:Au5jXjVgXjeE2n7dFQnPQwjLRQJ3eoGygRvt8ppzXrfx#india-joshua-adam -} ^ ..0xffffff}} -- urn:ubideco:semid:3WPrDGfcJCVwN9ZtFUf1w6SqsutP7xuJ8wRdby9VgPHF#slang-mars-belgium data ContractSuppl :: contractId RGB.ContractId {- urn:ubideco:semid:Bho42Xw8wPy2nWxgz6H51rNdBBusaPyrVQT8VypvpZ3w#alarm-danube-vampire -} , ticker TickerSuppl @@ -247,15 +250,15 @@ data GenesisIface :: metadata StrictTypes.SemId {- urn:ubideco:semid:8Ckj2p3 data GlobalIface :: semId StrictTypes.SemId {- urn:ubideco:semid:8Ckj2p3GLKina636pSKJkj7GB6ft8XeoP4jfGkRUNwtp#cargo-plasma-catalog -}? , required Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} , multiple Std.Bool {- urn:ubideco:semid:7ZhBHGSJm9ixmm8Z9vCX7i5Ga7j5xrW8t11nsb1Cgpnx#laser-madam-maxwell -} --- urn:ubideco:semid:Dj5dhUz7R5dprigumwNhJVuDkbDuMKKN8GVVgurArJc9#hello-carlo-roger +-- urn:ubideco:semid:6E6ifSMiXV7hbDa7GfwnPeLCqWtiFnxyn14WXGx8bDB5#domingo-love-athlete data Hoard :: schemata {RGB.SchemaId -> ^ ..0xff SchemaIfaces} , ifaces {IfaceId -> ^ ..0xff Iface} - , geneses {RGB.ContractId -> ^ ..0xff RGB.Genesis {- urn:ubideco:semid:9tBmxzHdxPM1Hn5g9GgBFHnSoydLtYzgJvuSDy11tSMw#brain-adam-oliver -}} + , geneses {RGB.ContractId -> ^ ..0xff RGB.Genesis {- urn:ubideco:semid:4XxCwnwCDc436z3KwMjbEYejaFd5MvBit7d6BKLw1sSh#laser-agatha-kansas -}} , suppl {RGB.ContractId -> ^ ..0xff {ContractSuppl ^ ..0xff}} , assetTags {RGB.ContractId -> ^ ..0xff {RGB.AssignmentType -> ^ ..0xff RGB.AssetTag {- urn:ubideco:semid:EZoxBpGenvb9UVze1zuwuEHqQJAqw2m3T8za5gbX1JZk#buzzer-pattern-craft -}}} - , bundles {RGB.BundleId -> ^ ..0xffffffff RGB.TransitionBundle {- urn:ubideco:semid:3Cg6aD1To23ZakSmMDxiJYDHkb34cimto3qPLJonMePt#palma-philips-enigma -}} - , extensions {RGB.OpId -> ^ ..0xffffffff RGB.Extension {- urn:ubideco:semid:38W6V9XrEWRP4CQBcKVTQxVwMgZGBKYGJJ2DThX73dDq#pioneer-modest-angel -}} - , anchors {BPCore.AnchorId -> ^ ..0xffffffff RGB.AnchorMerkleBlock {- urn:ubideco:semid:CwxenMEcD21WWNvFXXw9YvYexCNdiSHVKEe4jn3a6QxR#analog-textile-wave -}} + , bundles {RGB.BundleId -> ^ ..0xffffffff RGB.TransitionBundle {- urn:ubideco:semid:FP93oW4C5A4gkE17nD4haiUcYscHpJ1zkCh4GW2uA3H8#quality-gemini-susan -}} + , extensions {RGB.OpId -> ^ ..0xffffffff RGB.Extension {- urn:ubideco:semid:2h1VHwDQXaVLxXJkcmCT17FNjYEV6qnxepTvYaedfZ3k#sponsor-type-jimmy -}} + , anchors {RGB.WitnessId -> ^ ..0xffffffff RGB.AnchorMerkleBlock {- urn:ubideco:semid:GpWPZqsCuQeew8qBAAAL6YAFvGsP4B51ubTjZtcjyBfk#opus-ticket-ariel -}} , sigs {ContentId -> ContentSigs} -- urn:ubideco:semid:E3ntt6pxCqzXgJoqcM3471BBPLddnKcEGsdD3mYAkz5h#ibiza-nuclear-vision data IdSuite :: pgp:0 | ssh:1 | ssi:2 @@ -328,14 +331,14 @@ data OwnedStateSuppl :: meaning [Unicode ^ ..0xff], velocity VelocityHint data ReservedBytes04 :: [Byte ^ 4] -- urn:ubideco:semid:DTrbbsskfwyT3nspLiicSNA2Yb8vPk9JU9GcpAEVwvWb#lorenzo-strong-golf data SchemaIfaces :: schema RGB.SchemaSchema {- urn:ubideco:semid:26TTgwB87FyCG5CJMPNpdoUdwdcEHd1STgCMiNmGqe8c#antonio-octopus-dexter -}, iimpls {IfaceId -> ^ ..0xff IfaceImpl} --- urn:ubideco:semid:dxDGJVLkY5pySH1VgL9QLwUL45ZPAcLMLxPoU8Dhn4W#episode-aroma-benny +-- urn:ubideco:semid:3xZnYdvpMfwenxYBgABq1ig8UvX9FfK4k65Uyob4GQd2#henry-left-static data Stock :: hoard Hoard - , history {RGB.ContractId -> ^ ..0xff RGB.ContractHistory {- urn:ubideco:semid:wWuMgtgRtxV1sYctSZe4RQ5G9zdWqk913Sw9UBoeEZ1#flame-reverse-deliver -}} + , history {RGB.ContractId -> ^ ..0xff RGB.ContractHistory {- urn:ubideco:semid:54UnScbYJR9wEZ7RxfuUtGMtjmJAxQwWE8s1EnkbyiQc#depend-mercury-concept -}} , bundleOpIndex {RGB.OpId -> ^ ..0xffffff IndexedBundle} - , anchorBundleIndex {RGB.BundleId -> ^ ..0xffffff BPCore.AnchorId {- urn:ubideco:semid:HXreMRXsXhE6goE2JsF8g9jy4rZ7p7AEeYmxYgfPF2tN#dinner-single-alarm -}} + , anchorBundleIndex {RGB.BundleId -> ^ ..0xffffff RGB.WitnessId {- urn:ubideco:semid:EEYT7goTNgX2nNFoKosg6FKx1CDSyFWHKNK1TRySs6gr#axiom-gyro-album -}} , contractIndex {RGB.ContractId -> ^ ..0xff ContractIndex} , terminalIndex {BPCore.SecretSeal -> ^ ..0xffffff RGB.Opout {- urn:ubideco:semid:Au5jXjVgXjeE2n7dFQnPQwjLRQJ3eoGygRvt8ppzXrfx#india-joshua-adam -}} - , sealSecrets {RGB.SealDefinitionBlindSealTxPtr {- urn:ubideco:semid:67tiipcLpwkwMZTZj2rUbqZPTYWVFeCFs3U6rqc3UEdu#delphi-october-gregory -} ^ ..0xffffff} + , sealSecrets {RGB.XchainBlindSealTxPtr {- urn:ubideco:semid:3UQZiL3kChuaakzDdyt8Y2sFAVcxFVaJyTtjNC49D4Df#diamond-order-martin -} ^ ..0xffffff} -- urn:ubideco:semid:7wqgZas6f6Y7jWyDzLNxCeGEM8NXppB1f1gZNvNHJD72#partner-austin-dinner data SupplId :: [Byte ^ 32] -- urn:ubideco:semid:CXGPwRETAtPV783GHQKZmnpvrtbUzELpBP74ScXDBP22#system-billy-polaris