Adding calculated field on save

You might show your schema, and migration file. Be sure :fingerprint field exists, as string…

You might also insert some IO.inspect in your pipeline to show the changeset, like so

  def changeset(whatever, attrs) do
    whatever
    |> changeset(attrs)
    |> cast(attrs, @fields)
    |> validate ...
    |> IO.inspect()
    |> fingerprint()
    |> IO.inspect()
    |> unique_constraint(:fingerprint, message: "Fingerprint already in use")
  end

The function fingerprint take a changeset, and if it is valid, it should persists the change.

If it is clearer, You could have used pattern matching, like so.

defp fingerprint(%Ecto.Changeset{valid?: true, changes: %{address: address}} = changeset) do
  put_change(changeset, :fingerprint, create_fingerprint(address))
end
defp fingerprint(changeset), do: changeset

BTW Try to change the name of the function to generate_fingerprint(), just to be sure it does not conflict with the field name.