From 4873a0ee291b976659104cbbece0e8da1cb526db Mon Sep 17 00:00:00 2001 From: Dharit Tantiviramanond Date: Wed, 7 Feb 2024 18:50:22 -0500 Subject: [PATCH 1/3] [PAY-2450][PAY-2451] Purchase indexing uses access from memo --- .../src/services/audius-backend/solana.ts | 9 +- .../src/store/purchase-content/sagas.ts | 40 +- .../src/store/purchase-content/types.ts | 5 + .../tasks/payment_router_mock_transactions.py | 394 +++++++++++++ .../tasks/test_index_payment_router.py | 62 ++- .../tasks/test_index_user_bank.py | 67 ++- .../tasks/user_bank_mock_transactions.py | 525 ++++++++++++++++++ .../src/tasks/index_payment_router.py | 136 ++--- .../src/tasks/index_user_bank.py | 18 +- .../src/services/solana/SolanaWeb3Manager.ts | 23 +- 10 files changed, 1189 insertions(+), 90 deletions(-) diff --git a/packages/common/src/services/audius-backend/solana.ts b/packages/common/src/services/audius-backend/solana.ts index 5dcf7c30b94..7465e0d9cef 100644 --- a/packages/common/src/services/audius-backend/solana.ts +++ b/packages/common/src/services/audius-backend/solana.ts @@ -23,6 +23,7 @@ import { BN_USDC_CENT_WEI } from '~/utils/wallet' import { AnalyticsEvent, ID, Name, SolanaWalletAddress } from '../../models' import { AudiusBackend } from './AudiusBackend' +import { PurchaseAccess } from '~/store' const DEFAULT_RETRY_DELAY = 1000 const DEFAULT_MAX_RETRY_COUNT = 120 @@ -378,6 +379,7 @@ export type PurchaseContentArgs = { type: 'track' splits: Record purchaserUserId: ID + purchaseAccess: PurchaseAccess } export const purchaseContent = async ( audiusBackendInstance: AudiusBackend, @@ -397,6 +399,7 @@ export type PurchaseContentWithPaymentRouterArgs = { recentBlockhash?: string purchaserUserId: ID wallet: Keypair + purchaseAccess: PurchaseAccess } export const purchaseContentWithPaymentRouter = async ( @@ -408,7 +411,8 @@ export const purchaseContentWithPaymentRouter = async ( extraAmount = 0, purchaserUserId, splits, - wallet + wallet, + purchaseAccess }: PurchaseContentWithPaymentRouterArgs ) => { const solanaWeb3Manager = (await audiusBackendInstance.getAudiusLibs()) @@ -421,7 +425,8 @@ export const purchaseContentWithPaymentRouter = async ( splits, purchaserUserId, senderKeypair: wallet, - skipSendAndReturnTransaction: true + skipSendAndReturnTransaction: true, + purchaseAccess }) return tx } diff --git a/packages/common/src/store/purchase-content/sagas.ts b/packages/common/src/store/purchase-content/sagas.ts index 2101d66a8f7..37cc24699be 100644 --- a/packages/common/src/store/purchase-content/sagas.ts +++ b/packages/common/src/store/purchase-content/sagas.ts @@ -64,7 +64,12 @@ import { purchaseContentFlowFailed, startPurchaseContentFlow } from './slice' -import { ContentType, PurchaseContentError, PurchaseErrorCode } from './types' +import { + ContentType, + PurchaseAccess, + PurchaseContentError, + PurchaseErrorCode +} from './types' import { getBalanceNeeded } from './utils' const { getUserId, getAccountUser } = accountSelectors @@ -94,6 +99,10 @@ function* getContentInfo({ contentId, contentType }: GetPurchaseConfigArgs) { const trackInfo = yield* select(getTrack, { id: contentId }) const purchaseConditions = trackInfo?.stream_conditions ?? trackInfo?.download_conditions + const purchaseAccess = + trackInfo?.is_download_gated && !trackInfo?.is_stream_gated + ? PurchaseAccess.DOWNLOAD + : PurchaseAccess.STREAM if (!trackInfo || !isContentUSDCPurchaseGated(purchaseConditions)) { throw new Error('Content is missing purchase conditions') } @@ -105,7 +114,7 @@ function* getContentInfo({ contentId, contentType }: GetPurchaseConfigArgs) { const title = trackInfo.title const price = purchaseConditions.usdc_purchase.price - return { price, title, artistInfo, trackInfo } + return { price, title, artistInfo, purchaseAccess, trackInfo } } const getUserPurchaseMetadata = ({ @@ -261,6 +270,7 @@ type PurchaseWithCoinflowArgs = { purchaserUserId: ID /** USDC in dollars */ price: number + purchaseAccess: PurchaseAccess } function* purchaseWithCoinflow(args: PurchaseWithCoinflowArgs) { @@ -270,7 +280,8 @@ function* purchaseWithCoinflow(args: PurchaseWithCoinflowArgs) { splits, contentId, purchaserUserId, - price + price, + purchaseAccess } = args const audiusBackendInstance = yield* getContext('audiusBackendInstance') const feePayerAddress = yield* select(getFeePayer) @@ -292,7 +303,8 @@ function* purchaseWithCoinflow(args: PurchaseWithCoinflowArgs) { blocknumber, recentBlockhash, purchaserUserId, - wallet: rootAccount + wallet: rootAccount, + purchaseAccess } ) @@ -399,10 +411,13 @@ function* doStartPurchaseContentFlow({ const reportToSentry = yield* getContext('reportToSentry') const { track, make } = yield* getContext('analytics') - const { price, title, artistInfo } = yield* call(getContentInfo, { - contentId, - contentType - }) + const { price, title, artistInfo, purchaseAccess } = yield* call( + getContentInfo, + { + contentId, + contentType + } + ) const analyticsInfo = { price: price / 100, @@ -471,7 +486,8 @@ function* doStartPurchaseContentFlow({ extraAmount: extraAmountBN, splits, type: 'track', - purchaserUserId + purchaserUserId, + purchaseAccess }) } else { // We need to acquire USDC before the purchase can continue @@ -494,7 +510,8 @@ function* doStartPurchaseContentFlow({ contentId, contentType, purchaserUserId, - price: purchaseAmount + price: purchaseAmount, + purchaseAccess }) break case PurchaseVendor.STRIPE: @@ -506,7 +523,8 @@ function* doStartPurchaseContentFlow({ extraAmount: extraAmountBN, splits, type: 'track', - purchaserUserId + purchaserUserId, + purchaseAccess }) break } diff --git a/packages/common/src/store/purchase-content/types.ts b/packages/common/src/store/purchase-content/types.ts index 5acf76766c0..58574184918 100644 --- a/packages/common/src/store/purchase-content/types.ts +++ b/packages/common/src/store/purchase-content/types.ts @@ -21,6 +21,11 @@ export enum PurchaseContentPage { TRANSFER = 'crypto-transfer' } +export enum PurchaseAccess { + STREAM = 'stream', + DOWNLOAD = 'download' +} + export enum PurchaseErrorCode { Canceled = 'Canceled', InsufficientBalance = 'InsufficientBalance', diff --git a/packages/discovery-provider/integration_tests/tasks/payment_router_mock_transactions.py b/packages/discovery-provider/integration_tests/tasks/payment_router_mock_transactions.py index cd3abc1c490..8cd82c3fe6e 100644 --- a/packages/discovery-provider/integration_tests/tasks/payment_router_mock_transactions.py +++ b/packages/discovery-provider/integration_tests/tasks/payment_router_mock_transactions.py @@ -2973,3 +2973,397 @@ } ) ) + +mock_valid_track_purchase_stream_access = GetTransactionResp.from_json( + json.dumps( + { + "jsonrpc": "2.0", + "result": { + "slot": 190957, + "transaction": { + "signatures": [ + "5wPxiuLSF3MzXZt9XG99UEPNdxs8DtE2vWKezrB6zuMCrkMBJx6iU7xw5icaowpfgj96iLGnAgEAaBNSbneWdbZw" + ], + "message": { + "header": { + "numRequiredSignatures": 1, + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 5, + }, + "accountKeys": [ + "HXLN9UWwAjMPgHaFZDfgabT79SmLSdTeu2fUha2xHz9W", + "3XmVeZ6M1FYDdUQaNeQZf8dipvtzNP6NVb5xjDkdeiNb", + "A76eNhRrfdy6WfMoQf4ALasMxzRWHajH4TrVuX2NUjZT", + "7gfRGGdp89N9g3mCsZjaGmDDRdcTnZh9u3vYyBab2tRy", + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo", + "apaySbqV1XAmuiGszeN4NyWrXkkMrnuJVoNhzmS1AMa", + "G231EZsMoCNBiQKP5quEeAM3oG516Zspirjnh7ywP71i", + ], + "recentBlockhash": "6D65tSU7pjSmFvSj9qK2W2bjkESw4XZebeNmgA1rCqnF", + "instructions": [ + { + "programIdIndex": 4, + "accounts": [1, 5, 2, 0], + "data": "hYECWfYe8vYqs", + "stackHeight": None, + }, + { + "programIdIndex": 6, + "accounts": [0], + # "track:1:1:2:stream" + "data": "5q8ftnvrq1ERw9QN7ZQv8xZPA", + "stackHeight": None, + }, + { + "programIdIndex": 7, + "accounts": [2, 8, 4, 3], + "data": "BQD4GnQPrhbq6Y9NJLnwDUziXhfF6BjkLYFbnKZH", + "stackHeight": None, + }, + ], + "addressTableLookups": [], + }, + }, + "meta": { + "err": None, + "status": {"Ok": None}, + "fee": 5000, + "preBalances": [ + 8420804160, + 2039280, + 2039280, + 2039280, + 929020800, + 1461600, + 119712000, + 1141440, + 946560, + ], + "postBalances": [ + 8420799160, + 2039280, + 2039280, + 2039280, + 929020800, + 1461600, + 119712000, + 1141440, + 946560, + ], + "innerInstructions": [ + { + "index": 2, + "instructions": [ + { + "programIdIndex": 4, + "accounts": [2, 3, 8, 8], + "data": "3YKuzAsyicvj", + "stackHeight": 2, + } + ], + } + ], + "logMessages": [ + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]", + "Program log: Instruction: TransferChecked", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6173 of 600000 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo invoke [1]", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo consumed 480 of 593827 compute units", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo success", + "Program apaySbqV1XAmuiGszeN4NyWrXkkMrnuJVoNhzmS1AMa invoke [1]", + "Program log: Instruction: Route", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", + "Program log: Instruction: Transfer", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4728 of 576902 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program log: All transfers complete!", + "Program apaySbqV1XAmuiGszeN4NyWrXkkMrnuJVoNhzmS1AMa consumed 21782 of 593347 compute units", + "Program apaySbqV1XAmuiGszeN4NyWrXkkMrnuJVoNhzmS1AMa success", + ], + "preTokenBalances": [ + { + "accountIndex": 1, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": 10.0, + "decimals": 6, + "amount": "10000000", + "uiAmountString": "10.0", + }, + "owner": "HXLN9UWwAjMPgHaFZDfgabT79SmLSdTeu2fUha2xHz9W", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 2, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": None, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": "G231EZsMoCNBiQKP5quEeAM3oG516Zspirjnh7ywP71i", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 3, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": 0, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": "7vKR1WSmyHvBmCvKPZBiN66PHZqYQbXw51SZdwtVd9Dt", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + ], + "postTokenBalances": [ + { + "accountIndex": 1, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": 9.0, + "decimals": 6, + "amount": "9000000", + "uiAmountString": "9.0", + }, + "owner": "HXLN9UWwAjMPgHaFZDfgabT79SmLSdTeu2fUha2xHz9W", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 2, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": None, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": "G231EZsMoCNBiQKP5quEeAM3oG516Zspirjnh7ywP71i", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 3, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": 1.0, + "decimals": 6, + "amount": "1000000", + "uiAmountString": "1.0", + }, + "owner": "7vKR1WSmyHvBmCvKPZBiN66PHZqYQbXw51SZdwtVd9Dt", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + ], + "rewards": [], + "loadedAddresses": {"writable": [], "readonly": []}, + "computeUnitsConsumed": 28435, + }, + "version": 0, + "blockTime": 1701922096, + }, + "id": 0, + } + ) +) + +mock_valid_track_purchase_download_access = GetTransactionResp.from_json( + json.dumps( + { + "jsonrpc": "2.0", + "result": { + "slot": 190957, + "transaction": { + "signatures": [ + "5wPxiuLSF3MzXZt9XG99UEPNdxs8DtE2vWKezrB6zuMCrkMBJx6iU7xw5icaowpfgj96iLGnAgEAaBNSbneWdbZY" + ], + "message": { + "header": { + "numRequiredSignatures": 1, + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 5, + }, + "accountKeys": [ + "HXLN9UWwAjMPgHaFZDfgabT79SmLSdTeu2fUha2xHz9W", + "3XmVeZ6M1FYDdUQaNeQZf8dipvtzNP6NVb5xjDkdeiNb", + "A76eNhRrfdy6WfMoQf4ALasMxzRWHajH4TrVuX2NUjZT", + "7gfRGGdp89N9g3mCsZjaGmDDRdcTnZh9u3vYyBab2tRy", + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo", + "apaySbqV1XAmuiGszeN4NyWrXkkMrnuJVoNhzmS1AMa", + "G231EZsMoCNBiQKP5quEeAM3oG516Zspirjnh7ywP71i", + ], + "recentBlockhash": "6D65tSU7pjSmFvSj9qK2W2bjkESw4XZebeNmgA1rCqnF", + "instructions": [ + { + "programIdIndex": 4, + "accounts": [1, 5, 2, 0], + "data": "hYECWfYe8vYqs", + "stackHeight": None, + }, + { + "programIdIndex": 6, + "accounts": [0], + # "track:3:1:2:download" + "data": "2d6R8DNFA5Yr5TTvcCbhfZ1q2eY3", + "stackHeight": None, + }, + { + "programIdIndex": 7, + "accounts": [2, 8, 4, 3], + "data": "BQD4GnQPrhbq6Y9NJLnwDUziXhfF6BjkLYFbnKZH", + "stackHeight": None, + }, + ], + "addressTableLookups": [], + }, + }, + "meta": { + "err": None, + "status": {"Ok": None}, + "fee": 5000, + "preBalances": [ + 8420804160, + 2039280, + 2039280, + 2039280, + 929020800, + 1461600, + 119712000, + 1141440, + 946560, + ], + "postBalances": [ + 8420799160, + 2039280, + 2039280, + 2039280, + 929020800, + 1461600, + 119712000, + 1141440, + 946560, + ], + "innerInstructions": [ + { + "index": 2, + "instructions": [ + { + "programIdIndex": 4, + "accounts": [2, 3, 8, 8], + "data": "3YKuzAsyicvj", + "stackHeight": 2, + } + ], + } + ], + "logMessages": [ + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]", + "Program log: Instruction: TransferChecked", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6173 of 600000 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo invoke [1]", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo consumed 480 of 593827 compute units", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo success", + "Program apaySbqV1XAmuiGszeN4NyWrXkkMrnuJVoNhzmS1AMa invoke [1]", + "Program log: Instruction: Route", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", + "Program log: Instruction: Transfer", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4728 of 576902 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program log: All transfers complete!", + "Program apaySbqV1XAmuiGszeN4NyWrXkkMrnuJVoNhzmS1AMa consumed 21782 of 593347 compute units", + "Program apaySbqV1XAmuiGszeN4NyWrXkkMrnuJVoNhzmS1AMa success", + ], + "preTokenBalances": [ + { + "accountIndex": 1, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": 10.0, + "decimals": 6, + "amount": "10000000", + "uiAmountString": "10.0", + }, + "owner": "HXLN9UWwAjMPgHaFZDfgabT79SmLSdTeu2fUha2xHz9W", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 2, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": None, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": "G231EZsMoCNBiQKP5quEeAM3oG516Zspirjnh7ywP71i", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 3, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": 0, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": "7vKR1WSmyHvBmCvKPZBiN66PHZqYQbXw51SZdwtVd9Dt", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + ], + "postTokenBalances": [ + { + "accountIndex": 1, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": 9.0, + "decimals": 6, + "amount": "9000000", + "uiAmountString": "9.0", + }, + "owner": "HXLN9UWwAjMPgHaFZDfgabT79SmLSdTeu2fUha2xHz9W", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 2, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": None, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": "G231EZsMoCNBiQKP5quEeAM3oG516Zspirjnh7ywP71i", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 3, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": 1.0, + "decimals": 6, + "amount": "1000000", + "uiAmountString": "1.0", + }, + "owner": "7vKR1WSmyHvBmCvKPZBiN66PHZqYQbXw51SZdwtVd9Dt", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + ], + "rewards": [], + "loadedAddresses": {"writable": [], "readonly": []}, + "computeUnitsConsumed": 28435, + }, + "version": 0, + "blockTime": 1701922096, + }, + "id": 0, + } + ) +) diff --git a/packages/discovery-provider/integration_tests/tasks/test_index_payment_router.py b/packages/discovery-provider/integration_tests/tasks/test_index_payment_router.py index b768aff1aa0..faf39157869 100644 --- a/packages/discovery-provider/integration_tests/tasks/test_index_payment_router.py +++ b/packages/discovery-provider/integration_tests/tasks/test_index_payment_router.py @@ -7,11 +7,13 @@ mock_invalid_track_purchase_insufficient_split_tx, mock_invalid_track_purchase_missing_split_tx, mock_non_route_transfer_purchase_single_recipient_tx, + mock_valid_track_purchase_download_access, mock_valid_track_purchase_from_user_bank_single_recipient_tx, mock_valid_track_purchase_multi_recipient_pay_extra_tx, mock_valid_track_purchase_multi_recipient_tx, mock_valid_track_purchase_single_recipient_pay_extra_tx, mock_valid_track_purchase_single_recipient_tx, + mock_valid_track_purchase_stream_access, mock_valid_transfer_from_user_bank_without_purchase_single_recipient_tx, mock_valid_transfer_single_recipient_recovery_tx, mock_valid_transfer_without_purchase_multi_recipient_tx, @@ -81,13 +83,13 @@ "tracks": [ {"track_id": 1, "title": "track 1", "owner_id": 1}, {"track_id": 2, "title": "track 2", "owner_id": 1}, + {"track_id": 3, "title": "track 3", "owner_id": 1}, ], "track_price_history": [ { # pay full price to trackOwner "track_id": 1, "splits": {"7gfRGGdp89N9g3mCsZjaGmDDRdcTnZh9u3vYyBab2tRy": 1000000}, "total_price_cents": 100, - "access": PurchaseAccessType.stream, }, { # pay $1 each to track owner and third party "track_id": 2, @@ -96,7 +98,12 @@ "7dw7W4Yv7F1uWb9dVH1CFPm39mePyypuCji2zxcFA556": 1000000, }, "total_price_cents": 200, - "access": PurchaseAccessType.stream, + }, + { # download access type + "track_id": 3, + "splits": {"7gfRGGdp89N9g3mCsZjaGmDDRdcTnZh9u3vYyBab2tRy": 1000000}, + "total_price_cents": 100, + "access": PurchaseAccessType.download, }, ], } @@ -916,3 +923,54 @@ def test_process_payment_router_txs_details_skip_unknown_PDA_ATAs(app): .first() ) assert transaction_record is None + + +def test_process_payment_router_tx_details_access_types(app): + with app.app_context(): + db = get_db() + populate_mock_db(db, test_entries) + challenge_event_bus = create_autospec(ChallengeEventBus) + + tx_response = mock_valid_track_purchase_stream_access + transaction = tx_response.value.transaction.transaction + tx_sig_str = str(transaction.signatures[0]) + + with db.scoped_session() as session: + process_payment_router_tx_details( + session=session, + tx_info=tx_response, + tx_sig=tx_sig_str, + timestamp=datetime.now(), + challenge_event_bus=challenge_event_bus, + ) + + purchase = ( + session.query(USDCPurchase) + .filter(USDCPurchase.signature == tx_sig_str) + .first() + ) + + assert purchase is not None + assert purchase.access == PurchaseAccessType.stream + + tx_response = mock_valid_track_purchase_download_access + transaction = tx_response.value.transaction.transaction + tx_sig_str = str(transaction.signatures[0]) + + with db.scoped_session() as session: + process_payment_router_tx_details( + session=session, + tx_info=tx_response, + tx_sig=tx_sig_str, + timestamp=datetime.now(), + challenge_event_bus=challenge_event_bus, + ) + + purchase = ( + session.query(USDCPurchase) + .filter(USDCPurchase.signature == tx_sig_str) + .first() + ) + + assert purchase is not None + assert purchase.access == PurchaseAccessType.download diff --git a/packages/discovery-provider/integration_tests/tasks/test_index_user_bank.py b/packages/discovery-provider/integration_tests/tasks/test_index_user_bank.py index 8dc9798999f..36959a06089 100644 --- a/packages/discovery-provider/integration_tests/tasks/test_index_user_bank.py +++ b/packages/discovery-provider/integration_tests/tasks/test_index_user_bank.py @@ -20,6 +20,8 @@ mock_valid_create_usdc_token_account_tx, mock_valid_track_purchase_pay_extra_tx, mock_valid_track_purchase_tx, + mock_valid_track_purchase_tx_download_access, + mock_valid_track_purchase_tx_stream_access, mock_valid_transfer_prepare_withdrawal_tx, mock_valid_transfer_withdrawal_tx, mock_valid_transfer_without_purchase_tx, @@ -123,7 +125,6 @@ "track_id": 1, "splits": {RECIPIENT_USDC_USER_BANK_ADDRESS: 1000000}, "total_price_cents": 100, - "access": PurchaseAccessType.stream, }, { # pay $1 each to track owner and third party "track_id": 2, @@ -132,7 +133,12 @@ EXTERNAL_ACCOUNT_ADDRESS: 1000000, }, "total_price_cents": 200, - "access": PurchaseAccessType.stream, + }, + { # download access type + "track_id": 3, + "splits": {RECIPIENT_USDC_USER_BANK_ADDRESS: 1000000}, + "total_price_cents": 100, + "access": PurchaseAccessType.download, }, ], } @@ -147,6 +153,7 @@ def test_process_user_bank_tx_details_valid_purchase(app): solana_client_manager_mock = create_autospec(SolanaClientManager) transaction = tx_response.value.transaction.transaction + print(f"REED {transaction}") tx_sig_str = str(transaction.signatures[0]) @@ -177,6 +184,7 @@ def test_process_user_bank_tx_details_valid_purchase(app): assert purchase.extra_amount == 0 assert purchase.content_type == PurchaseType.track assert purchase.content_id == 1 + assert purchase.access == PurchaseAccessType.stream owner_transaction_record = ( session.query(USDCTransactionsHistory) @@ -997,3 +1005,58 @@ def test_process_user_bank_txs_details_transfer_audio_tip_challenge_event(app): calls = [call(ChallengeEvent.send_tip, tx_response.value.slot, sender_user_id)] challenge_event_bus.dispatch.assert_has_calls(calls) + + +def test_process_user_bank_txs_details_access_types(app): + with app.app_context(): + db = get_db() + redis = get_redis() + solana_client_manager_mock = create_autospec(SolanaClientManager) + challenge_event_bus = create_autospec(ChallengeEventBus) + populate_mock_db(db, test_entries) + + tx_response = mock_valid_track_purchase_tx_stream_access + transaction = tx_response.value.transaction.transaction + tx_sig_str = str(transaction.signatures[0]) + + with db.scoped_session() as session: + process_user_bank_tx_details( + solana_client_manager=solana_client_manager_mock, + session=session, + redis=redis, + tx_info=tx_response, + tx_sig=tx_sig_str, + timestamp=datetime.now(), + challenge_event_bus=challenge_event_bus, + ) + + purchase = ( + session.query(USDCPurchase) + .filter(USDCPurchase.signature == tx_sig_str) + .first() + ) + assert purchase is not None + assert purchase.access == PurchaseAccessType.stream + + tx_response = mock_valid_track_purchase_tx_download_access + transaction = tx_response.value.transaction.transaction + tx_sig_str = str(transaction.signatures[0]) + + with db.scoped_session() as session: + process_user_bank_tx_details( + solana_client_manager=solana_client_manager_mock, + session=session, + redis=redis, + tx_info=tx_response, + tx_sig=tx_sig_str, + timestamp=datetime.now(), + challenge_event_bus=challenge_event_bus, + ) + + purchase = ( + session.query(USDCPurchase) + .filter(USDCPurchase.signature == tx_sig_str) + .first() + ) + assert purchase is not None + assert purchase.access == PurchaseAccessType.download diff --git a/packages/discovery-provider/integration_tests/tasks/user_bank_mock_transactions.py b/packages/discovery-provider/integration_tests/tasks/user_bank_mock_transactions.py index b34dc2592e3..dcabc8562b4 100644 --- a/packages/discovery-provider/integration_tests/tasks/user_bank_mock_transactions.py +++ b/packages/discovery-provider/integration_tests/tasks/user_bank_mock_transactions.py @@ -20,6 +20,7 @@ MOCK_SIGNATURE = "3tD61jrsU4b6s7jMGR3hg7p9Dsm88NRR2RVgUUjdcHqfFf9JWRwyhPiRGvEqHnLN6qaoc1Gvqy9Nv2UyKcWe6u4C" +MOCK_SIGNATURE_2 = "3tD61jrsU4b6s7jMGR3hg7p9Dsm88NRR2RVgUUjdcHqfFf9JWRwyhPiRGvEqHnLN6qaoc1Gvqy9Nv2UyKcWe6u4D" FEE_PAYER = "HmqRrgrZjR1Fkwgbv1nDXuUYQYs6ocGSzmGPoEUZqK1X" USDC_MINT = "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y" @@ -56,6 +57,12 @@ PURCHASE_TRACK1_MEMO_DATA = "7YSwHDhdZsHu6X" # base58.b58encode("track:2:10").decode("utf-8") PURCHASE_TRACK2_MEMO_DATA = "7YSwHDhdZtmtNs" +# base58.b58encode("track:1:10:2:stream").decode("utf-8") +PURCHASE_TRACK_STREAM_ACCESS_MEMO_DATA = "NKSrfbiivt2H3Rc3uQd2JbGcAY" +# base58.b58encode("track:3:10:2:download").decode("utf-8") +PURCHASE_TRACK_DOWNLOAD_MEMO_DATA = "8AJuUqamV55ZcUtzu1FNWfHj9WwVm" + + # base58.b58encode("Prepare Withdrawal").decode("utf-8") PREPARE_WITHDRAWAL_MEMO = "4LXeTxmZydvvx9jk2DnmBAwcX" # base58.b58encode("Withdrawal").decode("utf-8") @@ -412,6 +419,178 @@ ) ) +# Purchase of track id 1 for $1 USDC with purchaser user id 2 including download access +ock_valid_track_purchase_tx = GetTransactionResp.from_json( + json.dumps( + { + "jsonrpc": "2.0", + "result": { + "slot": 227246439, + "transaction": { + "signatures": [MOCK_SIGNATURE], + "message": { + "header": { + "numRequiredSignatures": 1, + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 8, + }, + "accountKeys": [ + FEE_PAYER, + SENDER_USDC_USER_BANK_ADDRESS, + NONCE_ACCOUNT_ADDRESS, + RECIPIENT_USDC_USER_BANK_ADDRESS, + "11111111111111111111111111111111", + CLAIMABLE_TOKENS_PDA, + USDC_PDA, + "KeccakSecp256k11111111111111111111111111111", + "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo", + "Sysvar1nstructions1111111111111111111111111", + "SysvarRent111111111111111111111111111111111", + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + ], + "recentBlockhash": "5H434VMiHgK7RaJZaBKKcriu4eky8erb9QGfcHJSZquU", + "instructions": [ + { + "programIdIndex": 7, + "accounts": [], + "data": "H4eCheRWTZDTCFYUcyMzE6EhQMZvvvLKJ9g6YaUpbZeoLLgVj1uvwCTdzcb2MzbKHsRjN8DjLYdqxuQEZe2TjUKCuBMrFtpnnLd4RcvBnr4ieHCdH8ZU1N6XDfiqyKB4zenQ9S4viza4ob4gbtmiRS6o6KGEtL3fJQRvaA3tdtSx1rfFogZzwMXAxHrkuxHrpAqfm", + "stackHeight": None, + }, + { + "programIdIndex": 5, + "accounts": [0, 1, 3, 2, 6, 10, 9, 4, 11], + "data": "6dMrrkPeSzw2r5huQ6RToaJCaVuu", + "stackHeight": None, + }, + { + "programIdIndex": 8, + "accounts": [0], + "data": PURCHASE_TRACK1_MEMO_DATA, + "stackHeight": None, + }, + ], + }, + }, + "meta": { + "err": None, + "status": {"Ok": None}, + "fee": 10000, + "preBalances": [ + 1689358166, + 2039280, + 953520, + 2039280, + 1, + 1141440, + 0, + 1, + 121159680, + 0, + 1009200, + 934087680, + ], + "postBalances": [ + 1689348166, + 2039280, + 953520, + 2039280, + 1, + 1141440, + 0, + 1, + 121159680, + 0, + 1009200, + 934087680, + ], + "innerInstructions": [ + { + "index": 1, + "instructions": [ + { + "programIdIndex": 11, + "accounts": [1, 3, 6, 6], + "data": "3mhiKuxuaKy1", + "stackHeight": 2, + } + ], + } + ], + "logMessages": [ + f"Program {CLAIMABLE_TOKENS_PDA} invoke [1]", + "Program log: Instruction: Transfer", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", + "Program log: Instruction: Transfer", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4728 of 581084 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + f"Program {CLAIMABLE_TOKENS_PDA} consumed 24149 of 600000 compute units", + f"Program {CLAIMABLE_TOKENS_PDA} success", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo invoke [1]", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo consumed 588 of 575851 compute units", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo success", + ], + "preTokenBalances": [ + { + "accountIndex": 1, + "mint": USDC_MINT, + "uiTokenAmount": { + "uiAmount": 1.0, + "decimals": 6, + "amount": "1000000", + "uiAmountString": "1", + }, + "owner": USDC_PDA, + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 3, + "mint": USDC_MINT, + "uiTokenAmount": { + "uiAmount": None, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": USDC_PDA, + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + ], + "postTokenBalances": [ + { + "accountIndex": 1, + "mint": USDC_MINT, + "uiTokenAmount": { + "uiAmount": None, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": USDC_PDA, + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 3, + "mint": USDC_MINT, + "uiTokenAmount": { + "uiAmount": 1.0, + "decimals": 6, + "amount": "1000000", + "uiAmountString": "1", + }, + "owner": USDC_PDA, + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + ], + "rewards": [], + "loadedAddresses": {"writable": [], "readonly": []}, + "computeUnitsConsumed": 24737, + }, + "blockTime": 1698802811, + }, + "id": 0, + } + ) +) # Transfer $1 USDC between two user banks without a purchase mock_valid_transfer_without_purchase_tx = GetTransactionResp.from_json( @@ -2443,3 +2622,349 @@ } ) ) + +# Valid purchase transaction with "stream" access in memo +mock_valid_track_purchase_tx_stream_access = GetTransactionResp.from_json( + json.dumps( + { + "jsonrpc": "2.0", + "result": { + "slot": 227246439, + "transaction": { + "signatures": [MOCK_SIGNATURE], + "message": { + "header": { + "numRequiredSignatures": 1, + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 8, + }, + "accountKeys": [ + FEE_PAYER, + SENDER_USDC_USER_BANK_ADDRESS, + NONCE_ACCOUNT_ADDRESS, + RECIPIENT_USDC_USER_BANK_ADDRESS, + "11111111111111111111111111111111", + CLAIMABLE_TOKENS_PDA, + USDC_PDA, + "KeccakSecp256k11111111111111111111111111111", + "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo", + "Sysvar1nstructions1111111111111111111111111", + "SysvarRent111111111111111111111111111111111", + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + ], + "recentBlockhash": "5H434VMiHgK7RaJZaBKKcriu4eky8erb9QGfcHJSZquU", + "instructions": [ + { + "programIdIndex": 7, + "accounts": [], + "data": "H4eCheRWTZDTCFYUcyMzE6EhQMZvvvLKJ9g6YaUpbZeoLLgVj1uvwCTdzcb2MzbKHsRjN8DjLYdqxuQEZe2TjUKCuBMrFtpnnLd4RcvBnr4ieHCdH8ZU1N6XDfiqyKB4zenQ9S4viza4ob4gbtmiRS6o6KGEtL3fJQRvaA3tdtSx1rfFogZzwMXAxHrkuxHrpAqfm", + "stackHeight": None, + }, + { + "programIdIndex": 5, + "accounts": [0, 1, 3, 2, 6, 10, 9, 4, 11], + "data": "6dMrrkPeSzw2r5huQ6RToaJCaVuu", + "stackHeight": None, + }, + { + "programIdIndex": 8, + "accounts": [0], + "data": PURCHASE_TRACK_STREAM_ACCESS_MEMO_DATA, + "stackHeight": None, + }, + ], + }, + }, + "meta": { + "err": None, + "status": {"Ok": None}, + "fee": 10000, + "preBalances": [ + 1689358166, + 2039280, + 953520, + 2039280, + 1, + 1141440, + 0, + 1, + 121159680, + 0, + 1009200, + 934087680, + ], + "postBalances": [ + 1689348166, + 2039280, + 953520, + 2039280, + 1, + 1141440, + 0, + 1, + 121159680, + 0, + 1009200, + 934087680, + ], + "innerInstructions": [ + { + "index": 1, + "instructions": [ + { + "programIdIndex": 11, + "accounts": [1, 3, 6, 6], + "data": "3mhiKuxuaKy1", + "stackHeight": 2, + } + ], + } + ], + "logMessages": [ + f"Program {CLAIMABLE_TOKENS_PDA} invoke [1]", + "Program log: Instruction: Transfer", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", + "Program log: Instruction: Transfer", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4728 of 581084 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + f"Program {CLAIMABLE_TOKENS_PDA} consumed 24149 of 600000 compute units", + f"Program {CLAIMABLE_TOKENS_PDA} success", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo invoke [1]", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo consumed 588 of 575851 compute units", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo success", + ], + "preTokenBalances": [ + { + "accountIndex": 1, + "mint": USDC_MINT, + "uiTokenAmount": { + "uiAmount": 1.0, + "decimals": 6, + "amount": "1000000", + "uiAmountString": "1", + }, + "owner": USDC_PDA, + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 3, + "mint": USDC_MINT, + "uiTokenAmount": { + "uiAmount": None, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": USDC_PDA, + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + ], + "postTokenBalances": [ + { + "accountIndex": 1, + "mint": USDC_MINT, + "uiTokenAmount": { + "uiAmount": None, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": USDC_PDA, + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 3, + "mint": USDC_MINT, + "uiTokenAmount": { + "uiAmount": 1.0, + "decimals": 6, + "amount": "1000000", + "uiAmountString": "1", + }, + "owner": USDC_PDA, + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + ], + "rewards": [], + "loadedAddresses": {"writable": [], "readonly": []}, + "computeUnitsConsumed": 24737, + }, + "blockTime": 1698802811, + }, + "id": 0, + } + ) +) + +# Valid purchase transaction with "download" access in memo +mock_valid_track_purchase_tx_download_access = GetTransactionResp.from_json( + json.dumps( + { + "jsonrpc": "2.0", + "result": { + "slot": 227246439, + "transaction": { + "signatures": [MOCK_SIGNATURE_2], + "message": { + "header": { + "numRequiredSignatures": 1, + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 8, + }, + "accountKeys": [ + FEE_PAYER, + SENDER_USDC_USER_BANK_ADDRESS, + NONCE_ACCOUNT_ADDRESS, + RECIPIENT_USDC_USER_BANK_ADDRESS, + "11111111111111111111111111111111", + CLAIMABLE_TOKENS_PDA, + USDC_PDA, + "KeccakSecp256k11111111111111111111111111111", + "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo", + "Sysvar1nstructions1111111111111111111111111", + "SysvarRent111111111111111111111111111111111", + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + ], + "recentBlockhash": "5H434VMiHgK7RaJZaBKKcriu4eky8erb9QGfcHJSZquU", + "instructions": [ + { + "programIdIndex": 7, + "accounts": [], + "data": "H4eCheRWTZDTCFYUcyMzE6EhQMZvvvLKJ9g6YaUpbZeoLLgVj1uvwCTdzcb2MzbKHsRjN8DjLYdqxuQEZe2TjUKCuBMrFtpnnLd4RcvBnr4ieHCdH8ZU1N6XDfiqyKB4zenQ9S4viza4ob4gbtmiRS6o6KGEtL3fJQRvaA3tdtSx1rfFogZzwMXAxHrkuxHrpAqfm", + "stackHeight": None, + }, + { + "programIdIndex": 5, + "accounts": [0, 1, 3, 2, 6, 10, 9, 4, 11], + "data": "6dMrrkPeSzw2r5huQ6RToaJCaVuu", + "stackHeight": None, + }, + { + "programIdIndex": 8, + "accounts": [0], + "data": PURCHASE_TRACK_DOWNLOAD_MEMO_DATA, + "stackHeight": None, + }, + ], + }, + }, + "meta": { + "err": None, + "status": {"Ok": None}, + "fee": 10000, + "preBalances": [ + 1689358166, + 2039280, + 953520, + 2039280, + 1, + 1141440, + 0, + 1, + 121159680, + 0, + 1009200, + 934087680, + ], + "postBalances": [ + 1689348166, + 2039280, + 953520, + 2039280, + 1, + 1141440, + 0, + 1, + 121159680, + 0, + 1009200, + 934087680, + ], + "innerInstructions": [ + { + "index": 1, + "instructions": [ + { + "programIdIndex": 11, + "accounts": [1, 3, 6, 6], + "data": "3mhiKuxuaKy1", + "stackHeight": 2, + } + ], + } + ], + "logMessages": [ + f"Program {CLAIMABLE_TOKENS_PDA} invoke [1]", + "Program log: Instruction: Transfer", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", + "Program log: Instruction: Transfer", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4728 of 581084 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + f"Program {CLAIMABLE_TOKENS_PDA} consumed 24149 of 600000 compute units", + f"Program {CLAIMABLE_TOKENS_PDA} success", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo invoke [1]", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo consumed 588 of 575851 compute units", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo success", + ], + "preTokenBalances": [ + { + "accountIndex": 1, + "mint": USDC_MINT, + "uiTokenAmount": { + "uiAmount": 1.0, + "decimals": 6, + "amount": "1000000", + "uiAmountString": "1", + }, + "owner": USDC_PDA, + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 3, + "mint": USDC_MINT, + "uiTokenAmount": { + "uiAmount": None, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": USDC_PDA, + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + ], + "postTokenBalances": [ + { + "accountIndex": 1, + "mint": USDC_MINT, + "uiTokenAmount": { + "uiAmount": None, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": USDC_PDA, + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 3, + "mint": USDC_MINT, + "uiTokenAmount": { + "uiAmount": 1.0, + "decimals": 6, + "amount": "1000000", + "uiAmountString": "1", + }, + "owner": USDC_PDA, + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + ], + "rewards": [], + "loadedAddresses": {"writable": [], "readonly": []}, + "computeUnitsConsumed": 24737, + }, + "blockTime": 1698802811, + }, + "id": 0, + } + ) +) diff --git a/packages/discovery-provider/src/tasks/index_payment_router.py b/packages/discovery-provider/src/tasks/index_payment_router.py index 4a6f17af9da..ad242e0b15b 100644 --- a/packages/discovery-provider/src/tasks/index_payment_router.py +++ b/packages/discovery-provider/src/tasks/index_payment_router.py @@ -129,6 +129,7 @@ class PurchaseMetadataDict(TypedDict): id: int purchaser_user_id: int content_owner_id: int + access: PurchaseAccessType class RouteTransactionMemoType(str, enum.Enum): @@ -221,72 +222,83 @@ def parse_route_transaction_memo( blocknumber_str, purchaser_user_id_str, ) = content_metadata - type = PurchaseType[type_str.lower()] - id = int(id_str) - purchaser_user_id = int(purchaser_user_id_str) - blocknumber = int(blocknumber_str) - - # TODO: Wait for blocknumber to be indexed by ACDC - logger.debug( - f"index_payment_router.py | Found content_metadata in memo: type={type}, id={id}, blocknumber={blocknumber} user_id={purchaser_user_id}" + access_str = "stream" # Default to stream access + elif len(content_metadata) == 5: + ( + type_str, + id_str, + blocknumber_str, + purchaser_user_id_str, + access_str, + ) = content_metadata + else: + logger.info( + f"index_payment_router.py | Ignoring memo, no content metadata found: {memo}" ) - price = None - splits = None - content_owner_id = None - if type == PurchaseType.track: - env = shared_config["discprov"]["env"] - content_owner_id = get_track_owner_id(session, id) - if content_owner_id is None: - logger.error( - f"index_payment_router.py | Couldn't find content owner for track_id={id}" - ) - continue - query = session.query(TrackPriceHistory) - if env != "dev": - # In local stack, the blocktime of solana-test-validator is offset. - # The start time of the validator is baked into the prebuilt container. - # So if the container was built on 7/15, but you upped the container on 7/22, the blocktimes will still say 7/15 and be way behind. - # To remedy this locally would require getting the start time of the solana-test-validator container and getting its offset compared to when - # the the validator thinks the beginning of time is, and that's just too much work so I'm just not adding the blocktime filter in local dev - query.filter(TrackPriceHistory.block_timestamp < timestamp) - result = ( - query.filter( - TrackPriceHistory.track_id == id, - ) - .order_by(desc(TrackPriceHistory.block_timestamp)) - .first() - ) - if result is not None: - price = result.total_price_cents - splits = result.splits - else: + + type = PurchaseType[type_str.lower()] + id = int(id_str) + purchaser_user_id = int(purchaser_user_id_str) + blocknumber = int(blocknumber_str) + access = PurchaseAccessType[access_str.lower()] + + # TODO: Wait for blocknumber to be indexed by ACDC + logger.debug( + f"index_payment_router.py | Found content_metadata in memo: type={type}, id={id}, blocknumber={blocknumber} user_id={purchaser_user_id}" + ) + price = None + splits = None + content_owner_id = None + if type == PurchaseType.track: + env = shared_config["discprov"]["env"] + content_owner_id = get_track_owner_id(session, id) + if content_owner_id is None: logger.error( - f"index_payment_router.py | Unknown content type {type}" - ) - if ( - price is not None - and splits is not None - and isinstance(splits, dict) - and content_owner_id is not None - ): - return RouteTransactionMemo( - type=RouteTransactionMemoType.purchase, - metadata={ - "type": type, - "id": id, - "price": price * USDC_PER_USD_CENT, - "splits": splits, - "purchaser_user_id": purchaser_user_id, - "content_owner_id": content_owner_id, - }, + f"index_payment_router.py | Couldn't find content owner for track_id={id}" ) - else: - logger.error( - f"index_payment_router.py | Couldn't find relevant price for {content_metadata}" + continue + query = session.query(TrackPriceHistory) + if env != "dev": + # In local stack, the blocktime of solana-test-validator is offset. + # The start time of the validator is baked into the prebuilt container. + # So if the container was built on 7/15, but you upped the container on 7/22, the blocktimes will still say 7/15 and be way behind. + # To remedy this locally would require getting the start time of the solana-test-validator container and getting its offset compared to when + # the the validator thinks the beginning of time is, and that's just too much work so I'm just not adding the blocktime filter in local dev + query.filter(TrackPriceHistory.block_timestamp < timestamp) + result = ( + query.filter( + TrackPriceHistory.track_id == id, + TrackPriceHistory.access == access, ) + .order_by(desc(TrackPriceHistory.block_timestamp)) + .first() + ) + if result is not None: + price = result.total_price_cents + splits = result.splits else: - logger.info( - f"index_payment_router.py | Ignoring memo, no content metadata found: {memo}" + logger.error(f"index_payment_router.py | Unknown content type {type}") + if ( + price is not None + and splits is not None + and isinstance(splits, dict) + and content_owner_id is not None + ): + return RouteTransactionMemo( + type=RouteTransactionMemoType.purchase, + metadata={ + "type": type, + "id": id, + "price": price * USDC_PER_USD_CENT, + "splits": splits, + "purchaser_user_id": purchaser_user_id, + "content_owner_id": content_owner_id, + "access": access, + }, + ) + else: + logger.error( + f"index_payment_router.py | Couldn't find relevant price for {content_metadata}" ) except (ValueError, KeyError) as e: logger.info( @@ -341,7 +353,7 @@ def index_purchase( extra_amount=extra_amount, content_type=purchase_metadata["type"], content_id=purchase_metadata["id"], - access=PurchaseAccessType.stream, + access=purchase_metadata["access"], ) logger.debug( f"index_payment_router.py | tx: {tx_sig} | Creating usdc_purchase for purchase {usdc_purchase}" diff --git a/packages/discovery-provider/src/tasks/index_user_bank.py b/packages/discovery-provider/src/tasks/index_user_bank.py index 3e65aa237e9..e5848538d13 100644 --- a/packages/discovery-provider/src/tasks/index_user_bank.py +++ b/packages/discovery-provider/src/tasks/index_user_bank.py @@ -282,6 +282,7 @@ class PurchaseMetadataDict(TypedDict): type: PurchaseType id: int purchaser_user_id: Optional[int] + access: PurchaseAccessType def get_purchase_metadata_from_memo( @@ -294,6 +295,7 @@ def get_purchase_metadata_from_memo( if len(content_metadata) == 3: type_str, id_str, blocknumber_str = content_metadata purchaser_user_id_str = None + access_str = "stream" # default to stream access elif len(content_metadata) == 4: ( type_str, @@ -301,6 +303,15 @@ def get_purchase_metadata_from_memo( blocknumber_str, purchaser_user_id_str, ) = content_metadata + access_str = "stream" # default to stream access + elif len(content_metadata) == 5: + ( + type_str, + id_str, + blocknumber_str, + purchaser_user_id_str, + access_str, + ) = content_metadata else: logger.debug( f"index_user_bank.py | Ignoring memo, no content metadata found: {memo}" @@ -313,10 +324,11 @@ def get_purchase_metadata_from_memo( purchaser_user_id = ( int(purchaser_user_id_str) if purchaser_user_id_str else None ) + access = PurchaseAccessType[access_str.lower()] # TODO: Wait for blocknumber to be indexed by ACDC logger.debug( - f"index_user_bank.py | Found content_metadata in memo: type={type}, id={id}, blocknumber={blocknumber}, purchaser_user_id={purchaser_user_id}" + f"index_user_bank.py | Found content_metadata in memo: type={type}, id={id}, blocknumber={blocknumber}, purchaser_user_id={purchaser_user_id}, access={access}" ) price = None @@ -334,6 +346,7 @@ def get_purchase_metadata_from_memo( result = ( query.filter( TrackPriceHistory.track_id == id, + TrackPriceHistory.access == access, ) .order_by(desc(TrackPriceHistory.block_timestamp)) .first() @@ -352,6 +365,7 @@ def get_purchase_metadata_from_memo( "price": price * USDC_PER_USD_CENT, "splits": splits, "purchaser_user_id": purchaser_user_id, + "access": access, } logger.info( f"index_user_bank.py | Got purchase metadata {content_metadata}" @@ -416,7 +430,7 @@ def index_purchase( extra_amount=extra_amount, content_type=purchase_metadata["type"], content_id=purchase_metadata["id"], - access=PurchaseAccessType.stream, + access=purchase_metadata["access"], ) logger.debug( f"index_user_bank.py | Creating usdc_purchase for purchase {usdc_purchase}" diff --git a/packages/libs/src/services/solana/SolanaWeb3Manager.ts b/packages/libs/src/services/solana/SolanaWeb3Manager.ts index 4bf70405f78..85916214927 100644 --- a/packages/libs/src/services/solana/SolanaWeb3Manager.ts +++ b/packages/libs/src/services/solana/SolanaWeb3Manager.ts @@ -69,6 +69,7 @@ type CreateSenderParams = Omit< export type MintName = 'usdc' | 'audio' export const DEFAULT_MINT: MintName = 'audio' +export type PurchaseAccess = 'stream' | 'download' const MEMO_PROGRAM_ID = new PublicKey( 'Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo' @@ -521,7 +522,8 @@ export class SolanaWeb3Manager { blocknumber, extraAmount = 0, splits, - purchaserUserId + purchaserUserId, + purchaseAccess }: { id: number type: 'track' @@ -529,15 +531,13 @@ export class SolanaWeb3Manager { extraAmount?: number | BN blocknumber: number purchaserUserId: number + purchaseAccess: PurchaseAccess }) { if (!this.web3Manager) { throw new Error( 'A web3Manager is required for this solanaWeb3Manager method' ) } - if (!splits) { - throw new Error('Splits must be provided') - } if (Object.values(splits).length !== 1) { throw new Error( 'Purchasing content only supports a single split. Specifying more splits coming soon!' @@ -571,7 +571,7 @@ export class SolanaWeb3Manager { mintKey: this.mints.usdc }) - const data = `${type}:${id}:${blocknumber}:${purchaserUserId}` + const data = `${type}:${id}:${blocknumber}:${purchaserUserId}:${purchaseAccess}` const memoInstruction = new TransactionInstruction({ keys: [ @@ -609,7 +609,8 @@ export class SolanaWeb3Manager { extraAmount = 0, splits, purchaserUserId, - senderAccount + senderAccount, + purchaseAccess }: { id: number type: 'track' @@ -618,6 +619,7 @@ export class SolanaWeb3Manager { blocknumber: number purchaserUserId: number senderAccount: PublicKey + purchaseAccess: PurchaseAccess }) { if (!this.web3Manager) { throw new Error( @@ -691,7 +693,7 @@ export class SolanaWeb3Manager { this.paymentRouterProgramId ) - const data = `${type}:${id}:${blocknumber}:${purchaserUserId}` + const data = `${type}:${id}:${blocknumber}:${purchaserUserId}:${purchaseAccess}` const memoInstruction = new TransactionInstruction({ keys: [ @@ -721,7 +723,8 @@ export class SolanaWeb3Manager { splits, purchaserUserId, senderKeypair, - skipSendAndReturnTransaction + skipSendAndReturnTransaction, + purchaseAccess }: { id: number type: 'track' @@ -731,6 +734,7 @@ export class SolanaWeb3Manager { purchaserUserId: number senderKeypair: Keypair skipSendAndReturnTransaction?: boolean + purchaseAccess: PurchaseAccess }) { const instructions = await this.getPurchaseContentWithPaymentRouterInstructions({ @@ -740,7 +744,8 @@ export class SolanaWeb3Manager { extraAmount, splits, purchaserUserId, - senderAccount: senderKeypair.publicKey + senderAccount: senderKeypair.publicKey, + purchaseAccess }) const recentBlockhash = (await this.connection.getLatestBlockhash()) .blockhash From d396b0de94b60faa4af7c9708432e85ab782ccb9 Mon Sep 17 00:00:00 2001 From: Dharit Tantiviramanond Date: Wed, 7 Feb 2024 19:04:44 -0500 Subject: [PATCH 2/3] lint --- packages/common/src/services/audius-backend/solana.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/common/src/services/audius-backend/solana.ts b/packages/common/src/services/audius-backend/solana.ts index 7465e0d9cef..23d4f2e1bb1 100644 --- a/packages/common/src/services/audius-backend/solana.ts +++ b/packages/common/src/services/audius-backend/solana.ts @@ -479,7 +479,8 @@ export const createRootWalletRecoveryTransaction = async ( blocknumber: 0, // ignored splits: { [userBank.toString()]: new BN(amount.toString()) }, purchaserUserId: 0, // ignored - senderAccount: wallet.publicKey + senderAccount: wallet.publicKey, + purchaseAccess: 'stream' //ignored }) const recentBlockhash = await getRecentBlockhash(audiusBackendInstance) @@ -665,7 +666,8 @@ export const createPaymentRouterRouteTransaction = async ( blocknumber: 0, // ignored splits, purchaserUserId: 0, // ignored - senderAccount: sender + senderAccount: sender, + purchaseAccess: 'stream' //ignored }) return new Transaction({ recentBlockhash: blockhash, From 254793deec5bb637f7392b7e9016a0e3342e33a1 Mon Sep 17 00:00:00 2001 From: Dharit Tantiviramanond Date: Thu, 8 Feb 2024 12:01:39 -0500 Subject: [PATCH 3/3] PR comments --- packages/common/src/models/PurchaseContent.ts | 5 + .../src/services/audius-backend/solana.ts | 13 +- .../src/store/purchase-content/sagas.ts | 23 +- .../src/store/purchase-content/types.ts | 5 - .../tasks/payment_router_mock_transactions.py | 199 ++++++++++++++++++ .../tasks/test_index_payment_router.py | 12 +- .../tasks/test_index_user_bank.py | 14 +- .../tasks/user_bank_mock_transactions.py | 176 +--------------- 8 files changed, 249 insertions(+), 198 deletions(-) diff --git a/packages/common/src/models/PurchaseContent.ts b/packages/common/src/models/PurchaseContent.ts index 28180a5f16a..3a1dd33d003 100644 --- a/packages/common/src/models/PurchaseContent.ts +++ b/packages/common/src/models/PurchaseContent.ts @@ -8,3 +8,8 @@ export enum PurchaseVendor { STRIPE = 'Stripe', COINFLOW = 'Coinflow' } + +export enum PurchaseAccess { + STREAM = 'stream', + DOWNLOAD = 'download' +} diff --git a/packages/common/src/services/audius-backend/solana.ts b/packages/common/src/services/audius-backend/solana.ts index 23d4f2e1bb1..3bab93d3a01 100644 --- a/packages/common/src/services/audius-backend/solana.ts +++ b/packages/common/src/services/audius-backend/solana.ts @@ -20,10 +20,15 @@ import BN from 'bn.js' import { BN_USDC_CENT_WEI } from '~/utils/wallet' -import { AnalyticsEvent, ID, Name, SolanaWalletAddress } from '../../models' +import { + AnalyticsEvent, + ID, + Name, + SolanaWalletAddress, + PurchaseAccess +} from '../../models' import { AudiusBackend } from './AudiusBackend' -import { PurchaseAccess } from '~/store' const DEFAULT_RETRY_DELAY = 1000 const DEFAULT_MAX_RETRY_COUNT = 120 @@ -480,7 +485,7 @@ export const createRootWalletRecoveryTransaction = async ( splits: { [userBank.toString()]: new BN(amount.toString()) }, purchaserUserId: 0, // ignored senderAccount: wallet.publicKey, - purchaseAccess: 'stream' //ignored + purchaseAccess: PurchaseAccess.STREAM //ignored }) const recentBlockhash = await getRecentBlockhash(audiusBackendInstance) @@ -667,7 +672,7 @@ export const createPaymentRouterRouteTransaction = async ( splits, purchaserUserId: 0, // ignored senderAccount: sender, - purchaseAccess: 'stream' //ignored + purchaseAccess: PurchaseAccess.STREAM //ignored }) return new Transaction({ recentBlockhash: blockhash, diff --git a/packages/common/src/store/purchase-content/sagas.ts b/packages/common/src/store/purchase-content/sagas.ts index 37cc24699be..21b9f9e8fc8 100644 --- a/packages/common/src/store/purchase-content/sagas.ts +++ b/packages/common/src/store/purchase-content/sagas.ts @@ -5,7 +5,11 @@ import { call, put, race, select, take } from 'typed-redux-saga' import { FavoriteSource, Name } from '~/models/Analytics' import { ErrorLevel } from '~/models/ErrorReporting' import { ID } from '~/models/Identifiers' -import { PurchaseMethod, PurchaseVendor } from '~/models/PurchaseContent' +import { + PurchaseMethod, + PurchaseVendor, + PurchaseAccess +} from '~/models/PurchaseContent' import { Track, isContentUSDCPurchaseGated } from '~/models/Track' import { User } from '~/models/User' import { BNUSDC } from '~/models/Wallet' @@ -64,12 +68,7 @@ import { purchaseContentFlowFailed, startPurchaseContentFlow } from './slice' -import { - ContentType, - PurchaseAccess, - PurchaseContentError, - PurchaseErrorCode -} from './types' +import { ContentType, PurchaseContentError, PurchaseErrorCode } from './types' import { getBalanceNeeded } from './utils' const { getUserId, getAccountUser } = accountSelectors @@ -99,10 +98,12 @@ function* getContentInfo({ contentId, contentType }: GetPurchaseConfigArgs) { const trackInfo = yield* select(getTrack, { id: contentId }) const purchaseConditions = trackInfo?.stream_conditions ?? trackInfo?.download_conditions - const purchaseAccess = - trackInfo?.is_download_gated && !trackInfo?.is_stream_gated - ? PurchaseAccess.DOWNLOAD - : PurchaseAccess.STREAM + // Stream access is a superset of download access - purchasing a stream-gated + // track also gets you download access, but purchasing a download-gated track + // only gets you download access (because the track was already free to stream). + const purchaseAccess = trackInfo?.is_stream_gated + ? PurchaseAccess.STREAM + : PurchaseAccess.DOWNLOAD if (!trackInfo || !isContentUSDCPurchaseGated(purchaseConditions)) { throw new Error('Content is missing purchase conditions') } diff --git a/packages/common/src/store/purchase-content/types.ts b/packages/common/src/store/purchase-content/types.ts index 58574184918..5acf76766c0 100644 --- a/packages/common/src/store/purchase-content/types.ts +++ b/packages/common/src/store/purchase-content/types.ts @@ -21,11 +21,6 @@ export enum PurchaseContentPage { TRANSFER = 'crypto-transfer' } -export enum PurchaseAccess { - STREAM = 'stream', - DOWNLOAD = 'download' -} - export enum PurchaseErrorCode { Canceled = 'Canceled', InsufficientBalance = 'InsufficientBalance', diff --git a/packages/discovery-provider/integration_tests/tasks/payment_router_mock_transactions.py b/packages/discovery-provider/integration_tests/tasks/payment_router_mock_transactions.py index 8cd82c3fe6e..2a7a2e7f5a3 100644 --- a/packages/discovery-provider/integration_tests/tasks/payment_router_mock_transactions.py +++ b/packages/discovery-provider/integration_tests/tasks/payment_router_mock_transactions.py @@ -3171,6 +3171,205 @@ ) ) +# Valid purchase transaction specifying stream access purchase in memo +mock_valid_track_purchase_stream_access = GetTransactionResp.from_json( + json.dumps( + { + "jsonrpc": "2.0", + "result": { + "slot": 190957, + "transaction": { + "signatures": [ + "5wPxiuLSF3MzXZt9XG99UEPNdxs8DtE2vWKezrB6zuMCrkMBJx6iU7xw5icaowpfgj96iLGnAgEAaBNSbneWdbZw" + ], + "message": { + "header": { + "numRequiredSignatures": 1, + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 5, + }, + "accountKeys": [ + "HXLN9UWwAjMPgHaFZDfgabT79SmLSdTeu2fUha2xHz9W", + "3XmVeZ6M1FYDdUQaNeQZf8dipvtzNP6NVb5xjDkdeiNb", + "A76eNhRrfdy6WfMoQf4ALasMxzRWHajH4TrVuX2NUjZT", + "7gfRGGdp89N9g3mCsZjaGmDDRdcTnZh9u3vYyBab2tRy", + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo", + "apaySbqV1XAmuiGszeN4NyWrXkkMrnuJVoNhzmS1AMa", + "G231EZsMoCNBiQKP5quEeAM3oG516Zspirjnh7ywP71i", + ], + "recentBlockhash": "6D65tSU7pjSmFvSj9qK2W2bjkESw4XZebeNmgA1rCqnF", + "instructions": [ + { + "programIdIndex": 4, + "accounts": [1, 5, 2, 0], + "data": "hYECWfYe8vYqs", + "stackHeight": None, + }, + { + "programIdIndex": 6, + "accounts": [0], + # "track:1:1:2:stream" + "data": "5q8ftnvrq1ERw9QN7ZQv8xZPA", + "stackHeight": None, + }, + { + "programIdIndex": 7, + "accounts": [2, 8, 4, 3], + "data": "BQD4GnQPrhbq6Y9NJLnwDUziXhfF6BjkLYFbnKZH", + "stackHeight": None, + }, + ], + "addressTableLookups": [], + }, + }, + "meta": { + "err": None, + "status": {"Ok": None}, + "fee": 5000, + "preBalances": [ + 8420804160, + 2039280, + 2039280, + 2039280, + 929020800, + 1461600, + 119712000, + 1141440, + 946560, + ], + "postBalances": [ + 8420799160, + 2039280, + 2039280, + 2039280, + 929020800, + 1461600, + 119712000, + 1141440, + 946560, + ], + "innerInstructions": [ + { + "index": 2, + "instructions": [ + { + "programIdIndex": 4, + "accounts": [2, 3, 8, 8], + "data": "3YKuzAsyicvj", + "stackHeight": 2, + } + ], + } + ], + "logMessages": [ + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]", + "Program log: Instruction: TransferChecked", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6173 of 600000 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo invoke [1]", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo consumed 480 of 593827 compute units", + "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo success", + "Program apaySbqV1XAmuiGszeN4NyWrXkkMrnuJVoNhzmS1AMa invoke [1]", + "Program log: Instruction: Route", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", + "Program log: Instruction: Transfer", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4728 of 576902 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program log: All transfers complete!", + "Program apaySbqV1XAmuiGszeN4NyWrXkkMrnuJVoNhzmS1AMa consumed 21782 of 593347 compute units", + "Program apaySbqV1XAmuiGszeN4NyWrXkkMrnuJVoNhzmS1AMa success", + ], + "preTokenBalances": [ + { + "accountIndex": 1, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": 10.0, + "decimals": 6, + "amount": "10000000", + "uiAmountString": "10.0", + }, + "owner": "HXLN9UWwAjMPgHaFZDfgabT79SmLSdTeu2fUha2xHz9W", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 2, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": None, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": "G231EZsMoCNBiQKP5quEeAM3oG516Zspirjnh7ywP71i", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 3, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": 0, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": "7vKR1WSmyHvBmCvKPZBiN66PHZqYQbXw51SZdwtVd9Dt", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + ], + "postTokenBalances": [ + { + "accountIndex": 1, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": 9.0, + "decimals": 6, + "amount": "9000000", + "uiAmountString": "9.0", + }, + "owner": "HXLN9UWwAjMPgHaFZDfgabT79SmLSdTeu2fUha2xHz9W", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 2, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": None, + "decimals": 6, + "amount": "0", + "uiAmountString": "0", + }, + "owner": "G231EZsMoCNBiQKP5quEeAM3oG516Zspirjnh7ywP71i", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + { + "accountIndex": 3, + "mint": "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y", + "uiTokenAmount": { + "uiAmount": 1.0, + "decimals": 6, + "amount": "1000000", + "uiAmountString": "1.0", + }, + "owner": "7vKR1WSmyHvBmCvKPZBiN66PHZqYQbXw51SZdwtVd9Dt", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + }, + ], + "rewards": [], + "loadedAddresses": {"writable": [], "readonly": []}, + "computeUnitsConsumed": 28435, + }, + "version": 0, + "blockTime": 1701922096, + }, + "id": 0, + } + ) +) + +# Valid purchase transaction specifying download access purchase in memo mock_valid_track_purchase_download_access = GetTransactionResp.from_json( json.dumps( { diff --git a/packages/discovery-provider/integration_tests/tasks/test_index_payment_router.py b/packages/discovery-provider/integration_tests/tasks/test_index_payment_router.py index faf39157869..58c892a41fa 100644 --- a/packages/discovery-provider/integration_tests/tasks/test_index_payment_router.py +++ b/packages/discovery-provider/integration_tests/tasks/test_index_payment_router.py @@ -90,6 +90,7 @@ "track_id": 1, "splits": {"7gfRGGdp89N9g3mCsZjaGmDDRdcTnZh9u3vYyBab2tRy": 1000000}, "total_price_cents": 100, + "access": PurchaseAccessType.stream, }, { # pay $1 each to track owner and third party "track_id": 2, @@ -925,7 +926,8 @@ def test_process_payment_router_txs_details_skip_unknown_PDA_ATAs(app): assert transaction_record is None -def test_process_payment_router_tx_details_access_types(app): +# Index tx with stream access in memo correctly +def test_process_payment_router_tx_details_stream_access(app): with app.app_context(): db = get_db() populate_mock_db(db, test_entries) @@ -953,6 +955,14 @@ def test_process_payment_router_tx_details_access_types(app): assert purchase is not None assert purchase.access == PurchaseAccessType.stream + +# Index tx with download access in memo correctly +def test_process_payment_router_tx_details_download_access(app): + with app.app_context(): + db = get_db() + populate_mock_db(db, test_entries) + challenge_event_bus = create_autospec(ChallengeEventBus) + tx_response = mock_valid_track_purchase_download_access transaction = tx_response.value.transaction.transaction tx_sig_str = str(transaction.signatures[0]) diff --git a/packages/discovery-provider/integration_tests/tasks/test_index_user_bank.py b/packages/discovery-provider/integration_tests/tasks/test_index_user_bank.py index 36959a06089..8c7b0a7d8a8 100644 --- a/packages/discovery-provider/integration_tests/tasks/test_index_user_bank.py +++ b/packages/discovery-provider/integration_tests/tasks/test_index_user_bank.py @@ -153,7 +153,6 @@ def test_process_user_bank_tx_details_valid_purchase(app): solana_client_manager_mock = create_autospec(SolanaClientManager) transaction = tx_response.value.transaction.transaction - print(f"REED {transaction}") tx_sig_str = str(transaction.signatures[0]) @@ -1007,7 +1006,8 @@ def test_process_user_bank_txs_details_transfer_audio_tip_challenge_event(app): challenge_event_bus.dispatch.assert_has_calls(calls) -def test_process_user_bank_txs_details_access_types(app): +# Index tx with stream access in memo correctly +def test_process_user_bank_txs_details_stream_access(app): with app.app_context(): db = get_db() redis = get_redis() @@ -1038,6 +1038,16 @@ def test_process_user_bank_txs_details_access_types(app): assert purchase is not None assert purchase.access == PurchaseAccessType.stream + +# Index tx with download access in memo correctly +def test_process_user_bank_txs_details_download_access(app): + with app.app_context(): + db = get_db() + redis = get_redis() + solana_client_manager_mock = create_autospec(SolanaClientManager) + challenge_event_bus = create_autospec(ChallengeEventBus) + populate_mock_db(db, test_entries) + tx_response = mock_valid_track_purchase_tx_download_access transaction = tx_response.value.transaction.transaction tx_sig_str = str(transaction.signatures[0]) diff --git a/packages/discovery-provider/integration_tests/tasks/user_bank_mock_transactions.py b/packages/discovery-provider/integration_tests/tasks/user_bank_mock_transactions.py index dcabc8562b4..30c5ea206bf 100644 --- a/packages/discovery-provider/integration_tests/tasks/user_bank_mock_transactions.py +++ b/packages/discovery-provider/integration_tests/tasks/user_bank_mock_transactions.py @@ -20,7 +20,6 @@ MOCK_SIGNATURE = "3tD61jrsU4b6s7jMGR3hg7p9Dsm88NRR2RVgUUjdcHqfFf9JWRwyhPiRGvEqHnLN6qaoc1Gvqy9Nv2UyKcWe6u4C" -MOCK_SIGNATURE_2 = "3tD61jrsU4b6s7jMGR3hg7p9Dsm88NRR2RVgUUjdcHqfFf9JWRwyhPiRGvEqHnLN6qaoc1Gvqy9Nv2UyKcWe6u4D" FEE_PAYER = "HmqRrgrZjR1Fkwgbv1nDXuUYQYs6ocGSzmGPoEUZqK1X" USDC_MINT = "26Q7gP8UfkDzi7GMFEQxTJaNJ8D2ybCUjex58M5MLu8y" @@ -419,179 +418,6 @@ ) ) -# Purchase of track id 1 for $1 USDC with purchaser user id 2 including download access -ock_valid_track_purchase_tx = GetTransactionResp.from_json( - json.dumps( - { - "jsonrpc": "2.0", - "result": { - "slot": 227246439, - "transaction": { - "signatures": [MOCK_SIGNATURE], - "message": { - "header": { - "numRequiredSignatures": 1, - "numReadonlySignedAccounts": 0, - "numReadonlyUnsignedAccounts": 8, - }, - "accountKeys": [ - FEE_PAYER, - SENDER_USDC_USER_BANK_ADDRESS, - NONCE_ACCOUNT_ADDRESS, - RECIPIENT_USDC_USER_BANK_ADDRESS, - "11111111111111111111111111111111", - CLAIMABLE_TOKENS_PDA, - USDC_PDA, - "KeccakSecp256k11111111111111111111111111111", - "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo", - "Sysvar1nstructions1111111111111111111111111", - "SysvarRent111111111111111111111111111111111", - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", - ], - "recentBlockhash": "5H434VMiHgK7RaJZaBKKcriu4eky8erb9QGfcHJSZquU", - "instructions": [ - { - "programIdIndex": 7, - "accounts": [], - "data": "H4eCheRWTZDTCFYUcyMzE6EhQMZvvvLKJ9g6YaUpbZeoLLgVj1uvwCTdzcb2MzbKHsRjN8DjLYdqxuQEZe2TjUKCuBMrFtpnnLd4RcvBnr4ieHCdH8ZU1N6XDfiqyKB4zenQ9S4viza4ob4gbtmiRS6o6KGEtL3fJQRvaA3tdtSx1rfFogZzwMXAxHrkuxHrpAqfm", - "stackHeight": None, - }, - { - "programIdIndex": 5, - "accounts": [0, 1, 3, 2, 6, 10, 9, 4, 11], - "data": "6dMrrkPeSzw2r5huQ6RToaJCaVuu", - "stackHeight": None, - }, - { - "programIdIndex": 8, - "accounts": [0], - "data": PURCHASE_TRACK1_MEMO_DATA, - "stackHeight": None, - }, - ], - }, - }, - "meta": { - "err": None, - "status": {"Ok": None}, - "fee": 10000, - "preBalances": [ - 1689358166, - 2039280, - 953520, - 2039280, - 1, - 1141440, - 0, - 1, - 121159680, - 0, - 1009200, - 934087680, - ], - "postBalances": [ - 1689348166, - 2039280, - 953520, - 2039280, - 1, - 1141440, - 0, - 1, - 121159680, - 0, - 1009200, - 934087680, - ], - "innerInstructions": [ - { - "index": 1, - "instructions": [ - { - "programIdIndex": 11, - "accounts": [1, 3, 6, 6], - "data": "3mhiKuxuaKy1", - "stackHeight": 2, - } - ], - } - ], - "logMessages": [ - f"Program {CLAIMABLE_TOKENS_PDA} invoke [1]", - "Program log: Instruction: Transfer", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", - "Program log: Instruction: Transfer", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4728 of 581084 compute units", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", - f"Program {CLAIMABLE_TOKENS_PDA} consumed 24149 of 600000 compute units", - f"Program {CLAIMABLE_TOKENS_PDA} success", - "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo invoke [1]", - "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo consumed 588 of 575851 compute units", - "Program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo success", - ], - "preTokenBalances": [ - { - "accountIndex": 1, - "mint": USDC_MINT, - "uiTokenAmount": { - "uiAmount": 1.0, - "decimals": 6, - "amount": "1000000", - "uiAmountString": "1", - }, - "owner": USDC_PDA, - "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", - }, - { - "accountIndex": 3, - "mint": USDC_MINT, - "uiTokenAmount": { - "uiAmount": None, - "decimals": 6, - "amount": "0", - "uiAmountString": "0", - }, - "owner": USDC_PDA, - "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", - }, - ], - "postTokenBalances": [ - { - "accountIndex": 1, - "mint": USDC_MINT, - "uiTokenAmount": { - "uiAmount": None, - "decimals": 6, - "amount": "0", - "uiAmountString": "0", - }, - "owner": USDC_PDA, - "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", - }, - { - "accountIndex": 3, - "mint": USDC_MINT, - "uiTokenAmount": { - "uiAmount": 1.0, - "decimals": 6, - "amount": "1000000", - "uiAmountString": "1", - }, - "owner": USDC_PDA, - "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", - }, - ], - "rewards": [], - "loadedAddresses": {"writable": [], "readonly": []}, - "computeUnitsConsumed": 24737, - }, - "blockTime": 1698802811, - }, - "id": 0, - } - ) -) - # Transfer $1 USDC between two user banks without a purchase mock_valid_transfer_without_purchase_tx = GetTransactionResp.from_json( json.dumps( @@ -2804,7 +2630,7 @@ "result": { "slot": 227246439, "transaction": { - "signatures": [MOCK_SIGNATURE_2], + "signatures": [MOCK_SIGNATURE], "message": { "header": { "numRequiredSignatures": 1,