UNPKG

@magiceden/magiceden-sdk

Version:

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

405 lines (404 loc) 18.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const __1 = require("../../.."); const __2 = require("../../.."); const chains_1 = require("../../../types/chains"); const evm_1 = require("../../../services/nft/evm"); // Mock dependencies jest.mock('../../../mappers/nft/ethereum'); jest.mock('../../../adapters/transactions/ethereum'); jest.mock('../../../api/clients/v3'); jest.mock('../../../api/clients/v4'); describe('EvmNftService V4', () => { let service; let mockWallet; let mockV3ApiClient; let mockV4ApiClient; let mockTransaction; let mockV4Response; const validEvmAddress = '0x1234567890123456789012345678901234567890'; beforeEach(() => { // Reset mocks jest.clearAllMocks(); // Create mock transaction mockTransaction = { from: '0x1234567890123456789012345678901234567890', to: '0x0987654321098765432109876543210987654321', data: '0x123456', }; // Create mock V4 API response mockV4Response = { steps: [ { id: 'step1', chain: chains_1.Blockchain.ETHEREUM, method: 'eth_sendTransaction', params: { from: validEvmAddress, to: '0x0987654321098765432109876543210987654321', data: '0x123456', }, }, ], }; // Create mock wallet mockWallet = { getAddress: jest.fn().mockReturnValue(validEvmAddress), signAndSendTransaction: jest.fn().mockResolvedValue('0xmock-transaction-hash'), waitForTransactionConfirmation: jest.fn().mockResolvedValue({ txId: '0xmock-transaction-hash', status: 'confirmed', }), }; // Create service instance service = new evm_1.EvmNftService({ chain: chains_1.ChainType.EVM, apiKey: 'test-api-key', wallet: mockWallet, }); // Create mock API clients manually mockV3ApiClient = { list: jest.fn(), cancelListing: jest.fn(), makeItemOffer: jest.fn(), takeItemOffer: jest.fn(), cancelItemOffer: jest.fn(), buy: jest.fn(), transfer: jest.fn(), }; mockV4ApiClient = { createLaunchpad: jest.fn(), updateLaunchpad: jest.fn(), mint: jest.fn(), publishLaunchpad: jest.fn(), }; // Replace the service's API clients with our mocks service.v3ApiClient = mockV3ApiClient; service.v4ApiClient = mockV4ApiClient; // Setup default mock implementations __2.EvmTransactionAdapters.fromV4TransactionResponse.mockReturnValue([ mockTransaction, ]); }); // Helper function to create standard launchpad params const createLaunchpadParams = () => ({ name: 'Test Collection', symbol: 'TEST', description: 'Test description', chain: chains_1.Blockchain.ETHEREUM, creator: validEvmAddress, payoutRecipient: validEvmAddress, royaltyRecipients: [{ address: validEvmAddress, share: 100 }], imageUrl: 'https://example.com/image.png', tokenImageUrl: 'https://example.com/token.png', protocol: __1.EvmProtocolType.ERC721, royaltyBps: 500, mintStages: { stages: [ { kind: __2.MintStageKind.Public, price: { currency: { chain: chains_1.Blockchain.ETHEREUM, assetId: '0x0000000000000000000000000000000000000000' }, raw: '1000000000000000000', }, 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.ETHEREUM, collectionId: validEvmAddress, nftAmount: 1, stageId: 'stage1', kind: __2.MintStageKind.Public, protocol: __1.EvmProtocolType.ERC721, }); 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.EvmApiMappers.v4.createLaunchpadRequest.mockReturnValue(mockApiRequest); mockV4ApiClient.createLaunchpad.mockResolvedValue(mockResponseWithMetadata); // Call the method const result = await service.createLaunchpad(mockParams); // Verify the flow expect(__1.EvmApiMappers.v4.createLaunchpadRequest).toHaveBeenCalledWith(mockParams); expect(mockV4ApiClient.createLaunchpad).toHaveBeenCalledWith(mockApiRequest); expect(__2.EvmTransactionAdapters.fromV4TransactionResponse).toHaveBeenCalledWith(mockResponseWithMetadata); expect(mockWallet.signAndSendTransaction).toHaveBeenCalledWith(mockTransaction); expect(result).toEqual([{ txId: '0xmock-transaction-hash', 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.ETHEREUM, protocol: __1.EvmProtocolType.ERC721, collectionId: validEvmAddress, owner: validEvmAddress, name: 'Updated Collection', imageUrl: 'https://example.com/updated-image.png', description: 'Updated description', royaltyBps: 700, royaltyRecipients: [ { address: validEvmAddress, share: 70 }, { address: validEvmAddress, share: 30 }, ], payoutRecipient: validEvmAddress, }; const mockApiRequest = { collectionId: validEvmAddress /* 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.EvmApiMappers.v4.updateLaunchpadRequest.mockReturnValue(mockApiRequest); mockV4ApiClient.updateLaunchpad.mockResolvedValue(mockResponseWithMetadata); // Call the method const result = await service.updateLaunchpad(mockParams); // Verify the flow expect(__1.EvmApiMappers.v4.updateLaunchpadRequest).toHaveBeenCalledWith(mockParams); expect(mockV4ApiClient.updateLaunchpad).toHaveBeenCalledWith(mockApiRequest); expect(__2.EvmTransactionAdapters.fromV4TransactionResponse).toHaveBeenCalledWith(mockResponseWithMetadata); expect(mockWallet.signAndSendTransaction).toHaveBeenCalledWith(mockTransaction); expect(result).toEqual([{ txId: '0xmock-transaction-hash', status: 'confirmed' }]); }); it('should handle API errors', async () => { // Setup mocks const mockParams = { chain: chains_1.Blockchain.ETHEREUM, protocol: __1.EvmProtocolType.ERC721, collectionId: validEvmAddress, owner: validEvmAddress, message: 'Update collection message', signature: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890', 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: validEvmAddress, share: 70 }, { address: validEvmAddress, share: 30 }, ], payoutRecipient: validEvmAddress, }; // Setup mock to throw error mockV4ApiClient.updateLaunchpad.mockRejectedValue(new Error('API error')); // Call the method and expect it to throw await expect(service.updateLaunchpad(mockParams)).rejects.toThrow('API error'); }); }); describe('mint', () => { it('should map parameters, call API, and sign transaction', async () => { // Setup mocks const mockParams = { chain: chains_1.Blockchain.ETHEREUM, protocol: __1.EvmProtocolType.ERC721, collectionId: validEvmAddress, wallet: validEvmAddress, nftAmount: 3, stageId: 'stage1', kind: __2.MintStageKind.Public, }; const mockApiRequest = { collection: validEvmAddress /* other fields */ }; // Create mock response according to V4MintResponse (no metadata) const mockMintResponse = { steps: mockV4Response.steps, }; // Setup mock implementations __1.EvmApiMappers.v4.mintRequest.mockReturnValue(mockApiRequest); mockV4ApiClient.mint.mockResolvedValue(mockMintResponse); // Call the method const result = await service.mint(mockParams); // Verify the flow expect(__1.EvmApiMappers.v4.mintRequest).toHaveBeenCalledWith(mockParams); expect(mockV4ApiClient.mint).toHaveBeenCalledWith(mockApiRequest); expect(__2.EvmTransactionAdapters.fromV4TransactionResponse).toHaveBeenCalledWith(mockMintResponse); expect(mockWallet.signAndSendTransaction).toHaveBeenCalledWith(mockTransaction); expect(result).toEqual([{ txId: '0xmock-transaction-hash', status: 'confirmed' }]); }); it('should handle multiple transactions', async () => { // Setup mocks const mockParams = { chain: chains_1.Blockchain.ETHEREUM, protocol: __1.EvmProtocolType.ERC721, collectionId: validEvmAddress, wallet: validEvmAddress, nftAmount: 3, stageId: 'stage1', kind: __2.MintStageKind.Public, }; // Create a response with multiple transactions (no metadata for V4MintResponse) const multiTxResponse = { steps: [ { id: 'step1', chain: chains_1.Blockchain.ETHEREUM, method: 'eth_sendTransaction', params: { from: validEvmAddress, to: '0x0987654321098765432109876543210987654321', data: '0x123456', }, }, { id: 'step2', chain: chains_1.Blockchain.ETHEREUM, method: 'eth_sendTransaction', params: { from: validEvmAddress, to: '0x0987654321098765432109876543210987654321', data: '0x789abc', }, }, ], }; const mockTransaction1 = { from: '0x1234567890123456789012345678901234567890', to: '0x0987654321098765432109876543210987654321', data: '0x123456', }; const mockTransaction2 = { from: '0x1234567890123456789012345678901234567890', to: '0x0987654321098765432109876543210987654321', data: '0x789abc', }; // Setup mock implementations __1.EvmApiMappers.v4.mintRequest.mockReturnValue({ collection: validEvmAddress, }); mockV4ApiClient.mint.mockResolvedValue(multiTxResponse); __2.EvmTransactionAdapters.fromV4TransactionResponse.mockReturnValue([ mockTransaction1, mockTransaction2, ]); // Mock wallet to return different signatures for each transaction mockWallet.signAndSendTransaction .mockResolvedValueOnce('0xmock-transaction-hash-1') .mockResolvedValueOnce('0xmock-transaction-hash-2'); mockWallet.waitForTransactionConfirmation .mockResolvedValueOnce({ txId: '0xmock-transaction-hash-1', status: 'confirmed' }) .mockResolvedValueOnce({ txId: '0xmock-transaction-hash-2', status: 'confirmed' }); // Call the method const result = await service.mint(mockParams); // Verify the flow expect(mockV4ApiClient.mint).toHaveBeenCalledWith({ collection: validEvmAddress }); expect(__2.EvmTransactionAdapters.fromV4TransactionResponse).toHaveBeenCalledWith(multiTxResponse); expect(mockWallet.signAndSendTransaction).toHaveBeenCalledTimes(2); expect(mockWallet.waitForTransactionConfirmation).toHaveBeenCalledTimes(2); expect(result).toEqual([ { txId: '0xmock-transaction-hash-1', status: 'confirmed' }, { txId: '0xmock-transaction-hash-2', status: 'confirmed' }, ]); }); it('should handle adapter errors for V4 responses', async () => { // Setup mocks const mockParams = { chain: chains_1.Blockchain.ETHEREUM, protocol: __1.EvmProtocolType.ERC721, collectionId: validEvmAddress, nftAmount: 3, stageId: 'stage1', kind: __2.MintStageKind.Public, }; // Setup adapter to throw error __2.EvmTransactionAdapters.fromV4TransactionResponse.mockImplementation(() => { throw new Error('Adapter error'); }); // Setup mock implementations __1.EvmApiMappers.v4.mintRequest.mockReturnValue({ collection: validEvmAddress, }); 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.ETHEREUM, protocol: __1.EvmProtocolType.ERC721, collectionId: validEvmAddress, wallet: validEvmAddress, nftAmount: 3, stageId: 'stage1', kind: __2.MintStageKind.Public, }; // Create an invalid response const invalidResponse = { steps: [ { id: 'step1', chain: chains_1.Blockchain.SOLANA, // Wrong chain method: 'signAllAndSendTransactions', params: { feePayer: '11111111111111111111111111111111', transactions: [ { transaction: 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==', signerPubkeys: [], }, ], }, }, ], }; // Setup mock implementations __1.EvmApiMappers.v4.mintRequest.mockReturnValue({ collection: validEvmAddress, }); mockV4ApiClient.mint.mockResolvedValue(invalidResponse); // Make the adapter throw the expected error for invalid format __2.EvmTransactionAdapters.fromV4TransactionResponse.mockImplementation(() => { throw new Error('No valid EVM transaction steps found in response'); }); // Call the method and expect it to throw await expect(service.mint(mockParams)).rejects.toThrow('No valid EVM transaction steps found in response'); }); }); describe('publishLaunchpad', () => { it('should throw an error as it is not supported on EVM', async () => { const mockParams = { chain: chains_1.Blockchain.ETHEREUM, collection: validEvmAddress, }; await expect(service.publishLaunchpad(mockParams)).rejects.toThrow('Not supported on EVM'); }); }); });