diff --git a/src/background/config.ts b/src/background/config.ts index a9445776..3b80b984 100644 --- a/src/background/config.ts +++ b/src/background/config.ts @@ -1,3 +1,6 @@ -export const DEFAULT_AMOUNT = '0.6' export const DEFAULT_SCALE = 2 export const DEFAULT_INTERVAL_MS = 3_600_000 + +export const DEFAULT_RATE_OF_PAY = '60' +export const MIN_RATE_OF_PAY = '1' +export const MAX_RATE_OF_PAY = '100' diff --git a/src/background/services/openPayments.ts b/src/background/services/openPayments.ts index 892dac69..a9d4bcd0 100644 --- a/src/background/services/openPayments.ts +++ b/src/background/services/openPayments.ts @@ -13,12 +13,17 @@ import { type Request } from 'http-message-signatures' import { signMessage } from 'http-message-signatures/lib/httpbis' import { createContentDigestHeader } from 'httpbis-digest-headers' import { Browser } from 'webextension-polyfill' -import { getCurrentActiveTabId, toAmount } from '../utils' +import { getCurrentActiveTabId, getRateOfPay, toAmount } from '../utils' import { StorageService } from '@/background/services/storage' import { exportJWK, generateEd25519KeyPair } from '@/shared/crypto' import { bytesToHex } from '@noble/hashes/utils' -import { getWalletInformation } from '@/shared/helpers' +import { getExchangeRates, getWalletInformation } from '@/shared/helpers' import { ConnectWalletPayload } from '@/shared/messages' +import { + DEFAULT_RATE_OF_PAY, + MAX_RATE_OF_PAY, + MIN_RATE_OF_PAY +} from '../config' interface KeyInformation { privateKey: string @@ -217,6 +222,35 @@ export class OpenPaymentsService { recurring }: ConnectWalletPayload) { const walletAddress = await getWalletInformation(walletAddressUrl) + const exchangeRates = await getExchangeRates() + + let rateOfPay = DEFAULT_RATE_OF_PAY + let minRateOfPay = MIN_RATE_OF_PAY + let maxRateOfPay = MAX_RATE_OF_PAY + + if (!exchangeRates.rates[walletAddress.assetCode]) { + throw new Error(`Exchange rate for ${walletAddress.assetCode} not found.`) + } + + const exchangeRate = exchangeRates.rates[walletAddress.assetCode] + if (exchangeRate < 0.8 || exchangeRate > 1.5) { + rateOfPay = getRateOfPay({ + rate: DEFAULT_RATE_OF_PAY, + exchangeRate, + assetScale: walletAddress.assetScale + }) + minRateOfPay = getRateOfPay({ + rate: MIN_RATE_OF_PAY, + exchangeRate, + assetScale: walletAddress.assetScale + }) + maxRateOfPay = getRateOfPay({ + rate: MAX_RATE_OF_PAY, + exchangeRate, + assetScale: walletAddress.assetScale + }) + } + const transformedAmount = toAmount({ value: amount, recurring, @@ -262,6 +296,9 @@ export class OpenPaymentsService { this.storage.set({ walletAddress, + rateOfPay, + minRateOfPay, + maxRateOfPay, amount: transformedAmount, token: { value: continuation.access_token.value, diff --git a/src/background/services/storage.ts b/src/background/services/storage.ts index 46a9302c..6d589c9d 100644 --- a/src/background/services/storage.ts +++ b/src/background/services/storage.ts @@ -1,4 +1,4 @@ -import { DEFAULT_AMOUNT, DEFAULT_INTERVAL_MS } from '@/background/config' +import { DEFAULT_RATE_OF_PAY, DEFAULT_INTERVAL_MS } from '@/background/config' import { Logger } from '@/shared/logger' import type { PopupStore, @@ -16,6 +16,9 @@ const defaultStorage = { amount: null, token: null, grant: null, + rateOfPay: null, + minRateOfPay: null, + maxRateOfPay: null } satisfies Omit export class StorageService { @@ -87,7 +90,7 @@ export class StorageService { website.amount = data.exceptionList[url] } else { website.amount = { - value: DEFAULT_AMOUNT, + value: DEFAULT_RATE_OF_PAY, interval: DEFAULT_INTERVAL_MS } } diff --git a/src/background/utils.ts b/src/background/utils.ts index 380a6af7..62e08852 100644 --- a/src/background/utils.ts +++ b/src/background/utils.ts @@ -1,5 +1,6 @@ import { WalletAmount } from '@/shared/types' import { type Browser, action, runtime } from 'webextension-polyfill' +import { DEFAULT_SCALE } from './config' const iconActive34 = runtime.getURL('assets/icons/icon-active-34.png') const iconActive128 = runtime.getURL('assets/icons/icon-active-128.png') @@ -49,3 +50,20 @@ export const OPEN_PAYMENTS_ERRORS: Record = { 'invalid client': 'Please make sure that you uploaded the public key for your desired wallet address.' } + +export interface GetRateOfPayParams { + rate: string + exchangeRate: number + assetScale: number +} + +export const getRateOfPay = ({ + rate, + exchangeRate, + assetScale +}: GetRateOfPayParams) => { + const scaleDiff = assetScale - DEFAULT_SCALE + const scaledExchangeRate = (1 / exchangeRate) * 10 ** scaleDiff + + return BigInt(Math.round(Number(rate) * scaledExchangeRate)).toString() +} diff --git a/src/shared/helpers.ts b/src/shared/helpers.ts index fa4a4e37..6e5a849a 100644 --- a/src/shared/helpers.ts +++ b/src/shared/helpers.ts @@ -58,3 +58,25 @@ export const failure = (message: string) => ({ success: false, message }) + +interface ExchangeRates { + base: string + rates: Record +} + +export const getExchangeRates = async (): Promise => { + const response = await fetch( + 'https://telemetry-exchange-rates.s3.amazonaws.com/exchange-rates-usd.json' + ) + if (!response.ok) { + throw new Error( + `Could not fetch exchange rates. [Status code: ${response.status}]` + ) + } + const rates = await response.json() + if (!rates.base || !rates.rates) { + throw new Error('Invalid rates format') + } + + return rates +} diff --git a/src/shared/types.ts b/src/shared/types.ts index d4d3845f..433e0b56 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -33,6 +33,11 @@ export interface Storage { enabled: boolean /** If a wallet is connected or not */ connected: boolean + + rateOfPay?: string | undefined | null + minRateOfPay?: string | undefined | null + maxRateOfPay?: string | undefined | null + /** User wallet address information */ walletAddress?: WalletAddress | undefined | null /** Overall amount */ @@ -60,6 +65,5 @@ export type PopupStore = Omit< } export type DeepNonNullable = { - [P in keyof T]?: NonNullable; + [P in keyof T]?: NonNullable } -