Created
January 31, 2025 17:10
-
-
Save SilentCicero/99a5cf0621104a174e564ce194304762 to your computer and use it in GitHub Desktop.
An OTC order as a Sway Predicate that allows for partial fill.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
predicate; | |
use std::{ | |
inputs::*, | |
outputs::*, | |
hash::*, | |
auth::predicate_address | |
}; | |
configurable { | |
ORDER_HASH: b256 = 0x09c0b2d1a486c439a87bcba6b46a7a1a23f3897cc83a94521a96da5c23bc58db, | |
} | |
fn main(minimal_output_amount: u64, out_index: u64, cancel: Option<(u64, u64)>) -> bool { | |
let in_count = input_count().as_u64(); | |
let out_count = output_count().as_u64(); | |
let this_predicate = predicate_address().unwrap(); | |
let out_asset = output_asset_id(out_index).unwrap(); | |
let mut in_asset = AssetId::default(); | |
let mut input_index = 0; | |
let mut output_index = 0; | |
let mut total_in = 0; | |
let mut total_out_recipient_in: u64 = 0; | |
let mut total_out_recipient_out: u64 = 0; | |
let mut total_out_predicate_in: u64 = 0; | |
let mut will_cancel: bool = false; | |
let recipient_address = match input_coin_owner(out_index) { | |
Some(recipient) => recipient, | |
None => Address::zero(), | |
}; | |
while input_index < in_count { | |
let is_predicate = match input_coin_owner(input_index) { | |
Some(predicate_address) => predicate_address == this_predicate, | |
None => false, | |
}; | |
let is_coin = match input_type(input_index).unwrap() { | |
Input::Coin => true, | |
_ => false, | |
}; | |
let is_recipient = match input_coin_owner(input_index) { | |
Some(recipient) => recipient == recipient_address, | |
None => false, | |
}; | |
let is_in_asset = match input_asset_id(input_index) { | |
Some(asset_id) => { | |
if in_asset == AssetId::default() && is_predicate && is_coin { | |
in_asset = asset_id; | |
} | |
return in_asset == asset_id; | |
}, | |
None => false, | |
}; | |
if is_coin && is_predicate && is_in_asset { | |
let amount = input_amount(input_index).unwrap(); | |
total_in = total_in + amount; | |
} | |
match cancel { | |
Some(_) => { | |
if is_recipient { | |
will_cancel = true; | |
} | |
}, | |
None => {}, | |
} | |
input_index = input_index + 1; | |
} | |
while output_index < out_count { | |
let is_coin = match output_type(output_index).unwrap() { | |
Output::Coin => true, | |
_ => false, | |
}; | |
let is_predicate = match output_asset_to(output_index) { | |
Some(owner_address) => owner_address == this_predicate, | |
None => false, | |
}; | |
let is_recipient = match output_asset_to(output_index) { | |
Some(recipient) => recipient == recipient_address, | |
None => false, | |
}; | |
let is_in_asset = match output_asset_id(output_index) { | |
Some(asset_id) => asset_id == in_asset, | |
None => false, | |
}; | |
let is_out_asset = match output_asset_id(output_index) { | |
Some(asset_id) => asset_id == out_asset, | |
None => false, | |
}; | |
if (is_coin) { | |
let amount = output_amount(output_index).unwrap(); | |
if is_predicate && is_in_asset { | |
total_out_predicate_in = total_out_predicate_in + amount; | |
} | |
if is_recipient && is_in_asset { | |
total_out_recipient_in = total_out_recipient_in + amount; | |
} | |
if is_recipient && is_out_asset { | |
assert(amount >= minimal_output_amount); | |
total_out_recipient_out = total_out_recipient_out + amount; | |
} | |
} | |
output_index = output_index + 1; | |
} | |
let mut numerator = 0; | |
let mut denominator = 0; | |
match cancel { | |
Some(values) => { | |
numerator = values.0; | |
denominator = values.1; | |
}, | |
None => { | |
let mut gcd = total_in; | |
let mut b = total_out_recipient_out; | |
let mut remainder = 0; | |
while (b != 0) { | |
remainder = gcd % b; | |
gcd = b; | |
b = remainder; | |
} | |
numerator = total_in / gcd; | |
denominator = total_out_recipient_out / gcd; | |
} | |
} | |
let remainder_is_correct = u256::from(total_in) - (u256::from(total_in) * u256::from(numerator)) / u256::from(denominator) == u256::from(total_out_predicate_in); | |
sha256(( | |
(numerator, denominator, minimal_output_amount), | |
(recipient_address, in_asset, out_asset) | |
)) == ORDER_HASH && ((will_cancel && total_in >= total_out_recipient_in) || remainder_is_correct) | |
} | |
/* | |
Details: | |
- 157 LOC | |
- Compressed using an order hash (meaning minimal bytes over the wire aside from the blob setup) | |
- Supports orders from any wallet (EVM, Bako, etc.) | |
- Supports paid cancellation | |
- Supports partial fill | |
- Multiple different orders from different parties and assets can be compressed into the same transaction | |
- No signature checking required (reduces execution overhead per order) | |
- Supports swapping between any native asset | |
- Dust order prevention with a minimum output amount threshold | |
- Uses the transaction data as a way to rehydrate key values such as swap/return amount, recipient address and asset ids | |
- Can support multiple inputs of same kind and multiple outputs of same kind | |
- The order hash enforces that the price is correct, the asset ids are what is requested for the pair and the proper recipient id is used | |
How it Works: | |
- Instead of specifying the price you want, you specify the ratio between your asset and the ask asset (think uniswap pools) | |
- So long as the ratio is followed and the right amount is returned to the order predicate, the user can spend/complete the order | |
- In the event of cancellation, all funds must be returned to the original recipient address | |
- Some information will need to be communicated either to the off chain server or in the witness data to make sense of this, namely, the ratio / price, the recipient, the ask asset | |
Future Considerations: | |
- Once we can do transaction expiry policies, we can start considering an intent based version of this | |
- For now, doing things with this swap predicate will cover a majority of cases (yes cancellations are paid, but that can go away in the future with some intent / sequencer system) | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment