@magiceden/magiceden-sdk
Version:
A TypeScript SDK for interacting with Magic Eden's API across multiple chains.
405 lines (404 loc) • 18.6 kB
JavaScript
"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');
});
});
});