use std::collections::BTreeMap;
use canton_auth::{OidcConfig, TokenProvider};
use canton_daml as rt;
use canton_daml::Contract as _;
use canton_ledger::request::ActiveContractsRequest;
use canton_ledger::{CantonClient, Config};
use canton_token::RegistryClient;
use canton_token::types::metadata as md;
use canton_token::types::v2::allocation as al;
use futures_util::StreamExt as _;
fn var(name: &str) -> Result<String, String> {
std::env::var(name).map_err(|_| format!("set {name}"))
}
fn authenticate(config: Config) -> Config {
match (
std::env::var("CANTON_TOKEN"),
std::env::var("CANTON_TEST_TOKEN_URL"),
std::env::var("CANTON_TEST_CLIENT_ID"),
std::env::var("CANTON_TEST_CLIENT_SECRET"),
) {
(Ok(token), ..) => config.with_token(token),
(_, Ok(url), Ok(id), Ok(secret)) => {
let mut oidc = OidcConfig::new(url, id, secret);
if let Ok(audience) = std::env::var("CANTON_TEST_AUDIENCE") {
oidc = oidc.with_audience(audience);
}
config.with_oidc(TokenProvider::new(oidc))
}
_ => config,
}
}
async fn allocations_to_settle(
client: &CantonClient,
party: &rt::Party,
) -> Result<Vec<(String, al::AllocationView)>, Box<dyn std::error::Error>> {
let end = client.ledger_end().await?;
let id = al::Allocation::template_id();
let interface = format!("{}:{}:{}", id.package_id, id.module_name, id.entity_name);
let request =
ActiveContractsRequest::new(vec![party.to_string()], end).for_interfaces([interface])?;
let stream = client.active_contracts_with(request).await?;
futures_util::pin_mut!(stream);
let mut out = Vec::new();
while let Some(item) = stream.next().await {
let Some(event) = item?.created_event else {
continue;
};
let Some(record) = event
.interface_views
.iter()
.find_map(|v| v.view_value.as_ref())
else {
continue;
};
let view: al::AllocationView = rt::from_record(record)?;
if view.settlement.executors.contains(party) {
out.push((event.contract_id, view));
}
}
Ok(out)
}
fn legs_of(view: &al::AllocationView) -> Vec<al::TransferLeg> {
view.allocation
.transfer_leg_sides
.iter()
.map(|side| {
let (sender, receiver) = match side.side {
al::TransferSide::SenderSide => {
(view.allocation.authorizer.clone(), side.otherside.clone())
}
al::TransferSide::ReceiverSide => {
(side.otherside.clone(), view.allocation.authorizer.clone())
}
};
al::TransferLeg {
transfer_leg_id: side.transfer_leg_id.clone(),
sender,
receiver,
amount: side.amount.clone(),
instrument_id: side.instrument_id.clone(),
meta: md::Metadata {
values: rt::TextMap::new(),
},
}
})
.collect()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let registry = RegistryClient::new(&var("CANTON_TOKEN_REGISTRY_URL")?)?;
let executor = rt::Party::parse(&var("CANTON_TOKEN_EXECUTOR")?)?;
let client =
CantonClient::connect_lazy(authenticate(Config::new(var("CANTON_TEST_ENDPOINT")?)))?;
let mut found = allocations_to_settle(&client, &executor).await?;
if let Ok(only) = std::env::var("CANTON_TOKEN_SETTLEMENT") {
found.retain(|(_, view)| view.settlement.id == only);
}
println!("executor: {executor}");
println!("allocations: {} naming it as executor", found.len());
let mut batches: BTreeMap<String, Vec<(String, al::AllocationView)>> = BTreeMap::new();
for (cid, view) in found {
batches
.entry(view.settlement.id.clone())
.or_default()
.push((cid, view));
}
for (settlement_id, allocations) in batches {
let settlement = allocations[0].1.settlement.clone();
let mut by_id: BTreeMap<String, al::TransferLeg> = BTreeMap::new();
for leg in allocations.iter().flat_map(|(_, v)| legs_of(v)) {
by_id.entry(leg.transfer_leg_id.clone()).or_insert(leg);
}
let legs: Vec<al::TransferLeg> = by_id.into_values().collect();
let finalized: Vec<al::FinalizedAllocation> = allocations
.iter()
.map(|(cid, _)| al::FinalizedAllocation {
allocation_cid: rt::ContractId::new(cid.clone()),
extra_transfer_leg_sides: Vec::new(),
next_iteration_funding: None,
})
.collect();
println!(
"settlement {settlement_id}: {} allocation(s), {} leg(s)",
allocations.len(),
legs.len()
);
for leg in &legs {
println!(
" leg {}: {} -> {} : {} {}",
leg.transfer_leg_id,
leg.sender.owner.as_ref().map_or("?", |p| p.as_str()),
leg.receiver.owner.as_ref().map_or("?", |p| p.as_str()),
leg.amount,
leg.instrument_id
);
}
let command = canton_token::v2::settle_batch(
®istry,
settlement,
legs,
finalized,
vec![executor.clone()],
)
.await?;
println!(
" disclosing {} contract(s) the registry named",
command.disclosed_contracts().len()
);
if std::env::var_os("CANTON_TOKEN_DRY_RUN").is_some() {
println!(" dry run: built and not submitted");
continue;
}
let transaction = client
.submit_and_wait_for_transaction(command.into_submit(executor.as_str()))
.await?;
println!(
" settled: {} at offset {} with {} event(s)",
transaction.update_id,
transaction.offset,
transaction.events.len()
);
}
Ok(())
}