Skip to content

Commit

Permalink
Add Ordinal TX Planner Module
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremyRubin committed Jan 21, 2024
1 parent 8f22058 commit 1de7a1e
Show file tree
Hide file tree
Showing 5 changed files with 369 additions and 30 deletions.
64 changes: 47 additions & 17 deletions plugin-example/ordinal-example/src/plugin.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
use bitcoin::hashes::hex::ToHex;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash;
use std::collections::BTreeMap;

use bitcoin::util::amount::Amount;
use bitcoin::XOnlyPublicKey;
use sapio::contract::empty;
use sapio::contract::object::ObjectMetadata;
use sapio::contract::Compilable;
use sapio::contract::CompilationError;
use sapio::contract::Compiled;
use sapio::contract::Contract;
use sapio::contract::StatefulArgumentsTrait;
use sapio::ordinals::OrdinalPlanner;
use sapio::ordinals::OrdinalSpec;
use sapio::util::amountrange::AmountF64;
use sapio::*;
use sapio_base::Clause;
use sapio_wasm_nft_trait::*;
use sapio_wasm_plugin::client::*;
use sapio_wasm_plugin::plugin_handle::PluginHandle;
use sapio_wasm_plugin::*;
use schemars::*;
use serde::*;
use std::convert::TryFrom;
use std::convert::TryInto;
use std::sync::Arc;
/// # SimpleOrdinal
/// A really Ordinal Bearing Contract
#[derive(JsonSchema, Serialize, Deserialize)]
Expand All @@ -39,22 +34,57 @@ pub struct Sell {

#[derive(JsonSchema, Serialize, Deserialize, Default)]
pub struct Sale(Option<Sell>);
fn multimap<T: Ord + PartialOrd + Eq + Clone, U: Clone, const N: usize>(
v: [(T, U); N],
) -> BTreeMap<T, Vec<U>> {
let mut ret = BTreeMap::new();
for (t, u) in v.into_iter() {
ret.entry(t.clone()).or_insert(vec![]).push(u)
}
ret
}
// ASSUMES 500 sats after Ord are "dust"
impl SimpleOrdinal {
#[continuation(guarded_by = "[Self::signed]", web_api, coerce_args = "default_coerce")]
fn sell_with_planner(self, ctx: Context, opt_sale: Sale) {
if let Sale(Some(sale)) = opt_sale {
if let Some(ords) = ctx.get_ordinals().clone() {
let plan = ords.output_plan(&OrdinalSpec {
payouts: vec![sale.amount.into()],
payins: vec![Amount::from(sale.amount) + sale.fee.into() + sale.change.into()],
fees: sale.fee.into(),
ordinals: [Ordinal(self.ordinal)].into(),
})?;
let buyer: &dyn Compilable = &Compiled::from_address(sale.purchaser, None);
return plan
.build_plan(
ctx,
multimap([
(sale.amount.into(), (&self.owner, None)),
(sale.change.into(), (buyer, None)),
]),
[(Ordinal(self.ordinal), (buyer, None))].into(),
(&self.owner, None),
)?
.into();
}
}
empty()
}
#[continuation(guarded_by = "[Self::signed]", web_api, coerce_args = "default_coerce")]
fn sell(self, ctx: Context, opt_sale: Sale) {
if let Sale(Some(sale)) = opt_sale {
let o = ctx
let ords = ctx
.get_ordinals()
.as_ref()
.ok_or_else(|| CompilationError::OrdinalsError("Missing Ordinals Info".into()))?;
let mut index = 0;
for (a, b) in o.iter() {
if (*a..*b).contains(&self.ordinal) {
index += (self.ordinal) - a;
for (a, b) in ords.0.iter() {
if (*a..*b).contains(&Ordinal(self.ordinal)) {
index += self.ordinal - a.0;
break;
} else {
index += b - a
index += b.0 - a.0
}
}
let mut t = ctx.template();
Expand Down Expand Up @@ -82,14 +112,14 @@ impl StatefulArgumentsTrait for Sale {}
/// # The SimpleNFT Contract
impl Contract for SimpleOrdinal {
// Ordinals... only good for selling?
declare! {updatable<Sale>, Self::sell}
declare! {updatable<Sale>, Self::sell, Self::sell_with_planner}

fn ensure_amount(&self, ctx: Context) -> Result<Amount, CompilationError> {
let ords = ctx
.get_ordinals()
.as_ref()
.ok_or_else(|| CompilationError::OrdinalsError("Missing Ordinals Info".into()))?;
if ords.iter().any(|(a, b)| (*a..*b).contains(&self.ordinal)) {
if ords.0.iter().any(|(a, b)| (*a..*b).contains(&Ordinal(self.ordinal))) {
Ok(Amount::from_sat(1 + 500))
} else {
Err(CompilationError::OrdinalsError(
Expand Down
20 changes: 19 additions & 1 deletion sapio-base/src/plugin_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

//! arguments for passing into a sapio module
use crate::effects::MapEffectDB;
use bitcoin::Amount;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -51,5 +52,22 @@ pub struct ContextualArguments {

/// # the ranges of ordinals held in the input
#[serde(skip_serializing_if = "Option::is_none", default)]
pub ordinals_info: Option<Vec<(u64, u64)>>
pub ordinals_info: Option<OrdinalsInfo>,
}

/// Struct to contain Ordinal ID
#[derive(
Serialize, Deserialize, Eq, Ord, PartialEq, PartialOrd, Clone, Copy, Debug, JsonSchema,
)]
pub struct Ordinal(pub u64);

impl Ordinal {
/// How much padding in sats to require
/// TODO: Flexible padding
pub fn padding(&self) -> Amount {
Amount::from_sat(500)
}
}
/// Struct to contain Ordinal Spans
#[derive(Serialize, Deserialize, Eq, Ord, PartialEq, PartialOrd, Clone, Debug, JsonSchema)]
pub struct OrdinalsInfo(pub Vec<(Ordinal, Ordinal)>);
26 changes: 14 additions & 12 deletions sapio/src/contract/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
//! general non-parameter compilation state required by all contracts
use super::{Amount, Compilable, CompilationError, Compiled};
use crate::contract::compiler::InternalCompilerTag;
use crate::ordinals::Ordinal;
use crate::ordinals::OrdinalsInfo;

use bitcoin::Network;

Expand All @@ -33,30 +35,30 @@ pub struct Context {
path: Arc<EffectPath>,
already_derived: HashSet<PathFragment>,
effects: Arc<MapEffectDB>,
ordinals_info: Option<Vec<(u64, u64)>>,
ordinals_info: Option<OrdinalsInfo>,
}

fn allocate_ordinals(a: Amount, ords: &Vec<(u64, u64)>) -> [Vec<(u64, u64)>; 2] {
fn allocate_ordinals(a: Amount, ords: &OrdinalsInfo) -> [OrdinalsInfo; 2] {
let mut amt = a.as_sat();
let mut ret = [vec![], vec![]];
for (start, end) in ords.iter().copied() {
let sats = end - start;
let mut ret = [OrdinalsInfo(vec![]), OrdinalsInfo(vec![])];
for (start, end) in ords.0.iter().copied() {
let sats = end.0 - start.0;
if sats <= amt {
amt -= sats;
ret[0].push((start, end))
ret[0].0.push((start, end))
} else {
if sats != 0 {
ret[0].push((start, start + sats));
ret[0].0.push((start, Ordinal(start.0 + sats)));
}
ret[1].push((start + sats, end))
ret[1].0.push((Ordinal(start.0 + sats), end))
}
}
ret
}

impl Context {
/// Borrow the Ordinals Info
pub fn get_ordinals(&self) -> &Option<Vec<(u64, u64)>> {
pub fn get_ordinals(&self) -> &Option<OrdinalsInfo> {
&self.ordinals_info
}
/// create a context instance. Should only happen *once* at the very top
Expand All @@ -67,7 +69,7 @@ impl Context {
emulator: Arc<dyn CTVEmulator>,
path: EffectPath,
effects: Arc<MapEffectDB>,
ordinals_info: Option<Vec<(u64, u64)>>,
ordinals_info: Option<OrdinalsInfo>,
) -> Self {
Context {
available_funds,
Expand Down Expand Up @@ -171,7 +173,7 @@ impl Context {
effects: self.effects.clone(),
ordinals_info: self.ordinals_info.as_ref().map(|o| {
let mut a = allocate_ordinals(amount, o);
let mut v = vec![];
let mut v = OrdinalsInfo(vec![]);
mem::swap(&mut a[0], &mut v);
v
}),
Expand All @@ -187,7 +189,7 @@ impl Context {

self.ordinals_info = self.ordinals_info.as_ref().map(|o| {
let mut a = allocate_ordinals(amount, o);
let mut v = vec![];
let mut v = OrdinalsInfo(vec![]);
mem::swap(&mut a[1], &mut v);
v
});
Expand Down
1 change: 1 addition & 0 deletions sapio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ pub use sapio_base;
pub use sapio_macros;
pub use sapio_macros::*;
pub use schemars;
pub mod ordinals;
Loading

0 comments on commit 1de7a1e

Please sign in to comment.