From f3463152962519673eae03c2f46b06b3ee67fd24 Mon Sep 17 00:00:00 2001 From: silverpill Date: Sun, 26 Jun 2022 22:40:32 +0000 Subject: [PATCH] Allow to choose price when configuring subscription --- src/api/subscriptions.ts | 25 ++++++-- src/components/SubscriptionSetup.vue | 85 +++++++++++++++++++--------- src/utils/numbers.ts | 18 ++++++ tests/unit/numbers.spec.ts | 12 +++- 4 files changed, 107 insertions(+), 33 deletions(-) diff --git a/src/api/subscriptions.ts b/src/api/subscriptions.ts index 1731b5b..aaf85cb 100644 --- a/src/api/subscriptions.ts +++ b/src/api/subscriptions.ts @@ -4,17 +4,35 @@ import { DateTime } from "luxon" import { BACKEND_URL } from "@/constants" import { ethereumAddressMatch, EthereumSignature } from "@/utils/ethereum" -import { roundBigNumber } from "@/utils/numbers" +import { floatToBigNumber, roundBigNumber } from "@/utils/numbers" import { http } from "./common" import { Contracts, getContract } from "./contracts" +const SECONDS_IN_DAY = 3600 * 24 +const SECONDS_IN_MONTH = SECONDS_IN_DAY * 30 + +export async function getPricePerSec( + contractAddress: string, + signer: Signer, + pricePerMonth: number, +): Promise { + const adapter = await getContract(Contracts.Adapter, contractAddress, signer) + const tokenAddress = await adapter.subscriptionToken() + const token = await getContract(Contracts.ERC20, tokenAddress, signer) + const tokenDecimals = await token.decimals() + const pricePerMonthInt = floatToBigNumber(pricePerMonth, tokenDecimals) + return pricePerMonthInt.div(SECONDS_IN_MONTH) +} + export async function getSubscriptionAuthorization( authToken: string, + pricePerSec: BigNumber, ): Promise { const url = `${BACKEND_URL}/api/v1/accounts/authorize_subscription` const response = await http(url, { method: "GET", authToken, + queryParams: { price: pricePerSec.toString() }, }) const data = await response.json() if (response.status !== 200) { @@ -28,11 +46,13 @@ export async function configureSubscription( contractAddress: string, signer: Signer, recipientAddress: string, + pricePerSec: BigNumber, serverSignature: EthereumSignature, ): Promise { const adapter = await getContract(Contracts.Adapter, contractAddress, signer) const transaction = await adapter.configureSubscription( recipientAddress, + pricePerSec, serverSignature.v, "0x" + serverSignature.r, "0x" + serverSignature.s, @@ -40,9 +60,6 @@ export async function configureSubscription( return transaction } -const SECONDS_IN_DAY = 3600 * 24 -const SECONDS_IN_MONTH = SECONDS_IN_DAY * 30 - export class Subscription { recipientAddress: string; diff --git a/src/components/SubscriptionSetup.vue b/src/components/SubscriptionSetup.vue index 009f6c5..751d646 100644 --- a/src/components/SubscriptionSetup.vue +++ b/src/components/SubscriptionSetup.vue @@ -1,6 +1,6 @@ -
- -
+
@@ -40,6 +49,7 @@ import { $, $$, $ref } from "vue/macros" import { getVerifiedEthereumAddress, Profile } from "@/api/users" import { configureSubscription, + getPricePerSec, getSubscriptionAuthorization, getSubscriptionInfo, getSubscriptionState, @@ -57,10 +67,11 @@ const props = defineProps<{ profile: Profile, }>() -const { currentUser, ensureAuthToken } = $(useCurrentUser()) +const { ensureAuthToken } = $(useCurrentUser()) const { instance } = $(useInstanceInfo()) const { connectWallet: connectEthereumWallet, getSigner } = useWallet() const profileEthereumAddress = getVerifiedEthereumAddress(props.profile) +const subscriptionPrice = $ref(1) let { walletAddress, walletError } = $(useWallet()) let subscriptionConfigured = $ref(null) let subscription = $ref(null) @@ -128,28 +139,32 @@ async function checkSubscription() { function canConfigureSubscription(): boolean { return ( - Boolean(currentUser?.wallet_address) && + profileEthereumAddress !== null && subscriptionConfigured === false ) } async function onConfigureSubscription() { if ( - !currentUser || - !currentUser.wallet_address || + profileEthereumAddress === null || !instance || !instance.blockchain_contract_address ) { return } - // Subscription configuration tx can be sent from any address const signer = getSigner() const authToken = ensureAuthToken() - const signature = await getSubscriptionAuthorization(authToken) + const pricePerSec = await getPricePerSec( + instance.blockchain_contract_address, + signer, + subscriptionPrice, + ) + const signature = await getSubscriptionAuthorization(authToken, pricePerSec) const transaction = await configureSubscription( instance.blockchain_contract_address, signer, - currentUser.wallet_address, + profileEthereumAddress, + pricePerSec, signature, ) await transaction.wait() @@ -157,7 +172,7 @@ async function onConfigureSubscription() { subscription = await getSubscriptionInfo( instance.blockchain_contract_address, signer, - currentUser.wallet_address, + profileEthereumAddress, ) } @@ -201,19 +216,38 @@ async function onWithdrawReceived() { @import "../styles/theme"; .subscription { - @include block-btn; - - background-color: $block-background-color; - border-radius: $block-border-radius; display: flex; flex-direction: column; - gap: $block-inner-padding / 2; - margin-bottom: $block-outer-padding; - padding: $block-inner-padding; + gap: $block-outer-padding; + text-align: center; +} - h1 { - font-size: 20px; - margin: 0; +.info { + background-color: $block-background-color; + border-radius: $block-border-radius; + padding: $block-inner-padding; +} + +.setup { + align-items: center; + display: flex; + flex-direction: column; + gap: $block-inner-padding; + + .price { + align-items: center; + display: flex; + font-size: 16px; + gap: $input-padding; + justify-content: center; + + label { + font-weight: bold; + } + + input { + width: 100px; + } } } @@ -221,10 +255,5 @@ async function onWithdrawReceived() { display: flex; flex-wrap: wrap; gap: $block-inner-padding / 2; - - input { - border: 1px solid $btn-background-color; - border-radius: $btn-border-radius; - } } diff --git a/src/utils/numbers.ts b/src/utils/numbers.ts index 7f96adc..c502c76 100644 --- a/src/utils/numbers.ts +++ b/src/utils/numbers.ts @@ -11,3 +11,21 @@ export function roundBigNumber(value: BigNumber, precision: number): BigNumber { return value.div(divisor).mul(divisor) } } + +function getPrecision(value: number): number { + if (!isFinite(value)) { + return 0 + } + let precision = 0 + while (Math.round(value * Math.pow(10, precision)) / Math.pow(10, precision) !== value) { + precision++ + } + return precision +} + +export function floatToBigNumber(value: number, decimals: number): BigNumber { + const precision = getPrecision(value) + const denominator = 10 ** precision + const numerator = Math.round(value * denominator) + return BigNumber.from(10).pow(decimals).mul(numerator).div(denominator) +} diff --git a/tests/unit/numbers.spec.ts b/tests/unit/numbers.spec.ts index 837d309..3f37cfc 100644 --- a/tests/unit/numbers.spec.ts +++ b/tests/unit/numbers.spec.ts @@ -1,6 +1,6 @@ import { expect } from "chai" import { BigNumber } from "@ethersproject/bignumber" -import { roundBigNumber } from "@/utils/numbers" +import { floatToBigNumber, roundBigNumber } from "@/utils/numbers" describe("Numbers utils", () => { it("Should round big number", () => { @@ -11,4 +11,14 @@ describe("Numbers utils", () => { expect(roundBigNumber(value, 5).toNumber()).to.equal(534990) expect(roundBigNumber(value, 6).toNumber()).to.equal(534985) }) + + it("Should convert float to big number", () => { + const value1 = 3.94031726813 + const value2 = 1 + const decimals = 18 + expect(floatToBigNumber(value1, decimals).toString()) + .to.equal("3940317268130000000") + expect(floatToBigNumber(value2, decimals).toString()) + .to.equal("1000000000000000000") + }) })