diff --git a/.env.example b/.env.example index fb1e341..0ffa328 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,5 @@ # Use the testnet in development # ALEPH_API_URL=https://api.twentysix.testnet.network + +# APIs +LTAI_SUBSCRIPTIONS_API_URL=http://localhost:8000 diff --git a/package-lock.json b/package-lock.json index 8ca3900..2074604 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13994,22 +13994,6 @@ "thenify-all": "^1.0.0" } }, - "node_modules/nanoid": { - "version": "3.3.7", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "dev": true, @@ -15290,6 +15274,24 @@ "dev": true, "license": "MIT" }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/preact": { "version": "10.23.1", "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.1.tgz", diff --git a/quasar.config.js b/quasar.config.js index f02a537..a31f06d 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -30,7 +30,7 @@ module.exports = configure(function (ctx) { // app boot file (/src/boot) // --> boot files are part of "main.js" // https://v2.quasar.dev/quasar-cli-vite/boot-files - boot: ['utils', 'wagmi'], + boot: ['axios', 'utils', 'wagmi'], // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css css: ['app.scss', 'tailwind.css'], diff --git a/src/apis/subscriptions/services.gen.ts b/src/apis/subscriptions/services.gen.ts index 47139a0..e8e1ad0 100644 --- a/src/apis/subscriptions/services.gen.ts +++ b/src/apis/subscriptions/services.gen.ts @@ -1,10 +1,18 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type Options } from '@hey-api/client-axios'; -import type { SubscribeHoldSubscriptionPostData, SubscribeHoldSubscriptionPostError, SubscribeHoldSubscriptionPostResponse, UnsubscribeHoldSubscriptionDeleteData, UnsubscribeHoldSubscriptionDeleteError, UnsubscribeHoldSubscriptionDeleteResponse, RefreshActiveHoldSubscriptionsHoldRefreshPostError, RefreshActiveHoldSubscriptionsHoldRefreshPostResponse, HoldSubscriptionMessagesHoldMessageGetData, HoldSubscriptionMessagesHoldMessageGetError, HoldSubscriptionMessagesHoldMessageGetResponse, RefreshSubsRefreshPostError, RefreshSubsRefreshPostResponse, GetUserSubscriptionsSubscriptionsGetData, GetUserSubscriptionsSubscriptionsGetError, GetUserSubscriptionsSubscriptionsGetResponse } from './types.gen'; +import type { GetUserSubscriptionsSubscriptionsGetData, GetUserSubscriptionsSubscriptionsGetError, GetUserSubscriptionsSubscriptionsGetResponse, SubscribeHoldSubscriptionPostData, SubscribeHoldSubscriptionPostError, SubscribeHoldSubscriptionPostResponse, UnsubscribeHoldSubscriptionDeleteData, UnsubscribeHoldSubscriptionDeleteError, UnsubscribeHoldSubscriptionDeleteResponse, RefreshActiveHoldSubscriptionsHoldRefreshPostError, RefreshActiveHoldSubscriptionsHoldRefreshPostResponse, HoldSubscriptionMessagesHoldMessageGetData, HoldSubscriptionMessagesHoldMessageGetError, HoldSubscriptionMessagesHoldMessageGetResponse, RefreshSubsRefreshPostError, RefreshSubsRefreshPostResponse } from './types.gen'; export const client = createClient(createConfig()); +/** + * Get User Subscriptions + */ +export const getUserSubscriptionsSubscriptionsGet = (options: Options) => { return (options?.client ?? client).get({ + ...options, + url: '/subscriptions' +}); }; + /** * Subscribe */ @@ -45,12 +53,4 @@ export const holdSubscriptionMessagesHoldMessageGet = (options?: Options) => { return (options?.client ?? client).post({ ...options, url: '/subs/refresh' -}); }; - -/** - * Get User Subscriptions - */ -export const getUserSubscriptionsSubscriptionsGet = (options: Options) => { return (options?.client ?? client).get({ - ...options, - url: '/subscriptions' }); }; \ No newline at end of file diff --git a/src/apis/subscriptions/types.gen.ts b/src/apis/subscriptions/types.gen.ts index fe3e8c9..ab029a9 100644 --- a/src/apis/subscriptions/types.gen.ts +++ b/src/apis/subscriptions/types.gen.ts @@ -79,6 +79,16 @@ export type ValidationError = { type: string; }; +export type GetUserSubscriptionsSubscriptionsGetData = { + query: { + address: string; + }; +}; + +export type GetUserSubscriptionsSubscriptionsGetResponse = (GetUserSubscriptionsResponse); + +export type GetUserSubscriptionsSubscriptionsGetError = (HTTPValidationError); + export type SubscribeHoldSubscriptionPostData = { body: HoldPostSubscriptionBody; }; @@ -111,14 +121,4 @@ export type HoldSubscriptionMessagesHoldMessageGetError = (HTTPValidationError); export type RefreshSubsRefreshPostResponse = (SubsPostRefreshSubscriptionsResponse); -export type RefreshSubsRefreshPostError = unknown; - -export type GetUserSubscriptionsSubscriptionsGetData = { - query: { - address: string; - }; -}; - -export type GetUserSubscriptionsSubscriptionsGetResponse = (GetUserSubscriptionsResponse); - -export type GetUserSubscriptionsSubscriptionsGetError = (HTTPValidationError); \ No newline at end of file +export type RefreshSubsRefreshPostError = unknown; \ No newline at end of file diff --git a/src/boot/axios.ts b/src/boot/axios.ts new file mode 100644 index 0000000..e9f10dc --- /dev/null +++ b/src/boot/axios.ts @@ -0,0 +1,8 @@ +import { boot } from 'quasar/wrappers'; +import { client } from 'src/apis/subscriptions/services.gen'; + +export default boot(() => { + client.setConfig({ + baseURL: process.env.LTAI_SUBSCRIPTIONS_API_URL, + }); +}); diff --git a/src/pages/Subscriptions.vue b/src/pages/Subscriptions.vue new file mode 100644 index 0000000..9c18174 --- /dev/null +++ b/src/pages/Subscriptions.vue @@ -0,0 +1,42 @@ + + + diff --git a/src/router/routes.ts b/src/router/routes.ts index 07d82b3..839123f 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -32,6 +32,10 @@ const routes = [ path: 'persona-management', component: () => import('pages/PersonaManagement.vue'), }, + { + path: 'subscriptions', + component: () => import('pages/Subscriptions.vue'), + }, ], }, diff --git a/src/stores/account.ts b/src/stores/account.ts index 6a3e517..517c3d3 100644 --- a/src/stores/account.ts +++ b/src/stores/account.ts @@ -6,6 +6,7 @@ import { config } from 'src/config/wagmi'; import { base } from '@wagmi/vue/chains'; import { useTokensStore } from 'stores/tokens'; import { useKnowledgeStore } from 'stores/knowledge'; +import { useSubscriptionStore } from 'stores/subscription'; const LTAI_BASE_ADDRESS = '0xF8B1b47AA748F5C7b5D0e80C726a843913EB573a'; @@ -23,12 +24,13 @@ export const useAccountStore = defineStore('account', { async onAccountChange() { const tokensStore = useTokensStore(); const knowledgeStore = useKnowledgeStore(); + const subscriptionsStore = useSubscriptionStore(); this.ltaiBalance = await this.getLTAIBalance(); await this.initAlephStorage(); - await tokensStore.update(); - await knowledgeStore.load(); + + await Promise.all([tokensStore.update(), knowledgeStore.load(), subscriptionsStore.load()]); }, async initAlephStorage() { diff --git a/src/stores/settings.ts b/src/stores/settings.ts index c5f234c..d7d263c 100644 --- a/src/stores/settings.ts +++ b/src/stores/settings.ts @@ -61,7 +61,7 @@ export const useSettingsStore = defineStore('settings', { }, async persistOnAleph(settings: SettingsPersistedOnAleph) { - const account: any = useAccountStore(); + const account = useAccountStore(); if (account.alephStorage !== null) { await account.alephStorage.saveSettings(settings); diff --git a/src/stores/subscription.ts b/src/stores/subscription.ts new file mode 100644 index 0000000..8c9c9d9 --- /dev/null +++ b/src/stores/subscription.ts @@ -0,0 +1,68 @@ +import { defineStore } from 'pinia'; +import { + BaseSubscription, + getUserSubscriptionsSubscriptionsGet, + holdSubscriptionMessagesHoldMessageGet, + subscribeHoldSubscriptionPost, + SubscriptionType, +} from 'src/apis/subscriptions'; +import { getAccount, signMessage } from '@wagmi/core'; +import { config } from 'src/config/wagmi'; + +type SubscriptionState = { + subscriptions: BaseSubscription[]; + isLoaded: boolean; +}; + +export const useSubscriptionStore = defineStore('subscriptions', { + state: (): SubscriptionState => ({ + subscriptions: [], + isLoaded: false, + }), + actions: { + async load() { + const account = getAccount(config); + const address = account.address; + + if (address === undefined) { + return; + } + + const response = await getUserSubscriptionsSubscriptionsGet({ query: { address } }); + + this.subscriptions = response.data?.subscriptions ?? []; + this.isLoaded = true; + }, + + async holdSubscribe(subscriptionType: SubscriptionType) { + const account = getAccount(config); + const address = account.address; + + if (address === undefined) { + return; + } + + const messagesResponse = await holdSubscriptionMessagesHoldMessageGet({ + query: { subscription_type: subscriptionType }, + }); + + if (messagesResponse.data === undefined) { + throw new Error( + messagesResponse.error.detail?.toString() ?? 'Unable to fetch the message to sign to subscribe', + ); + } + + const messageToSign = messagesResponse.data.subscribe_message; + const hash = await signMessage(config, { message: messageToSign }); + + const subscriptionResponse = await subscribeHoldSubscriptionPost({ + body: { + signature: hash, + type: 'standard', + account: { chain: 'base', address }, + }, + }); + // TODO: handle errors and success + }, + }, +}); diff --git a/src/utils/aleph-persistent-storage.ts b/src/utils/aleph-persistent-storage.ts index f2eed2c..f681d8e 100644 --- a/src/utils/aleph-persistent-storage.ts +++ b/src/utils/aleph-persistent-storage.ts @@ -34,7 +34,7 @@ export class AlephPersistentStorage { private encryptionPrivateKey: PrivateKey, ) {} - static async signBaseMessage() { + static signBaseMessage() { return signMessage(config, { message: MESSAGE }); }