diff --git a/.changeset/calm-pens-teach.md b/.changeset/calm-pens-teach.md new file mode 100644 index 00000000000..dffb3669e96 --- /dev/null +++ b/.changeset/calm-pens-teach.md @@ -0,0 +1,5 @@ +--- +'@audius/sdk': minor +--- + +Add sdk.albums.purchase() diff --git a/packages/commands/src/purchase-content.mjs b/packages/commands/src/purchase-content.mjs index c1e3c7b9fba..638155489ad 100644 --- a/packages/commands/src/purchase-content.mjs +++ b/packages/commands/src/purchase-content.mjs @@ -117,3 +117,39 @@ program.command('purchase-track') } process.exit(0) }) + +program.command('purchase-album') + .description('Buys an album using USDC') + .argument('', 'The album ID') + .argument('', 'The expected price of the album', parseFloat) + .option('-f, --from [from]', 'The account purchasing the content (handle)') + .option( + '-e, --extra-amount [amount]', + 'Extra amount to pay in addition to the price (in dollars)' + , parseFloat) + .action(async (id, price, { from, extraAmount }) => { + const audiusLibs = await initializeAudiusLibs(from) + const userIdNumber = audiusLibs.userStateManager.getCurrentUserId() + const userId = Utils.encodeHashId(userIdNumber) + const albumId = Utils.encodeHashId(id) + + // extract privkey and pubkey from hedgehog + // only works with accounts created via audius-cmd + const wallet = audiusLibs?.hedgehog?.getWallet() + const privKey = wallet?.getPrivateKeyString() + const pubKey = wallet?.getAddressString() + + // init sdk with priv and pub keys as api keys and secret + // this enables writes via sdk + const audiusSdk = await initializeAudiusSdk({ apiKey: pubKey, apiSecret: privKey }) + + try { + console.log('Purchasing album...', { albumId, userId, price, extraAmount }) + const response = await audiusSdk.albums.purchase({ albumId, userId, price, extraAmount }) + console.log(chalk.green('Successfully purchased album')) + console.log(chalk.yellow('Transaction Signature:'), response) + } catch (err) { + program.error(err) + } + process.exit(0) + }) diff --git a/packages/discovery-provider/src/api/v1/models/playlists.py b/packages/discovery-provider/src/api/v1/models/playlists.py index a37c11b7d1f..ad14d7955e9 100644 --- a/packages/discovery-provider/src/api/v1/models/playlists.py +++ b/packages/discovery-provider/src/api/v1/models/playlists.py @@ -1,5 +1,7 @@ from flask_restx import fields +from src.api.v1.models.access_gate import access_gate +from src.api.v1.models.extensions.fields import NestedOneOf from src.api.v1.models.tracks import track_full from src.api.v1.models.users import user_model, user_model_full @@ -30,6 +32,7 @@ playlist_model = ns.model( "playlist", { + "blocknumber": fields.Integer(required=True), "artwork": fields.Nested(playlist_artwork, allow_null=True), "description": fields.String, "permalink": fields.String, @@ -47,6 +50,12 @@ "ddex_app": fields.String(allow_null=True), "access": fields.Nested(access), "upc": fields.String(allow_null=True), + "is_stream_gated": fields.Boolean(required=True), + "stream_conditions": NestedOneOf( + access_gate, + allow_null=True, + description="How to unlock stream access to the track", + ), }, ) @@ -54,7 +63,6 @@ "playlist_full_without_tracks", playlist_model, { - "blocknumber": fields.Integer(required=True), "created_at": fields.String, "followee_reposts": fields.List(fields.Nested(repost), required=True), "followee_favorites": fields.List(fields.Nested(favorite), required=True), @@ -73,8 +81,6 @@ "cover_art_sizes": fields.String, "cover_art_cids": fields.Nested(playlist_artwork, allow_null=True), "track_count": fields.Integer(required=True), - "is_stream_gated": fields.Boolean(required=True), - "stream_conditions": fields.Raw(allow_null=True), }, ) diff --git a/packages/libs/src/sdk/api/albums/AlbumsApi.test.ts b/packages/libs/src/sdk/api/albums/AlbumsApi.test.ts index ecd9b1078df..3f000c59420 100644 --- a/packages/libs/src/sdk/api/albums/AlbumsApi.test.ts +++ b/packages/libs/src/sdk/api/albums/AlbumsApi.test.ts @@ -3,10 +3,21 @@ import path from 'path' import { beforeAll, expect, jest } from '@jest/globals' +import { developmentConfig } from '../../config/development' import { DefaultAuth } from '../../services/Auth/DefaultAuth' import { DiscoveryNodeSelector } from '../../services/DiscoveryNodeSelector' import { EntityManager } from '../../services/EntityManager' import { Logger } from '../../services/Logger' +import { SolanaRelay } from '../../services/Solana/SolanaRelay' +import { SolanaRelayWalletAdapter } from '../../services/Solana/SolanaRelayWalletAdapter' +import { + ClaimableTokensClient, + getDefaultClaimableTokensConfig +} from '../../services/Solana/programs/ClaimableTokensClient' +import { + PaymentRouterClient, + getDefaultPaymentRouterClientConfig +} from '../../services/Solana/programs/PaymentRouterClient' import { Storage } from '../../services/Storage' import { StorageNodeSelector } from '../../services/StorageNodeSelector' import { Genre } from '../../types/Genre' @@ -110,12 +121,27 @@ describe('AlbumsApi', () => { }) beforeAll(() => { + const solanaWalletAdapter = new SolanaRelayWalletAdapter({ + solanaRelay: new SolanaRelay( + new Configuration({ + middleware: [discoveryNodeSelector.createMiddleware()] + }) + ) + }) albums = new AlbumsApi( new Configuration(), new Storage({ storageNodeSelector, logger: new Logger() }), new EntityManager({ discoveryNodeSelector: new DiscoveryNodeSelector() }), auth, - logger + logger, + new ClaimableTokensClient({ + ...getDefaultClaimableTokensConfig(developmentConfig), + solanaWalletAdapter + }), + new PaymentRouterClient({ + ...getDefaultPaymentRouterClientConfig(developmentConfig), + solanaWalletAdapter + }) ) jest.spyOn(console, 'warn').mockImplementation(() => {}) jest.spyOn(console, 'info').mockImplementation(() => {}) diff --git a/packages/libs/src/sdk/api/albums/AlbumsApi.ts b/packages/libs/src/sdk/api/albums/AlbumsApi.ts index 0f51ea89fb6..d0dc609fe7b 100644 --- a/packages/libs/src/sdk/api/albums/AlbumsApi.ts +++ b/packages/libs/src/sdk/api/albums/AlbumsApi.ts @@ -1,11 +1,22 @@ -import type { AuthService, StorageService } from '../../services' +import { USDC } from '@audius/fixed-decimal' + +import type { + AuthService, + ClaimableTokensClient, + PaymentRouterClient, + StorageService +} from '../../services' import type { EntityManagerService, AdvancedOptions } from '../../services/EntityManager/types' import type { LoggerService } from '../../services/Logger' import { parseParams } from '../../utils/parseParams' -import type { Configuration } from '../generated/default' +import { + instanceOfPurchaseGate, + UsdcGate, + type Configuration +} from '../generated/default' import { PlaylistsApi } from '../playlists/PlaylistsApi' import { @@ -17,6 +28,8 @@ import { FavoriteAlbumSchema, getAlbumRequest, getAlbumTracksRequest, + PurchaseAlbumRequest, + PurchaseAlbumSchema, RepostAlbumRequest, RepostAlbumSchema, UnfavoriteAlbumRequest, @@ -33,8 +46,10 @@ export class AlbumsApi { configuration: Configuration, storage: StorageService, entityManager: EntityManagerService, - auth: AuthService, - logger: LoggerService + private auth: AuthService, + private logger: LoggerService, + private claimableTokensClient: ClaimableTokensClient, + private paymentRouterClient: PaymentRouterClient ) { this.playlistsApi = new PlaylistsApi( configuration, @@ -214,4 +229,179 @@ export class AlbumsApi { advancedOptions ) } + + /** + * Purchases stream access to an album + * + * @hidden + */ + async purchase(params: PurchaseAlbumRequest) { + const { + userId, + albumId, + price: priceNumber, + extraAmount: extraAmountNumber = 0, + walletAdapter + } = await parseParams('purchase', PurchaseAlbumSchema)(params) + + const contentType = 'album' + const mint = 'USDC' + + // Fetch album + this.logger.debug('Fetching album...', { albumId }) + const { data: albums } = await this.getAlbum({ + userId: params.userId, // use hashed userId + albumId: params.albumId // use hashed albumId + }) + + const album = albums ? albums[0] : undefined + + // Validate purchase attempt + if (!album) { + throw new Error('Album not found.') + } + + if (!album.isStreamGated) { + throw new Error('Attempted to purchase free album.') + } + + if (album.user.id === params.userId) { + throw new Error('Attempted to purchase own album.') + } + + let numberSplits: UsdcGate['splits'] = {} + let centPrice: number + const accessType: 'stream' | 'download' = 'stream' + + // Get conditions + if ( + album.streamConditions && + instanceOfPurchaseGate(album.streamConditions) + ) { + centPrice = album.streamConditions.usdcPurchase.price + numberSplits = album.streamConditions.usdcPurchase.splits + } else { + this.logger.debug(album.streamConditions) + throw new Error('Album is not available for purchase.') + } + + // Check if already purchased + if (accessType === 'stream' && album.access?.stream) { + throw new Error('Album already purchased') + } + + // Check if price changed + if (USDC(priceNumber).value < USDC(centPrice / 100).value) { + throw new Error('Track price increased.') + } + + let extraAmount = USDC(extraAmountNumber).value + const total = USDC(centPrice / 100.0).value + extraAmount + this.logger.debug('Purchase total:', total) + + // Convert splits to big int and spread extra amount to every split + const splits = Object.entries(numberSplits).reduce( + (prev, [key, value], index, arr) => { + const amountToAdd = extraAmount / BigInt(arr.length - index) + extraAmount = USDC(extraAmount - amountToAdd).value + return { + ...prev, + [key]: BigInt(value) + amountToAdd + } + }, + {} + ) + this.logger.debug('Calculated splits after extra amount:', splits) + + // Create user bank for recipient if not exists + this.logger.debug('Checking for recipient user bank...') + const { userBank: recipientUserBank, didExist } = + await this.claimableTokensClient.getOrCreateUserBank({ + ethWallet: album.user.wallet, + mint: 'USDC' + }) + if (!didExist) { + this.logger.debug('Created user bank', { + recipientUserBank: recipientUserBank.toBase58() + }) + } else { + this.logger.debug('User bank exists', { + recipientUserBank: recipientUserBank.toBase58() + }) + } + + const routeInstruction = + await this.paymentRouterClient.createRouteInstruction({ + splits, + total, + mint + }) + const memoInstruction = + await this.paymentRouterClient.createPurchaseMemoInstruction({ + contentId: albumId, + contentType, + blockNumber: album.blocknumber, + buyerUserId: userId, + accessType + }) + + if (walletAdapter) { + this.logger.debug('Using connected wallet to purchase...') + if (!walletAdapter.publicKey) { + throw new Error('Could not get connected wallet address') + } + // Use the specified Solana wallet + const transferInstruction = + await this.paymentRouterClient.createTransferInstruction({ + sourceWallet: walletAdapter.publicKey, + total, + mint + }) + const transaction = await this.paymentRouterClient.buildTransaction({ + feePayer: walletAdapter.publicKey, + instructions: [transferInstruction, routeInstruction, memoInstruction] + }) + return await walletAdapter.sendTransaction( + transaction, + this.paymentRouterClient.connection + ) + } else { + // Use the authed wallet's userbank and relay + const ethWallet = await this.auth.getAddress() + this.logger.debug( + `Using userBank ${await this.claimableTokensClient.deriveUserBank({ + ethWallet, + mint: 'USDC' + })} to purchase...` + ) + const paymentRouterTokenAccount = + await this.paymentRouterClient.getOrCreateProgramTokenAccount({ + mint + }) + + const transferSecpInstruction = + await this.claimableTokensClient.createTransferSecpInstruction({ + ethWallet, + destination: paymentRouterTokenAccount.address, + mint, + amount: total, + auth: this.auth + }) + const transferInstruction = + await this.claimableTokensClient.createTransferInstruction({ + ethWallet, + destination: paymentRouterTokenAccount.address, + mint + }) + const transaction = await this.paymentRouterClient.buildTransaction({ + instructions: [ + transferSecpInstruction, + transferInstruction, + routeInstruction, + memoInstruction + ] + }) + return await this.paymentRouterClient.sendTransaction(transaction) + } + } } diff --git a/packages/libs/src/sdk/api/albums/types.ts b/packages/libs/src/sdk/api/albums/types.ts index 6ac3bb3f9dd..ce5175c9eef 100644 --- a/packages/libs/src/sdk/api/albums/types.ts +++ b/packages/libs/src/sdk/api/albums/types.ts @@ -1,3 +1,4 @@ +import { WalletAdapter } from '@solana/wallet-adapter-base' import { z } from 'zod' import { DDEXResourceContributor, DDEXCopyright } from '../../types/DDEX' @@ -8,7 +9,7 @@ import { Mood } from '../../types/Mood' import { createUploadTrackMetadataSchema } from '../tracks/types' export const getAlbumSchema = z.object({ - userId: z.string(), + userId: z.string().optional(), albumId: z.string() }) @@ -148,3 +149,28 @@ export const UnrepostAlbumSchema = z .strict() export type UnrepostAlbumRequest = z.input + +export const PurchaseAlbumSchema = z + .object({ + /** The ID of the user purchasing the album. */ + userId: HashId, + /** The ID of the album to purchase. */ + albumId: HashId, + /** + * The price of the album at the time of purchase (in dollars if number, USDC if bigint). + * Used to check against current album price in case it changed, + * effectively setting a "max price" for the purchase. + */ + price: z.union([z.number().min(0), z.bigint().min(BigInt(0))]), + /** Any extra amount the user wants to donate (in dollars if number, USDC if bigint) */ + extraAmount: z + .union([z.number().min(0), z.bigint().min(BigInt(0))]) + .optional(), + /** A wallet to use to purchase (defaults to the authed user's user bank if not specified) */ + walletAdapter: z + .custom>() + .optional() + }) + .strict() + +export type PurchaseAlbumRequest = z.input diff --git a/packages/libs/src/sdk/api/generated/default/models/Playlist.ts b/packages/libs/src/sdk/api/generated/default/models/Playlist.ts index 84853e98f7e..6b7798c1a2e 100644 --- a/packages/libs/src/sdk/api/generated/default/models/Playlist.ts +++ b/packages/libs/src/sdk/api/generated/default/models/Playlist.ts @@ -20,6 +20,12 @@ import { AccessFromJSONTyped, AccessToJSON, } from './Access'; +import type { AccessGate } from './AccessGate'; +import { + AccessGateFromJSON, + AccessGateFromJSONTyped, + AccessGateToJSON, +} from './AccessGate'; import type { PlaylistAddedTimestamp } from './PlaylistAddedTimestamp'; import { PlaylistAddedTimestampFromJSON, @@ -45,6 +51,12 @@ import { * @interface Playlist */ export interface Playlist { + /** + * + * @type {number} + * @memberof Playlist + */ + blocknumber: number; /** * * @type {PlaylistArtwork} @@ -135,6 +147,18 @@ export interface Playlist { * @memberof Playlist */ upc?: string; + /** + * + * @type {boolean} + * @memberof Playlist + */ + isStreamGated: boolean; + /** + * How to unlock stream access to the track + * @type {AccessGate} + * @memberof Playlist + */ + streamConditions?: AccessGate; } /** @@ -142,6 +166,7 @@ export interface Playlist { */ export function instanceOfPlaylist(value: object): value is Playlist { let isInstance = true; + isInstance = isInstance && "blocknumber" in value && value["blocknumber"] !== undefined; isInstance = isInstance && "id" in value && value["id"] !== undefined; isInstance = isInstance && "isAlbum" in value && value["isAlbum"] !== undefined; isInstance = isInstance && "isImageAutogenerated" in value && value["isImageAutogenerated"] !== undefined; @@ -151,6 +176,7 @@ export function instanceOfPlaylist(value: object): value is Playlist { isInstance = isInstance && "favoriteCount" in value && value["favoriteCount"] !== undefined; isInstance = isInstance && "totalPlayCount" in value && value["totalPlayCount"] !== undefined; isInstance = isInstance && "user" in value && value["user"] !== undefined; + isInstance = isInstance && "isStreamGated" in value && value["isStreamGated"] !== undefined; return isInstance; } @@ -165,6 +191,7 @@ export function PlaylistFromJSONTyped(json: any, ignoreDiscriminator: boolean): } return { + 'blocknumber': json['blocknumber'], 'artwork': !exists(json, 'artwork') ? undefined : PlaylistArtworkFromJSON(json['artwork']), 'description': !exists(json, 'description') ? undefined : json['description'], 'permalink': !exists(json, 'permalink') ? undefined : json['permalink'], @@ -180,6 +207,8 @@ export function PlaylistFromJSONTyped(json: any, ignoreDiscriminator: boolean): 'ddexApp': !exists(json, 'ddex_app') ? undefined : json['ddex_app'], 'access': !exists(json, 'access') ? undefined : AccessFromJSON(json['access']), 'upc': !exists(json, 'upc') ? undefined : json['upc'], + 'isStreamGated': json['is_stream_gated'], + 'streamConditions': !exists(json, 'stream_conditions') ? undefined : AccessGateFromJSON(json['stream_conditions']), }; } @@ -192,6 +221,7 @@ export function PlaylistToJSON(value?: Playlist | null): any { } return { + 'blocknumber': value.blocknumber, 'artwork': PlaylistArtworkToJSON(value.artwork), 'description': value.description, 'permalink': value.permalink, @@ -207,6 +237,8 @@ export function PlaylistToJSON(value?: Playlist | null): any { 'ddex_app': value.ddexApp, 'access': AccessToJSON(value.access), 'upc': value.upc, + 'is_stream_gated': value.isStreamGated, + 'stream_conditions': AccessGateToJSON(value.streamConditions), }; } diff --git a/packages/libs/src/sdk/api/generated/full/models/PlaylistFull.ts b/packages/libs/src/sdk/api/generated/full/models/PlaylistFull.ts index 48b679dfe68..64751153bdd 100644 --- a/packages/libs/src/sdk/api/generated/full/models/PlaylistFull.ts +++ b/packages/libs/src/sdk/api/generated/full/models/PlaylistFull.ts @@ -20,6 +20,12 @@ import { AccessFromJSONTyped, AccessToJSON, } from './Access'; +import type { AccessGate } from './AccessGate'; +import { + AccessGateFromJSON, + AccessGateFromJSONTyped, + AccessGateToJSON, +} from './AccessGate'; import type { Favorite } from './Favorite'; import { FavoriteFromJSON, @@ -63,6 +69,12 @@ import { * @interface PlaylistFull */ export interface PlaylistFull { + /** + * + * @type {number} + * @memberof PlaylistFull + */ + blocknumber: number; /** * * @type {PlaylistArtwork} @@ -155,10 +167,16 @@ export interface PlaylistFull { upc?: string; /** * - * @type {number} + * @type {boolean} * @memberof PlaylistFull */ - blocknumber: number; + isStreamGated: boolean; + /** + * How to unlock stream access to the track + * @type {AccessGate} + * @memberof PlaylistFull + */ + streamConditions?: AccessGate; /** * * @type {string} @@ -249,18 +267,6 @@ export interface PlaylistFull { * @memberof PlaylistFull */ trackCount: number; - /** - * - * @type {boolean} - * @memberof PlaylistFull - */ - isStreamGated: boolean; - /** - * - * @type {object} - * @memberof PlaylistFull - */ - streamConditions?: object; } /** @@ -268,6 +274,7 @@ export interface PlaylistFull { */ export function instanceOfPlaylistFull(value: object): value is PlaylistFull { let isInstance = true; + isInstance = isInstance && "blocknumber" in value && value["blocknumber"] !== undefined; isInstance = isInstance && "id" in value && value["id"] !== undefined; isInstance = isInstance && "isAlbum" in value && value["isAlbum"] !== undefined; isInstance = isInstance && "isImageAutogenerated" in value && value["isImageAutogenerated"] !== undefined; @@ -277,7 +284,7 @@ export function instanceOfPlaylistFull(value: object): value is PlaylistFull { isInstance = isInstance && "favoriteCount" in value && value["favoriteCount"] !== undefined; isInstance = isInstance && "totalPlayCount" in value && value["totalPlayCount"] !== undefined; isInstance = isInstance && "user" in value && value["user"] !== undefined; - isInstance = isInstance && "blocknumber" in value && value["blocknumber"] !== undefined; + isInstance = isInstance && "isStreamGated" in value && value["isStreamGated"] !== undefined; isInstance = isInstance && "followeeReposts" in value && value["followeeReposts"] !== undefined; isInstance = isInstance && "followeeFavorites" in value && value["followeeFavorites"] !== undefined; isInstance = isInstance && "hasCurrentUserReposted" in value && value["hasCurrentUserReposted"] !== undefined; @@ -288,7 +295,6 @@ export function instanceOfPlaylistFull(value: object): value is PlaylistFull { isInstance = isInstance && "userId" in value && value["userId"] !== undefined; isInstance = isInstance && "tracks" in value && value["tracks"] !== undefined; isInstance = isInstance && "trackCount" in value && value["trackCount"] !== undefined; - isInstance = isInstance && "isStreamGated" in value && value["isStreamGated"] !== undefined; return isInstance; } @@ -303,6 +309,7 @@ export function PlaylistFullFromJSONTyped(json: any, ignoreDiscriminator: boolea } return { + 'blocknumber': json['blocknumber'], 'artwork': !exists(json, 'artwork') ? undefined : PlaylistArtworkFromJSON(json['artwork']), 'description': !exists(json, 'description') ? undefined : json['description'], 'permalink': !exists(json, 'permalink') ? undefined : json['permalink'], @@ -318,7 +325,8 @@ export function PlaylistFullFromJSONTyped(json: any, ignoreDiscriminator: boolea 'ddexApp': !exists(json, 'ddex_app') ? undefined : json['ddex_app'], 'access': !exists(json, 'access') ? undefined : AccessFromJSON(json['access']), 'upc': !exists(json, 'upc') ? undefined : json['upc'], - 'blocknumber': json['blocknumber'], + 'isStreamGated': json['is_stream_gated'], + 'streamConditions': !exists(json, 'stream_conditions') ? undefined : AccessGateFromJSON(json['stream_conditions']), 'createdAt': !exists(json, 'created_at') ? undefined : json['created_at'], 'followeeReposts': ((json['followee_reposts'] as Array).map(RepostFromJSON)), 'followeeFavorites': ((json['followee_favorites'] as Array).map(FavoriteFromJSON)), @@ -334,8 +342,6 @@ export function PlaylistFullFromJSONTyped(json: any, ignoreDiscriminator: boolea 'coverArtSizes': !exists(json, 'cover_art_sizes') ? undefined : json['cover_art_sizes'], 'coverArtCids': !exists(json, 'cover_art_cids') ? undefined : PlaylistArtworkFromJSON(json['cover_art_cids']), 'trackCount': json['track_count'], - 'isStreamGated': json['is_stream_gated'], - 'streamConditions': !exists(json, 'stream_conditions') ? undefined : json['stream_conditions'], }; } @@ -348,6 +354,7 @@ export function PlaylistFullToJSON(value?: PlaylistFull | null): any { } return { + 'blocknumber': value.blocknumber, 'artwork': PlaylistArtworkToJSON(value.artwork), 'description': value.description, 'permalink': value.permalink, @@ -363,7 +370,8 @@ export function PlaylistFullToJSON(value?: PlaylistFull | null): any { 'ddex_app': value.ddexApp, 'access': AccessToJSON(value.access), 'upc': value.upc, - 'blocknumber': value.blocknumber, + 'is_stream_gated': value.isStreamGated, + 'stream_conditions': AccessGateToJSON(value.streamConditions), 'created_at': value.createdAt, 'followee_reposts': ((value.followeeReposts as Array).map(RepostToJSON)), 'followee_favorites': ((value.followeeFavorites as Array).map(FavoriteToJSON)), @@ -379,8 +387,6 @@ export function PlaylistFullToJSON(value?: PlaylistFull | null): any { 'cover_art_sizes': value.coverArtSizes, 'cover_art_cids': PlaylistArtworkToJSON(value.coverArtCids), 'track_count': value.trackCount, - 'is_stream_gated': value.isStreamGated, - 'stream_conditions': value.streamConditions, }; } diff --git a/packages/libs/src/sdk/api/generated/full/models/PlaylistFullWithoutTracks.ts b/packages/libs/src/sdk/api/generated/full/models/PlaylistFullWithoutTracks.ts index 8677e9b1eb8..59e2ba55744 100644 --- a/packages/libs/src/sdk/api/generated/full/models/PlaylistFullWithoutTracks.ts +++ b/packages/libs/src/sdk/api/generated/full/models/PlaylistFullWithoutTracks.ts @@ -20,6 +20,12 @@ import { AccessFromJSONTyped, AccessToJSON, } from './Access'; +import type { AccessGate } from './AccessGate'; +import { + AccessGateFromJSON, + AccessGateFromJSONTyped, + AccessGateToJSON, +} from './AccessGate'; import type { Favorite } from './Favorite'; import { FavoriteFromJSON, @@ -63,6 +69,12 @@ import { * @interface PlaylistFullWithoutTracks */ export interface PlaylistFullWithoutTracks { + /** + * + * @type {number} + * @memberof PlaylistFullWithoutTracks + */ + blocknumber: number; /** * * @type {PlaylistArtwork} @@ -155,10 +167,16 @@ export interface PlaylistFullWithoutTracks { upc?: string; /** * - * @type {number} + * @type {boolean} * @memberof PlaylistFullWithoutTracks */ - blocknumber: number; + isStreamGated: boolean; + /** + * How to unlock stream access to the track + * @type {AccessGate} + * @memberof PlaylistFullWithoutTracks + */ + streamConditions?: AccessGate; /** * * @type {string} @@ -249,18 +267,6 @@ export interface PlaylistFullWithoutTracks { * @memberof PlaylistFullWithoutTracks */ trackCount: number; - /** - * - * @type {boolean} - * @memberof PlaylistFullWithoutTracks - */ - isStreamGated: boolean; - /** - * - * @type {object} - * @memberof PlaylistFullWithoutTracks - */ - streamConditions?: object; } /** @@ -268,6 +274,7 @@ export interface PlaylistFullWithoutTracks { */ export function instanceOfPlaylistFullWithoutTracks(value: object): value is PlaylistFullWithoutTracks { let isInstance = true; + isInstance = isInstance && "blocknumber" in value && value["blocknumber"] !== undefined; isInstance = isInstance && "id" in value && value["id"] !== undefined; isInstance = isInstance && "isAlbum" in value && value["isAlbum"] !== undefined; isInstance = isInstance && "isImageAutogenerated" in value && value["isImageAutogenerated"] !== undefined; @@ -277,7 +284,7 @@ export function instanceOfPlaylistFullWithoutTracks(value: object): value is Pla isInstance = isInstance && "favoriteCount" in value && value["favoriteCount"] !== undefined; isInstance = isInstance && "totalPlayCount" in value && value["totalPlayCount"] !== undefined; isInstance = isInstance && "user" in value && value["user"] !== undefined; - isInstance = isInstance && "blocknumber" in value && value["blocknumber"] !== undefined; + isInstance = isInstance && "isStreamGated" in value && value["isStreamGated"] !== undefined; isInstance = isInstance && "followeeReposts" in value && value["followeeReposts"] !== undefined; isInstance = isInstance && "followeeFavorites" in value && value["followeeFavorites"] !== undefined; isInstance = isInstance && "hasCurrentUserReposted" in value && value["hasCurrentUserReposted"] !== undefined; @@ -287,7 +294,6 @@ export function instanceOfPlaylistFullWithoutTracks(value: object): value is Pla isInstance = isInstance && "addedTimestamps" in value && value["addedTimestamps"] !== undefined; isInstance = isInstance && "userId" in value && value["userId"] !== undefined; isInstance = isInstance && "trackCount" in value && value["trackCount"] !== undefined; - isInstance = isInstance && "isStreamGated" in value && value["isStreamGated"] !== undefined; return isInstance; } @@ -302,6 +308,7 @@ export function PlaylistFullWithoutTracksFromJSONTyped(json: any, ignoreDiscrimi } return { + 'blocknumber': json['blocknumber'], 'artwork': !exists(json, 'artwork') ? undefined : PlaylistArtworkFromJSON(json['artwork']), 'description': !exists(json, 'description') ? undefined : json['description'], 'permalink': !exists(json, 'permalink') ? undefined : json['permalink'], @@ -317,7 +324,8 @@ export function PlaylistFullWithoutTracksFromJSONTyped(json: any, ignoreDiscrimi 'ddexApp': !exists(json, 'ddex_app') ? undefined : json['ddex_app'], 'access': !exists(json, 'access') ? undefined : AccessFromJSON(json['access']), 'upc': !exists(json, 'upc') ? undefined : json['upc'], - 'blocknumber': json['blocknumber'], + 'isStreamGated': json['is_stream_gated'], + 'streamConditions': !exists(json, 'stream_conditions') ? undefined : AccessGateFromJSON(json['stream_conditions']), 'createdAt': !exists(json, 'created_at') ? undefined : json['created_at'], 'followeeReposts': ((json['followee_reposts'] as Array).map(RepostFromJSON)), 'followeeFavorites': ((json['followee_favorites'] as Array).map(FavoriteFromJSON)), @@ -333,8 +341,6 @@ export function PlaylistFullWithoutTracksFromJSONTyped(json: any, ignoreDiscrimi 'coverArtSizes': !exists(json, 'cover_art_sizes') ? undefined : json['cover_art_sizes'], 'coverArtCids': !exists(json, 'cover_art_cids') ? undefined : PlaylistArtworkFromJSON(json['cover_art_cids']), 'trackCount': json['track_count'], - 'isStreamGated': json['is_stream_gated'], - 'streamConditions': !exists(json, 'stream_conditions') ? undefined : json['stream_conditions'], }; } @@ -347,6 +353,7 @@ export function PlaylistFullWithoutTracksToJSON(value?: PlaylistFullWithoutTrack } return { + 'blocknumber': value.blocknumber, 'artwork': PlaylistArtworkToJSON(value.artwork), 'description': value.description, 'permalink': value.permalink, @@ -362,7 +369,8 @@ export function PlaylistFullWithoutTracksToJSON(value?: PlaylistFullWithoutTrack 'ddex_app': value.ddexApp, 'access': AccessToJSON(value.access), 'upc': value.upc, - 'blocknumber': value.blocknumber, + 'is_stream_gated': value.isStreamGated, + 'stream_conditions': AccessGateToJSON(value.streamConditions), 'created_at': value.createdAt, 'followee_reposts': ((value.followeeReposts as Array).map(RepostToJSON)), 'followee_favorites': ((value.followeeFavorites as Array).map(FavoriteToJSON)), @@ -378,8 +386,6 @@ export function PlaylistFullWithoutTracksToJSON(value?: PlaylistFullWithoutTrack 'cover_art_sizes': value.coverArtSizes, 'cover_art_cids': PlaylistArtworkToJSON(value.coverArtCids), 'track_count': value.trackCount, - 'is_stream_gated': value.isStreamGated, - 'stream_conditions': value.streamConditions, }; } diff --git a/packages/libs/src/sdk/sdk.ts b/packages/libs/src/sdk/sdk.ts index abd6a450dbd..e1b5eee8693 100644 --- a/packages/libs/src/sdk/sdk.ts +++ b/packages/libs/src/sdk/sdk.ts @@ -263,7 +263,9 @@ const initializeApis = ({ services.storage, services.entityManager, services.auth, - services.logger + services.logger, + services.claimableTokensClient, + services.paymentRouterClient ) const playlists = new PlaylistsApi( generatedApiClientConfig,