Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sdk): add NFT actions in the JS Dash SDK #2444

Open
wants to merge 36 commits into
base: v2.0-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3b511ce
feat(wasm-dpp): add transfer transition to documents batch wip
pshenmic Oct 5, 2024
d3383a1
feat(js-dash-sk): add transfer to document broadcast
pshenmic Oct 5, 2024
fae5915
fix(dpp): add missing import
pshenmic Oct 5, 2024
fd48355
feat(js-dash-sdk): add transfer function wip
pshenmic Oct 5, 2024
e17f1a1
Merge remote-tracking branch 'refs/remotes/origin/v1.7-dev' into feat…
pshenmic Dec 1, 2024
5fd4329
Merge remote-tracking branch 'refs/remotes/origin/v1.7-dev' into feat…
pshenmic Dec 3, 2024
10998f7
Merge remote-tracking branch 'origin/v1.8-dev' into feat/document-tra…
pshenmic Dec 23, 2024
fbe5dbc
feat(js-sdk): implement working document transfers
pshenmic Dec 24, 2024
8f79b69
chore(js-sdk): cleanup
pshenmic Dec 24, 2024
9925a68
chore(js-sdk): cleanup
pshenmic Dec 24, 2024
e8defe8
chore(wasm-dpp): cleanup
pshenmic Dec 24, 2024
1f0f7c6
chore(js-sdk): cleanup
pshenmic Dec 24, 2024
43cc7f1
chore(js-sdk): revert
pshenmic Dec 24, 2024
cf99fe6
chore(js-sdk): cleanup
pshenmic Dec 24, 2024
44df2e6
chore(js-sdk): cleanup
pshenmic Dec 24, 2024
5894a7f
chore(js-sdk): cleanup
pshenmic Dec 24, 2024
d7d5d90
chore(js-sdk): update doc
pshenmic Dec 24, 2024
2131f00
chore(wasm-dpp): cleanup
pshenmic Dec 24, 2024
9bca0cd
feat(wasm-dpp): add nft operations
pshenmic Jan 20, 2025
9dad661
feat(js-sdk): add transfer def in Platform.ts
pshenmic Jan 20, 2025
9c9c913
Merge branch 'refs/heads/feat/document-transfer' into feat/wasm-dpp-nft
pshenmic Jan 26, 2025
cbc86c4
feat(js-dash-sdk): uncomment
pshenmic Jan 26, 2025
7940a09
feat(wasm-dpp): move nft create state transition from extended docume…
pshenmic Jan 30, 2025
dac6932
fix(js-dash-sdk): fix types
pshenmic Jan 30, 2025
b7da382
fix(js-dash-sdk): finalize nft transitions
pshenmic Jan 31, 2025
ccf0921
Merge branch 'v2.0-dev' into feat/wasm-dpp-nft
pshenmic Jan 31, 2025
1a2520e
fix(wasm-dpp): lint fix
pshenmic Jan 31, 2025
dbf1b7c
chore(wasm-dpp): remove unused
pshenmic Jan 31, 2025
1207597
feat(js-dash-sdk): add NFT state transitions docs
pshenmic Jan 31, 2025
3443b3f
chore(wasm-dpp): fix lint
pshenmic Jan 31, 2025
16e2a50
chore(rs-dpp): fix lint
pshenmic Jan 31, 2025
cfe2b60
fix(rs-dpp): fix features
pshenmic Jan 31, 2025
eb4c953
fix(rs-dpp): lint fix
pshenmic Jan 31, 2025
30e86b7
fix(sdk): fix clippy warnings
pshenmic Feb 7, 2025
e0798b5
fix(wasm-dpp): revert some lint
pshenmic Feb 8, 2025
c79d7e3
fix(wasm-dpp): lint fix
pshenmic Feb 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
import { ExtendedDocument } from '@dashevo/wasm-dpp';
import { ExtendedDocument, Identifier } from '@dashevo/wasm-dpp';
import { Platform } from '../../Platform';
import broadcastStateTransition from '../../broadcastStateTransition';
import { signStateTransition } from '../../signStateTransition';

class DocumentTransitionParams {
receiver?: Identifier;

price?: bigint;
}

/**
* Broadcast document onto the platform
*
* @param {Platform} this - bound instance class
* @param {Object} documents
* @param {ExtendedDocument[]} [documents.create]
* @param {ExtendedDocument[]} [documents.replace]
* @param {ExtendedDocument[]} [documents.delete]
* @param identity - identity
* @param {Identity} identity
* @param options {DocumentTransitionParams} optional params for NFT functions
*/
export default async function broadcast(
this: Platform,
documents: {
create?: ExtendedDocument[],
replace?: ExtendedDocument[],
delete?: ExtendedDocument[]
delete?: ExtendedDocument[],
transfer?: ExtendedDocument[],
updatePrice?: ExtendedDocument[],
purchase?: ExtendedDocument[],
},
identity: any,
options?: DocumentTransitionParams,
): Promise<any> {
this.logger.debug('[Document#broadcast] Broadcast documents', {
create: documents.create?.length || 0,
replace: documents.replace?.length || 0,
delete: documents.delete?.length || 0,
transfer: documents.transfer?.length || 0,
updatePrice: documents.updatePrice?.length || 0,
purchase: documents.purchase?.length || 0,
});
await this.initialize();

Expand All @@ -36,20 +49,46 @@ export default async function broadcast(
...(documents.create || []),
...(documents.replace || []),
...(documents.delete || []),
...(documents.transfer || []),
...(documents.updatePrice || []),
...(documents.purchase || []),
][0]?.getDataContractId();

if (!dataContractId) {
throw new Error('Data contract ID is not found');
}

if (documents.transfer?.length && !options?.receiver) {
throw new Error('Receiver identity is not found for transfer transition');
}

if (documents.updatePrice?.length && !options?.price) {
throw new Error('Price must be provided for UpdatePrice operation');
}

if (documents.purchase?.length) {
if (!options?.price && !options?.receiver) {
throw new Error('Price and Receiver must be provided for Purchase operation');
}

documents.purchase.forEach((document) => document.setOwnerId(options.receiver));
}

const identityContractNonce = await this.nonceManager
.bumpIdentityContractNonce(identityId, dataContractId);

const documentsBatchTransition = dpp.document.createStateTransition(documents, {
const identityNonceObj = {
[identityId.toString()]: {
[dataContractId.toString()]: identityContractNonce,
},
});
};

const documentsBatchTransition = dpp.document.createStateTransition(
documents,
identityNonceObj,
options?.receiver,
options?.price,
);

this.logger.silly('[Document#broadcast] Created documents batch transition');

Expand Down Expand Up @@ -79,6 +118,7 @@ export default async function broadcast(
create: documents.create?.length || 0,
replace: documents.replace?.length || 0,
delete: documents.delete?.length || 0,
transfer: documents.transfer?.length || 0,
});

return documentsBatchTransition;
Expand Down
5 changes: 4 additions & 1 deletion packages/rs-dpp/src/document/document_factory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::state_transition::documents_batch_transition::{
};
use crate::util::entropy_generator::EntropyGenerator;
pub use v0::DocumentFactoryV0;
use crate::fee::Credits;

/// # Document Factory
///
Expand Down Expand Up @@ -120,9 +121,11 @@ impl DocumentFactory {
),
>,
nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce
recipient: Option<Identifier>,
price: Option<Credits>,
) -> Result<DocumentsBatchTransition, ProtocolError> {
match self {
DocumentFactory::V0(v0) => v0.create_state_transition(documents_iter, nonce_counter),
DocumentFactory::V0(v0) => v0.create_state_transition(documents_iter, nonce_counter, recipient, price),
}
}

Expand Down
177 changes: 173 additions & 4 deletions packages/rs-dpp/src/document/document_factory/v0/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ use crate::state_transition::documents_batch_transition::{
DocumentsBatchTransition, DocumentsBatchTransitionV0,
};
use itertools::Itertools;
use crate::fee::Credits;
use crate::state_transition::documents_batch_transition::document_transition::{DocumentPurchaseTransition, DocumentTransferTransition, DocumentUpdatePriceTransition};

const PROPERTY_FEATURE_VERSION: &str = "$version";
const PROPERTY_ENTROPY: &str = "$entropy";
Expand Down Expand Up @@ -209,7 +211,9 @@ impl DocumentFactoryV0 {
Vec<(Document, DocumentTypeRef<'a>, Bytes32)>,
),
>,
nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce
nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce,
recipient: Option<Identifier>,
price: Option<Credits>
) -> Result<DocumentsBatchTransition, ProtocolError> {
let platform_version = PlatformVersion::get(self.protocol_version)?;
let documents: Vec<(
Expand Down Expand Up @@ -262,9 +266,40 @@ impl DocumentFactoryV0 {
nonce_counter,
platform_version,
),
_ => Err(ProtocolError::InvalidStateTransitionType(
"action type not accounted for".to_string(),
)),
DocumentTransitionActionType::Transfer => Self::document_transfer_transitions(
documents
.into_iter()
.map(|(document, document_type, _)| (document, document_type))
.collect(),
nonce_counter,
platform_version,
recipient
),
DocumentTransitionActionType::UpdatePrice => Self::document_update_price_transitions(
documents
.into_iter()
.map(|(document, document_type, _)| (document, document_type))
.collect(),
nonce_counter,
platform_version,
price
),
DocumentTransitionActionType::Purchase => Self::document_purchase_transitions(
documents
.into_iter()
.map(|(document, document_type, _)| (document, document_type))
.collect(),
nonce_counter,
platform_version,
price,
recipient
),
_ => {
let action_type_name: &str = action.into();

Err(ProtocolError::InvalidStateTransitionType(
action_type_name.to_string(),
))},
})
.collect::<Result<Vec<_>, ProtocolError>>()?
.into_iter()
Expand Down Expand Up @@ -548,6 +583,140 @@ impl DocumentFactoryV0 {
.collect()
}

#[cfg(feature = "state-transitions")]
fn document_transfer_transitions(
documents: Vec<(Document, DocumentTypeRef)>,
nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce
platform_version: &PlatformVersion,
recipient_owner_id: Option<Identifier>
) -> Result<Vec<DocumentTransition>, ProtocolError> {
documents
.into_iter()
.map(|(mut document, document_type)| {
if !document_type.documents_transferable().is_transferable() {
return Err(DocumentError::TryingToTransferNonTransferableDocument {
document: Box::new(document),
}
.into());
}
let Some(_document_revision) = document.revision() else {
return Err(DocumentError::RevisionAbsentError {
document: Box::new(document),
}
.into());
};

document.increment_revision()?;
document.set_updated_at(Some(Utc::now().timestamp_millis() as TimestampMillis));

let nonce = nonce_counter
.entry((document.owner_id(), document_type.data_contract_id()))
.or_default();

let transition = DocumentTransferTransition::from_document(
document,
document_type,
*nonce,
recipient_owner_id.unwrap(),
platform_version,
None,
None,
)?;

*nonce += 1;

Ok(transition.into())
})
.collect()
}

#[cfg(feature = "state-transitions")]
fn document_update_price_transitions(
documents: Vec<(Document, DocumentTypeRef)>,
nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce
platform_version: &PlatformVersion,
price: Option<Credits>
) -> Result<Vec<DocumentTransition>, ProtocolError> {
documents
.into_iter()
.map(|(mut document, document_type)| {
let Some(_document_revision) = document.revision() else {
return Err(DocumentError::RevisionAbsentError {
document: Box::new(document),
}
.into());
};

let nonce = nonce_counter
.entry((document.owner_id(), document_type.data_contract_id()))
.or_default();

let now = Utc::now().timestamp_millis() as TimestampMillis;

document.increment_revision()?;
document.set_updated_at(Some(now));
document.set_transferred_at(Some(now));

let transition = DocumentUpdatePriceTransition::from_document(
document,
document_type,
price.unwrap(),
*nonce,
platform_version,
None,
None,
)?;

*nonce += 1;

Ok(transition.into())
})
.collect()
}

#[cfg(feature = "state-transitions")]
fn document_purchase_transitions(
documents: Vec<(Document, DocumentTypeRef)>,
nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce
platform_version: &PlatformVersion,
price: Option<Credits>,
recipient: Option<Identifier>
) -> Result<Vec<DocumentTransition>, ProtocolError> {
documents
.into_iter()
.map(|(mut document, document_type)| {
let Some(_document_revision) = document.revision() else {
return Err(DocumentError::RevisionAbsentError {
document: Box::new(document),
}
.into());
};

let nonce = nonce_counter
.entry((recipient.unwrap(), document_type.data_contract_id()))
.or_default();

//document.set_owner_id(recipient.unwrap());
document.increment_revision()?;
document.set_updated_at(Some(Utc::now().timestamp_millis() as TimestampMillis));

let transition = DocumentPurchaseTransition::from_document(
document,
document_type,
price.unwrap(),
*nonce,
platform_version,
None,
None,
)?;

*nonce += 1;

Ok(transition.into())
})
.collect()
}

fn is_ownership_the_same<'a>(ids: impl IntoIterator<Item = &'a Identifier>) -> bool {
ids.into_iter().all_equal()
}
Expand Down
3 changes: 3 additions & 0 deletions packages/rs-dpp/src/document/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ pub enum DocumentError {
#[error("Trying to delete indelible document")]
TryingToDeleteIndelibleDocument { document: Box<Document> },

#[error("Trying to transfer non-transferable document")]
TryingToTransferNonTransferableDocument { document: Box<Document> },

#[error("Documents have mixed owner ids")]
MismatchOwnerIdsError { documents: Vec<Document> },

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,26 @@ impl TryFrom<&str> for DocumentTransitionActionType {
"replace" => Ok(DocumentTransitionActionType::Replace),
"delete" => Ok(DocumentTransitionActionType::Delete),
"transfer" => Ok(DocumentTransitionActionType::Transfer),
"updatePrice" => Ok(DocumentTransitionActionType::UpdatePrice),
"purchase" => Ok(DocumentTransitionActionType::Purchase),
action_type => Err(ProtocolError::Generic(format!(
"unknown action type {action_type}"
))),
}
}
}


impl From<DocumentTransitionActionType> for &str {
fn from(value: DocumentTransitionActionType) -> Self {
match value {
DocumentTransitionActionType::Create => "Create",
DocumentTransitionActionType::Replace => "Replace",
DocumentTransitionActionType::Delete => "Delete",
DocumentTransitionActionType::Transfer => "Transfer",
DocumentTransitionActionType::Purchase => "Purchase",
DocumentTransitionActionType::UpdatePrice => "UpdatePrice",
DocumentTransitionActionType::IgnoreWhileBumpingRevision => "IgnoreWhileBumpingRevision"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ impl DocumentsBatchTransitionInternalTransformerV0 for DocumentsBatchTransition
StateError::InvalidDocumentRevisionError(InvalidDocumentRevisionError::new(
document_id,
Some(previous_revision),
transition_revision,
expected_revision,
)),
))
}
Expand Down
Loading