Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 16 additions & 3 deletions .github/workflows/integration_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ on:
name: Integration Test

env:
RIPPLED_DOCKER_IMAGE: rippleci/rippled:develop
# Pin to known-good digest; rippleci/rippled:develop broke after 2026-04-01
RIPPLED_DOCKER_IMAGE: rippleci/rippled:develop@sha256:328175bf14b7b83db9e5e6b50c7458bf828b02b2855453efc038233094aa8d85

jobs:
integration_test:
Expand Down Expand Up @@ -41,10 +42,22 @@ jobs:

- name: Wait for rippled to be healthy
run: |
until docker inspect --format='{{.State.Health.Status}}' rippled-service | grep -q healthy; do
echo "Waiting for rippled to be ready..."
for i in $(seq 1 30); do
if ! docker ps -q -f name=rippled-service | grep -q .; then
echo "Container exited unexpectedly"
docker logs rippled-service 2>&1 || true
exit 1
fi
STATUS=$(docker inspect --format='{{.State.Health.Status}}' rippled-service 2>/dev/null || echo "unknown")
echo "Attempt $i/30: $STATUS"
if [ "$STATUS" = "healthy" ]; then
exit 0
fi
sleep 2
done
echo "Timed out waiting for rippled"
docker logs rippled-service 2>&1 || true
exit 1

- uses: dtolnay/rust-toolchain@stable

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [[Unreleased]]

### Added
- Support for Decentralized Identity (DID, Amendment ID: DB432C3A09D9D5DFC7859F39AE5FF767ABC59AED0A9FB441E83B814D8946C109)

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion src/asynch/clients/async_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub trait XRPLAsyncClient: XRPLClient {
let server_state = self.request(ServerState::new(None).into()).await?;
let server_state: ServerStateResult = server_state.try_into()?;
let common_fields = CommonFields {
network_id: None, // TODO Server state has no network ID.
network_id: server_state.state.network_id,
build_version: Some(server_state.state.build_version),
};

Expand Down
12 changes: 9 additions & 3 deletions src/models/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ use crate::XRPLSerdeJsonError;
use super::{
results::exceptions::XRPLResultException,
transactions::exceptions::{
XRPLAccountSetException, XRPLNFTokenCancelOfferException, XRPLNFTokenCreateOfferException,
XRPLPaymentException, XRPLSignerListSetException, XRPLTransactionException,
XRPLXChainClaimException, XRPLXChainCreateBridgeException,
XRPLAccountSetException, XRPLDIDSetException, XRPLNFTokenCancelOfferException,
XRPLNFTokenCreateOfferException, XRPLPaymentException, XRPLSignerListSetException,
XRPLTransactionException, XRPLXChainClaimException, XRPLXChainCreateBridgeException,
XRPLXChainCreateClaimIDException, XRPLXChainModifyBridgeException,
},
};
Expand Down Expand Up @@ -105,6 +105,12 @@ impl From<XRPLAccountSetException> for XRPLModelException {
}
}

impl From<XRPLDIDSetException> for XRPLModelException {
fn from(error: XRPLDIDSetException) -> Self {
XRPLModelException::XRPLTransactionError(error.into())
}
}

impl From<XRPLNFTokenCancelOfferException> for XRPLModelException {
fn from(error: XRPLNFTokenCancelOfferException) -> Self {
XRPLModelException::XRPLTransactionError(error.into())
Expand Down
137 changes: 137 additions & 0 deletions src/models/ledger/objects/did.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use crate::models::ledger::objects::LedgerEntryType;
use crate::models::FlagCollection;
use crate::models::Model;
use crate::models::NoFlags;
use alloc::borrow::Cow;

use serde::{Deserialize, Serialize};

use serde_with::skip_serializing_none;

use super::{CommonFields, LedgerObject};

/// The `DID` object type holds references to, or data associated with, a single
/// Decentralized Identifier (DID).
///
/// `<https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/did>`
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct DID<'a> {
/// The base fields for all ledger object models.
///
/// See Ledger Object Common Fields:
/// `<https://xrpl.org/ledger-entry-common-fields.html>`
#[serde(flatten)]
pub common_fields: CommonFields<'a, NoFlags>,
/// The account that controls the DID.
pub account: Cow<'a, str>,
/// The W3C standard DID document associated with the DID.
/// Limited to a maximum length of 256 bytes.
#[serde(rename = "DIDDocument")]
pub did_document: Option<Cow<'a, str>>,
/// The public attestations of identity credentials associated with the DID.
/// Limited to a maximum length of 256 bytes.
pub data: Option<Cow<'a, str>>,
/// The Universal Resource Identifier that points to the corresponding
/// DID document or the data associated with the DID.
/// Limited to a maximum length of 256 bytes.
#[serde(rename = "URI")]
pub uri: Option<Cow<'a, str>>,
/// A hint indicating which page of the owner directory links to this object.
pub owner_node: Cow<'a, str>,
/// The identifying hash of the transaction that most recently modified this object.
#[serde(rename = "PreviousTxnID")]
pub previous_txn_id: Cow<'a, str>,
/// The index of the ledger that contains the transaction that most recently
/// modified this object.
pub previous_txn_lgr_seq: u32,
}

impl<'a> Model for DID<'a> {}

impl<'a> LedgerObject<NoFlags> for DID<'a> {
fn get_ledger_entry_type(&self) -> LedgerEntryType {
self.common_fields.get_ledger_entry_type()
}
}

impl<'a> DID<'a> {
pub fn new(
index: Option<Cow<'a, str>>,
ledger_index: Option<Cow<'a, str>>,
account: Cow<'a, str>,
did_document: Option<Cow<'a, str>>,
data: Option<Cow<'a, str>>,
uri: Option<Cow<'a, str>>,
owner_node: Cow<'a, str>,
previous_txn_id: Cow<'a, str>,
previous_txn_lgr_seq: u32,
) -> Self {
Self {
common_fields: CommonFields {
flags: FlagCollection::default(),
ledger_entry_type: LedgerEntryType::DID,
index,
ledger_index,
},
account,
did_document,
data,
uri,
owner_node,
previous_txn_id,
previous_txn_lgr_seq,
}
}
}

#[cfg(test)]
mod test_serde {
use super::*;
use alloc::borrow::Cow;

#[test]
fn test_serialize() {
let did = DID::new(
Some(Cow::from(
"46813BE38B798B3752CA590D44E7FEADB17485649074403AD1761A2835CE91FF",
)),
None,
Cow::from("rpfqJrXg5uidNo2ZsRhRY6TiF1cvYmV9Fg"),
Some(Cow::from("646F63")),
Some(Cow::from("617474657374")),
Some(Cow::from("6469645F6578616D706C65")),
Cow::from("0"),
Cow::from("A4C15DA185E6092DF5954FF62A1446220C61A5F60F0D93B4B09F708778E41120"),
4,
);
let serialized = serde_json::to_string(&did).unwrap();
let deserialized: DID = serde_json::from_str(&serialized).unwrap();
assert_eq!(did, deserialized);
}

#[test]
fn test_deserialize_from_json() {
let json = r#"{
"Account": "rpfqJrXg5uidNo2ZsRhRY6TiF1cvYmV9Fg",
"DIDDocument": "646F63",
"Data": "617474657374",
"Flags": 0,
"LedgerEntryType": "DID",
"OwnerNode": "0",
"PreviousTxnID": "A4C15DA185E6092DF5954FF62A1446220C61A5F60F0D93B4B09F708778E41120",
"PreviousTxnLgrSeq": 4,
"URI": "6469645F6578616D706C65",
"index": "46813BE38B798B3752CA590D44E7FEADB17485649074403AD1761A2835CE91FF"
}"#;

let did: DID = serde_json::from_str(json).unwrap();
assert_eq!(did.account, "rpfqJrXg5uidNo2ZsRhRY6TiF1cvYmV9Fg");
assert_eq!(did.did_document.as_deref(), Some("646F63"));
assert_eq!(did.data.as_deref(), Some("617474657374"));
assert_eq!(did.uri.as_deref(), Some("6469645F6578616D706C65"));
assert_eq!(did.owner_node, "0");
assert_eq!(did.previous_txn_lgr_seq, 4);
}
}
4 changes: 4 additions & 0 deletions src/models/ledger/objects/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod amm;
pub mod bridge;
pub mod check;
pub mod deposit_preauth;
pub mod did;
pub mod directory_node;
pub mod escrow;
pub mod fee_settings;
Expand All @@ -26,6 +27,7 @@ use bridge::Bridge;
use check::Check;
use deposit_preauth::DepositPreauth;
use derive_new::new;
use did::DID;
use directory_node::DirectoryNode;
use escrow::Escrow;
use fee_settings::FeeSettings;
Expand Down Expand Up @@ -57,6 +59,7 @@ pub enum LedgerEntryType {
AMM = 0x0079,
Bridge = 0x0069,
Check = 0x0043,
DID = 0x0049,
DepositPreauth = 0x0070,
DirectoryNode = 0x0064,
Escrow = 0x0075,
Expand All @@ -81,6 +84,7 @@ pub enum LedgerEntry<'a> {
AMM(AMM<'a>),
Bridge(Bridge<'a>),
Check(Check<'a>),
DID(DID<'a>),
DepositPreauth(DepositPreauth<'a>),
DirectoryNode(DirectoryNode<'a>),
Escrow(Escrow<'a>),
Expand Down
3 changes: 3 additions & 0 deletions src/models/requests/account_objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ use super::{CommonFields, LedgerIndex, LookupByLedgerRequest, Marker, Request};
#[serde(rename_all = "snake_case")]
pub enum AccountObjectType {
Check,
#[serde(rename = "did")]
#[strum(serialize = "did")]
DID,
DepositPreauth,
Escrow,
Offer,
Expand Down
2 changes: 2 additions & 0 deletions src/models/results/server_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub struct State<'a> {
pub closed_ledger: Option<ValidatedLedger<'a>>,
/// Amount of time spent waiting for I/O operations, in milliseconds
pub io_latency_ms: Option<u32>,
/// The network ID of the chain this server is connected to.
pub network_id: Option<u32>,
/// Number of times server had over 250 transactions waiting to be processed
pub jq_trans_overflow: Option<Cow<'a, str>>,
/// Information about the last time the server closed a ledger
Expand Down
Loading
Loading