Allow to choose price when configuring subscription
This commit is contained in:
parent
b856f23eb1
commit
f346315296
4 changed files with 107 additions and 33 deletions
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue