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— last modification time as a Unix timestamp (integer)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.
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() | 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 / %{}.