Skip to content
Closed
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
41 changes: 10 additions & 31 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@ import (
"strings"
)

// The TAIP-5 roles this package attaches meaning to. Others (CustodialService,
// EscrowAgent, and anything a future TAIP adds) pass through untouched.
//
// TAIP-5 also allows `role` to be an array of strings. Agent.Role is a plain
// string, so such an agent fails to unmarshal rather than validating wrongly —
// supporting it needs a string-or-array type like ForField.
// The TAIP-5 roles this package attaches meaning to; others pass through
// untouched. TAIP-5's prose also allows `role` to be an array, but its JSON
// schema and TypeScript types do not, so Role stays a string.
const (
RoleSourceAddress = "SourceAddress"
RoleSettlementAddress = "SettlementAddress"
Expand Down Expand Up @@ -52,25 +49,10 @@ type Agent struct {
ServiceURL string `json:"serviceUrl,omitempty"`
}

// Validate checks that the Agent can be placed by a receiver.
//
// TAIP-5 marks two attributes REQUIRED — `@id` and `for` — but has no way to
// say "who owns this agent is not established yet", which is a state a real
// flow passes through: an address may be seen before anybody has resolved who
// custodies it. Rejecting those outright would mean inventing an owner, and an
// invented `for` is worse than an absent one — a receiver stores it as a fact,
// and downstream it is the difference between a self-hosted wallet (which must
// prove ownership) and a custodied one (which must not).
//
// So the line is drawn where the sender cannot honestly be unsure:
// - `@id` is always required; an agent nobody can name is unusable.
// - `for` is required on the blockchain-address roles (`SourceAddress`,
// `SettlementAddress`). Whoever puts an address on a transaction knows
// whose address it is — that is the case TAIP-5 exists to pin down, and
// the one that was silently going out empty.
// - `for`, when present, must not hold empty DIDs, whatever the role.
//
// Agents whose `for` is genuinely not yet known travel without it.
// Validate enforces `for` on address agents only, though TAIP-5 marks it
// REQUIRED on every agent. The spec cannot express "owner not established yet",
// and an invented `for` is worse than an absent one: a receiver stores it as
// fact, and it decides whether a wallet must prove ownership.
func (a Agent) Validate() error {
if a.ID == "" {
return fmt.Errorf("%w: agent missing @id", ErrInvalidBody)
Expand All @@ -86,12 +68,9 @@ func (a Agent) Validate() error {
return nil
}

// addressRole reports whether this agent is a blockchain address rather than an
// institution — the roles whose owner the sender necessarily knows.
//
// Matched case-insensitively although TAIP-5 mandates PascalCase: a caller who
// mistypes the casing should still have a missing `for` caught, rather than
// slipping past the check on a technicality.
// addressRole reports whether this agent is a blockchain address — the roles
// whose owner the sender necessarily knows. Case-insensitive despite TAIP-5
// mandating PascalCase, so a mistyped role still gets its `for` checked.
func (a Agent) addressRole() bool {
return strings.EqualFold(a.Role, RoleSourceAddress) ||
strings.EqualFold(a.Role, RoleSettlementAddress)
Expand Down
Loading