Skip to content

Commit

Permalink
Experimental Ordinals Support
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremyRubin committed Jan 20, 2024
1 parent d2fe87d commit 8f22058
Show file tree
Hide file tree
Showing 26 changed files with 318 additions and 42 deletions.
4 changes: 4 additions & 0 deletions cli/src/contracts/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ pub struct Bind {
pub outpoint: Option<OutPoint>,
pub use_txn: Option<String>,
pub compiled: Compiled,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub ordinals_info: Option<Vec<(u64, u64)>>
}
pub type BindReturn = Program;
#[derive(Serialize, Deserialize, JsonSchema)]
Expand Down Expand Up @@ -271,6 +273,7 @@ impl Bind {
use_txn,
compiled,
outpoint,
ordinals_info
} = self;
let use_txn = use_txn
.map(|buf| base64::decode(buf.as_bytes()))
Expand All @@ -285,6 +288,7 @@ impl Bind {
emulator.clone(),
"mock".try_into()?,
Arc::new(MapEffectDB::default()),
ordinals_info
);
let mut tx = ctx
.template()
Expand Down
2 changes: 2 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ async fn bind_command(
client_auth: bitcoincore_rpc_async::Auth,
) -> Result<Command, Box<dyn Error>> {
let use_mock = args.is_present("mock");
let ordinals_info = None;
let use_base64 = args.is_present("base64_psbt");
let outpoint: Option<bitcoin::OutPoint> = args
.value_of("outpoint")
Expand All @@ -520,5 +521,6 @@ async fn bind_command(
outpoint,
use_txn,
compiled,
ordinals_info
}))
}
3 changes: 1 addition & 2 deletions examples/dcf_mining_pool/src/miner_payout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,7 @@ impl MiningPayout {
for pay in v.iter() {
builder = builder.add_output(pay.0, pay.1.as_compilable(), None)?;
}
builder = builder.add_fees(self.fee_sats_per_tx)?;
return builder.into();
return builder.add_fees(self.fee_sats_per_tx)?.into();
} else {
let pay = Box::new(PayoutBundle {
contracts: v,
Expand Down
3 changes: 2 additions & 1 deletion plugin-example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ members = ["treepay"
, "nft-sale"
, "nft-auction"
, "clause-module"
, "clause-module-trampoline"]
, "clause-module-trampoline"
, "ordinal-example"]
3 changes: 2 additions & 1 deletion plugin-example/clause-module-trampoline/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ impl Callable for Wrapper {
amount: ctx.funds(),
network: ctx.network,
effects: unsafe { ctx.get_effects_internal() }.as_ref().clone(),
ordinals_info: ctx.get_ordinals().clone()
},
arguments: self.g.clone(),
};
self.v.call(ctx.path(), &create_args)
self.v.clone().call(ctx.path(), &create_args)
}
}

Expand Down
4 changes: 2 additions & 2 deletions plugin-example/nft-auction/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ use schemars::*;
use serde::*;
use std::convert::TryFrom;


use std::sync::Arc;

/// # Dutch Auction Data
Expand Down Expand Up @@ -145,7 +144,7 @@ impl NFTDutchAuction {
let amt = ctx.funds();
// first, let's get the module that should be used to 're-mint' this NFT
// to the new owner
let minting_module = self
let mut minting_module = self
.main
.data
.minting_module
Expand All @@ -162,6 +161,7 @@ impl NFTDutchAuction {
amount: ctx.funds(),
network: ctx.network,
effects: unsafe { ctx.get_effects_internal() }.as_ref().clone(),
ordinals_info: ctx.get_ordinals().clone(),
},
arguments: mint_impl::Versions::Mint_NFT_Trait_Version_0_1_0(mint_data),
};
Expand Down
5 changes: 3 additions & 2 deletions plugin-example/nft-sale/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ impl SimpleNFTSale {
let amt = ctx.funds();
// first, let's get the module that should be used to 're-mint' this NFT
// to the new owner
let minting_module =
let mut minting_module =
self.0.data.minting_module.as_ref().ok_or_else(|| {
CompilationError::TerminateWith("Must Provide Module Hash".into())
})?;
})?.clone();
// let's make a copy of the old nft metadata..
let mut mint_data = self.0.data.clone();
// and change the owner to the buyer
Expand All @@ -68,6 +68,7 @@ impl SimpleNFTSale {
amount: ctx.funds(),
network: ctx.network,
effects: unsafe { ctx.get_effects_internal() }.as_ref().clone(),
ordinals_info: ctx.get_ordinals().clone(),
},
arguments: mint_impl::Versions::Mint_NFT_Trait_Version_0_1_0(mint_data),
};
Expand Down
2 changes: 1 addition & 1 deletion plugin-example/nft-trait/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ serde_derive = "1.0"

[dependencies.simp-pack]
path = "../../simp-pack"
version = "0.1.0"
version = "0.2.4"
[dependencies.schemars]
version = "0.8.0"
features = ['impl_json_schema']
Expand Down
3 changes: 2 additions & 1 deletion plugin-example/nft/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl SellableNFT for SimpleNFT {
fn sell(self, mut ctx: Context, sale: Sell) {
if let Sell::MakeSale {
sale_info_partial,
which_sale,
mut which_sale,
} = sale
{
let sale_info = sale_info_partial.fill(self.data.clone());
Expand All @@ -83,6 +83,7 @@ impl SellableNFT for SimpleNFT {
amount: ctx.funds(),
network: ctx.network,
effects: unsafe { ctx.get_effects_internal() }.as_ref().clone(),
ordinals_info: ctx.get_ordinals().clone(),
},
arguments: sale_impl::Versions::NFT_Sale_Trait_Version_0_1_0(sale_info.clone()),
};
Expand Down
66 changes: 66 additions & 0 deletions plugin-example/ordinal-example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
[package]
name = "sapio-wasm-ordinal-example"
version = "0.1.0"
license = "MPL-2.0"
authors = ["Jeremy Rubin <[email protected]>"]
edition = "2021"
repository = "https://github.com/sapio-lang/sapio"
homepage = "https://sapio-lang.org"
description = "An Example Sapio Application"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib", "rlib"]
path = "src/plugin.rs"
[package.metadata.wasm-pack.profile.release]
wasm-opt = false

[dependencies]
serde_json = "1.0"
serde = "1.0"
serde_derive = "1.0"


[dependencies.schemars]
version = "0.8.0"
features = ['impl_json_schema']
[dependencies.bitcoin]
package = "sapio-bitcoin"
version = "0.28.0"
features = ['use-serde']
[dependencies.sapio]
path = "../../sapio"
version = "0.2.0"

[dependencies.batching-trait]
path = "../batching-trait"
version = "0.1.0"

[dependencies.sapio-base]
path = "../../sapio-base"
version = "0.2.0"
[dependencies.sapio-contrib]
path = "../../sapio-contrib"
version = "0.2.0"


[dependencies.sapio-ctv-emulator-trait]
path = "../../emulator-trait"
version = "0.2.0"

[dependencies.miniscript]
package = "sapio-miniscript"
version = "^7.0.0"
features = ['compiler', 'use-serde', 'use-schemars', 'serde']
optional = true


[dependencies.sapio-wasm-plugin]
path = "../../plugins"
version = "0.2.0"
features = ["client"]

[dependencies.sapio-wasm-nft-trait]
path = "../nft-trait"
version = "0.1.0"
6 changes: 6 additions & 0 deletions plugin-example/ordinal-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Sapio Vault Example

This crate can be compiled with `wasm-pack build`. The `*.wasm` artifact will
be created in the `pkg` directory, not in `target`.

Feel free to modify this code to experiment with creating your own Sapio plugins.
Binary file added plugin-example/ordinal-example/src/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
116 changes: 116 additions & 0 deletions plugin-example/ordinal-example/src/plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use bitcoin::hashes::hex::ToHex;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash;
use bitcoin::util::amount::Amount;
use bitcoin::XOnlyPublicKey;
use sapio::contract::empty;
use sapio::contract::object::ObjectMetadata;
use sapio::contract::CompilationError;
use sapio::contract::Compiled;
use sapio::contract::Contract;
use sapio::contract::StatefulArgumentsTrait;
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)]
pub struct SimpleOrdinal {
ordinal: u64,
#[schemars(with = "bitcoin::hashes::sha256::Hash")]
owner: XOnlyPublicKey,
}
#[derive(JsonSchema, Serialize, Deserialize)]
pub struct Sell {
purchaser: bitcoin::Address,
amount: AmountF64,
change: AmountF64,
fee: AmountF64,
}

#[derive(JsonSchema, Serialize, Deserialize, Default)]
pub struct Sale(Option<Sell>);
// ASSUMES 500 sats after Ord are "dust"
impl SimpleOrdinal {
#[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
.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;
break;
} else {
index += b - a
}
}
let mut t = ctx.template();
// TODO: Check Index calculation
if index != 0 {
t = t.add_output(Amount::from_sat(index - 1), &self.owner, None)?;
}
let buyer = Compiled::from_address(sale.purchaser, None);
t = t.add_output(Amount::from_sat(501), &buyer, None)?;
let remaining = t.ctx().funds();
t = t.add_amount(sale.amount.into());
t = t.add_output(remaining + sale.amount.into(), &self.owner, None)?;
t = t.add_sequence();
t = t.add_amount(sale.change.into());
t = t.add_output(sale.change.into(), &buyer, None)?;
t = t.add_amount(sale.fee.into());
t.add_fees(sale.fee.into())?.into()
} else {
empty()
}
}
}
impl StatefulArgumentsTrait for Sale {}

/// # The SimpleNFT Contract
impl Contract for SimpleOrdinal {
// Ordinals... only good for selling?
declare! {updatable<Sale>, Self::sell}

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)) {
Ok(Amount::from_sat(1 + 500))
} else {
Err(CompilationError::OrdinalsError(
"Missing Intended Ordinal".into(),
))
}
}
}

impl SimpleOrdinal {
/// # signed
/// Get the current owners signature.
#[guard]
fn signed(self, _ctx: Context) {
Clause::Key(self.owner.clone())
}
}
fn default_coerce(
k: <SimpleOrdinal as sapio::contract::Contract>::StatefulArguments,
) -> Result<Sale, CompilationError> {
Ok(k)
}

REGISTER![SimpleOrdinal, "logo.png"];
3 changes: 2 additions & 1 deletion plugin-example/trampolinepay/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ pub struct TrampolinePay {
impl TrampolinePay {
#[then]
fn expand(self, mut ctx: Context) {
let contract = self.handle.call(
let contract = self.handle.clone().call(
&ctx.derive_str(Arc::new("plugin_trampoline".into()))?.path(),
&CreateArgs {
context: ContextualArguments {
amount: ctx.funds(),
network: ctx.network,
effects: unsafe { ctx.get_effects_internal() }.as_ref().clone(),
ordinals_info: ctx.get_ordinals().clone(),
},
arguments: batching_trait::Versions::BatchingTraitVersion0_1_1(self.data.clone()),
},
Expand Down
3 changes: 1 addition & 2 deletions plugin-example/treepay/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,7 @@ impl TreePay {
builder = builder.set_sequence(0, timelock)?;
}
}
builder = builder.add_fees(self.fee_sats_per_tx)?;
return builder.into();
return builder.add_fees(self.fee_sats_per_tx)?.into();
} else {
let pay = Box::new(PayThese {
contracts: v,
Expand Down
2 changes: 2 additions & 0 deletions plugins/src/client/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ where
network,
amount,
effects,
ordinals_info
},
} = serde_json::from_slice(s.to_bytes()).map_err(CompilationError::DeserializationError)?;
// TODO: In theory, these trampoline bounds are robust/serialization safe...
Expand Down Expand Up @@ -105,6 +106,7 @@ where
path,
// TODO: load database?
Arc::new(effects),
ordinals_info
);
let converted = Self::try_from(arguments)?;
converted.call(ctx)
Expand Down
4 changes: 4 additions & 0 deletions sapio-base/src/plugin_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,8 @@ pub struct ContextualArguments {
/// # Effects to augment compilations with
#[serde(skip_serializing_if = "MapEffectDB::skip_serializing", default)]
pub effects: MapEffectDB,

/// # the ranges of ordinals held in the input
#[serde(skip_serializing_if = "Option::is_none", default)]
pub ordinals_info: Option<Vec<(u64, u64)>>
}
1 change: 1 addition & 0 deletions sapio-contrib/src/contracts/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ mod tests {
std::sync::Arc::new(CTVAvailable),
"root".try_into().unwrap(),
Default::default(),
None
);
Compilable::compile(&x, ctx.derive_str(Arc::new("X".into())).unwrap()).ok();
Compilable::compile(&y, ctx.derive_str(Arc::new("Y".into())).unwrap()).ok();
Expand Down
1 change: 1 addition & 0 deletions sapio-contrib/src/contracts/derivatives/dlc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ mod tests {
Arc::new(CTVAvailable),
EffectPath::try_from("dlc").unwrap(),
Arc::new(Default::default()),
None
);
let _r = d.compile(ctx).unwrap();
}
Expand Down
Loading

0 comments on commit 8f22058

Please sign in to comment.