Repository: ordinals/ord (rust)

Issue:
`ord wallet inscribe` with multiple parents
ord allows inscriptions with multiple parents (#2494) but not by it's own command.

```
$ ord wallet inscribe ... --parent X --parent Y
error: the argument '--parent <PARENT>' cannot be used multiple times
```

Pull request description:
Add multi parent support to wallet
Closes #3586 and #2494

Patch:
diff --git a/batch.yaml b/batch.yaml
index 20c05b38..3d60bd79 100644
--- a/batch.yaml
+++ b/batch.yaml
@@ -7,8 +7,9 @@
 # - `shared-output`: inscribe on a single output separated by postage
 mode: separate-outputs
 
-# parent inscription:
-parent: 6ac5cacb768794f4fd7a78bf00f2074891fce68bd65c4ff36e77177237aacacai0
+# parent inscriptions:
+parents:
+- 6ac5cacb768794f4fd7a78bf00f2074891fce68bd65c4ff36e77177237aacacai0
 
 # postage for each inscription:
 postage: 12345
diff --git a/src/subcommand/wallet/batch_command.rs b/src/subcommand/wallet/batch_command.rs
index b5fd8374..6a5d6b7c 100644
--- a/src/subcommand/wallet/batch_command.rs
+++ b/src/subcommand/wallet/batch_command.rs
@@ -17,12 +17,12 @@ impl Batch {
 
     let batchfile = batch::File::load(&self.batch)?;
 
-    let parent_info = wallet.get_parent_info(batchfile.parent)?;
+    let parent_info = wallet.get_parent_info(&batchfile.parents)?;
 
     let (inscriptions, reveal_satpoints, postages, destinations) = batchfile.inscriptions(
       &wallet,
       utxos,
-      parent_info.as_ref().map(|info| info.tx_out.value),
+      parent_info.iter().map(|info| info.tx_out.value).collect(),
       self.shared.compress,
     )?;
 
@@ -204,7 +204,8 @@ mod tests {
       &batch_path,
       format!(
         "mode: separate-outputs
-parent: {parent}
+parents:
+- {parent}
 inscriptions:
 - file: {}
   metadata:
@@ -241,7 +242,7 @@ inscriptions:
             ..default()
           }
         ],
-        parent: Some(parent),
+        parents: vec![parent],
         ..default()
       }
     );
diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs
index 24cf80a7..93c1eebb 100644
--- a/src/subcommand/wallet/inscribe.rs
+++ b/src/subcommand/wallet/inscribe.rs
@@ -81,7 +81,8 @@ impl Inscribe {
       mode: batch::Mode::SeparateOutputs,
       no_backup: self.shared.no_backup,
       no_limit: self.shared.no_limit,
-      parent_info: wallet.get_parent_info(self.parent)?,
+      parent_info: wallet
+        .get_parent_info(&self.parent.map(|parent| vec![parent]).unwrap_or_default())?,
       postages: vec![self.postage.unwrap_or(TARGET_POSTAGE)],
       reinscribe: self.reinscribe,
       reveal_fee_rate: self.shared.fee_rate,
diff --git a/src/wallet.rs b/src/wallet.rs
index 1aa01b65..85c664d9 100644
--- a/src/wallet.rs
+++ b/src/wallet.rs
@@ -216,18 +216,16 @@ impl Wallet {
     )
   }
 
-  pub(crate) fn get_parent_info(
-    &self,
-    parent: Option<InscriptionId>,
-  ) -> Result<Option<ParentInfo>> {
-    if let Some(parent_id) = parent {
-      if !self.inscription_exists(parent_id)? {
+  pub(crate) fn get_parent_info(&self, parents: &[InscriptionId]) -> Result<Vec<ParentInfo>> {
+    let mut parent_info = Vec::new();
+    for parent_id in parents {
+      if !self.inscription_exists(*parent_id)? {
         return Err(anyhow!("parent {parent_id} does not exist"));
       }
 
       let satpoint = self
         .inscription_info
-        .get(&parent_id)
+        .get(parent_id)
         .ok_or_else(|| anyhow!("parent {parent_id} not in wallet"))?
         .satpoint;
 
@@ -237,15 +235,15 @@ impl Wallet {
         .ok_or_else(|| anyhow!("parent {parent_id} not in wallet"))?
         .clone();
 
-      Ok(Some(ParentInfo {
+      parent_info.push(ParentInfo {
         destination: self.get_change_address()?,
-        id: parent_id,
+        id: *parent_id,
         location: satpoint,
         tx_out,
-      }))
-    } else {
-      Ok(None)
+      });
     }
+
+    Ok(parent_info)
   }
 
   pub(crate) fn get_runic_outputs(&self) -> Result<BTreeSet<OutPoint>> {
diff --git a/src/wallet/batch.rs b/src/wallet/batch.rs
index 35b4903c..5158c9a0 100644
--- a/src/wallet/batch.rs
+++ b/src/wallet/batch.rs
@@ -34,7 +34,7 @@ pub struct Output {
   pub commit: Txid,
   pub commit_psbt: Option<String>,
   pub inscriptions: Vec<InscriptionInfo>,
-  pub parent: Option<InscriptionId>,
+  pub parents: Vec<InscriptionId>,
   pub reveal: Txid,
   pub reveal_broadcast: bool,
   pub reveal_psbt: Option<String>,
@@ -86,7 +86,7 @@ mod tests {
       ..
     } = batch::Plan {
       satpoint: Some(satpoint(1, 0)),
-      parent_info: None,
+      parent_info: Vec::new(),
       inscriptions: vec![inscription],
       destinations: vec![reveal_address],
       commit_fee_rate: FeeRate::try_from(1.0).unwrap(),
@@ -132,7 +132,7 @@ mod tests {
       ..
     } = batch::Plan {
       satpoint: Some(satpoint(1, 0)),
-      parent_info: None,
+      parent_info: Vec::new(),
       inscriptions: vec![inscription],
       destinations: vec![reveal_address],
       commit_fee_rate: FeeRate::try_from(1.0).unwrap(),
@@ -177,7 +177,7 @@ mod tests {
 
     let error = batch::Plan {
       satpoint,
-      parent_info: None,
+      parent_info: Vec::new(),
       inscriptions: vec![inscription],
       destinations: vec![reveal_address],
       commit_fee_rate: FeeRate::try_from(1.0).unwrap(),
@@ -229,7 +229,7 @@ mod tests {
 
     assert!(batch::Plan {
       satpoint,
-      parent_info: None,
+      parent_info: Vec::new(),
       inscriptions: vec![inscription],
       destinations: vec![reveal_address],
       commit_fee_rate: FeeRate::try_from(1.0).unwrap(),
@@ -279,7 +279,7 @@ mod tests {
       ..
     } = batch::Plan {
       satpoint,
-      parent_info: None,
+      parent_info: Vec::new(),
       inscriptions: vec![inscription],
       destinations: vec![reveal_address],
       commit_fee_rate: FeeRate::try_from(fee_rate).unwrap(),
@@ -367,7 +367,7 @@ mod tests {
       ..
     } = batch::Plan {
       satpoint: None,
-      parent_info: Some(parent_info.clone()),
+      parent_info: vec![parent_info.clone()],
       inscriptions: vec![child_inscription],
       destinations: vec![reveal_address],
       commit_fee_rate: FeeRate::try_from(fee_rate).unwrap(),
@@ -454,7 +454,7 @@ mod tests {
       ..
     } = batch::Plan {
       satpoint,
-      parent_info: None,
+      parent_info: Vec::new(),
       inscriptions: vec![inscription],
       destinations: vec![reveal_address],
       commit_fee_rate: FeeRate::try_from(commit_fee_rate).unwrap(),
@@ -513,7 +513,7 @@ mod tests {
 
     let error = batch::Plan {
       satpoint,
-      parent_info: None,
+      parent_info: Vec::new(),
       inscriptions: vec![inscription],
       destinations: vec![reveal_address],
       commit_fee_rate: FeeRate::try_from(1.0).unwrap(),
@@ -554,7 +554,7 @@ mod tests {
 
     let batch::Transactions { reveal_tx, .. } = batch::Plan {
       satpoint,
-      parent_info: None,
+      parent_info: Vec::new(),
       inscriptions: vec![inscription],
       destinations: vec![reveal_address],
       commit_fee_rate: FeeRate::try_from(1.0).unwrap(),
@@ -635,7 +635,7 @@ mod tests {
       ..
     } = batch::Plan {
       satpoint: None,
-      parent_info: Some(parent_info.clone()),
+      parent_info: vec![parent_info.clone()],
       inscriptions,
       destinations: reveal_addresses,
       commit_fee_rate: fee_rate,
@@ -766,7 +766,7 @@ mod tests {
       ..
     } = batch::Plan {
       reveal_satpoints: reveal_satpoints.clone(),
-      parent_info: Some(parent_info.clone()),
+      parent_info: vec![parent_info.clone()],
       inscriptions,
       destinations: reveal_addresses,
       commit_fee_rate: fee_rate,
@@ -868,7 +868,7 @@ mod tests {
 
     let error = batch::Plan {
       satpoint: None,
-      parent_info: Some(parent_info.clone()),
+      parent_info: vec![parent_info.clone()],
       inscriptions,
       destinations: reveal_addresses,
       commit_fee_rate: 4.0.try_into().unwrap(),
@@ -945,7 +945,7 @@ mod tests {
 
     let _ = batch::Plan {
       satpoint: None,
-      parent_info: Some(parent_info.clone()),
+      parent_info: vec![parent_info.clone()],
       inscriptions,
       destinations: reveal_addresses,
       commit_fee_rate: 4.0.try_into().unwrap(),
@@ -984,7 +984,7 @@ mod tests {
 
     let error = batch::Plan {
       satpoint: None,
-      parent_info: None,
+      parent_info: Vec::new(),
       inscriptions,
       destinations: reveal_addresses,
       commit_fee_rate: 1.0.try_into().unwrap(),
@@ -1042,7 +1042,7 @@ mod tests {
 
     let batch::Transactions { reveal_tx, .. } = batch::Plan {
       satpoint: None,
-      parent_info: None,
+      parent_info: Vec::new(),
       inscriptions,
       destinations: reveal_addresses,
       commit_fee_rate: fee_rate,
@@ -1131,7 +1131,7 @@ mod tests {
       ..
     } = batch::Plan {
       satpoint: None,
-      parent_info: Some(parent_info.clone()),
+      parent_info: vec![parent_info.clone()],
       inscriptions,
       destinations: reveal_addresses,
       commit_fee_rate: fee_rate,
diff --git a/src/wallet/batch/file.rs b/src/wallet/batch/file.rs
index b2f51dce..b839c9c8 100644
--- a/src/wallet/batch/file.rs
+++ b/src/wallet/batch/file.rs
@@ -5,7 +5,8 @@ use super::*;
 #[serde(deny_unknown_fields)]
 pub struct File {
   pub mode: Mode,
-  pub parent: Option<InscriptionId>,
+  #[serde(default)]
+  pub parents: Vec<InscriptionId>,
   pub postage: Option<u64>,
   #[serde(default)]
   pub reinscribe: bool,
@@ -108,7 +109,7 @@ impl File {
     &self,
     wallet: &Wallet,
     utxos: &BTreeMap<OutPoint, TxOut>,
-    parent_value: Option<u64>,
+    parents_values: Vec<u64>,
     compress: bool,
   ) -> Result<(
     Vec<Inscription>,
@@ -120,7 +121,7 @@ impl File {
     let mut reveal_satpoints = Vec::new();
     let mut postages = Vec::new();
 
-    let mut pointer = parent_value.unwrap_or_default();
+    let mut pointer = parents_values.iter().sum();
 
     for (i, entry) in self.inscriptions.iter().enumerate() {
       if let Some(delegate) = entry.delegate {
@@ -136,7 +137,7 @@ impl File {
         entry.delegate,
         entry.metadata()?,
         entry.metaprotocol.clone(),
-        self.parent.into_iter().collect(),
+        self.parents.clone(),
         entry.file.clone(),
         Some(pointer),
         self
@@ -367,11 +368,11 @@ inscriptions:
       batch::File::load(Path::new("batch.yaml")).unwrap(),
       batch::File {
         mode: batch::Mode::SeparateOutputs,
-        parent: Some(
+        parents: vec![
           "6ac5cacb768794f4fd7a78bf00f2074891fce68bd65c4ff36e77177237aacacai0"
             .parse()
             .unwrap()
-        ),
+        ],
         postage: Some(12345),
         reinscribe: true,
         sat: None,
diff --git a/src/wallet/batch/plan.rs b/src/wallet/batch/plan.rs
index 49d8a7d1..ce49ee60 100644
--- a/src/wallet/batch/plan.rs
+++ b/src/wallet/batch/plan.rs
@@ -9,7 +9,7 @@ pub struct Plan {
   pub(crate) mode: Mode,
   pub(crate) no_backup: bool,
   pub(crate) no_limit: bool,
-  pub(crate) parent_info: Option<ParentInfo>,
+  pub(crate) parent_info: Vec<ParentInfo>,
   pub(crate) postages: Vec<Amount>,
   pub(crate) reinscribe: bool,
   pub(crate) reveal_fee_rate: FeeRate,
@@ -28,7 +28,7 @@ impl Default for Plan {
       mode: Mode::SharedOutput,
       no_backup: false,
       no_limit: false,
-      parent_info: None,
+      parent_info: Vec::new(),
       postages: vec![Amount::from_sat(10_000)],
       reinscribe: false,
       reveal_fee_rate: 1.0.try_into().unwrap(),
@@ -206,19 +206,9 @@ impl Plan {
       let index = u32::try_from(i).unwrap();
 
       let vout = match self.mode {
-        Mode::SharedOutput | Mode::SameSat => {
-          if self.parent_info.is_some() {
-            1
-          } else {
-            0
-          }
-        }
+        Mode::SharedOutput | Mode::SameSat => self.parent_info.len().try_into().unwrap(),
         Mode::SeparateOutputs | Mode::SatPoints => {
-          if self.parent_info.is_some() {
-            index + 1
-          } else {
-            index
-          }
+          index + u32::try_from(self.parent_info.len()).unwrap()
         }
       };
 
@@ -252,7 +242,7 @@ impl Plan {
       commit,
       commit_psbt,
       inscriptions: inscriptions_output,
-      parent: self.parent_info.clone().map(|info| info.id),
+      parents: self.parent_info.iter().map(|info| info.id).collect(),
       reveal,
       reveal_broadcast,
       reveal_psbt,
@@ -271,10 +261,15 @@ impl Plan {
     commit_change: [Address; 2],
     reveal_change: Address,
   ) -> Result<Transactions> {
-    if let Some(parent_info) = &self.parent_info {
-      for inscription in &self.inscriptions {
-        assert_eq!(inscription.parents(), vec![parent_info.id]);
-      }
+    for inscription in &self.inscriptions {
+      assert_eq!(
+        inscription.parents(),
+        self
+          .parent_info
+          .iter()
+          .map(|info| info.id)
+          .collect::<Vec<InscriptionId>>()
+      );
     }
 
     match self.mode {
@@ -396,12 +391,12 @@ impl Plan {
     let mut reveal_inputs = Vec::new();
     let mut reveal_outputs = Vec::new();
 
-    if let Some(ParentInfo {
+    for ParentInfo {
       location,
       id: _,
       destination,
       tx_out,
-    }) = self.parent_info.clone()
+    } in &self.parent_info
     {
       reveal_inputs.push(location.outpoint);
       reveal_outputs.push(TxOut {
@@ -505,7 +500,7 @@ impl Plan {
       runestone = None;
     }
 
-    let commit_input = usize::from(self.parent_info.is_some()) + self.reveal_satpoints.len();
+    let commit_input = self.parent_info.len() + self.reveal_satpoints.len();
 
     let (_reveal_tx, reveal_fee) = Self::build_reveal_transaction(
       commit_input,
@@ -572,8 +567,8 @@ impl Plan {
 
     let mut prevouts = Vec::new();
 
-    if let Some(parent_info) = self.parent_info.clone() {
-      prevouts.push(parent_info.tx_out);
+    for parent_info in &self.parent_info {
+      prevouts.push(parent_info.tx_out.clone());
     }
 
     if self.mode == Mode::SatPoints {
diff --git a/src/wallet/entry.rs b/src/wallet/entry.rs
index 78331754..223c9302 100644
--- a/src/wallet/entry.rs
+++ b/src/wallet/entry.rs
@@ -76,7 +76,7 @@ mod tests {
       commit: txid,
       commit_psbt: None,
       inscriptions: Vec::new(),
-      parent: None,
+      parents: Vec::new(),
       reveal: txid,
       reveal_broadcast: true,
       reveal_psbt: None,


Test patch:
diff --git a/tests/wallet/batch_command.rs b/tests/wallet/batch_command.rs
index 781e3c06..4da3159e 100644
--- a/tests/wallet/batch_command.rs
+++ b/tests/wallet/batch_command.rs
@@ -145,7 +145,7 @@ fn batch_inscribe_with_multiple_inscriptions_with_parent() {
     .write("meow.wav", [0; 2048])
     .write(
       "batch.yaml",
-      format!("parent: {parent_id}\nmode: shared-output\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n")
+      format!("parents:\n- {parent_id}\nmode: shared-output\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n")
     )
     .core(&core)
     .ord(&ord)
@@ -168,6 +168,129 @@ fn batch_inscribe_with_multiple_inscriptions_with_parent() {
   assert_eq!(request.headers().get("content-type").unwrap(), "audio/wav");
 }
 
+#[test]
+fn batch_inscribe_inscriptions_with_multiple_parents() {
+  let core = mockcore::spawn();
+
+  let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
+
+  create_wallet(&core, &ord);
+
+  core.mine_blocks(1);
+
+  let parent_output_1 = CommandBuilder::new("wallet inscribe --fee-rate 5.0 --file parent.png")
+    .write("parent.png", [1; 520])
+    .core(&core)
+    .ord(&ord)
+    .run_and_deserialize_output::<Batch>();
+
+  core.mine_blocks(1);
+
+  let parent_output_2 = CommandBuilder::new("wallet inscribe --fee-rate 5.0 --file parent.png")
+    .write("parent.png", [1; 520])
+    .core(&core)
+    .ord(&ord)
+    .run_and_deserialize_output::<Batch>();
+
+  core.mine_blocks(1);
+
+  let parent_output_3 = CommandBuilder::new("wallet inscribe --fee-rate 5.0 --file parent.png")
+    .write("parent.png", [1; 520])
+    .core(&core)
+    .ord(&ord)
+    .run_and_deserialize_output::<Batch>();
+
+  core.mine_blocks(1);
+
+  let parent_id_1 = parent_output_1.inscriptions[0].id;
+  let parent_id_2 = parent_output_2.inscriptions[0].id;
+  let parent_id_3 = parent_output_3.inscriptions[0].id;
+
+  let output = CommandBuilder::new("wallet batch --fee-rate 1 --batch batch.yaml")
+    .write("inscription.txt", "Hello World")
+    .write("tulip.png", [0; 555])
+    .write("meow.wav", [0; 2048])
+    .write(
+      "batch.yaml",
+      format!("parents:\n- {parent_id_1}\n- {parent_id_2}\n- {parent_id_3}\nmode: separate-outputs\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n")
+    )
+    .core(&core)
+    .ord(&ord)
+    .run_and_deserialize_output::<Batch>();
+
+  core.mine_blocks(1);
+
+  ord.assert_response_regex(
+    format!("/inscription/{}", output.inscriptions[0].id),
+    format!(r".*<dt>parents</dt>\s*<dd>.*{parent_id_1}.*{parent_id_2}.*{parent_id_3}.*",),
+  );
+
+  ord.assert_response_regex(
+    format!("/inscription/{}", output.inscriptions[1].id),
+    format!(r".*<dt>parents</dt>\s*<dd>.*{parent_id_1}.*{parent_id_2}.*{parent_id_3}.*",),
+  );
+}
+
+#[test]
+fn batch_inscribe_and_etch_with_two_parents() {
+  let core = mockcore::spawn();
+
+  let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
+
+  create_wallet(&core, &ord);
+
+  core.mine_blocks(1);
+
+  let parent_output_1 = CommandBuilder::new("wallet inscribe --fee-rate 5.0 --file parent.png")
+    .write("parent.png", [1; 520])
+    .core(&core)
+    .ord(&ord)
+    .run_and_deserialize_output::<Batch>();
+
+  core.mine_blocks(1);
+
+  let parent_output_2 = CommandBuilder::new("wallet inscribe --fee-rate 5.0 --file parent.png")
+    .write("parent.png", [1; 520])
+    .core(&core)
+    .ord(&ord)
+    .run_and_deserialize_output::<Batch>();
+
+  core.mine_blocks(1);
+
+  assert_eq!(core.descriptors().len(), 4);
+
+  let parent_id_1 = parent_output_1.inscriptions[0].id;
+  let parent_id_2 = parent_output_2.inscriptions[0].id;
+
+  let output = CommandBuilder::new("wallet batch --fee-rate 1 --batch batch.yaml")
+    .write("inscription.txt", "Hello World")
+    .write("tulip.png", [0; 555])
+    .write("meow.wav", [0; 2048])
+    .write(
+      "batch.yaml",
+      format!("parents:\n- {parent_id_1}\n- {parent_id_2}\nmode: shared-output\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n")
+    )
+    .core(&core)
+    .ord(&ord)
+    .run_and_deserialize_output::<Batch>();
+
+  core.mine_blocks(1);
+
+  ord.assert_response_regex(
+    format!("/inscription/{}", output.inscriptions[0].id),
+    format!(r".*<dt>parents</dt>\s*<dd>.*{parent_id_1}.*{parent_id_2}.*",),
+  );
+
+  ord.assert_response_regex(
+    format!("/inscription/{}", output.inscriptions[1].id),
+    format!(r".*<dt>parents</dt>\s*<dd>.*{parent_id_1}.*{parent_id_2}.*",),
+  );
+
+  let request = ord.request(format!("/content/{}", output.inscriptions[2].id));
+  assert_eq!(request.status(), 200);
+  assert_eq!(request.headers().get("content-type").unwrap(), "audio/wav");
+}
+
 #[test]
 fn batch_inscribe_respects_dry_run_flag() {
   let core = mockcore::spawn();
@@ -342,7 +465,7 @@ fn batch_in_separate_outputs_with_parent() {
     .write("meow.wav", [0; 2048])
     .write(
       "batch.yaml",
-      format!("parent: {parent_id}\nmode: separate-outputs\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n")
+      format!("parents:\n- {parent_id}\nmode: separate-outputs\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n")
     )
     .core(&core)
     .ord(&ord)
@@ -351,6 +474,7 @@ fn batch_in_separate_outputs_with_parent() {
   for inscription in &output.inscriptions {
     assert_eq!(inscription.location.offset, 0);
   }
+
   let mut outpoints = output
     .inscriptions
     .iter()
@@ -419,7 +543,7 @@ fn batch_in_separate_outputs_with_parent_and_non_default_postage() {
     .write("meow.wav", [0; 2048])
     .write(
       "batch.yaml",
-      format!("parent: {parent_id}\nmode: separate-outputs\npostage: 777\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n")
+      format!("parents:\n- {parent_id}\nmode: separate-outputs\npostage: 777\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n")
     )
     .core(&core)
     .ord(&ord)
@@ -661,7 +785,7 @@ fn batch_same_sat_with_parent() {
     .write("meow.wav", [0; 2048])
     .write(
       "batch.yaml",
-      format!("mode: same-sat\nparent: {parent_id}\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n")
+      format!("mode: same-sat\nparents:\n- {parent_id}\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n")
     )
     .core(&core)
     .ord(&ord)
@@ -821,7 +945,7 @@ fn batch_inscribe_with_sat_argument_with_parent() {
     .write("meow.wav", [0; 2048])
     .write(
       "batch.yaml",
-      format!("parent: {parent_id}\nmode: same-sat\nsat: 5000111111\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n")
+      format!("parents:\n- {parent_id}\nmode: same-sat\nsat: 5000111111\ninscriptions:\n- file: inscription.txt\n- file: tulip.png\n- file: meow.wav\n")
     )
     .core(&core)
     .ord(&ord)
@@ -1115,7 +1239,8 @@ fn batch_inscribe_with_satpoints_with_parent() {
       format!(
         r#"
 mode: satpoints
-parent: {parent_id}
+parents:
+- {parent_id}
 inscriptions:
 - file: inscription.txt
   satpoint: {}
diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs
index 36d063e9..b65af8fc 100644
--- a/tests/wallet/inscribe.rs
+++ b/tests/wallet/inscribe.rs
@@ -623,7 +623,7 @@ fn inscribe_with_parent_inscription_and_fee_rate() {
   .run_and_deserialize_output::<Batch>();
 
   assert_eq!(core.descriptors().len(), 4);
-  assert_eq!(parent_id, child_output.parent.unwrap());
+  assert_eq!(parent_id, *child_output.parents.first().unwrap());
 
   let commit_tx = &core.mempool()[0];
   let reveal_tx = &core.mempool()[1];
@@ -639,7 +639,7 @@ fn inscribe_with_parent_inscription_and_fee_rate() {
   core.mine_blocks(1);
 
   ord.assert_response_regex(
-    format!("/inscription/{}", child_output.parent.unwrap()),
+    format!("/inscription/{}", child_output.parents.first().unwrap()),
     format!(
       ".*<dt>children</dt>.*<a href=/inscription/{}>.*",
       child_output.inscriptions[0].id
@@ -650,7 +650,7 @@ fn inscribe_with_parent_inscription_and_fee_rate() {
     format!("/inscription/{}", child_output.inscriptions[0].id),
     format!(
       ".*<dt>parents</dt>.*<a href=/inscription/{}>.*",
-      child_output.parent.unwrap()
+      child_output.parents.first().unwrap()
     ),
   );
 }