10.63. DD 63: LibEufin Conversion Rate Class#

10.63.1. Summary#

We need an efficient solution to set specific conversion rates for some users. By efficient this means you do not have to set or update them manually for all accounts and a good way to see what are the current rates and which accounts use them.

10.63.2. Proposed Solution#

The current global conversion rate schema is:

interface ConversionRate {
  // Minimum fiat amount authorised for cashin before conversion
  cashin_min_amount: Amount;

  // Exchange rate to buy regional currency from fiat
  cashin_ratio: DecimalNumber;

  // Regional amount fee to subtract after applying the cashin ratio.
  cashin_fee: Amount;

  // Smallest possible regional amount, converted amount is rounded to this amount
  cashin_tiny_amount: Amount;

  // Rounding mode used during cashin conversion
  cashin_rounding_mode: "zero" | "up" | "nearest";

  // Minimum regional amount authorised for cashout before conversion
  cashout_min_amount: Amount;

  // Exchange rate to sell regional currency for fiat
  cashout_ratio: DecimalNumber;

  // Fiat amount fee to subtract after applying the cashout ratio.
  cashout_fee: Amount;

  // Smallest possible fiat amount, converted amount is rounded to this amount
  cashout_tiny_amount: Amount;

  // Rounding mode used during cashout conversion
  cashout_rounding_mode: "zero" | "up" | "nearest";
}

This would become the default class, that will be used by all created users. The administrator would be able to create new classes that override some of the default properties:

interface ConversionRateClassRequest {
  // The name of this class
  name: string;

  // A description of the class
  description?: string;

  // Minimum fiat amount authorised for cashin before conversion
  cashin_min_amount?: Amount;

  // Exchange rate to buy regional currency from fiat
  cashin_ratio?: DecimalNumber;

  // Regional amount fee to subtract after applying the cashin ratio.
  cashin_fee?: Amount;

  // Smallest possible regional amount, converted amount is rounded to this amount
  cashin_tiny_amount?: Amount;

  // Rounding mode used during cashin conversion
  cashin_rounding_mode?: "zero" | "up" | "nearest";

  // Minimum regional amount authorised for cashout before conversion
  cashout_min_amount?: Amount;

  // Exchange rate to sell regional currency for fiat
  cashout_ratio?: DecimalNumber;

  // Fiat amount fee to subtract after applying the cashout ratio.
  cashout_fee?: Amount;

  // Smallest possible fiat amount, converted amount is rounded to this amount
  cashout_tiny_amount?: Amount;

  // Rounding mode used during cashout conversion
  cashout_rounding_mode?: "zero" | "up" | "nearest";
}
interface ConversionRateClass {
  // The name of this class
  name: string;

  // A description of the class
  description?: string;

  // Class unique ID
  row_id: Integer;

  // Number of users affected to this class
  num_users: Integer;

  // Applied conversion rate
  conversion_rate: ConversionRate;

  // Minimum fiat amount authorised for cashin before conversion
  cashin_min_amount?: Amount;

  // Exchange rate to buy regional currency from fiat
  cashin_ratio?: DecimalNumber;

  // Regional amount fee to subtract after applying the cashin ratio.
  cashin_fee?: Amount;

  // Smallest possible regional amount, converted amount is rounded to this amount
  cashin_tiny_amount?: Amount;

  // Rounding mode used during cashin conversion
  cashin_rounding_mode?: "zero" | "up" | "nearest";

  // Minimum regional amount authorised for cashout before conversion
  cashout_min_amount?: Amount;

  // Exchange rate to sell regional currency for fiat
  cashout_ratio?: DecimalNumber;

  // Fiat amount fee to subtract after applying the cashout ratio.
  cashout_fee?: Amount;

  // Smallest possible fiat amount, converted amount is rounded to this amount
  cashout_tiny_amount?: Amount;

  // Rounding mode used during cashout conversion
  cashout_rounding_mode?: "zero" | "up" | "nearest";
}
interface ConversionRateClasses {
  default: ConversionRate;
  classes: ConversionRateClass[];
}

When we run the conversion logic we take values from the user class and fallback to the default values when they are null. If the ratio is zero it disable the conversion.

10.63.2.1. Taler Conversion Info API#

We need to move the current conversion-info API from /conversion-info/* to /accounts/$USERNAME/conversion-info/*. We keep the current API to only show the default rate for retro compatibility.

We deprecate POST /conversion-rate to make the API readonly (the Info in the name was a hint).

TODO: How hard is it to migrate to this in the wallets ?

10.63.2.2. Taler Core Bank API#

We migrate POST /conversion-rate here to set the default conversion rate class value.

We add new admin only conversion rate class management endpoints:

POST /conversion-rate-class GET /conversion-rate-class GET /conversion-rate-class/$CLASS-ID PATCH /conversion-rate-class/$CLASS-ID DELETE /conversion-rate-class/$CLASS-ID

add /conversion-rate-class/$CLASS-ID/conversion-info/*

We add admin only conversion_rate_class Integer optional field to POST /accounts and PATCH /accounts/$USERNAME.

We add conversion_rate_class Integer optional field and query filter to GET /accounts We add conversion_rate_class ConversionRateClass field of GET /accounts/$USERNAME