UNPKG

@magiceden/magiceden-sdk

Version:

A TypeScript SDK for interacting with Magic Eden's API across multiple chains.

380 lines (379 loc) 17.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const __1 = require("../../.."); const __2 = require("../../.."); const chains_1 = require("../../../types/chains"); const __3 = require("../../.."); // Mock dependencies jest.mock('../../../mappers/nft/solana'); jest.mock('../../../adapters/transactions/solana'); jest.mock('../../../api/clients/v2'); jest.mock('../../../api/clients/v4'); describe('SolanaNftService V4', () => { let service; let mockWallet; let mockV2ApiClient; let mockV4ApiClient; let mockTransaction; let mockV4Response; const validSolanaPubkey = '11111111111111111111111111111111'; const mockTransactionBase64 = 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='; beforeEach(() => { // Reset mocks jest.clearAllMocks(); // Create mock transaction mockTransaction = { /* mock transaction object */ serialize: jest.fn().mockReturnValue(new Uint8Array([1, 2, 3])), }; // Create mock V4 API response mockV4Response = { steps: [ { id: 'step1', chain: chains_1.Blockchain.SOLANA, method: 'signAllAndSendTransactions', params: { feePayer: validSolanaPubkey, transactions: [ { transaction: mockTransactionBase64, signerPubkeys: [], }, ], }, }, ], }; // Create mock wallet mockWallet = { getAddress: jest.fn().mockReturnValue(validSolanaPubkey), signAndSendTransaction: jest.fn().mockResolvedValue('mock-signature'), waitForTransactionConfirmation: jest.fn().mockResolvedValue({ txId: 'mock-signature', status: 'confirmed', }), }; // Create service instance service = new __3.SolanaNftService({ chain: chains_1.ChainType.SOLANA, apiKey: 'test-api-key', wallet: mockWallet, }); // Create mock API clients manually instead of trying to access instances mockV2ApiClient = { getInstructions: jest.fn(), }; mockV4ApiClient = { createLaunchpad: jest.fn(), updateLaunchpad: jest.fn(), mint: jest.fn(), }; // Replace the service's API clients with our mocks service.v2ApiClient = mockV2ApiClient; service.v4ApiClient = mockV4ApiClient; // Setup default mock implementations __2.SolanaTransactionAdapters.fromInstructionsResponse.mockReturnValue([ mockTransaction, ]); __2.SolanaTransactionAdapters.fromV4TransactionResponse.mockReturnValue([ mockTransaction, ]); }); // Helper function to create standard launchpad params const createLaunchpadParams = () => ({ name: 'Test Collection', symbol: 'TEST', description: 'Test description', chain: chains_1.Blockchain.SOLANA, protocol: __2.SolProtocolType.METAPLEX_CORE, creator: validSolanaPubkey, payoutRecipient: validSolanaPubkey, royaltyRecipients: [{ address: validSolanaPubkey, share: 100 }], imageUrl: 'https://example.com/image.png', tokenImageUrl: 'https://example.com/token.png', isOpenEdition: false, royaltyBps: 500, mintStages: { stages: [ { kind: __2.MintStageKind.Public, price: { currency: { chain: chains_1.Blockchain.SOLANA, assetId: 'So11111111111111111111111111111111111111112' }, raw: '1000000000', }, startTime: '2023-01-01T00:00:00Z', endTime: '2023-01-02T00:00:00Z', walletLimit: 5, maxSupply: 100, }, ], maxSupply: 100, }, }); // Helper function to create standard mint params const createMintParams = () => ({ chain: chains_1.Blockchain.SOLANA, collectionId: validSolanaPubkey, nftAmount: 1, stageId: 'stage1', kind: __2.MintStageKind.Public, candyMachineId: validSolanaPubkey, symbol: 'TEST', recipient: validSolanaPubkey, }); describe('createLaunchpad', () => { it('should map parameters, call API, and sign transaction', async () => { // Setup mocks const mockParams = createLaunchpadParams(); const mockApiRequest = { name: 'Test Launchpad' /* other fields */ }; // Create mock response with metadata const mockResponseWithMetadata = { steps: mockV4Response.steps, metadata: { imageUrl: 'https://example.com/image.png', tokenImage: 'https://example.com/token.png', metadataUrl: 'https://example.com/metadata.json', }, }; // Setup mock implementations __1.SolanaApiMappers.v4.createLaunchpadRequest.mockReturnValue(mockApiRequest); mockV4ApiClient.createLaunchpad.mockResolvedValue(mockResponseWithMetadata); // Call the method const result = await service.createLaunchpad(mockParams); // Verify the flow expect(__1.SolanaApiMappers.v4.createLaunchpadRequest).toHaveBeenCalledWith(mockParams); expect(mockV4ApiClient.createLaunchpad).toHaveBeenCalledWith(mockApiRequest); expect(__2.SolanaTransactionAdapters.fromV4TransactionResponse).toHaveBeenCalledWith(mockResponseWithMetadata); expect(mockWallet.signAndSendTransaction).toHaveBeenCalledWith(mockTransaction); expect(result).toEqual([{ txId: 'mock-signature', status: 'confirmed' }]); }); it('should handle API errors', async () => { // Setup mocks const mockParams = createLaunchpadParams(); // Setup mock to throw error mockV4ApiClient.createLaunchpad.mockRejectedValue(new Error('API error')); // Call the method and expect it to throw await expect(service.createLaunchpad(mockParams)).rejects.toThrow('API error'); }); }); describe('updateLaunchpad', () => { it('should map parameters, call API, and sign transaction', async () => { // Setup mocks with the correct parameter structure const mockParams = { chain: chains_1.Blockchain.SOLANA, protocol: __2.SolProtocolType.METAPLEX_CORE, collectionId: 'collectionAddress123', owner: 'ownerAddress123', social: { discordUrl: 'https://discord.com/test', externalUrl: 'https://example.com', twitterUsername: 'testuser', }, name: 'Updated Collection', imageUrl: 'https://example.com/updated-image.png', description: 'Updated description', royaltyBps: 700, royaltyRecipients: [ { address: 'recipient1', share: 70 }, { address: 'recipient2', share: 30 }, ], payoutRecipient: 'updatedPayoutAddress123', candyMachineId: 'candyMachineId123', symbol: 'TEST', newSymbol: 'UPDT', externalLink: 'https://example.com/external', payer: 'payerAddress123', }; const mockApiRequest = { launchpadAddress: validSolanaPubkey /* other fields */ }; // Create mock response with metadata according to V4UpdateLaunchpadResponse const mockResponseWithMetadata = { steps: mockV4Response.steps, metadata: { imageUrl: 'https://example.com/updated-image.png', tokenImage: 'https://example.com/updated-token.png', metadataUrl: 'https://example.com/updated-metadata.json', }, }; // Setup mock implementations __1.SolanaApiMappers.v4.updateLaunchpadRequest.mockReturnValue(mockApiRequest); mockV4ApiClient.updateLaunchpad.mockResolvedValue(mockResponseWithMetadata); // Call the method const result = await service.updateLaunchpad(mockParams); // Verify the flow expect(__1.SolanaApiMappers.v4.updateLaunchpadRequest).toHaveBeenCalledWith(mockParams); expect(mockV4ApiClient.updateLaunchpad).toHaveBeenCalledWith(mockApiRequest); expect(__2.SolanaTransactionAdapters.fromV4TransactionResponse).toHaveBeenCalledWith(mockResponseWithMetadata); expect(mockWallet.signAndSendTransaction).toHaveBeenCalledWith(mockTransaction); expect(result).toEqual([{ txId: 'mock-signature', status: 'confirmed' }]); }); }); describe('mint', () => { it('should map parameters, call API, and sign transaction', async () => { // Setup mocks const mockParams = { chain: chains_1.Blockchain.SOLANA, collectionId: 'collectionId123', nftAmount: 3, stageId: 'stage1', kind: __2.MintStageKind.Public, candyMachineId: 'candyMachineId123', symbol: 'TEST', recipient: 'recipientAddress123', }; const mockApiRequest = { launchpadAddress: validSolanaPubkey /* other fields */ }; // Create mock response according to V4MintResponse (no metadata) const mockMintResponse = { steps: mockV4Response.steps, }; // Setup mock implementations __1.SolanaApiMappers.v4.mintRequest.mockReturnValue(mockApiRequest); mockV4ApiClient.mint.mockResolvedValue(mockMintResponse); // Call the method const result = await service.mint(mockParams); // Verify the flow expect(__1.SolanaApiMappers.v4.mintRequest).toHaveBeenCalledWith(mockParams); expect(mockV4ApiClient.mint).toHaveBeenCalledWith(mockApiRequest); expect(__2.SolanaTransactionAdapters.fromV4TransactionResponse).toHaveBeenCalledWith(mockMintResponse); expect(mockWallet.signAndSendTransaction).toHaveBeenCalledWith(mockTransaction); expect(result).toEqual([{ txId: 'mock-signature', status: 'confirmed' }]); }); it('should handle multiple transactions', async () => { // Setup mocks const mockParams = { chain: chains_1.Blockchain.SOLANA, collectionId: 'collectionId123', nftAmount: 3, stageId: 'stage1', kind: __2.MintStageKind.Public, candyMachineId: 'candyMachineId123', symbol: 'TEST', recipient: 'recipientAddress123', }; // Create a response with multiple transactions (no metadata for V4MintResponse) const multiTxResponse = { steps: [ { id: 'step1', chain: chains_1.Blockchain.SOLANA, method: 'signAllAndSendTransactions', params: { feePayer: validSolanaPubkey, transactions: [ { transaction: mockTransactionBase64, signerPubkeys: [], }, { transaction: mockTransactionBase64, signerPubkeys: [], }, ], }, }, ], }; const mockTransaction1 = { serialize: jest.fn().mockReturnValue(new Uint8Array([1, 2, 3])), }; const mockTransaction2 = { serialize: jest.fn().mockReturnValue(new Uint8Array([4, 5, 6])), }; // Setup mock implementations __1.SolanaApiMappers.v4.mintRequest.mockReturnValue({ launchpadAddress: validSolanaPubkey, }); mockV4ApiClient.mint.mockResolvedValue(multiTxResponse); __2.SolanaTransactionAdapters.fromV4TransactionResponse.mockReturnValue([ mockTransaction1, mockTransaction2, ]); // Mock wallet to return different signatures for each transaction mockWallet.signAndSendTransaction .mockResolvedValueOnce('mock-signature-1') .mockResolvedValueOnce('mock-signature-2'); mockWallet.waitForTransactionConfirmation .mockResolvedValueOnce({ txId: 'mock-signature-1', status: 'confirmed' }) .mockResolvedValueOnce({ txId: 'mock-signature-2', status: 'confirmed' }); // Call the method const result = await service.mint(mockParams); // Verify the flow expect(mockV4ApiClient.mint).toHaveBeenCalledWith({ launchpadAddress: validSolanaPubkey }); expect(__2.SolanaTransactionAdapters.fromV4TransactionResponse).toHaveBeenCalledWith(multiTxResponse); expect(mockWallet.signAndSendTransaction).toHaveBeenCalledTimes(2); expect(mockWallet.waitForTransactionConfirmation).toHaveBeenCalledTimes(2); expect(result).toEqual([ { txId: 'mock-signature-1', status: 'confirmed' }, { txId: 'mock-signature-2', status: 'confirmed' }, ]); }); it('should handle adapter errors for V4 responses', async () => { // Setup mocks const mockParams = { chain: chains_1.Blockchain.SOLANA, collectionId: 'collectionId123', wallet: 'walletAddress123', nftAmount: 3, stageId: 'stage1', kind: __2.MintStageKind.Public, candyMachineId: 'candyMachineId123', symbol: 'TEST', payer: 'payerAddress123', recipient: 'recipientAddress123', }; // Setup adapter to throw error __2.SolanaTransactionAdapters.fromV4TransactionResponse.mockImplementation(() => { throw new Error('Adapter error'); }); // Setup mock implementations __1.SolanaApiMappers.v4.mintRequest.mockReturnValue({ launchpadAddress: validSolanaPubkey, }); mockV4ApiClient.mint.mockResolvedValue(mockV4Response); // Call the method and expect it to throw await expect(service.mint(mockParams)).rejects.toThrow('Adapter error'); }); it('should handle invalid V4 response format', async () => { // Setup mocks const mockParams = { chain: chains_1.Blockchain.SOLANA, collectionId: 'collectionId123', wallet: 'walletAddress123', nftAmount: 3, stageId: 'stage1', kind: __2.MintStageKind.Public, candyMachineId: 'candyMachineId123', symbol: 'TEST', payer: 'payerAddress123', recipient: 'recipientAddress123', }; // Create an invalid response const invalidResponse = { steps: [ { id: 'step1', chain: chains_1.Blockchain.ETHEREUM, // Wrong chain method: 'eth_sendTransaction', params: { from: '0x123', to: '0x456', value: '0x0', data: '0x0', }, }, ], }; // Setup mock implementations __1.SolanaApiMappers.v4.mintRequest.mockReturnValue({ launchpadAddress: validSolanaPubkey, }); mockV4ApiClient.mint.mockResolvedValue(invalidResponse); // Make the adapter throw the expected error for invalid format __2.SolanaTransactionAdapters.fromV4TransactionResponse.mockImplementation(() => { throw new Error('Invalid transaction response format'); }); // Call the method and expect it to throw await expect(service.mint(mockParams)).rejects.toThrow('Invalid transaction response format'); }); }); });