Skip to content

Commit 2ea52ce

Browse files
RishabhS7semantic-release-botnghaninn
authored
feat: mint function (#78)
* fix: add w3c credential status check (#72) * fix: add w3c credential status check * fix: update test * fix: update enum status codes * fix: remove console * chore(release): 1.5.4 [skip ci] ## [1.5.4](v1.5.3...v1.5.4) (2025-06-17) ### Bug Fixes * add w3c credential status check ([#72](#72)) ([0111cb3](0111cb3)) * fix: upgrade package (#73) * chore(release): 1.5.5 [skip ci] ## [1.5.5](v1.5.4...v1.5.5) (2025-06-18) ### Bug Fixes * upgrade package ([#73](#73)) ([3c6c9c7](3c6c9c7)) * feat: add transfer holder function * feat: add transfer owners beneficiary * chore: tests cleanup * chore: tests cleanup * fix: remove console * fix: rever useendorement chain * fix: remove console * feat: add reject transfer functions * chore: trigger rebuild after rebase * chore(release): 1.5.4 [skip ci] * add w3c credential status check ([#72](#72)) ([0111cb3](0111cb3)) * feat: add transfer holder function * feat: add transfer owners beneficiary * chore: tests cleanup * fix: remove console * fix: rever useendorement chain * feat: add reject transfer functions * chore: trigger rebuild after rebase * chore: trigger rebuild after rebase * feat: token registry return functions * feat: add mint function * fix: update fixes * fix: update tests * revert: revert changes --------- Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net> Co-authored-by: Ng Han Inn <43451336+nghaninn@users.noreply.github.com>
1 parent 73847b7 commit 2ea52ce

13 files changed

Lines changed: 2135 additions & 245 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { vi } from 'vitest';
2+
import { ethers as ethersV5 } from 'ethers';
3+
import { JsonRpcProvider as JsonRpcProviderV6 } from 'ethersV6';
4+
export const MOCK_V5_ADDRESS = '0xV5TokenRegistryContract';
5+
export const MOCK_V4_ADDRESS = '0xV4TokenRegistryContract';
6+
7+
vi.mock('src/core', () => ({
8+
encrypt: vi.fn(() => 'encrypted_remarks'),
9+
getTitleEscrowAddress: vi.fn(),
10+
isTitleEscrowVersion: vi.fn(() => Promise.resolve(true)),
11+
checkSupportsInterface: vi.fn(),
12+
13+
TitleEscrowInterface: {
14+
V4: '0xTitleEscrowIdV4',
15+
V5: '0xTitleEscrowIdV5',
16+
},
17+
}));
18+
19+
vi.mock('src/token-registry-v5', () => {
20+
return {
21+
v5Contracts: {
22+
TitleEscrow__factory: {
23+
connect: vi.fn(() => mockV5TitleEscrowContract),
24+
},
25+
TradeTrustToken__factory: {
26+
connect: vi.fn(() => mockV5TradeTrustTokenContract),
27+
},
28+
TitleEscrowFactory__factory: {
29+
connect: vi.fn(() => mockV5TitleEscrowFactoryContract),
30+
},
31+
},
32+
v5SupportInterfaceIds: {
33+
TitleEscrow: '0xTitleEscrowIdV5',
34+
TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV5',
35+
TradeTrustTokenRestorable: '0xTradeTrustTokenRestorableIdV5',
36+
TradeTrustTokenBurnable: '0xTradeTrustTokenBurnableIdV5',
37+
},
38+
};
39+
});
40+
41+
vi.mock('src/token-registry-v4', () => {
42+
return {
43+
v4Contracts: {
44+
TitleEscrow__factory: {
45+
connect: vi.fn(() => mockV4TitleEscrowContract),
46+
},
47+
TradeTrustToken__factory: {
48+
connect: vi.fn(() => mockV4TradeTrustTokenContract),
49+
},
50+
TitleEscrowFactory__factory: {
51+
connect: vi.fn(() => mockV4TitleEscrowFactoryContract),
52+
},
53+
},
54+
v4SupportInterfaceIds: {
55+
TitleEscrow: '0xTitleEscrowIdV4',
56+
TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV4',
57+
TradeTrustTokenRestorable: '0xTradeTrustTokenRestorableIdV4',
58+
TradeTrustTokenBurnable: '0xTradeTrustTokenBurnableIdV4',
59+
},
60+
};
61+
});
62+
63+
export const mockV5TitleEscrowFactoryContract = {
64+
callStatic: {
65+
getEscrowAddress: vi.fn(),
66+
},
67+
getEscrowAddress: vi.fn(() => Promise.resolve('0xV5titleescrow')),
68+
};
69+
70+
export const mockV5TradeTrustTokenContract = {
71+
callStatic: {
72+
burn: vi.fn(),
73+
restore: vi.fn(),
74+
mint: vi.fn(),
75+
},
76+
supportsInterface: vi.fn(),
77+
titleEscrowFactory: vi.fn(() => Promise.resolve('0xV5titleescrowfactory')),
78+
burn: vi.fn(() => Promise.resolve('v5_burn_tx_hash')),
79+
restore: vi.fn(() => Promise.resolve('v5_restore_tx_hash')),
80+
mint: vi.fn(() => Promise.resolve('v5_mint_tx_hash')),
81+
};
82+
83+
export const mockV5TitleEscrowContract = {
84+
supportsInterface: vi.fn(),
85+
callStatic: {
86+
transferHolder: vi.fn(),
87+
transferBeneficiary: vi.fn(),
88+
transferOwners: vi.fn(),
89+
nominate: vi.fn(),
90+
rejectTransferHolder: vi.fn(),
91+
rejectTransferBeneficiary: vi.fn(),
92+
rejectTransferOwners: vi.fn(),
93+
returnToIssuer: vi.fn(),
94+
},
95+
transferHolder: vi.fn(() => Promise.resolve('v5_transfer_holder_tx_hash')),
96+
transferBeneficiary: vi.fn(() => Promise.resolve('v5_transfer_beneficiary_tx_hash')),
97+
transferOwners: vi.fn(() => Promise.resolve('v5_transfer_owners_tx_hash')),
98+
nominate: vi.fn(() => Promise.resolve('v5_nominate_tx_hash')),
99+
holder: vi.fn(() => Promise.resolve('0xcurrent_holder')),
100+
beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')),
101+
rejectTransferHolder: vi.fn(() => Promise.resolve('v5_reject_transfer_holder_tx_hash')),
102+
rejectTransferBeneficiary: vi.fn(() => Promise.resolve('v5_reject_transfer_beneficiary_tx_hash')),
103+
rejectTransferOwners: vi.fn(() => Promise.resolve('v5_reject_transfer_owners_tx_hash')),
104+
returnToIssuer: vi.fn(() => Promise.resolve('v5_return_to_issuer_tx_hash')),
105+
};
106+
107+
export const mockV4TitleEscrowContract = {
108+
supportsInterface: vi.fn(),
109+
callStatic: {
110+
transferHolder: vi.fn(),
111+
transferBeneficiary: vi.fn(),
112+
transferOwners: vi.fn(),
113+
nominate: vi.fn(),
114+
surrender: vi.fn(),
115+
},
116+
transferHolder: vi.fn(() => Promise.resolve('v4_transfer_holder_tx_hash')),
117+
transferBeneficiary: vi.fn(() => Promise.resolve('v4_transfer_beneficiary_tx_hash')),
118+
transferOwners: vi.fn(() => Promise.resolve('v4_transfer_owners_tx_hash')),
119+
nominate: vi.fn(() => Promise.resolve('v4_nominate_tx_hash')),
120+
holder: vi.fn(() => Promise.resolve('0xcurrent_holder')),
121+
beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')),
122+
surrender: vi.fn(() => Promise.resolve('v4_surrender_tx_hash')),
123+
};
124+
export const mockV4TitleEscrowFactoryContract = {
125+
callStatic: {
126+
getEscrowAddress: vi.fn(),
127+
},
128+
getEscrowAddress: vi.fn(() => Promise.resolve('0xV4titleescrow')),
129+
};
130+
131+
export const mockV4TradeTrustTokenContract = {
132+
callStatic: {
133+
burn: vi.fn(),
134+
restore: vi.fn(),
135+
mint: vi.fn(),
136+
},
137+
titleEscrowFactory: vi.fn(() => Promise.resolve('0xV4titleescrowfactory')),
138+
supportsInterface: vi.fn(),
139+
burn: vi.fn(() => Promise.resolve('v4_burn_tx_hash')),
140+
restore: vi.fn(() => Promise.resolve('v4_restore_tx_hash')),
141+
mint: vi.fn(() => Promise.resolve('v4_mint_tx_hash')),
142+
};
143+
144+
export const PRIVATE_KEY = '0x59c6995e998f97a5a004497e5f1ebce0c16828d44b3f8d0bfa3a89d271d5b6b9'; // random local key
145+
146+
export const providerV5 = new ethersV5.providers.JsonRpcProvider();
147+
export const providerV6 = new JsonRpcProviderV6();
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import './fixtures.js';
2+
import { describe, it, expect, beforeEach, vi } from 'vitest';
3+
import { ethers as ethersV5, Wallet as WalletV5 } from 'ethers';
4+
import { Wallet as WalletV6, Network, ethers as ethersV6 } from 'ethersV6';
5+
import * as coreModule from 'src/core';
6+
7+
import { CHAIN_ID } from '@tradetrust-tt/tradetrust-utils';
8+
import { v5Contracts } from 'src/token-registry-v5';
9+
import { v4Contracts } from 'src/token-registry-v4';
10+
import { mint } from 'src/token-registry-functions/mint';
11+
import {
12+
MOCK_V4_ADDRESS,
13+
MOCK_V5_ADDRESS,
14+
mockV4TradeTrustTokenContract,
15+
mockV5TradeTrustTokenContract,
16+
PRIVATE_KEY,
17+
providerV5,
18+
providerV6,
19+
} from './fixtures.js';
20+
import { ProviderInfo } from 'src/token-registry-functions/types.js';
21+
22+
const providers: ProviderInfo[] = [
23+
{
24+
Provider: providerV5,
25+
ethersVersion: 'v5',
26+
titleEscrowVersion: 'v5',
27+
},
28+
{
29+
Provider: providerV5,
30+
ethersVersion: 'v5',
31+
titleEscrowVersion: 'v4',
32+
},
33+
{
34+
Provider: providerV6,
35+
ethersVersion: 'v6',
36+
titleEscrowVersion: 'v5',
37+
},
38+
{
39+
Provider: providerV6,
40+
ethersVersion: 'v6',
41+
titleEscrowVersion: 'v4',
42+
},
43+
];
44+
describe('Mint Token', () => {
45+
const mockTokenId = '0xTokenId';
46+
const mockRemarks = 'Mint remarks';
47+
const mockChainId = CHAIN_ID.local;
48+
describe.each(providers)(
49+
'Mint Token with TR version $titleEscrowVersion and ethers version $ethersVersion',
50+
async ({ Provider, ethersVersion, titleEscrowVersion }) => {
51+
const isV5TT = titleEscrowVersion === 'v5';
52+
// let mockContract = isV5TT ? mockV5TradeTrustTokenContract : mockV4TradeTrustTokenContract;
53+
const mockTxResponse = titleEscrowVersion === 'v5' ? 'v5_mint_tx_hash' : 'v4_mint_tx_hash';
54+
55+
let wallet: ethersV5.Wallet | ethersV6.Wallet;
56+
if (ethersVersion === 'v5') {
57+
wallet = new WalletV5(PRIVATE_KEY, Provider as any) as ethersV5.Wallet;
58+
vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.local as unknown as number);
59+
} else {
60+
wallet = new WalletV6(PRIVATE_KEY, Provider as any);
61+
vi.spyOn(Provider, 'getNetwork').mockResolvedValue({
62+
chainId: CHAIN_ID.local,
63+
} as unknown as Network);
64+
}
65+
const mockTokenRegistryAddress = isV5TT ? MOCK_V5_ADDRESS : MOCK_V4_ADDRESS;
66+
const mockBeneficiaryAddress = '0xBeneficiaryAddress';
67+
const mockHolderAddress = '0xHolderAddress';
68+
// const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract';
69+
beforeEach(() => {
70+
vi.clearAllMocks();
71+
// vi.spyOn(coreModule, 'encrypt').mockReturnValue(mockEncryptedRemarks.slice(2));
72+
vi.spyOn(coreModule, 'checkSupportsInterface').mockImplementation(
73+
async (address, interfaceId) => {
74+
return (
75+
interfaceId ===
76+
(isV5TT ? '0xTradeTrustTokenMintableIdV5' : '0xTradeTrustTokenMintableIdV4')
77+
);
78+
},
79+
);
80+
mockV5TradeTrustTokenContract.callStatic.mint.mockResolvedValue(true);
81+
mockV4TradeTrustTokenContract.callStatic.mint.mockResolvedValue(true);
82+
});
83+
84+
it('should Mint token with remarks', async () => {
85+
const result = await mint(
86+
{ tokenRegistryAddress: mockTokenRegistryAddress },
87+
wallet,
88+
{
89+
beneficiaryAddress: mockBeneficiaryAddress,
90+
holderAddress: mockHolderAddress,
91+
tokenId: mockTokenId,
92+
remarks: mockRemarks,
93+
},
94+
{ chainId: mockChainId, id: 'encryption-id' },
95+
);
96+
97+
expect(result).toEqual(mockTxResponse);
98+
if (isV5TT) expect(coreModule.encrypt).toHaveBeenCalledWith(mockRemarks, 'encryption-id');
99+
expect(
100+
(isV5TT ? v5Contracts : v4Contracts).TradeTrustToken__factory.connect,
101+
).toHaveBeenCalled();
102+
});
103+
104+
it('should mint token without remarks', async () => {
105+
const result = await mint(
106+
{ tokenRegistryAddress: mockTokenRegistryAddress },
107+
wallet,
108+
{
109+
beneficiaryAddress: mockBeneficiaryAddress,
110+
holderAddress: mockHolderAddress,
111+
tokenId: mockTokenId,
112+
},
113+
{ chainId: mockChainId, titleEscrowVersion },
114+
);
115+
116+
expect(result).toEqual(mockTxResponse);
117+
expect(coreModule.encrypt).not.toHaveBeenCalled();
118+
});
119+
120+
it('should throw when callStatic fails', async () => {
121+
const mockError = new Error('callStatic error');
122+
if (isV5TT) {
123+
mockV5TradeTrustTokenContract.callStatic.mint.mockRejectedValue(mockError);
124+
} else {
125+
mockV4TradeTrustTokenContract.callStatic.mint.mockRejectedValue(mockError);
126+
}
127+
await expect(
128+
mint(
129+
{ tokenRegistryAddress: mockTokenRegistryAddress },
130+
wallet,
131+
{
132+
beneficiaryAddress: mockBeneficiaryAddress,
133+
holderAddress: mockHolderAddress,
134+
tokenId: mockTokenId,
135+
remarks: mockRemarks,
136+
},
137+
{ chainId: mockChainId, id: 'encryption-id' },
138+
),
139+
).rejects.toThrow('Pre-check (callStatic) for mint failed');
140+
if (isV5TT) {
141+
mockV5TradeTrustTokenContract.callStatic.mint = vi.fn();
142+
} else {
143+
mockV4TradeTrustTokenContract.callStatic.mint = vi.fn();
144+
}
145+
});
146+
147+
it('should throw when token registry address is missing', async () => {
148+
await expect(
149+
mint(
150+
{ tokenRegistryAddress: '' },
151+
wallet,
152+
{
153+
beneficiaryAddress: mockBeneficiaryAddress,
154+
holderAddress: mockHolderAddress,
155+
tokenId: mockTokenId,
156+
},
157+
{ chainId: mockChainId },
158+
),
159+
).rejects.toThrow('Token registry address is required');
160+
});
161+
162+
it('should throw when provider is missing', async () => {
163+
const signerWithoutProvider = new WalletV5('0x'.padEnd(66, '1'));
164+
165+
await expect(
166+
mint(
167+
{ tokenRegistryAddress: mockTokenRegistryAddress },
168+
signerWithoutProvider,
169+
{
170+
beneficiaryAddress: mockBeneficiaryAddress,
171+
holderAddress: mockHolderAddress,
172+
tokenId: mockTokenId,
173+
remarks: mockRemarks,
174+
},
175+
{ chainId: mockChainId },
176+
),
177+
).rejects.toThrow('Provider is required');
178+
});
179+
180+
it('should throw when version is unsupported', async () => {
181+
vi.spyOn(coreModule, 'checkSupportsInterface').mockResolvedValue(false);
182+
183+
await expect(
184+
mint(
185+
{ tokenRegistryAddress: mockTokenRegistryAddress },
186+
wallet,
187+
{
188+
beneficiaryAddress: mockBeneficiaryAddress,
189+
holderAddress: mockHolderAddress,
190+
tokenId: mockTokenId,
191+
remarks: mockRemarks,
192+
},
193+
{ chainId: mockChainId },
194+
),
195+
).rejects.toThrow('Only Token Registry V4/V5 is supported');
196+
});
197+
198+
it('should work with explicit V5/V4 version', async () => {
199+
const result = await mint(
200+
{ tokenRegistryAddress: mockTokenRegistryAddress },
201+
wallet,
202+
{
203+
beneficiaryAddress: mockBeneficiaryAddress,
204+
holderAddress: mockHolderAddress,
205+
tokenId: mockTokenId,
206+
remarks: mockRemarks,
207+
},
208+
{ chainId: mockChainId, id: 'encryption-id', titleEscrowVersion },
209+
);
210+
211+
expect(result).toEqual(mockTxResponse);
212+
expect(coreModule.checkSupportsInterface).not.toHaveBeenCalled();
213+
});
214+
},
215+
);
216+
});

0 commit comments

Comments
 (0)