Skip to content

Commit b996c79

Browse files
committed
feat: update mint input process
1 parent 7b45aa5 commit b996c79

7 files changed

Lines changed: 675 additions & 185 deletions

File tree

src/commands/helpers.ts

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,54 @@
11
// External dependencies
2-
import { BytesLike, Wallet, HDNodeWallet, ZeroAddress } from 'ethers';
2+
import { BytesLike, Wallet, HDNodeWallet, ZeroAddress, Provider } from 'ethers';
33
import signale from 'signale';
4-
import { v5Contracts } from '@trustvc/trustvc';
4+
import { v5Contracts, checkSupportsInterface, v4SupportInterfaceIds, v5SupportInterfaceIds } from '@trustvc/trustvc';
55
import { encrypt } from '@trustvc/trustvc';
66

77
// Internal utilities
88
import { ConnectedSigner } from '../utils';
99

1010
// Contract factories from TrustVC v5
1111
const { TitleEscrow__factory, TradeTrustToken__factory } = v5Contracts;
12+
13+
// Interface for connectToTokenRegistry function arguments
14+
interface ConnectToTokenRegistryArgs {
15+
address: string;
16+
wallet: Wallet | HDNodeWallet | ConnectedSigner;
17+
}
18+
19+
/**
20+
* Connects to a token registry contract instance.
21+
*
22+
* @param address - The address of the token registry contract
23+
* @param wallet - The wallet or signer to use for the connection
24+
* @returns Promise resolving to the connected TradeTrustToken contract instance
25+
* @throws Error if connection fails
26+
*/
27+
export const connectToTokenRegistry = async ({
28+
address,
29+
wallet,
30+
}: ConnectToTokenRegistryArgs): Promise<InstanceType<typeof TradeTrustToken__factory>> => {
31+
try {
32+
// Connect to the token registry contract
33+
signale.info(`Connecting to token registry at: ${address}`);
34+
const tokenRegistry = TradeTrustToken__factory.connect(address, wallet);
35+
36+
// Validate the connection was successful
37+
if (!tokenRegistry) {
38+
const error = `Failed to connect to token registry at address: ${address}`;
39+
signale.error(error);
40+
throw new Error(error);
41+
}
42+
43+
signale.success(`Successfully connected to token registry`);
44+
return tokenRegistry;
45+
} catch (error) {
46+
signale.error(
47+
`Error in connectToTokenRegistry: ${error instanceof Error ? error.message : String(error)}`,
48+
);
49+
throw error;
50+
}
51+
};
1252
// Interface for connectToTitleEscrow function arguments
1353
interface ConnectToTitleEscrowArgs {
1454
tokenId: string;
@@ -232,3 +272,44 @@ export const validateAndEncryptRemark = (remark?: string, keyId?: string): Bytes
232272
const encrpyted = encrypt(remark, keyId ?? '');
233273
return encrpyted.startsWith('0x') ? encrpyted : `0x${encrpyted}`;
234274
};
275+
276+
/**
277+
* Determines the version of a token registry contract by checking supported interfaces.
278+
* Checks for V5 first, then V4 compatibility.
279+
*
280+
* @param tokenRegistryAddress - The address of the token registry contract
281+
* @param provider - The provider to use for the contract call
282+
* @returns Promise resolving to 'v5', 'v4', or 'unknown'
283+
*/
284+
export const getTokenRegistryVersion = async (
285+
tokenRegistryAddress: string,
286+
provider: Provider,
287+
): Promise<'v5' | 'v4' | 'unknown'> => {
288+
try {
289+
// Check if it's V5
290+
const isV5 = await checkSupportsInterface(
291+
tokenRegistryAddress,
292+
v5SupportInterfaceIds.SBT,
293+
provider,
294+
);
295+
296+
if (isV5) {
297+
return 'v5';
298+
}
299+
300+
// Check if it's V4
301+
const isV4 = await checkSupportsInterface(
302+
tokenRegistryAddress,
303+
v4SupportInterfaceIds.SBT,
304+
provider,
305+
);
306+
307+
if (isV4) {
308+
return 'v4';
309+
}
310+
311+
return 'unknown';
312+
} catch (error) {
313+
throw new Error(`Failed to determine token registry version: ${error instanceof Error ? error.message : String(error)}`);
314+
}
315+
};

src/commands/token-registry/mint.ts

Lines changed: 77 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { input } from '@inquirer/prompts';
21
import signale, { error, info, success } from 'signale';
32
import { TokenRegistryMintCommand } from '../../types';
43
import {
@@ -7,14 +6,18 @@ import {
76
getErrorMessage,
87
getEtherscanAddress,
98
NetworkCmdName,
10-
promptRemarkAndEncryptionKey,
11-
promptNetworkSelection,
129
promptWalletSelection,
1310
getWalletOrSigner,
1411
canEstimateGasPrice,
1512
getGasFees,
13+
extractDocumentInfo,
14+
promptAndReadDocument,
15+
promptRemark,
16+
promptAddress,
17+
performDryRunWithConfirmation,
1618
} from '../../utils';
17-
import { BigNumberish, TransactionReceipt } from 'ethers';
19+
import { connectToTokenRegistry, validateAndEncryptRemark } from '../helpers';
20+
import { TransactionReceipt } from 'ethers';
1821
import { mint } from '@trustvc/trustvc';
1922

2023
export const command = 'mint';
@@ -34,76 +37,32 @@ export const handler = async (): Promise<void> => {
3437

3538
// Prompt user for all required inputs
3639
export const promptForInputs = async (): Promise<TokenRegistryMintCommand> => {
37-
// Network selection
38-
const network = await promptNetworkSelection();
39-
40-
// Token Registry Address
41-
const address = await input({
42-
message: 'Enter the token registry contract address:',
43-
required: true,
44-
validate: (value: string) => {
45-
if (!value || value.trim() === '') {
46-
return 'Token registry address is required';
47-
}
48-
if (!/^0x[a-fA-F0-9]{40}$/.test(value)) {
49-
return 'Invalid Ethereum address format';
50-
}
51-
return true;
52-
},
53-
});
40+
// Extract document information using utility function
41+
const document = await promptAndReadDocument();
5442

55-
// Token ID (Document Hash)
56-
const tokenId = await input({
57-
message: 'Enter the document hash (tokenId) to mint:',
58-
required: true,
59-
validate: (value: string) => {
60-
if (!value || value.trim() === '') {
61-
return 'Token ID is required';
62-
}
63-
return true;
64-
},
65-
});
43+
// Extract document information using utility function
44+
const { tokenRegistry, tokenId, network, documentId, registryVersion } =
45+
await extractDocumentInfo(document);
6646

6747
// Beneficiary Address
68-
const beneficiary = await input({
69-
message: 'Enter the beneficiary address (initial recipient):',
70-
required: true,
71-
validate: (value: string) => {
72-
if (!value || value.trim() === '') {
73-
return 'Beneficiary address is required';
74-
}
75-
if (!/^0x[a-fA-F0-9]{40}$/.test(value)) {
76-
return 'Invalid Ethereum address format';
77-
}
78-
return true;
79-
},
80-
});
48+
const beneficiary = await promptAddress('beneficiary', 'initial recipient');
8149

8250
// Holder Address
83-
const holder = await input({
84-
message: 'Enter the holder address (initial holder):',
85-
required: true,
86-
validate: (value: string) => {
87-
if (!value || value.trim() === '') {
88-
return 'Holder address is required';
89-
}
90-
if (!/^0x[a-fA-F0-9]{40}$/.test(value)) {
91-
return 'Invalid Ethereum address format';
92-
}
93-
return true;
94-
},
95-
});
51+
const holder = await promptAddress('holder', 'initial holder');
9652

9753
// Wallet selection
9854
const { encryptedWalletPath, key, keyFile } = await promptWalletSelection();
9955

100-
// Optional: Remark and Encryption Key
101-
const { remark, encryptionKey } = await promptRemarkAndEncryptionKey();
56+
// Optional: Remark (only for V5)
57+
const remark = await promptRemark(registryVersion);
58+
59+
// Use document ID as encryption key
60+
const encryptionKey = documentId;
10261

10362
// Build the result object with proper typing
10463
const baseResult = {
10564
network,
106-
address,
65+
address: tokenRegistry,
10766
tokenId,
10867
beneficiary,
10968
holder,
@@ -177,30 +136,76 @@ const mintToTokenRegistry = async ({
177136
...rest
178137
}: TokenRegistryMintCommand): Promise<TransactionReceipt> => {
179138
const wallet = await getWalletOrSigner({ network, ...rest });
180-
let transactionOptions: { maxFeePerGas?: BigNumberish; maxPriorityFeePerGas?: BigNumberish } = {};
181139

182140
if (dryRun) {
183141
console.log('🔧 Dry run mode is currently undergoing upgrades and will be available soon.');
184142
process.exit(0);
185143
}
186144

145+
// Automatic dry run for Ethereum and Polygon networks
146+
const shouldProceed = await performDryRunWithConfirmation({
147+
network,
148+
getTransactionCallback: async () => {
149+
const tokenRegistry = await connectToTokenRegistry({ address, wallet });
150+
151+
// Validate and encrypt the remark with document ID as encryption key
152+
const encryptedRemark = validateAndEncryptRemark(remark, encryptionKey);
153+
154+
// Populate the transaction for gas estimation
155+
const tx = await tokenRegistry.mint.populateTransaction(
156+
beneficiary,
157+
holder,
158+
tokenId,
159+
encryptedRemark
160+
);
161+
162+
// Ensure the transaction has a 'from' address for proper gas estimation
163+
return {
164+
...tx,
165+
from: await wallet.getAddress(),
166+
};
167+
},
168+
});
169+
170+
if (!shouldProceed) {
171+
process.exit(0);
172+
}
173+
174+
let transaction;
175+
176+
// Execute transaction with appropriate gas settings based on network capabilities
187177
if (canEstimateGasPrice(network)) {
178+
// Ensure provider is available for gas estimation
188179
if (!wallet.provider) {
189180
throw new Error('Provider is required for gas estimation');
190181
}
182+
183+
// Get current gas fees from the network
191184
const gasFees = await getGasFees({ provider: wallet.provider, ...rest });
192-
transactionOptions = {
193-
maxFeePerGas: gasFees.maxFeePerGas as BigNumberish,
194-
maxPriorityFeePerGas: gasFees.maxPriorityFeePerGas as BigNumberish,
195-
};
185+
186+
// Execute mint with EIP-1559 gas parameters
187+
transaction = await mint(
188+
{ tokenRegistryAddress: address },
189+
wallet,
190+
{ beneficiaryAddress: beneficiary, holderAddress: holder, tokenId, remarks: remark },
191+
{
192+
id: encryptionKey,
193+
maxFeePerGas: gasFees.maxFeePerGas?.toString(),
194+
maxPriorityFeePerGas: gasFees.maxPriorityFeePerGas?.toString(),
195+
},
196+
);
197+
} else {
198+
// Execute mint without gas estimation (for networks that don't support it)
199+
transaction = await mint(
200+
{ tokenRegistryAddress: address },
201+
wallet,
202+
{ beneficiaryAddress: beneficiary, holderAddress: holder, tokenId, remarks: remark },
203+
{
204+
id: encryptionKey,
205+
},
206+
);
196207
}
197208

198-
const transaction = await mint(
199-
{ tokenRegistryAddress: address },
200-
wallet,
201-
{ beneficiaryAddress: beneficiary, holderAddress: holder, tokenId, remarks: remark },
202-
{ id: encryptionKey, ...transactionOptions },
203-
);
204209
signale.await(`Waiting for transaction ${transaction.hash} to be mined`);
205210
const receipt = (await transaction.wait()) as unknown as TransactionReceipt;
206211
if (!receipt) {

src/types.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import { credentialStatus, issuer, RawVerifiableCredential } from '@trustvc/trustvc';
2-
import { GasOption, NetworkOption, RpcUrlOption, WalletOrSignerOption } from './utils';
2+
import {
3+
GasOption,
4+
NetworkAndWalletSignerOption,
5+
NetworkOption,
6+
RpcUrlOption,
7+
WalletOrSignerOption,
8+
} from './utils';
39

410
export type SignInput = {
511
credential: RawVerifiableCredential;
612
keyPairData: typeof issuer.IssuedDIDOption;
713
encryptionAlgorithm: typeof credentialStatus.cryptoSuiteName;
814
pathToSignedVC: string;
9-
}
15+
};
1016
export type DidInput = {
1117
keyPairPath: string;
1218
domainName: string;

0 commit comments

Comments
 (0)