diff --git a/src/document-store/document-store-roles.ts b/src/document-store/document-store-roles.ts index 35eca0c..6053e7b 100644 --- a/src/document-store/document-store-roles.ts +++ b/src/document-store/document-store-roles.ts @@ -30,16 +30,14 @@ export const getRoleString = async ( // eslint-disable-next-line @typescript-eslint/no-explicit-any provider as any, ); - switch (role) { - case 'admin': - return await documentStore.DEFAULT_ADMIN_ROLE(); - case 'issuer': - return await documentStore.ISSUER_ROLE(); - case 'revoker': - return await documentStore.REVOKER_ROLE(); - default: - throw new Error('Invalid role'); + + // Role should be the actual contract method name (e.g., 'DEFAULT_ADMIN_ROLE', 'ISSUER_ROLE', 'REVOKER_ROLE') + if (typeof documentStore[role as keyof typeof documentStore] !== 'function') { + throw new Error(`Invalid role: ${role}`); } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return await (documentStore as any)[role](); }; -export const rolesList = ['admin', 'issuer', 'revoker']; +export const rolesList = ['DEFAULT_ADMIN_ROLE', 'ISSUER_ROLE', 'REVOKER_ROLE']; diff --git a/src/document-store/index.ts b/src/document-store/index.ts index a84743f..7eeacd3 100644 --- a/src/document-store/index.ts +++ b/src/document-store/index.ts @@ -4,6 +4,7 @@ export { documentStoreRevokeRole } from './revoke-role'; export { documentStoreGrantRole } from './grant-role'; export { documentStoreTransferOwnership } from './transferOwnership'; export { deployDocumentStore } from '../deploy/document-store'; +export { getRoleString } from './document-store-roles'; export { supportInterfaceIds } from './supportInterfaceIds'; export { DocumentStore__factory, diff --git a/src/document-store/transferOwnership.ts b/src/document-store/transferOwnership.ts index c06e114..2e2d796 100644 --- a/src/document-store/transferOwnership.ts +++ b/src/document-store/transferOwnership.ts @@ -1,10 +1,25 @@ -import { Signer as SignerV6, ContractTransactionResponse as ContractTransactionV6 } from 'ethersV6'; -import { ContractTransaction as ContractTransactionV5, Signer as SignerV5 } from 'ethers'; +import { + Signer as SignerV6, + ContractTransactionResponse as ContractTransactionV6, + Contract as ContractV6, +} from 'ethersV6'; +import { + ContractTransaction as ContractTransactionV5, + Signer as SignerV5, + Contract as ContractV5, +} from 'ethers'; import { documentStoreRevokeRole } from './revoke-role'; - import { documentStoreGrantRole } from './grant-role'; import { getRoleString } from './document-store-roles'; import { CommandOptions } from './types'; +import { checkSupportsInterface } from '../core'; +import { supportInterfaceIds } from './supportInterfaceIds'; +import { TT_DOCUMENT_STORE_ABI } from './tt-document-store-abi'; +import { getEthersContractFromProvider, isV6EthersProvider } from '../utils/ethers'; +import { + DocumentStore__factory, + TransferableDocumentStore__factory, +} from '@trustvc/document-store'; /** * Transfers ownership of a DocumentStore contract to a new owner. @@ -17,10 +32,10 @@ import { CommandOptions } from './types'; * @param {string} account - The account to transfer ownership to. * @param {SignerV5 | SignerV6} signer - Signer instance (Ethers v5 or v6) that authorizes the transfer ownership transaction. * @param {CommandOptions} options - Optional transaction metadata including gas values and chain ID. - * @returns {Promise<{grantTransaction: Promise; revokeTransaction: Promise}>} A promise resolving to the transaction result from the grant and revoke role calls. + * @returns {Promise<{grantTransaction: ContractTransactionV5 | ContractTransactionV6; revokeTransaction: ContractTransactionV5 | ContractTransactionV6}>} A promise resolving to the transaction result from the grant and revoke role calls. * @throws {Error} If the document store address or signer provider is not provided. * @throws {Error} If the role is invalid. - * @throws {Error} If the `callStatic.revokeRole` fails as a pre-check. + * @throws {Error} If either the `callStatic.grantRole` or `callStatic.revokeRole` pre-check fails. */ export const documentStoreTransferOwnership = async ( documentStoreAddress: string, @@ -28,47 +43,100 @@ export const documentStoreTransferOwnership = async ( signer: SignerV5 | SignerV6, options: CommandOptions = {}, ): Promise<{ - grantTransaction: Promise; - revokeTransaction: Promise; + grantTransaction: ContractTransactionV5 | ContractTransactionV6; + revokeTransaction: ContractTransactionV5 | ContractTransactionV6; }> => { if (!documentStoreAddress) throw new Error('Document store address is required'); if (!signer.provider) throw new Error('Provider is required'); if (!account) throw new Error('Account is required'); const ownerAddress = await signer.getAddress(); - const roleString = await getRoleString(documentStoreAddress, 'admin', { + const roleString = await getRoleString(documentStoreAddress, 'DEFAULT_ADMIN_ROLE', { provider: signer.provider, }); - //call the transferOwnership function of the document store contract - //call grant and revoke function here - const grantTransaction = documentStoreGrantRole( + // Get the contract instance for pre-checks + const Contract = getEthersContractFromProvider(signer.provider); + const isDocumentStore = await checkSupportsInterface( + documentStoreAddress, + supportInterfaceIds.IDocumentStore, + signer.provider, + ); + const isTransferableDocumentStore = await checkSupportsInterface( + documentStoreAddress, + supportInterfaceIds.ITransferableDocumentStore, + signer.provider, + ); + + let documentStoreAbi; + if (isDocumentStore || isTransferableDocumentStore) { + const DocumentStoreFactory = isTransferableDocumentStore + ? TransferableDocumentStore__factory + : DocumentStore__factory; + documentStoreAbi = DocumentStoreFactory.abi; + } else { + documentStoreAbi = TT_DOCUMENT_STORE_ABI; + } + + const documentStoreContract: ContractV5 | ContractV6 = new Contract( + documentStoreAddress, + documentStoreAbi, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + signer as any, + ); + + // CRITICAL: Perform BOTH static call pre-checks BEFORE executing any real transactions + // This prevents partial ownership transfer if revoke would fail after grant succeeds + const isV6 = isV6EthersProvider(signer.provider); + + try { + // Pre-check 1: Verify grant will succeed + if (isV6) { + await (documentStoreContract as ContractV6).grantRole.staticCall(roleString, account); + } else { + await (documentStoreContract as ContractV5).callStatic.grantRole(roleString, account); + } + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for grant-role failed'); + } + + try { + // Pre-check 2: Verify revoke will succeed + if (isV6) { + await (documentStoreContract as ContractV6).revokeRole.staticCall(roleString, ownerAddress); + } else { + await (documentStoreContract as ContractV5).callStatic.revokeRole(roleString, ownerAddress); + } + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for revoke-role failed'); + } + + // Both pre-checks passed - now execute the real transactions + const grantTransaction = await documentStoreGrantRole( documentStoreAddress, roleString, account, signer, options, ); - //check if the grant transaction is successful - const grantTransactionResult = await grantTransaction; - if (!grantTransactionResult) { - //add custom error message not proceeding eith the revoke transaction + + if (!grantTransaction) { throw new Error('Grant transaction failed, not proceeding with revoke transaction'); - //return the grant transaction result } - //call revoke function here - const revokeTransaction = documentStoreRevokeRole( + + const revokeTransaction = await documentStoreRevokeRole( documentStoreAddress, roleString, ownerAddress, signer, options, ); - //check if the revoke transaction is successful - const revokeTransactionResult = await revokeTransaction; - if (!revokeTransactionResult) { + + if (!revokeTransaction) { throw new Error('Revoke transaction failed'); - //return the revoke transaction result } + return { grantTransaction, revokeTransaction }; }; diff --git a/src/index.ts b/src/index.ts index 974c908..64da810 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,6 +27,8 @@ import { documentStoreRevoke, DocumentStore__factory, TransferableDocumentStore__factory, + getRoleString, + documentStoreTransferOwnership, } from './document-store'; export type { TypedContractMethod } from './token-registry-v5/typedContractMethod'; export * from './token-registry-functions'; @@ -61,6 +63,8 @@ export { documentStoreRevokeRole, documentStoreIssue, documentStoreRevoke, + getRoleString, + documentStoreTransferOwnership, DocumentStore__factory, TransferableDocumentStore__factory, };