Allow to choose price when configuring subscription

This commit is contained in:
silverpill 2022-06-26 22:40:32 +00:00
parent b856f23eb1
commit f346315296
4 changed files with 107 additions and 33 deletions

View file

@ -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<BigNumber> {
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<EthereumSignature> {
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<TransactionResponse> {
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;

View file

@ -1,6 +1,6 @@
<template>
<div class="subscription">
<h1>Configure subscription</h1>
<div class="subscription">
<div class="connect-wallet" v-if="canConnectWallet()">
<button class="btn" @click="connectWallet()">Connect wallet</button>
</div>
@ -15,14 +15,23 @@
<div>Price of one month: {{ subscription.pricePerMonth }}</div>
</template>
<template v-else>
Subscription is not configured.
Subscription is not configured
</template>
</div>
<div class="setup" v-if="canConfigureSubscription()">
<button class="btn" @click="onConfigureSubscription()">
<form class="setup" v-if="canConfigureSubscription()">
<div class="price">
<label for="price">Price</label>
<input type="number" id="price" v-model="subscriptionPrice" min="0.00">
<span>per month</span>
</div>
<button
class="btn primary"
:disabled="subscriptionPrice <= 0"
@click.prevent="onConfigureSubscription()"
>
Set up subscription
</button>
</div>
</form>
<div class="withdraw" v-if="subscription !== null">
<input v-model="subscriberAddress" placeholder="Subscriber address">
<button class="btn" @click="onCheckSubsciptionState()">Check</button>
@ -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<number>(1)
let { walletAddress, walletError } = $(useWallet())
let subscriptionConfigured = $ref<boolean | null>(null)
let subscription = $ref<Subscription | null>(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;
}
}
</style>

View file

@ -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)
}

View file

@ -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")
})
})