1#![cfg_attr(not(feature = "std"), no_std, no_main)]
2
3extern crate alloc;
4
5mod macros;
6
7use alloc::collections::BTreeSet;
8#[cfg(not(feature = "as-library"))]
9use alloc::vec;
10
11use alloc::vec::Vec;
12
13pub use polymesh_api::ink::basic_types::{AssetId, IdentityId};
14pub use polymesh_api::ink::extension::PolymeshEnvironment;
15pub use polymesh_api::ink::Error as PolymeshInkError;
16pub use polymesh_api::polymesh::types::pallet_corporate_actions;
17pub use polymesh_api::polymesh::types::pallet_corporate_actions::CAId;
18pub use polymesh_api::polymesh::types::polymesh_contracts::Api as ContractRuntimeApi;
19pub use polymesh_api::polymesh::types::polymesh_primitives::asset::{
20 AssetName, AssetType, CheckpointId,
21};
22pub use polymesh_api::polymesh::types::polymesh_primitives::asset_metadata::{
23 AssetMetadataKey, AssetMetadataLocalKey, AssetMetadataName, AssetMetadataValue,
24};
25pub use polymesh_api::polymesh::types::polymesh_primitives::identity_id::{
26 PortfolioId, PortfolioKind, PortfolioName, PortfolioNumber,
27};
28pub use polymesh_api::polymesh::types::polymesh_primitives::nft::{NFTId, NFTs};
29pub use polymesh_api::polymesh::types::polymesh_primitives::portfolio::{Fund, FundDescription};
30pub use polymesh_api::polymesh::types::polymesh_primitives::settlement::{
31 InstructionId, Leg, SettlementType, VenueDetails, VenueId, VenueType,
32};
33pub use polymesh_api::polymesh::Api;
34
35pub const API_VERSION: ContractRuntimeApi = ContractRuntimeApi {
36 desc: *b"POLY",
37 major: 7,
38};
39
40#[derive(Debug, scale::Encode, scale::Decode)]
42#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
43pub enum PolymeshError {
44 PolymeshRuntime(PolymeshInkError),
46 MissingIdentity,
48 InvalidPortfolioAuthorization,
50 InkDelegateCallError {
52 selector: [u8; 4],
53 err: Option<InkEnvError>,
54 },
55 NotNftOwner,
57}
58
59#[derive(Debug, scale::Encode, scale::Decode)]
61#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
62pub enum InkEnvError {
63 ScaleDecodeError,
65 CalleeTrapped,
67 CalleeReverted,
69 KeyNotFound,
71 TransferFailed,
74 _EndowmentTooLow,
76 CodeNotFound,
78 NotCallable,
80 LoggingDisabled,
83 EcdsaRecoveryFailed,
85}
86
87impl PolymeshError {
88 pub fn from_delegate_error(err: ink::env::Error, selector: ink::env::call::Selector) -> Self {
89 use ink::env::Error::*;
90 Self::InkDelegateCallError {
91 selector: selector.to_bytes(),
92 err: match err {
93 Decode(_) => Some(InkEnvError::ScaleDecodeError),
94 CalleeTrapped => Some(InkEnvError::CalleeTrapped),
95 CalleeReverted => Some(InkEnvError::CalleeReverted),
96 KeyNotFound => Some(InkEnvError::KeyNotFound),
97 TransferFailed => Some(InkEnvError::TransferFailed),
98 _EndowmentTooLow => Some(InkEnvError::_EndowmentTooLow),
99 CodeNotFound => Some(InkEnvError::CodeNotFound),
100 NotCallable => Some(InkEnvError::NotCallable),
101 LoggingDisabled => Some(InkEnvError::LoggingDisabled),
102 EcdsaRecoveryFailed => Some(InkEnvError::EcdsaRecoveryFailed),
103 _ => None,
104 },
105 }
106 }
107}
108
109impl From<PolymeshInkError> for PolymeshError {
110 fn from(err: PolymeshInkError) -> Self {
111 Self::PolymeshRuntime(err)
112 }
113}
114
115pub type PolymeshResult<T> = core::result::Result<T, PolymeshError>;
117
118#[cfg(feature = "as-library")]
119pub type Hash = <PolymeshEnvironment as ink::env::Environment>::Hash;
120#[cfg(feature = "as-library")]
121pub type AccountId = <PolymeshEnvironment as ink::env::Environment>::AccountId;
122pub type Balance = <PolymeshEnvironment as ink::env::Environment>::Balance;
123pub type Timestamp = <PolymeshEnvironment as ink::env::Environment>::Timestamp;
124
125#[derive(Debug, scale::Encode, scale::Decode)]
126#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
127pub struct DistributionSummary {
128 pub currency: AssetId,
129 pub per_share: Balance,
130 pub reclaimed: bool,
131 pub payment_at: Timestamp,
132 pub expires_at: Option<Timestamp>,
133}
134
135impl From<pallet_corporate_actions::distribution::Distribution> for DistributionSummary {
136 fn from(distribution: pallet_corporate_actions::distribution::Distribution) -> Self {
137 Self {
138 currency: distribution.currency,
139 per_share: distribution.per_share,
140 reclaimed: distribution.reclaimed,
141 payment_at: distribution.payment_at,
142 expires_at: distribution.expires_at,
143 }
144 }
145}
146
147#[derive(Debug, scale::Encode, scale::Decode)]
148#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
149pub struct SimpleDividend {
150 pub asset_id: AssetId,
151 pub decl_date: Timestamp,
152 pub record_date: Timestamp,
153 pub portfolio: Option<PortfolioNumber>,
154 pub currency: AssetId,
155 pub per_share: Balance,
156 pub amount: Balance,
157 pub payment_at: Timestamp,
158 pub expires_at: Option<Timestamp>,
159}
160
161upgradable_api! {
162 mod polymesh_ink {
163 impl PolymeshInk {
164 #[ink(message)]
166 pub fn system_remark(&self, remark: Vec<u8>) -> PolymeshResult<()> {
167 let api = Api::new();
168 api.call().system().remark(remark).submit()?;
169 Ok(())
170 }
171
172 #[ink(message)]
174 pub fn create_portfolio(&self, name: Vec<u8>) -> PolymeshResult<PortfolioId> {
175 let api = Api::new();
176
177 let contracts_did = Self::get_our_did()?;
179 let portfolio_number = api
181 .query()
182 .portfolio()
183 .next_portfolio_number(contracts_did)?;
184
185 api.call()
186 .portfolio()
187 .create_portfolio(PortfolioName(name))
188 .submit()?;
189
190 Ok(PortfolioId {
191 did: contracts_did,
192 kind: PortfolioKind::User(portfolio_number),
193 })
194 }
195
196 #[ink(message)]
198 pub fn accept_portfolio_custody(
199 &self,
200 auth_id: u64,
201 portfolio_kind: PortfolioKind
202 ) -> PolymeshResult<PortfolioId> {
203 let api = Api::new();
204
205 let caller_did = Self::get_caller_did()?;
207 let contracts_did = Self::get_our_did()?;
209
210 api.call()
211 .portfolio()
212 .accept_portfolio_custody(auth_id)
213 .submit()?;
214
215 let portfolio_id = PortfolioId {
216 did: caller_did,
217 kind: portfolio_kind,
218 };
219
220 if !api
221 .query()
222 .portfolio()
223 .portfolios_in_custody(contracts_did, portfolio_id)?
224 {
225 return Err(PolymeshError::InvalidPortfolioAuthorization);
226 }
227
228 Ok(portfolio_id)
229 }
230
231 #[ink(message)]
233 pub fn quit_portfolio_custody(&self, portfolio_id: PortfolioId) -> PolymeshResult<()> {
234 let api = Api::new();
235
236 api.call()
237 .portfolio()
238 .quit_portfolio_custody(portfolio_id)
239 .submit()?;
240 Ok(())
241 }
242
243 #[ink(message)]
245 pub fn move_portfolio_funds(
246 &self,
247 source_portfolio_id: PortfolioId,
248 destination_portfolio_id: PortfolioId,
249 funds: Vec<Fund>
250 ) -> PolymeshResult<()> {
251 let api = Api::new();
252
253 api.call()
254 .portfolio()
255 .move_portfolio_funds(source_portfolio_id, destination_portfolio_id, funds)
256 .submit()?;
257 Ok(())
258 }
259
260 #[ink(message)]
262 pub fn portfolio_asset_balances(
263 &self,
264 portfolio_id: PortfolioId,
265 asset_id: AssetId
266 ) -> PolymeshResult<Balance> {
267 let api = Api::new();
268
269 let balance = api
270 .query()
271 .portfolio()
272 .portfolio_asset_balances(portfolio_id, asset_id)?;
273 Ok(balance)
274 }
275
276 #[ink(message)]
278 pub fn check_portfolios_in_custody(
279 &self,
280 custodian_did: IdentityId,
281 portfolio_id: PortfolioId
282 ) -> PolymeshResult<bool> {
283 let api = Api::new();
284
285 let is_custodian = api.query()
286 .portfolio()
287 .portfolios_in_custody(custodian_did, portfolio_id)?;
288 Ok(is_custodian)
289 }
290
291 #[ink(message)]
293 pub fn create_venue(
294 &self,
295 venue_details: VenueDetails,
296 venue_type: VenueType
297 ) -> PolymeshResult<VenueId> {
298 let api = Api::new();
299
300 let venue_id = api.query().settlement().venue_counter()?;
302
303 api.call()
304 .settlement()
305 .create_venue(venue_details, vec![], venue_type)
306 .submit()?;
307 Ok(venue_id)
308 }
309
310 #[ink(message)]
312 pub fn settlement_execute(
313 &self,
314 venue_id: Option<VenueId>,
315 legs: Vec<Leg>,
316 portfolios: BTreeSet<PortfolioId>
317 ) -> PolymeshResult<()> {
318 let api = Api::new();
319
320 let (fungible, nfts, offchain) =
322 legs.iter()
323 .fold((0, 0, 0), |(fungible, nfts, offchain), leg| match leg {
324 Leg::Fungible { .. } => (fungible + 1, nfts, offchain),
325 Leg::NonFungible { .. } => (fungible, nfts + 1, offchain),
326 Leg::OffChain { .. } => (fungible, nfts, offchain + 1),
327 });
328
329 let instruction_id = api
331 .query()
332 .settlement()
333 .instruction_counter()?;
334
335 api.call()
336 .settlement()
337 .add_and_affirm_instruction(
338 venue_id,
339 SettlementType::SettleManual(0),
340 None,
341 None,
342 legs,
343 portfolios,
344 None,
345 )
346 .submit()?;
347
348 api.call()
349 .settlement()
350 .execute_manual_instruction(instruction_id, None, fungible, nfts, offchain, None)
351 .submit()?;
352 Ok(())
353 }
354
355 #[ink(message)]
357 pub fn asset_issue(
358 &self,
359 asset_id: AssetId,
360 amount_to_issue: Balance,
361 portfolio_kind: PortfolioKind
362 ) -> PolymeshResult<()> {
363 let api = Api::new();
364 api.call().asset().issue(asset_id, amount_to_issue, portfolio_kind).submit()?;
365 Ok(())
366 }
367
368 #[ink(message)]
370 pub fn asset_redeem(
371 &self,
372 asset_id: AssetId,
373 amount_to_redeem: Balance,
374 portfolio_kind: PortfolioKind
375 ) -> PolymeshResult<()> {
376 let api = Api::new();
377 api.call().asset().redeem(asset_id, amount_to_redeem, portfolio_kind).submit()?;
378 Ok(())
379 }
380
381 #[ink(message)]
383 pub fn asset_create_and_issue(
384 &self,
385 asset_name: AssetName,
386 asset_type: AssetType,
387 divisible: bool,
388 amount_to_issue: Option<Balance>
389 ) -> PolymeshResult<AssetId> {
390 let api = Api::new();
391
392 let asset_id = Self::get_our_next_asset_id()?;
393
394 api.call()
395 .asset()
396 .create_asset(asset_name, divisible, asset_type, vec![], None)
397 .submit()?;
398
399 if let Some(amount_to_issue) = amount_to_issue {
400 api.call()
401 .asset()
402 .issue(asset_id, amount_to_issue, PortfolioKind::Default)
403 .submit()?;
404 }
405
406 Ok(asset_id)
407 }
408
409 #[ink(message)]
411 pub fn asset_balance_of(
412 &self,
413 asset_id: AssetId,
414 did: IdentityId
415 ) -> PolymeshResult<Balance> {
416 let api = Api::new();
417 let balance = api.query().asset().balance_of(asset_id, did)?;
418 Ok(balance)
419 }
420
421 #[ink(message)]
423 pub fn asset_total_supply(
424 &self,
425 asset_id: AssetId
426 ) -> PolymeshResult<Balance> {
427 let api = Api::new();
428
429 let asset_details = api.query().asset().assets(asset_id)?;
430 let total_supply = asset_details
431 .map(|asset| asset.total_supply)
432 .unwrap_or_default();
433 Ok(total_supply)
434 }
435
436 #[ink(message)]
438 pub fn distribution_summary(
439 &self,
440 ca_id: CAId
441 ) -> PolymeshResult<Option<DistributionSummary>> {
442 let api = Api::new();
443 let distribution = api.query().capital_distribution().distributions(ca_id)?;
444 Ok(distribution.map(|d| d.into()))
445 }
446
447 #[ink(message)]
449 pub fn dividend_claim(
450 &self,
451 ca_id: CAId
452 ) -> PolymeshResult<()> {
453 let api = Api::new();
454 api.call().capital_distribution().claim(ca_id).submit()?;
455 Ok(())
456 }
457
458 #[ink(message)]
460 pub fn create_dividend(
461 &self,
462 dividend: SimpleDividend
463 ) -> PolymeshResult<()> {
464 let api = Api::new();
465 let ca_args = pallet_corporate_actions::InitiateCorporateActionArgs {
467 asset_id: dividend.asset_id,
468 kind: pallet_corporate_actions::CAKind::PredictableBenefit,
469 decl_date: dividend.decl_date,
470 record_date: Some(pallet_corporate_actions::RecordDateSpec::Scheduled(dividend.record_date)),
471 details: pallet_corporate_actions::CADetails(vec![]),
472 targets: None,
473 default_withholding_tax: None,
474 withholding_tax: None,
475 };
476 api.call()
478 .corporate_action()
479 .initiate_corporate_action_and_distribute(
480 ca_args,
481 dividend.portfolio,
482 dividend.currency,
483 dividend.per_share,
484 dividend.amount,
485 dividend.payment_at,
486 dividend.expires_at,
487 )
488 .submit()?;
489 Ok(())
490 }
491
492 #[ink(message)]
494 pub fn add_and_affirm_instruction(
495 &self,
496 venue_id: Option<VenueId>,
497 legs: Vec<Leg>,
498 portfolios: BTreeSet<PortfolioId>
499 ) -> PolymeshResult<InstructionId> {
500 let api = Api::new();
501
502 let instruction_id = api
503 .query()
504 .settlement()
505 .instruction_counter()?;
506
507 api.call()
508 .settlement()
509 .add_and_affirm_instruction(
510 venue_id,
511 SettlementType::SettleOnAffirmation,
512 None,
513 None,
514 legs,
515 portfolios,
516 None,
517 )
518 .submit()?;
519 Ok(instruction_id)
520 }
521
522 #[ink(message)]
525 pub fn create_custody_portfolio(
526 &self,
527 portfolio_owner_id: IdentityId,
528 portfolio_name: PortfolioName
529 ) -> PolymeshResult<PortfolioId> {
530 let api = Api::new();
531
532 let portfolio_number = api
533 .query()
534 .portfolio()
535 .next_portfolio_number(portfolio_owner_id)?;
536 let portfolio_id = PortfolioId {
537 did: portfolio_owner_id,
538 kind: PortfolioKind::User(portfolio_number),
539 };
540
541 api.call()
542 .portfolio()
543 .create_custody_portfolio(portfolio_owner_id, portfolio_name)
544 .submit()?;
545 Ok(portfolio_id)
546 }
547
548 #[ink(message)]
550 pub fn asset_metadata_local_name_to_key(
551 &self,
552 asset_id: AssetId,
553 asset_metadata_name: AssetMetadataName
554 ) -> PolymeshResult<Option<AssetMetadataLocalKey>> {
555 Ok(Api::new()
556 .query()
557 .asset()
558 .asset_metadata_local_name_to_key(asset_id, asset_metadata_name)?)
559 }
560
561 #[ink(message)]
563 pub fn asset_metadata_value(
564 &self,
565 asset_id: AssetId,
566 asset_metadata_key: AssetMetadataKey
567 ) -> PolymeshResult<Option<AssetMetadataValue>> {
568 Ok(Api::new()
569 .query()
570 .asset()
571 .asset_metadata_values(asset_id, asset_metadata_key)?)
572 }
573
574 #[ink(message)]
576 pub fn nft_owner(
577 &self,
578 asset_id: AssetId,
579 nft: NFTId,
580 ) -> PolymeshResult<Option<PortfolioId>> {
581 let api = Api::new();
582 Ok(api.query().nft().nft_owner(asset_id, nft)?)
583 }
584
585 #[ink(message)]
587 pub fn holds_nfts(
588 &self,
589 portfolio_id: PortfolioId,
590 asset_id: AssetId,
591 nfts: Vec<NFTId>,
592 ) -> PolymeshResult<()> {
593 let api = Api::new();
594 for nft_id in nfts {
595 let nft_holder = api
596 .query()
597 .nft()
598 .nft_owner(asset_id, nft_id)?
599 .ok_or(PolymeshError::NotNftOwner)?;
600
601 if nft_holder != portfolio_id {
602 return Err(PolymeshError::NotNftOwner);
603 }
604 }
605 Ok(())
606 }
607 }
608
609 impl PolymeshInk {
611 pub fn get_caller_did() -> PolymeshResult<IdentityId> {
613 Self::get_key_did(ink::env::caller::<PolymeshEnvironment>())
614 }
615
616 pub fn get_our_did() -> PolymeshResult<IdentityId> {
618 Self::get_key_did(ink::env::account_id::<PolymeshEnvironment>())
619 }
620
621 pub fn get_key_did(acc: AccountId) -> PolymeshResult<IdentityId> {
623 let api = Api::new();
624 api.runtime()
625 .get_key_did(acc)?
626 .ok_or(PolymeshError::MissingIdentity)
627 }
628
629 pub fn get_our_next_asset_id() -> PolymeshResult<AssetId> {
631 Self::get_next_asset_id(ink::env::account_id::<PolymeshEnvironment>())
632 }
633
634 pub fn get_next_asset_id(account_id: AccountId) -> PolymeshResult<AssetId> {
636 let api = Api::new();
637 Ok(api.runtime().get_next_asset_id(account_id)?)
638 }
639 }
640 }
641}