Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0111cb3
fix: add w3c credential status check (#72)
RishabhS7 Jun 17, 2025
4b2c3d1
chore(release): 1.5.4 [skip ci]
semantic-release-bot Jun 17, 2025
3c6c9c7
fix: upgrade package (#73)
nghaninn Jun 18, 2025
c125eb2
chore(release): 1.5.5 [skip ci]
semantic-release-bot Jun 18, 2025
fcb5cb7
feat: add transfer holder function
RishabhS7 Jun 23, 2025
28682cd
feat: add transfer owners beneficiary
RishabhS7 Jun 26, 2025
ce8e23e
chore: tests cleanup
RishabhS7 Jun 27, 2025
9ed24b5
chore: tests cleanup
RishabhS7 Jun 27, 2025
e76df5c
fix: remove console
RishabhS7 Jun 27, 2025
1bcf3ab
fix: rever useendorement chain
RishabhS7 Jun 27, 2025
b8e2685
fix: remove console
RishabhS7 Jun 27, 2025
1d1f38e
feat: add reject transfer functions
RishabhS7 Jun 30, 2025
76aa936
chore: trigger rebuild after rebase
RishabhS7 Jun 30, 2025
3103e26
chore(release): 1.5.4 [skip ci]
semantic-release-bot Jun 17, 2025
9037983
feat: add transfer holder function
RishabhS7 Jun 23, 2025
fd265f7
feat: add transfer owners beneficiary
RishabhS7 Jun 26, 2025
5cd7b26
chore: tests cleanup
RishabhS7 Jun 27, 2025
f8a0c8b
fix: remove console
RishabhS7 Jun 27, 2025
082224f
fix: rever useendorement chain
RishabhS7 Jun 27, 2025
f4b3d39
feat: add reject transfer functions
RishabhS7 Jun 30, 2025
bf02278
chore: trigger rebuild after rebase
RishabhS7 Jun 30, 2025
4bb486f
chore: trigger rebuild after rebase
RishabhS7 Jun 30, 2025
314db89
fix: resolve conflicts
RishabhS7 Jun 30, 2025
8b113db
feat: token registry return functions
RishabhS7 Jul 2, 2025
bb490e4
feat: add mint function
RishabhS7 Jul 2, 2025
a06de56
fix: update fixes
RishabhS7 Jul 3, 2025
684f543
fix: update tests
RishabhS7 Jul 3, 2025
1130d6d
revert: revert changes
nghaninn Jul 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions src/__tests__/token-registry-functions/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { vi } from 'vitest';
import { ethers as ethersV5 } from 'ethers';
import { JsonRpcProvider as JsonRpcProviderV6 } from 'ethersV6';
export const MOCK_V5_ADDRESS = '0xV5TokenRegistryContract';
export const MOCK_V4_ADDRESS = '0xV4TokenRegistryContract';

vi.mock('src/core', () => ({
encrypt: vi.fn(() => 'encrypted_remarks'),
getTitleEscrowAddress: vi.fn(),
isTitleEscrowVersion: vi.fn(() => Promise.resolve(true)),
checkSupportsInterface: vi.fn(),

TitleEscrowInterface: {
V4: '0xTitleEscrowIdV4',
V5: '0xTitleEscrowIdV5',
},
}));

vi.mock('src/token-registry-v5', () => {
return {
v5Contracts: {
TitleEscrow__factory: {
connect: vi.fn(() => mockV5TitleEscrowContract),
},
TradeTrustToken__factory: {
connect: vi.fn(() => mockV5TradeTrustTokenContract),
},
TitleEscrowFactory__factory: {
connect: vi.fn(() => mockV5TitleEscrowFactoryContract),
},
},
v5SupportInterfaceIds: {
TitleEscrow: '0xTitleEscrowIdV5',
TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV5',
TradeTrustTokenRestorable: '0xTradeTrustTokenRestorableIdV5',
TradeTrustTokenBurnable: '0xTradeTrustTokenBurnableIdV5',
},
};
});

vi.mock('src/token-registry-v4', () => {
return {
v4Contracts: {
TitleEscrow__factory: {
connect: vi.fn(() => mockV4TitleEscrowContract),
},
TradeTrustToken__factory: {
connect: vi.fn(() => mockV4TradeTrustTokenContract),
},
TitleEscrowFactory__factory: {
connect: vi.fn(() => mockV4TitleEscrowFactoryContract),
},
},
v4SupportInterfaceIds: {
TitleEscrow: '0xTitleEscrowIdV4',
TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV4',
TradeTrustTokenRestorable: '0xTradeTrustTokenRestorableIdV4',
TradeTrustTokenBurnable: '0xTradeTrustTokenBurnableIdV4',
},
};
});

export const mockV5TitleEscrowFactoryContract = {
callStatic: {
getEscrowAddress: vi.fn(),
},
getEscrowAddress: vi.fn(() => Promise.resolve('0xV5titleescrow')),
};

export const mockV5TradeTrustTokenContract = {
callStatic: {
burn: vi.fn(),
restore: vi.fn(),
mint: vi.fn(),
},
supportsInterface: vi.fn(),
titleEscrowFactory: vi.fn(() => Promise.resolve('0xV5titleescrowfactory')),
burn: vi.fn(() => Promise.resolve('v5_burn_tx_hash')),
restore: vi.fn(() => Promise.resolve('v5_restore_tx_hash')),
mint: vi.fn(() => Promise.resolve('v5_mint_tx_hash')),
};

export const mockV5TitleEscrowContract = {
supportsInterface: vi.fn(),
callStatic: {
transferHolder: vi.fn(),
transferBeneficiary: vi.fn(),
transferOwners: vi.fn(),
nominate: vi.fn(),
rejectTransferHolder: vi.fn(),
rejectTransferBeneficiary: vi.fn(),
rejectTransferOwners: vi.fn(),
returnToIssuer: vi.fn(),
},
transferHolder: vi.fn(() => Promise.resolve('v5_transfer_holder_tx_hash')),
transferBeneficiary: vi.fn(() => Promise.resolve('v5_transfer_beneficiary_tx_hash')),
transferOwners: vi.fn(() => Promise.resolve('v5_transfer_owners_tx_hash')),
nominate: vi.fn(() => Promise.resolve('v5_nominate_tx_hash')),
holder: vi.fn(() => Promise.resolve('0xcurrent_holder')),
beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')),
rejectTransferHolder: vi.fn(() => Promise.resolve('v5_reject_transfer_holder_tx_hash')),
rejectTransferBeneficiary: vi.fn(() => Promise.resolve('v5_reject_transfer_beneficiary_tx_hash')),
rejectTransferOwners: vi.fn(() => Promise.resolve('v5_reject_transfer_owners_tx_hash')),
returnToIssuer: vi.fn(() => Promise.resolve('v5_return_to_issuer_tx_hash')),
};

export const mockV4TitleEscrowContract = {
supportsInterface: vi.fn(),
callStatic: {
transferHolder: vi.fn(),
transferBeneficiary: vi.fn(),
transferOwners: vi.fn(),
nominate: vi.fn(),
surrender: vi.fn(),
},
transferHolder: vi.fn(() => Promise.resolve('v4_transfer_holder_tx_hash')),
transferBeneficiary: vi.fn(() => Promise.resolve('v4_transfer_beneficiary_tx_hash')),
transferOwners: vi.fn(() => Promise.resolve('v4_transfer_owners_tx_hash')),
nominate: vi.fn(() => Promise.resolve('v4_nominate_tx_hash')),
holder: vi.fn(() => Promise.resolve('0xcurrent_holder')),
beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')),
surrender: vi.fn(() => Promise.resolve('v4_surrender_tx_hash')),
};
export const mockV4TitleEscrowFactoryContract = {
callStatic: {
getEscrowAddress: vi.fn(),
},
getEscrowAddress: vi.fn(() => Promise.resolve('0xV4titleescrow')),
};

export const mockV4TradeTrustTokenContract = {
callStatic: {
burn: vi.fn(),
restore: vi.fn(),
mint: vi.fn(),
},
titleEscrowFactory: vi.fn(() => Promise.resolve('0xV4titleescrowfactory')),
supportsInterface: vi.fn(),
burn: vi.fn(() => Promise.resolve('v4_burn_tx_hash')),
restore: vi.fn(() => Promise.resolve('v4_restore_tx_hash')),
mint: vi.fn(() => Promise.resolve('v4_mint_tx_hash')),
};

export const PRIVATE_KEY = '0x59c6995e998f97a5a004497e5f1ebce0c16828d44b3f8d0bfa3a89d271d5b6b9'; // random local key

export const providerV5 = new ethersV5.providers.JsonRpcProvider();
export const providerV6 = new JsonRpcProviderV6();
216 changes: 216 additions & 0 deletions src/__tests__/token-registry-functions/mint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import './fixtures.js';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ethers as ethersV5, Wallet as WalletV5 } from 'ethers';
import { Wallet as WalletV6, Network, ethers as ethersV6 } from 'ethersV6';
import * as coreModule from 'src/core';

import { CHAIN_ID } from '@tradetrust-tt/tradetrust-utils';
import { v5Contracts } from 'src/token-registry-v5';
import { v4Contracts } from 'src/token-registry-v4';
import { mint } from 'src/token-registry-functions/mint';
import {
MOCK_V4_ADDRESS,
MOCK_V5_ADDRESS,
mockV4TradeTrustTokenContract,
mockV5TradeTrustTokenContract,
PRIVATE_KEY,
providerV5,
providerV6,
} from './fixtures.js';
import { ProviderInfo } from 'src/token-registry-functions/types.js';

const providers: ProviderInfo[] = [
{
Provider: providerV5,
ethersVersion: 'v5',
titleEscrowVersion: 'v5',
},
{
Provider: providerV5,
ethersVersion: 'v5',
titleEscrowVersion: 'v4',
},
{
Provider: providerV6,
ethersVersion: 'v6',
titleEscrowVersion: 'v5',
},
{
Provider: providerV6,
ethersVersion: 'v6',
titleEscrowVersion: 'v4',
},
];
describe('Mint Token', () => {
const mockTokenId = '0xTokenId';
const mockRemarks = 'Mint remarks';
const mockChainId = CHAIN_ID.local;
describe.each(providers)(
'Mint Token with TR version $titleEscrowVersion and ethers version $ethersVersion',
async ({ Provider, ethersVersion, titleEscrowVersion }) => {
const isV5TT = titleEscrowVersion === 'v5';
// let mockContract = isV5TT ? mockV5TradeTrustTokenContract : mockV4TradeTrustTokenContract;
const mockTxResponse = titleEscrowVersion === 'v5' ? 'v5_mint_tx_hash' : 'v4_mint_tx_hash';

let wallet: ethersV5.Wallet | ethersV6.Wallet;
if (ethersVersion === 'v5') {
wallet = new WalletV5(PRIVATE_KEY, Provider as any) as ethersV5.Wallet;
vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.local as unknown as number);
} else {
wallet = new WalletV6(PRIVATE_KEY, Provider as any);
vi.spyOn(Provider, 'getNetwork').mockResolvedValue({
chainId: CHAIN_ID.local,
} as unknown as Network);
}
const mockTokenRegistryAddress = isV5TT ? MOCK_V5_ADDRESS : MOCK_V4_ADDRESS;
const mockBeneficiaryAddress = '0xBeneficiaryAddress';
const mockHolderAddress = '0xHolderAddress';
// const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract';
beforeEach(() => {
vi.clearAllMocks();
// vi.spyOn(coreModule, 'encrypt').mockReturnValue(mockEncryptedRemarks.slice(2));
vi.spyOn(coreModule, 'checkSupportsInterface').mockImplementation(
async (address, interfaceId) => {
return (
interfaceId ===
(isV5TT ? '0xTradeTrustTokenMintableIdV5' : '0xTradeTrustTokenMintableIdV4')
);
},
);
mockV5TradeTrustTokenContract.callStatic.mint.mockResolvedValue(true);
mockV4TradeTrustTokenContract.callStatic.mint.mockResolvedValue(true);
});

it('should Mint token with remarks', async () => {
const result = await mint(
{ tokenRegistryAddress: mockTokenRegistryAddress },
wallet,
{
beneficiaryAddress: mockBeneficiaryAddress,
holderAddress: mockHolderAddress,
tokenId: mockTokenId,
remarks: mockRemarks,
},
{ chainId: mockChainId, id: 'encryption-id' },
);

expect(result).toEqual(mockTxResponse);
if (isV5TT) expect(coreModule.encrypt).toHaveBeenCalledWith(mockRemarks, 'encryption-id');
expect(
(isV5TT ? v5Contracts : v4Contracts).TradeTrustToken__factory.connect,
).toHaveBeenCalled();
});

it('should mint token without remarks', async () => {
const result = await mint(
{ tokenRegistryAddress: mockTokenRegistryAddress },
wallet,
{
beneficiaryAddress: mockBeneficiaryAddress,
holderAddress: mockHolderAddress,
tokenId: mockTokenId,
},
{ chainId: mockChainId, titleEscrowVersion },
);

expect(result).toEqual(mockTxResponse);
expect(coreModule.encrypt).not.toHaveBeenCalled();
});

it('should throw when callStatic fails', async () => {
const mockError = new Error('callStatic error');
if (isV5TT) {
mockV5TradeTrustTokenContract.callStatic.mint.mockRejectedValue(mockError);
} else {
mockV4TradeTrustTokenContract.callStatic.mint.mockRejectedValue(mockError);
}
await expect(
mint(
{ tokenRegistryAddress: mockTokenRegistryAddress },
wallet,
{
beneficiaryAddress: mockBeneficiaryAddress,
holderAddress: mockHolderAddress,
tokenId: mockTokenId,
remarks: mockRemarks,
},
{ chainId: mockChainId, id: 'encryption-id' },
),
).rejects.toThrow('Pre-check (callStatic) for mint failed');
if (isV5TT) {
mockV5TradeTrustTokenContract.callStatic.mint = vi.fn();
} else {
mockV4TradeTrustTokenContract.callStatic.mint = vi.fn();
}
});

it('should throw when token registry address is missing', async () => {
await expect(
mint(
{ tokenRegistryAddress: '' },
wallet,
{
beneficiaryAddress: mockBeneficiaryAddress,
holderAddress: mockHolderAddress,
tokenId: mockTokenId,
},
{ chainId: mockChainId },
),
).rejects.toThrow('Token registry address is required');
});

it('should throw when provider is missing', async () => {
const signerWithoutProvider = new WalletV5('0x'.padEnd(66, '1'));

await expect(
mint(
{ tokenRegistryAddress: mockTokenRegistryAddress },
signerWithoutProvider,
{
beneficiaryAddress: mockBeneficiaryAddress,
holderAddress: mockHolderAddress,
tokenId: mockTokenId,
remarks: mockRemarks,
},
{ chainId: mockChainId },
),
).rejects.toThrow('Provider is required');
});

it('should throw when version is unsupported', async () => {
vi.spyOn(coreModule, 'checkSupportsInterface').mockResolvedValue(false);

await expect(
mint(
{ tokenRegistryAddress: mockTokenRegistryAddress },
wallet,
{
beneficiaryAddress: mockBeneficiaryAddress,
holderAddress: mockHolderAddress,
tokenId: mockTokenId,
remarks: mockRemarks,
},
{ chainId: mockChainId },
),
).rejects.toThrow('Only Token Registry V4/V5 is supported');
});

it('should work with explicit V5/V4 version', async () => {
const result = await mint(
{ tokenRegistryAddress: mockTokenRegistryAddress },
wallet,
{
beneficiaryAddress: mockBeneficiaryAddress,
holderAddress: mockHolderAddress,
tokenId: mockTokenId,
remarks: mockRemarks,
},
{ chainId: mockChainId, id: 'encryption-id', titleEscrowVersion },
);

expect(result).toEqual(mockTxResponse);
expect(coreModule.checkSupportsInterface).not.toHaveBeenCalled();
});
},
);
});
Loading