Skip to content

Commit

Permalink
Add USDC gated downloads flow (#6899)
Browse files Browse the repository at this point in the history
Co-authored-by: Saliou Diallo <saliou@audius.co>
Co-authored-by: Reed <3893871+dharit-tan@users.noreply.github.com>
Co-authored-by: audius-infra <audius-infra@audius.co>
Co-authored-by: JD Francis <jd.johndavidfrancis@gmail.com>
Co-authored-by: Steve Perkins <stereosteve@users.noreply.github.com>
Co-authored-by: Michelle Brier <michelle.brier4@gmail.com>
  • Loading branch information
7 people authored Jan 16, 2024
1 parent 966e438 commit ab1d3e9
Show file tree
Hide file tree
Showing 261 changed files with 6,209 additions and 3,550 deletions.
42 changes: 27 additions & 15 deletions docs/docs/developers/uploadTrackMetadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ Contains required and optional fields for uploading a track.
tags?: string; // Comma separated list of tags
remixOf?: { tracks: Array<{ parentTrackId: string }> }; // For specifying the track(s) that your track is a remix of
aiAttributionUserId?: string; // Audius user ID of the artist whom your AI-generated track was trained on. Note: Only artists who have opted into AI attribution can be used.
isPremium?: boolean; // Whether your track is only available to users who meet certain criteria, which must be specified by `premiumConditions`.
premiumConditions?: PremiumConditions; // See "Specifying Premium Conditions" section below
isStreamGated?: boolean; // Whether streaming your track is only available to users who meet certain criteria, which must be specified by `streamConditions`.
streamConditions?: AccessConditions; // See "Specifying Stream Conditions" section below
isDownloadGated?: boolean; // Whether downloading your track is only available to users who meet certain criteria, which must be specified by `downloadConditions`. Note that stream gated tracks are automatically download gated, whereas the reverse is not true.
downloadConditions?: AccessConditions;
isUnlisted?: boolean; // If set to true, only users with a link to your track will be able to listen, and your track will not show up in your profile or in any feed. Defaults to false.
fieldVisibility?: {
mood?: boolean;
Expand Down Expand Up @@ -65,9 +67,19 @@ const { trackId } = await audiusSdk.tracks.uploadTrack({
tags: "plantlife,love,monstera",
remixOf: { tracks: [{ parentTrackId: "KVx2xpO" }] },
aiAttributionUserId: "3aE1p",
isPremium: true,
premiumConditions: {
tipUserId: "7eP5n", // Require tipping user to unlock track
isStreamGated: true,
streamConditions: {
tipUserId: "7eP5n", // Require tipping user to unlock track for streaming
},
isDownloadGated: true,
downloadConditions: {
usdcPurchase: { // Require usdc purchase to unlock track for download
price: 1,
splits: {
// usdc user bank and usdc amount padded with correct number of decimals
'FwtT6g2tmwbgY6gf4NWBhupJBqJjgkaHRzCJpA1YHrL2': 10000
}
}
},
isUnlisted: true,
fieldVisibility: {
Expand All @@ -89,9 +101,9 @@ const { trackId } = await audiusSdk.tracks.uploadTrack({
});
```

## Specifying Premium Conditions
## Specifying Stream Conditions

Use the `PremiumConditions` field to specify the criteria required to unlock a track.
Use the `AccessConditions` field to specify the criteria required to unlock a track.

### Tip-gated

Expand All @@ -104,8 +116,8 @@ const { trackId } = await audiusSdk.tracks.uploadTrack({
// ...
metadata: {
// ...
isPremium: true,
premiumConditions: {
isStreamGated: true,
streamConditions: {
tipUserId: "7eP5n", // Require tipping user with user ID "7eP5n" to unlock track
},
},
Expand All @@ -123,8 +135,8 @@ const { trackId } = await audiusSdk.tracks.uploadTrack({
// ...
metadata: {
// ...
isPremium: true,
premiumConditions: {
isStreamGated: true,
streamConditions: {
followUserId: "7eP5n", // Require following user with user ID "7eP5n" to unlock track
},
},
Expand All @@ -142,8 +154,8 @@ const { trackId } = await audiusSdk.tracks.uploadTrack({
// ...
metadata: {
// ...
isPremium: true,
premiumConditions: {
isStreamGated: true,
streamConditions: {
chain: "eth",
address: "0xAbCdEfGhIjKlMnOpQrStUvWxYz", // The Ethereum address of the NFT contract
standard: "ERC-721", // The standard followed by the NFT - either "ERC-721" or "ERC-1155"
Expand All @@ -163,8 +175,8 @@ const { trackId } = await audiusSdk.tracks.uploadTrack({
// ...
metadata: {
// ...
isPremium: true,
premiumConditions: {
isStreamGated: true,
streamConditions: {
chain: "sol",
address: "ABCDEF1234567890", // The address of the NFT on the Solana blockchain
name: "Example NFT", // The name of the NFT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,6 @@ Create an object with the following fields and pass it as the first argument, as
| :-------- | :------- | :------------------ | :----------- |
| `trackId` | `string` | The ID of the track | **Required** |

TODO: should we document premium content signature?

#### Returns

Returns a `Promise` containing a `string` url which can be used to stream the track.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ Contains required and optional fields for uploading a track.
tags?: string; // Comma separated list of tags
remixOf?: { tracks: Array<{ parentTrackId: string }> }; // For specifying the track(s) that your track is a remix of
aiAttributionUserId?: string; // Audius user ID of the artist whom your AI-generated track was trained on. Note: Only artists who have opted into AI attribution can be used.
isPremium?: boolean; // Whether your track is only available to users who meet certain criteria, which must be specified by `premiumConditions`.
premiumConditions?: PremiumConditions; // See "Specifying Premium Conditions" section below
isStreamGated?: boolean; // Whether streaming your track is only available to users who meet certain criteria, which must be specified by `streamConditions`.
streamConditions?: AccessConditions; // See "Specifying Stream Conditions" section below
isDownloadGated?: boolean; // Whether downloading your track is only available to users who meet certain criteria, which must be specified by `downloadConditions`. Note that stream gated tracks are automatically download gated, whereas the reverse is not true.
downloadConditions?: AccessConditions;
isUnlisted?: boolean; // If set to true, only users with a link to your track will be able to listen, and your track will not show up in your profile or in any feed. Defaults to false.
fieldVisibility?: {
mood?: boolean;
Expand Down Expand Up @@ -65,8 +67,8 @@ const { trackId } = await audiusSdk.tracks.uploadTrack({
tags: "plantlife,love,monstera",
remixOf: { tracks: [{ parentTrackId: "KVx2xpO" }] },
aiAttributionUserId: "3aE1p",
isPremium: true,
premiumConditions: {
isStreamGated: true,
streamConditions: {
tipUserId: "7eP5n", // Require tipping user to unlock track
},
isUnlisted: true,
Expand All @@ -89,9 +91,9 @@ const { trackId } = await audiusSdk.tracks.uploadTrack({
});
```

## Specifying Premium Conditions
## Specifying Stream Conditions

Use the `PremiumConditions` field to specify the criteria required to unlock a track.
Use the `AccessConditions` field to specify the criteria required to unlock a track.

### Tip-gated

Expand All @@ -104,8 +106,8 @@ const { trackId } = await audiusSdk.tracks.uploadTrack({
// ...
metadata: {
// ...
isPremium: true,
premiumConditions: {
isStreamGated: true,
streamConditions: {
tipUserId: "7eP5n", // Require tipping user with user ID "7eP5n" to unlock track
},
},
Expand All @@ -123,8 +125,8 @@ const { trackId } = await audiusSdk.tracks.uploadTrack({
// ...
metadata: {
// ...
isPremium: true,
premiumConditions: {
isStreamGated: true,
streamConditions: {
followUserId: "7eP5n", // Require following user with user ID "7eP5n" to unlock track
},
},
Expand All @@ -142,8 +144,8 @@ const { trackId } = await audiusSdk.tracks.uploadTrack({
// ...
metadata: {
// ...
isPremium: true,
premiumConditions: {
isStreamGated: true,
streamConditions: {
chain: "eth",
address: "0xAbCdEfGhIjKlMnOpQrStUvWxYz", // The Ethereum address of the NFT contract
standard: "ERC-721", // The standard followed by the NFT - either "ERC-721" or "ERC-1155"
Expand All @@ -163,8 +165,8 @@ const { trackId } = await audiusSdk.tracks.uploadTrack({
// ...
metadata: {
// ...
isPremium: true,
premiumConditions: {
isStreamGated: true,
streamConditions: {
chain: "sol",
address: "ABCDEF1234567890", // The address of the NFT on the Solana blockchain
name: "Example NFT", // The name of the NFT
Expand Down
2 changes: 1 addition & 1 deletion mediorum/.version.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"version": "0.6.19",
"service": "content-node"
}
}
2 changes: 1 addition & 1 deletion mediorum/server/serve_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ func (ss *MediorumServer) logTrackListen(c echo.Context) {
}
}

// checks signature from discovery node for cidstream endpoint + premium content.
// checks signature from discovery node for cidstream endpoint + gated content.
// based on: https://github.com/AudiusProject/audius-protocol/blob/main/creator-node/src/middlewares/contentAccess/contentAccessMiddleware.ts
func (s *MediorumServer) requireRegisteredSignature(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
Expand Down
8 changes: 4 additions & 4 deletions packages/commands/src/edit-track.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ program.command("edit-track")
.option("-s, --preview-start-seconds <seconds>", "Track preview start time (seconds)", null)
.option("-l, --license <license>", "License of track")
.option("-f, --from <from>", "The account to edit the track from")
.option("-p, --premium-conditions <premium conditions>", "The premium conditions object; sets track as premium", "")
.action(async (trackId, { title, tags, description, mood, genre, previewStartSeconds, license, from, premiumConditions }) => {
.option("-r, --stream-conditions <stream conditions>", "The stream conditions object; sets track as stream gated", "")
.action(async (trackId, { title, tags, description, mood, genre, previewStartSeconds, license, from, streamConditions }) => {
const audiusLibs = await initializeAudiusLibs(from);
try {
const track = (await audiusLibs.Track.getTracks(100, 0, [trackId]))[0]
Expand All @@ -30,8 +30,8 @@ program.command("edit-track")
mood: mood || track.mood,
genre: genre || track.genre,
license: license || track.license,
is_premium: premiumConditions ? true : track.is_premium,
premium_conditions: premiumConditions ? JSON.parse(premiumConditions) : track.premium_conditions,
is_stream_gated: streamConditions ? true : track.is_stream_gated,
stream_conditions: streamConditions ? JSON.parse(streamConditions) : track.stream_conditions,
preview_start_seconds: previewStartSeconds ? parseInt(previewStartSeconds) : track.preview_start_seconds
}

Expand Down
8 changes: 4 additions & 4 deletions packages/commands/src/purchase-track.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ program
const user = audiusLibs.userStateManager.getCurrentUser();

const track = (await audiusLibs.Track.getTracks(100, 0, [trackId]))[0]
if (!track.premium_conditions || !track.is_premium) {
program.error('Track is not premium')
if (!track.stream_conditions || !track.is_stream_gated) {
program.error('Track is not stream gated')
}
if (!track.premium_conditions?.usdc_purchase?.splits) {
if (!track.stream_conditions?.usdc_purchase?.splits) {
program.error('Track is not purchaseable')
}

Expand All @@ -39,7 +39,7 @@ program
extraAmount,
type: 'track',
blocknumber: track.blocknumber,
splits: track.premium_conditions.usdc_purchase.splits,
splits: track.stream_conditions.usdc_purchase.splits,
purchaserUserId: user.user_id
})
if (response.error) {
Expand Down
Loading

0 comments on commit ab1d3e9

Please sign in to comment.