diff --git a/Makefile.am b/Makefile.am
index 3685234d22..37c66c2723 100755
--- a/Makefile.am
+++ b/Makefile.am
@@ -46,6 +46,7 @@ src_libbitcoin_system_la_SOURCES = \
src/chain/header.cpp \
src/chain/input.cpp \
src/chain/operation.cpp \
+ src/chain/outpoint.cpp \
src/chain/output.cpp \
src/chain/point.cpp \
src/chain/script.cpp \
@@ -234,6 +235,7 @@ test_libbitcoin_system_test_SOURCES = \
test/chain/header.cpp \
test/chain/input.cpp \
test/chain/operation.cpp \
+ test/chain/outpoint.cpp \
test/chain/output.cpp \
test/chain/point.cpp \
test/chain/satoshi_words.cpp \
@@ -482,6 +484,7 @@ include_bitcoin_system_chain_HEADERS = \
include/bitcoin/system/chain/header.hpp \
include/bitcoin/system/chain/input.hpp \
include/bitcoin/system/chain/operation.hpp \
+ include/bitcoin/system/chain/outpoint.hpp \
include/bitcoin/system/chain/output.hpp \
include/bitcoin/system/chain/point.hpp \
include/bitcoin/system/chain/prevout.hpp \
diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt
index 19b988f59f..5c4ddbe36a 100644
--- a/builds/cmake/CMakeLists.txt
+++ b/builds/cmake/CMakeLists.txt
@@ -479,6 +479,7 @@ add_library( ${CANONICAL_LIB_NAME}
"../../src/chain/header.cpp"
"../../src/chain/input.cpp"
"../../src/chain/operation.cpp"
+ "../../src/chain/outpoint.cpp"
"../../src/chain/output.cpp"
"../../src/chain/point.cpp"
"../../src/chain/script.cpp"
@@ -713,6 +714,7 @@ if (with-tests)
"../../test/chain/header.cpp"
"../../test/chain/input.cpp"
"../../test/chain/operation.cpp"
+ "../../test/chain/outpoint.cpp"
"../../test/chain/output.cpp"
"../../test/chain/point.cpp"
"../../test/chain/satoshi_words.cpp"
diff --git a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj
index ede84f0802..bf96c9857f 100644
--- a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj
+++ b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj
@@ -135,6 +135,7 @@
+
diff --git a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters
index f28e674d60..43f0d7bbaf 100644
--- a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters
+++ b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters
@@ -147,6 +147,9 @@
src\chain
+
+ src\chain
+
src\chain
diff --git a/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj b/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj
index 627ebdde83..29d3fa8ec5 100644
--- a/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj
+++ b/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj
@@ -138,6 +138,7 @@
$(IntDir)src_chain_input.obj
+
$(IntDir)src_chain_output.obj
@@ -323,6 +324,7 @@
+
diff --git a/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj.filters
index 0758b0430e..05e1e4e235 100644
--- a/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj.filters
+++ b/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj.filters
@@ -276,6 +276,9 @@
src\chain
+
+ src\chain
+
src\chain
@@ -719,6 +722,9 @@
include\bitcoin\system\chain
+
+ include\bitcoin\system\chain
+
include\bitcoin\system\chain
diff --git a/include/bitcoin/system.hpp b/include/bitcoin/system.hpp
index 77254596e3..3a9f0209fd 100755
--- a/include/bitcoin/system.hpp
+++ b/include/bitcoin/system.hpp
@@ -42,6 +42,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/include/bitcoin/system/chain/chain.hpp b/include/bitcoin/system/chain/chain.hpp
index 45efecb2fd..8b8da93f8a 100644
--- a/include/bitcoin/system/chain/chain.hpp
+++ b/include/bitcoin/system/chain/chain.hpp
@@ -41,6 +41,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/include/bitcoin/system/chain/outpoint.hpp b/include/bitcoin/system/chain/outpoint.hpp
new file mode 100644
index 0000000000..82f5fd7e04
--- /dev/null
+++ b/include/bitcoin/system/chain/outpoint.hpp
@@ -0,0 +1,123 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_SYSTEM_CHAIN_OUTPOINT_HPP
+#define LIBBITCOIN_SYSTEM_CHAIN_OUTPOINT_HPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace libbitcoin {
+namespace system {
+namespace chain {
+
+class BC_API outpoint
+{
+public:
+ DEFAULT_COPY_MOVE_DESTRUCT(outpoint);
+
+ typedef std::shared_ptr cptr;
+
+ static constexpr size_t serialized_size() NOEXCEPT
+ {
+ return point::serialized_size() + sizeof(uint32_t);
+ }
+
+ /// Constructors.
+ /// -----------------------------------------------------------------------
+
+ /// Default outpoint is an invalid null outpoint (null_hash/null_index/0).
+ outpoint() NOEXCEPT;
+
+ outpoint(chain::point&& point, uint64_t value) NOEXCEPT;
+ outpoint(const chain::point& point, uint64_t value) NOEXCEPT;
+
+ outpoint(const data_slice& data) NOEXCEPT;
+ outpoint(stream::in::fast& stream) NOEXCEPT;
+ outpoint(std::istream& stream) NOEXCEPT;
+ outpoint(reader& source) NOEXCEPT;
+
+ /// Operators.
+ /// -----------------------------------------------------------------------
+
+ bool operator==(const outpoint& other) const NOEXCEPT;
+ bool operator!=(const outpoint& other) const NOEXCEPT;
+
+ /// Serialization.
+ /// -----------------------------------------------------------------------
+
+ data_chunk to_data() const NOEXCEPT;
+ void to_data(std::ostream& stream) const NOEXCEPT;
+ void to_data(writer& sink) const NOEXCEPT;
+
+ /// Properties.
+ /// -----------------------------------------------------------------------
+
+ /// Native properties.
+ bool is_valid() const NOEXCEPT;
+ const chain::point& point() const NOEXCEPT;
+ uint64_t value() const NOEXCEPT;
+
+ /// Computed properties.
+ bool is_null() const NOEXCEPT;
+
+protected:
+ outpoint(stream::in::fast&& stream) NOEXCEPT;
+ outpoint(reader&& source) NOEXCEPT;
+ outpoint(chain::point&& point, uint64_t value, bool valid) NOEXCEPT;
+ outpoint(const chain::point& point, uint64_t value, bool valid) NOEXCEPT;
+
+private:
+ chain::point point_;
+ uint64_t value_;
+
+ // Cache.
+ bool valid_;
+};
+
+/// Arbitrary compare, for uniqueness sorting.
+bool operator<(const outpoint& left, const outpoint& right) NOEXCEPT;
+
+typedef std_vector outpoints;
+
+DECLARE_JSON_TAG_INVOKE(outpoint);
+DECLARE_JSON_TAG_INVOKE(outpoint::cptr);
+
+} // namespace chain
+} // namespace system
+} // namespace libbitcoin
+
+namespace std
+{
+template<>
+struct hash
+{
+ size_t operator()(const bc::system::chain::outpoint& value) const NOEXCEPT
+ {
+ // Value does not contribute to identity.
+ return std::hash{}(value.point());
+ }
+};
+} // namespace std
+
+#endif
diff --git a/include/bitcoin/system/chain/output.hpp b/include/bitcoin/system/chain/output.hpp
index be3dda317b..30253ea098 100644
--- a/include/bitcoin/system/chain/output.hpp
+++ b/include/bitcoin/system/chain/output.hpp
@@ -20,6 +20,7 @@
#define LIBBITCOIN_SYSTEM_CHAIN_OUTPUT_HPP
#include
+#include
#include
#include
#include
diff --git a/src/chain/outpoint.cpp b/src/chain/outpoint.cpp
new file mode 100644
index 0000000000..35ba3373a6
--- /dev/null
+++ b/src/chain/outpoint.cpp
@@ -0,0 +1,198 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include
+
+#include
+#include
+#include
+#include
+
+namespace libbitcoin {
+namespace system {
+namespace chain {
+
+// Constructors.
+// ----------------------------------------------------------------------------
+
+// Invalid default used in signature hashing.
+outpoint::outpoint() NOEXCEPT
+ : outpoint({}, zero, false)
+{
+}
+
+outpoint::outpoint(chain::point&& point, uint64_t value) NOEXCEPT
+ : outpoint(std::move(point), value, true)
+{
+}
+
+outpoint::outpoint(const chain::point& point, uint64_t value) NOEXCEPT
+ : outpoint(point, value, true)
+{
+}
+
+outpoint::outpoint(const data_slice& data) NOEXCEPT
+ : outpoint(stream::in::fast(data))
+{
+}
+
+// protected
+outpoint::outpoint(stream::in::fast&& stream) NOEXCEPT
+ : outpoint(read::bytes::fast(stream))
+{
+}
+
+outpoint::outpoint(stream::in::fast& stream) NOEXCEPT
+ : outpoint(read::bytes::fast(stream))
+{
+}
+outpoint::outpoint(std::istream& stream) NOEXCEPT
+ : outpoint(read::bytes::istream(stream))
+{
+}
+
+// protected
+outpoint::outpoint(reader&& source) NOEXCEPT
+ : outpoint(source)
+{
+}
+
+// outpoint is not part of block, so doesn't use custom allocator.
+outpoint::outpoint(reader& source) NOEXCEPT
+ : point_{ source },
+ value_{ source.read_8_bytes_little_endian() },
+ valid_{ source }
+{
+}
+
+// protected
+outpoint::outpoint(chain::point&& point, uint64_t value, bool valid) NOEXCEPT
+ : point_(std::move(point)), value_(value), valid_(valid)
+{
+}
+
+// protected
+outpoint::outpoint(const chain::point& point, uint64_t value,
+ bool valid) NOEXCEPT
+ : point_(point), value_(value), valid_(valid)
+{
+}
+
+// Operators.
+// ----------------------------------------------------------------------------
+// Value does not contribute to identity.
+
+bool outpoint::operator==(const outpoint& other) const NOEXCEPT
+{
+ return point() == other.point();
+}
+
+bool outpoint::operator!=(const outpoint& other) const NOEXCEPT
+{
+ return !(*this == other);
+}
+
+bool operator<(const outpoint& left, const outpoint& right) NOEXCEPT
+{
+ return left.point() < right.point();
+}
+
+// Serialization.
+// ----------------------------------------------------------------------------
+
+data_chunk outpoint::to_data() const NOEXCEPT
+{
+ data_chunk data(serialized_size());
+ stream::out::fast ostream(data);
+ write::bytes::fast out(ostream);
+ to_data(out);
+ return data;
+}
+
+void outpoint::to_data(std::ostream& stream) const NOEXCEPT
+{
+ write::bytes::ostream out(stream);
+ to_data(out);
+}
+
+void outpoint::to_data(writer& sink) const NOEXCEPT
+{
+ point_.to_data(sink);
+ sink.write_8_bytes_little_endian(value_);
+}
+
+// Properties.
+// ----------------------------------------------------------------------------
+
+bool outpoint::is_valid() const NOEXCEPT
+{
+ return valid_;
+}
+
+const chain::point& outpoint::point() const NOEXCEPT
+{
+ return point_;
+}
+
+uint64_t outpoint::value() const NOEXCEPT
+{
+ return value_;
+}
+
+// Validation.
+// ----------------------------------------------------------------------------
+
+bool outpoint::is_null() const NOEXCEPT
+{
+ return point_.is_null();
+}
+
+// JSON value convertors.
+// ----------------------------------------------------------------------------
+
+DEFINE_JSON_TO_TAG(outpoint)
+{
+ return
+ {
+ value_to(value.at("point")),
+ value.at("value").to_number()
+ };
+}
+
+DEFINE_JSON_FROM_TAG(outpoint)
+{
+ value =
+ {
+ { "point", value_from(instance.point()) },
+ { "value", instance.value() }
+ };
+}
+
+DEFINE_JSON_TO_TAG(outpoint::cptr)
+{
+ return to_shared(tag_invoke(to_tag{}, value));
+}
+
+DEFINE_JSON_FROM_TAG(outpoint::cptr)
+{
+ tag_invoke(from_tag{}, value, *instance);
+}
+
+} // namespace chain
+} // namespace system
+} // namespace libbitcoin
diff --git a/src/chain/output.cpp b/src/chain/output.cpp
index 6a3aa71fb5..72400d075e 100644
--- a/src/chain/output.cpp
+++ b/src/chain/output.cpp
@@ -264,7 +264,7 @@ DEFINE_JSON_FROM_TAG(output)
value =
{
{ "value", instance.value() },
- { "script", value_from(instance.script()) },
+ { "script", value_from(instance.script()) }
};
}
diff --git a/test/chain/outpoint.cpp b/test/chain/outpoint.cpp
new file mode 100644
index 0000000000..2831e86c14
--- /dev/null
+++ b/test/chain/outpoint.cpp
@@ -0,0 +1,217 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include "../test.hpp"
+
+BOOST_AUTO_TEST_SUITE(outpoint_tests)
+
+namespace json = boost::json;
+using namespace system::chain;
+
+static const auto outpoint_hash = base16_hash(
+ "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
+
+static const auto outpoint_data = base16_chunk(
+ "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f000000150000000000000042");
+
+static const outpoint expected_outpoint{ outpoint_data };
+
+// constructors
+// ----------------------------------------------------------------------------
+// tests construction, native properties, is_valid
+
+BOOST_AUTO_TEST_CASE(output__constructor__default__invalid)
+{
+ const outpoint instance;
+ BOOST_REQUIRE(!instance.is_valid());
+}
+
+BOOST_AUTO_TEST_CASE(output__constructor__move__valid)
+{
+ auto copy = expected_outpoint;
+ const outpoint instance{ std::move(copy) };
+ BOOST_REQUIRE(instance.is_valid());
+ BOOST_REQUIRE(instance == expected_outpoint);
+}
+
+BOOST_AUTO_TEST_CASE(output__constructor__copy__valid)
+{
+ const outpoint instance{ expected_outpoint };
+ BOOST_REQUIRE(instance.is_valid());
+ BOOST_REQUIRE(instance == expected_outpoint);
+}
+
+BOOST_AUTO_TEST_CASE(output__constructor__move_parameters__valid)
+{
+ constexpr uint32_t value{ 42 };
+ constexpr uint32_t index{ 1234 };
+ outpoint instance{ { outpoint_hash, index }, value };
+ BOOST_REQUIRE(instance.is_valid());
+ BOOST_REQUIRE_EQUAL(instance.point().hash(), outpoint_hash);
+ BOOST_REQUIRE_EQUAL(instance.point().index(), index);
+ BOOST_REQUIRE_EQUAL(instance.value(), value);
+}
+
+BOOST_AUTO_TEST_CASE(output__constructor__copy_parameters__valid)
+{
+ constexpr uint32_t value{ 42 };
+ constexpr uint32_t index{ 1234 };
+ point copy{ outpoint_hash, index };
+ outpoint instance{ std::move(copy), value };
+ BOOST_REQUIRE(instance.is_valid());
+ BOOST_REQUIRE_EQUAL(instance.point().hash(), outpoint_hash);
+ BOOST_REQUIRE_EQUAL(instance.point().index(), index);
+ BOOST_REQUIRE_EQUAL(instance.value(), value);
+}
+
+// operators
+// ----------------------------------------------------------------------------
+
+BOOST_AUTO_TEST_CASE(outpoint__assign__copy__expected)
+{
+ const auto& alpha = expected_outpoint;
+ auto gamma = alpha;
+ const auto beta = std::move(gamma);
+ BOOST_REQUIRE(alpha == beta);
+}
+
+BOOST_AUTO_TEST_CASE(outpoint__assign__move__expected)
+{
+ const auto& alpha = expected_outpoint;
+ const auto beta = alpha;
+ BOOST_REQUIRE(alpha == beta);
+}
+
+BOOST_AUTO_TEST_CASE(outpoint__equality__same__true)
+{
+ const auto& alpha = expected_outpoint;
+ const outpoint beta(alpha);
+ BOOST_REQUIRE(alpha == beta);
+}
+
+BOOST_AUTO_TEST_CASE(outpoint__equality__different__false)
+{
+ const auto& alpha = expected_outpoint;
+ const outpoint beta;
+ BOOST_REQUIRE(!(alpha == beta));
+}
+
+BOOST_AUTO_TEST_CASE(outpoint__inequality__same__false)
+{
+ const auto& alpha = expected_outpoint;
+ const outpoint beta(alpha);
+ BOOST_REQUIRE(!(alpha != beta));
+}
+
+BOOST_AUTO_TEST_CASE(outpoint__inequality__different__true)
+{
+ const auto& alpha = expected_outpoint;
+ const outpoint beta;
+ BOOST_REQUIRE(alpha != beta);
+}
+
+// to_data
+// ----------------------------------------------------------------------------
+
+BOOST_AUTO_TEST_CASE(outpoint__to_data__data__expected)
+{
+ const auto size = expected_outpoint.to_data().size();
+ BOOST_REQUIRE_EQUAL(size, expected_outpoint.serialized_size());
+}
+
+BOOST_AUTO_TEST_CASE(outpoint__to_data__stream__expected)
+{
+ // Write outpoint to stream.
+ std::stringstream iostream;
+ expected_outpoint.to_data(iostream);
+ BOOST_REQUIRE(iostream);
+
+ // Verify stream contents.
+ const outpoint copy(iostream);
+ BOOST_REQUIRE(iostream);
+ BOOST_REQUIRE(copy.is_valid());
+ BOOST_REQUIRE(copy == expected_outpoint);
+}
+
+BOOST_AUTO_TEST_CASE(outpoint__to_data__writer__expected)
+{
+ // Write outpoint to stream.
+ std::stringstream iostream;
+ write::bytes::ostream out(iostream);
+ expected_outpoint.to_data(out);
+ BOOST_REQUIRE(iostream);
+
+ // Verify stream contents.
+ const outpoint copy(iostream);
+ BOOST_REQUIRE(iostream);
+ BOOST_REQUIRE(copy.is_valid());
+ BOOST_REQUIRE(copy == expected_outpoint);
+}
+
+// properties
+// ----------------------------------------------------------------------------
+
+BOOST_AUTO_TEST_CASE(outpoint__is_null__not_null__false)
+{
+ BOOST_REQUIRE(!expected_outpoint.is_null());
+}
+
+BOOST_AUTO_TEST_CASE(outpoint__is_null__default_null__true)
+{
+ BOOST_REQUIRE(outpoint{}.is_null());
+}
+
+BOOST_AUTO_TEST_CASE(outpoint__serialized_size__always__expected)
+{
+ static_assert(outpoint::serialized_size() == point::serialized_size() + sizeof(uint32_t));
+ BOOST_REQUIRE_EQUAL(outpoint::serialized_size(), point::serialized_size() + sizeof(uint32_t));
+}
+
+// json
+// ----------------------------------------------------------------------------
+
+BOOST_AUTO_TEST_CASE(outpoint__json__conversions__expected)
+{
+ const std::string text
+ {
+ "{"
+ "\"point\":"
+ "{"
+ "\"hash\":\"0000000000000000000000000000000000000000000000000000000000000001\","
+ "\"index\":42"
+ "},"
+ "\"value\":24"
+ "}"
+ };
+
+ const chain::outpoint instance
+ {
+ chain::point{ one_hash, 42 },
+ 24
+ };
+
+ const auto value = json::value_from(instance);
+
+ BOOST_REQUIRE_EQUAL(json::serialize(value), text);
+ BOOST_REQUIRE(json::parse(text) == value);
+
+ BOOST_REQUIRE(json::value_from(instance) == value);
+ BOOST_REQUIRE(json::value_to(value) == instance);
+}
+
+BOOST_AUTO_TEST_SUITE_END()