A DIDComm v2 attachment.
Attachments carry payloads that travel alongside a message but are not part of the message body — signed credentials, large binaries, file references, etc.
Fields
id— unique attachment identifier (within the containing message)description— human-readable descriptionfilename— suggested filename for downloaded contentmedia_type— IANA media type of the content. For a Verifiable Grant this is the load-bearing field — see below.format— protocol-specific format identifierlastmod_time— a hint about when the content was last modified; an integer or a string, passed through unread. See below.byte_count— size of the attached content in bytesdata— map carrying the payload; see DIDComm v2 spec §5
data map
The data map may contain one or more of:
"base64"— base64url-encoded bytes (no padding)"json"— inline JSON value"jws"— the JWS; the cloud-node's credential extractor reads a compact JWS string from here"hash"— base64url-encoded SHA-256 of the referenced content"links"— list of URLs pointing to the content
Attaching a credential for authorization
Contract: ~/Developments/contracts/sender-cn-vg-attachment.md. In normal use
Layr8.Wallet builds these attachments automatically on every send — this
section is for hand-built ones.
%Layr8.Attachment{
id: "urn:uuid:…", # the credential's own id
media_type: "application/vc+jwt",
data: %{"jws" => compact_jws}
}media_type is the only thing the node's credential extractor filters on,
by exact string equality, and it drops every other attachment SILENTLY before
looking at the data at all. Two consequences that have each cost a team a day:
- Putting
"application/vc+jwt"informatand something else inmedia_typeattaches nothing, as far as the node is concerned. (These docs used to say to do exactly that.) - A Verifiable Presentation (
application/vp+jwt) — the standard envelope for a holder showing a credential, and whatLayr8.Presentations.sign_presentation/3produces — is dropped on the same rule. Attach the credential bare.
Either way the denial that follows is byte-for-byte the one you get for attaching nothing at all, which is why the mistake is expensive to find.
data.base64 also works (the extractor falls back to it and base64url-decodes
it), but prefer "jws": it is read first and it is what the whole ecosystem
writes.
lastmod_time is an integer OR a string, and this SDK reads neither
DIDComm v2 states no type for the field. Its Attachments section says
only "OPTIONAL. A hint about when the content in this attachment was last
modified", while the same document pins created_time and expires_time to
"UTC Epoch Seconds (seconds since 1970-01-01T00:00:00Z) as an integer". The
authors knew how to spell "epoch integer" and did not spell it here, so a
receiver is not entitled to demand one. Epoch seconds are what this SDK
writes and what the ecosystem mostly sends; an ISO-8601 string has also been
sent. Both arrive here unchanged.
The typespec used to say non_neg_integer() | nil while parse/1 converted
nothing, so a string sat in a field declared an integer.
Nothing here interprets the hint. A caller that wants a moment out of it matches on what arrived:
case att.lastmod_time do
nil -> nil # the sender sent no hint
secs when is_integer(secs) -> DateTime.from_unix!(secs)
iso when is_binary(iso) -> elem(DateTime.from_iso8601(iso), 1)
endAbsent, integer and string are three different values and stay three different values. "The sender sent no hint" and "the sender sent one I have to match on" are not the same fact.
Wire format
%{
"id" => "...",
"media_type" => "...",
"data" => %{"jws" => "..."}
}Optional fields are omitted when empty/nil.
Summary
Functions
Serializes an Attachment into a DIDComm v2 JSON envelope map.
Parses a DIDComm v2 attachment map into an Attachment struct.
Types
@type t() :: %Layr8.Attachment{ byte_count: non_neg_integer() | nil, data: data_map(), description: String.t(), filename: String.t(), format: String.t(), id: String.t(), lastmod_time: non_neg_integer() | String.t() | nil, media_type: String.t() }
Functions
Serializes an Attachment into a DIDComm v2 JSON envelope map.
Optional string fields are omitted when empty; optional numeric fields are
omitted when nil. data is always included (defaults to %{}).
Parses a DIDComm v2 attachment map into an Attachment struct.
Missing fields default to empty strings / nil / %{}.