@@ -1755,6 +1755,80 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request)
1755
1755
return EncodeBase64 ((unsigned char *)ssTx.data (), ssTx.size ());
1756
1756
}
1757
1757
1758
+ UniValue joinpsbts (const JSONRPCRequest& request)
1759
+ {
1760
+ if (request.fHelp || request.params .size () != 1 ) {
1761
+ throw std::runtime_error (
1762
+ RPCHelpMan{" joinpsbts" ,
1763
+ " \n Joins multiple distinct PSBTs with different inputs and outputs into one PSBT with inputs and outputs from all of the PSBTs\n "
1764
+ " No input in any of the PSBTs can be in more than one of the PSBTs.\n " ,
1765
+ {
1766
+ {" txs" , RPCArg::Type::ARR, RPCArg::Optional::NO, " A json array of base64 strings of partially signed transactions" ,
1767
+ {
1768
+ {" psbt" , RPCArg::Type::STR, RPCArg::Optional::NO, " A base64 string of a PSBT" }
1769
+ }}
1770
+ },
1771
+ RPCResult {
1772
+ " \" psbt\" (string) The base64-encoded partially signed transaction\n "
1773
+ },
1774
+ RPCExamples {
1775
+ HelpExampleCli (" joinpsbts" , " \" psbt\" " )
1776
+ }}.ToString ());
1777
+ }
1778
+
1779
+ RPCTypeCheck (request.params , {UniValue::VARR}, true );
1780
+
1781
+ // Unserialize the transactions
1782
+ std::vector<PartiallySignedTransaction> psbtxs;
1783
+ UniValue txs = request.params [0 ].get_array ();
1784
+
1785
+ if (txs.size () <= 1 ) {
1786
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " At least two PSBTs are required to join PSBTs." );
1787
+ }
1788
+
1789
+ int32_t best_version = 1 ;
1790
+ uint32_t best_locktime = 0xffffffff ;
1791
+ for (unsigned int i = 0 ; i < txs.size (); ++i) {
1792
+ PartiallySignedTransaction psbtx;
1793
+ std::string error;
1794
+ if (!DecodeBase64PSBT (psbtx, txs[i].get_str (), error)) {
1795
+ throw JSONRPCError (RPC_DESERIALIZATION_ERROR, strprintf (" TX decode failed %s" , error));
1796
+ }
1797
+ psbtxs.push_back (psbtx);
1798
+ // Choose the highest version number
1799
+ if (psbtx.tx ->nVersion > best_version) {
1800
+ best_version = psbtx.tx ->nVersion ;
1801
+ }
1802
+ // Choose the lowest lock time
1803
+ if (psbtx.tx ->nLockTime < best_locktime) {
1804
+ best_locktime = psbtx.tx ->nLockTime ;
1805
+ }
1806
+ }
1807
+
1808
+ // Create a blank psbt where everything will be added
1809
+ PartiallySignedTransaction merged_psbt;
1810
+ merged_psbt.tx = CMutableTransaction ();
1811
+ merged_psbt.tx ->nVersion = best_version;
1812
+ merged_psbt.tx ->nLockTime = best_locktime;
1813
+
1814
+ // Merge
1815
+ for (auto & psbt : psbtxs) {
1816
+ for (unsigned int i = 0 ; i < psbt.tx ->vin .size (); ++i) {
1817
+ if (!merged_psbt.AddInput (psbt.tx ->vin [i], psbt.inputs [i])) {
1818
+ throw JSONRPCError (RPC_INVALID_PARAMETER, strprintf (" Input %s:%d exists in multiple PSBTs" , psbt.tx ->vin [i].prevout .hash .ToString ().c_str (), psbt.tx ->vin [i].prevout .n ));
1819
+ }
1820
+ }
1821
+ for (unsigned int i = 0 ; i < psbt.tx ->vout .size (); ++i) {
1822
+ merged_psbt.AddOutput (psbt.tx ->vout [i], psbt.outputs [i]);
1823
+ }
1824
+ merged_psbt.unknown .insert (psbt.unknown .begin (), psbt.unknown .end ());
1825
+ }
1826
+
1827
+ CDataStream ssTx (SER_NETWORK, PROTOCOL_VERSION);
1828
+ ssTx << merged_psbt;
1829
+ return EncodeBase64 ((unsigned char *)ssTx.data (), ssTx.size ());
1830
+ }
1831
+
1758
1832
// clang-format off
1759
1833
static const CRPCCommand commands[] =
1760
1834
{ // category name actor (function) argNames
@@ -1774,6 +1848,7 @@ static const CRPCCommand commands[] =
1774
1848
{ " rawtransactions" , " createpsbt" , &createpsbt, {" inputs" ," outputs" ," locktime" ," replaceable" } },
1775
1849
{ " rawtransactions" , " converttopsbt" , &converttopsbt, {" hexstring" ," permitsigdata" ," iswitness" } },
1776
1850
{ " rawtransactions" , " utxoupdatepsbt" , &utxoupdatepsbt, {" psbt" } },
1851
+ { " rawtransactions" , " joinpsbts" , &joinpsbts, {" txs" } },
1777
1852
1778
1853
{ " blockchain" , " gettxoutproof" , &gettxoutproof, {" txids" , " blockhash" } },
1779
1854
{ " blockchain" , " verifytxoutproof" , &verifytxoutproof, {" proof" } },
0 commit comments