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 { BACKEND_URL } from "@/constants"
import { ethereumAddressMatch, EthereumSignature } from "@/utils/ethereum" import { ethereumAddressMatch, EthereumSignature } from "@/utils/ethereum"
import { roundBigNumber } from "@/utils/numbers" import { floatToBigNumber, roundBigNumber } from "@/utils/numbers"
import { http } from "./common" import { http } from "./common"
import { Contracts, getContract } from "./contracts" 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( export async function getSubscriptionAuthorization(
authToken: string, authToken: string,
pricePerSec: BigNumber,
): Promise<EthereumSignature> { ): Promise<EthereumSignature> {
const url = `${BACKEND_URL}/api/v1/accounts/authorize_subscription` const url = `${BACKEND_URL}/api/v1/accounts/authorize_subscription`
const response = await http(url, { const response = await http(url, {
method: "GET", method: "GET",
authToken, authToken,
queryParams: { price: pricePerSec.toString() },
}) })
const data = await response.json() const data = await response.json()
if (response.status !== 200) { if (response.status !== 200) {
@ -28,11 +46,13 @@ export async function configureSubscription(
contractAddress: string, contractAddress: string,
signer: Signer, signer: Signer,
recipientAddress: string, recipientAddress: string,
pricePerSec: BigNumber,
serverSignature: EthereumSignature, serverSignature: EthereumSignature,
): Promise<TransactionResponse> { ): Promise<TransactionResponse> {
const adapter = await getContract(Contracts.Adapter, contractAddress, signer) const adapter = await getContract(Contracts.Adapter, contractAddress, signer)
const transaction = await adapter.configureSubscription( const transaction = await adapter.configureSubscription(
recipientAddress, recipientAddress,
pricePerSec,
serverSignature.v, serverSignature.v,
"0x" + serverSignature.r, "0x" + serverSignature.r,
"0x" + serverSignature.s, "0x" + serverSignature.s,
@ -40,9 +60,6 @@ export async function configureSubscription(
return transaction return transaction
} }
const SECONDS_IN_DAY = 3600 * 24
const SECONDS_IN_MONTH = SECONDS_IN_DAY * 30
export class Subscription { export class Subscription {
recipientAddress: string; recipientAddress: string;

View file

@ -1,6 +1,6 @@
<template> <template>
<h1>Configure subscription</h1>
<div class="subscription"> <div class="subscription">
<h1>Configure subscription</h1>
<div class="connect-wallet" v-if="canConnectWallet()"> <div class="connect-wallet" v-if="canConnectWallet()">
<button class="btn" @click="connectWallet()">Connect wallet</button> <button class="btn" @click="connectWallet()">Connect wallet</button>
</div> </div>
@ -15,14 +15,23 @@
<div>Price of one month: {{ subscription.pricePerMonth }}</div> <div>Price of one month: {{ subscription.pricePerMonth }}</div>
</template> </template>
<template v-else> <template v-else>
Subscription is not configured. Subscription is not configured
</template> </template>
</div> </div>
<div class="setup" v-if="canConfigureSubscription()"> <form class="setup" v-if="canConfigureSubscription()">
<button class="btn" @click="onConfigureSubscription()"> <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 Set up subscription
</button> </button>
</div> </form>
<div class="withdraw" v-if="subscription !== null"> <div class="withdraw" v-if="subscription !== null">
<input v-model="subscriberAddress" placeholder="Subscriber address"> <input v-model="subscriberAddress" placeholder="Subscriber address">
<button class="btn" @click="onCheckSubsciptionState()">Check</button> <button class="btn" @click="onCheckSubsciptionState()">Check</button>
@ -40,6 +49,7 @@ import { $, $$, $ref } from "vue/macros"
import { getVerifiedEthereumAddress, Profile } from "@/api/users" import { getVerifiedEthereumAddress, Profile } from "@/api/users"
import { import {
configureSubscription, configureSubscription,
getPricePerSec,
getSubscriptionAuthorization, getSubscriptionAuthorization,
getSubscriptionInfo, getSubscriptionInfo,
getSubscriptionState, getSubscriptionState,
@ -57,10 +67,11 @@ const props = defineProps<{
profile: Profile, profile: Profile,
}>() }>()
const { currentUser, ensureAuthToken } = $(useCurrentUser()) const { ensureAuthToken } = $(useCurrentUser())
const { instance } = $(useInstanceInfo()) const { instance } = $(useInstanceInfo())
const { connectWallet: connectEthereumWallet, getSigner } = useWallet() const { connectWallet: connectEthereumWallet, getSigner } = useWallet()
const profileEthereumAddress = getVerifiedEthereumAddress(props.profile) const profileEthereumAddress = getVerifiedEthereumAddress(props.profile)
const subscriptionPrice = $ref<number>(1)
let { walletAddress, walletError } = $(useWallet()) let { walletAddress, walletError } = $(useWallet())
let subscriptionConfigured = $ref<boolean | null>(null) let subscriptionConfigured = $ref<boolean | null>(null)
let subscription = $ref<Subscription | null>(null) let subscription = $ref<Subscription | null>(null)
@ -128,28 +139,32 @@ async function checkSubscription() {
function canConfigureSubscription(): boolean { function canConfigureSubscription(): boolean {
return ( return (
Boolean(currentUser?.wallet_address) && profileEthereumAddress !== null &&
subscriptionConfigured === false subscriptionConfigured === false
) )
} }
async function onConfigureSubscription() { async function onConfigureSubscription() {
if ( if (
!currentUser || profileEthereumAddress === null ||
!currentUser.wallet_address ||
!instance || !instance ||
!instance.blockchain_contract_address !instance.blockchain_contract_address
) { ) {
return return
} }
// Subscription configuration tx can be sent from any address
const signer = getSigner() const signer = getSigner()
const authToken = ensureAuthToken() 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( const transaction = await configureSubscription(
instance.blockchain_contract_address, instance.blockchain_contract_address,
signer, signer,
currentUser.wallet_address, profileEthereumAddress,
pricePerSec,
signature, signature,
) )
await transaction.wait() await transaction.wait()
@ -157,7 +172,7 @@ async function onConfigureSubscription() {
subscription = await getSubscriptionInfo( subscription = await getSubscriptionInfo(
instance.blockchain_contract_address, instance.blockchain_contract_address,
signer, signer,
currentUser.wallet_address, profileEthereumAddress,
) )
} }
@ -201,19 +216,38 @@ async function onWithdrawReceived() {
@import "../styles/theme"; @import "../styles/theme";
.subscription { .subscription {
@include block-btn;
background-color: $block-background-color;
border-radius: $block-border-radius;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $block-inner-padding / 2; gap: $block-outer-padding;
margin-bottom: $block-outer-padding; text-align: center;
padding: $block-inner-padding; }
h1 { .info {
font-size: 20px; background-color: $block-background-color;
margin: 0; 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; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: $block-inner-padding / 2; gap: $block-inner-padding / 2;
input {
border: 1px solid $btn-background-color;
border-radius: $btn-border-radius;
}
} }
</style> </style>

View file

@ -11,3 +11,21 @@ export function roundBigNumber(value: BigNumber, precision: number): BigNumber {
return value.div(divisor).mul(divisor) 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 { expect } from "chai"
import { BigNumber } from "@ethersproject/bignumber" import { BigNumber } from "@ethersproject/bignumber"
import { roundBigNumber } from "@/utils/numbers" import { floatToBigNumber, roundBigNumber } from "@/utils/numbers"
describe("Numbers utils", () => { describe("Numbers utils", () => {
it("Should round big number", () => { it("Should round big number", () => {
@ -11,4 +11,14 @@ describe("Numbers utils", () => {
expect(roundBigNumber(value, 5).toNumber()).to.equal(534990) expect(roundBigNumber(value, 5).toNumber()).to.equal(534990)
expect(roundBigNumber(value, 6).toNumber()).to.equal(534985) 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")
})
}) })