Skip to main content

TransferRecord

Struct TransferRecord 

Source
pub struct TransferRecord {
    pub transfer_id: Uuid,
    pub passport_id: PassportId,
    pub from_operator: ResponsibleOperator,
    pub to_operator: ResponsibleOperator,
    pub reason: TransferReason,
    pub from_signature: Option<String>,
    pub node_acceptance_attestation: Option<String>,
    pub initiated_at: DateTime<Utc>,
    pub completed_at: Option<DateTime<Utc>>,
    pub rejected_at: Option<DateTime<Utc>>,
    pub cancelled_at: Option<DateTime<Utc>>,
    pub notes: Option<String>,
}
Expand description

A single transfer-of-responsibility event in the DPP lifecycle.

Recorded in two steps — the outgoing operator authorises the handover, and the hosting node then attests that the incoming operator accepted it. The two are not symmetric: see TransferRecord::from_signature and TransferRecord::node_acceptance_attestation, only the first of which is a party’s own signature.

The chain this builds is an auditable local record of what the node was told. It is not proof of who holds the obligations: under Implementing Regulation (EU) 2026/1778 Art. 6a that is the EU registry’s record, between actors whose identity is verified by eIDAS means under its Arts. 4-5.

Fields§

§transfer_id: Uuid

Unique identifier for this transfer event.

§passport_id: PassportId

The passport being transferred.

§from_operator: ResponsibleOperator

The outgoing (previous) responsible operator.

§to_operator: ResponsibleOperator

The incoming (new) responsible operator.

§reason: TransferReason

The reason for this transfer.

§from_signature: Option<String>

Compact JWS signature from the outgoing operator, signing over the transfer payload to authorise the handover.

Produced by the node that hosts the outgoing operator, using that operator’s key — so in the managed single-node model this genuinely is the outgoing party’s signature. Its counterpart below is not.

§node_acceptance_attestation: Option<String>

This node’s attestation that the incoming operator accepted the handover. Not a signature by the incoming operator.

The incoming operator is a party with no key on this node, so nothing here can be signed by them. What is recorded is a JWS over the same Self::signing_payload, produced by the same key that produced Self::from_signature, at the moment the acceptance step ran. Because the payload is RFC 8785 canonical, the JWS header carries no nonce or timestamp, and Ed25519 is deterministic, the two values are byte- identical in the managed model. The second one therefore carries exactly one bit — that the acceptance step ran — and completed_at carries the same bit with a timestamp attached.

It is named for what it is because the previous name, to_signature, was not. That name invited every consumer — including the registry notification built from this record — to read the value as the incoming operator’s own authorisation, which is a claim no node can make about a counterparty whose key it does not hold.

The authoritative record of who holds the obligations is the EU registry, under Implementing Regulation (EU) 2026/1778 Art. 6a, between actors whose identity is verified by eIDAS means under its Arts. 4–5. A chain of did:web identifiers is not evidence of that standing. See docs/regulatory/COMPLIANCE.md § Transfer-of-Responsibility Article Pin.

alias keeps chains stored under the old key readable: without it every already-completed transfer would deserialize with this field None and Self::is_complete would start answering false for handovers that did complete.

§initiated_at: DateTime<Utc>

Timestamp when the transfer was initiated.

§completed_at: Option<DateTime<Utc>>

Timestamp when the transfer was completed — the outgoing operator had authorised it and the acceptance step had run. None if the transfer is still pending acceptance.

§rejected_at: Option<DateTime<Utc>>

Timestamp when the incoming operator explicitly rejected the transfer. Set by TransferRecord::reject; makes the record terminal.

§cancelled_at: Option<DateTime<Utc>>

Timestamp when the outgoing operator cancelled the transfer. Set by TransferRecord::cancel; makes the record terminal.

§notes: Option<String>

Free-text notes (e.g. conditions, regulatory references).

Implementations§

Source§

impl TransferRecord

Source

pub fn signing_payload(&self) -> Value

The canonical content signed over at both steps: the immutable core of the transfer, excluding the signatures themselves and the lifecycle timestamps set after signing (completed_at/rejected_at/cancelled_at).

Self::from_signature and Self::node_acceptance_attestation are both JWS over the JCS canonicalisation of this value, so both bind the same immutable handover terms and tampering any bound field invalidates both.

Note what that implies: the same payload signed by the same key yields the same JWS, so in the managed single-node model the two values are byte-identical. That is a property of this design rather than a defect — the second records that the acceptance step ran, and nothing more.

Source

pub fn status(&self) -> TransferStatus

Determine the current status of this transfer.

Terminal states (Rejected, Cancelled) take priority over signatures, so a cancelled transfer that already had the from_signature still reports Cancelled rather than Initiated.

Source

pub fn is_complete(&self) -> bool

Returns true if the handover was authorised, accepted and finalised.

Source

pub fn reject(&mut self) -> Result<(), TransferError>

The incoming operator explicitly rejects the transfer.

Only valid from Initiated state. After rejection the record is terminal; a new transfer may be initiated on the chain.

Source

pub fn cancel(&mut self) -> Result<(), TransferError>

The outgoing operator cancels the transfer before it completes.

Valid from Initiated or Accepted state. After cancellation the record is terminal; a new transfer may be initiated on the chain.

Source

pub fn complete(&mut self) -> Result<(), TransferError>

Mark the transfer as completed once it is authorised and accepted.

Only valid from Accepted state (Self::from_signature and Self::node_acceptance_attestation both present, no completed_at yet). This is the final step before the incoming operator becomes the current responsible operator in this node’s chain — which is a local record, not the registry’s determination of who holds the obligations.

Trait Implementations§

Source§

impl Clone for TransferRecord

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for TransferRecord

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for TransferRecord

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Serialize for TransferRecord

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.