Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 8 additions & 10 deletions src/document-store/document-store-roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
1 change: 1 addition & 0 deletions src/document-store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
112 changes: 90 additions & 22 deletions src/document-store/transferOwnership.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -17,58 +32,111 @@ 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<ContractTransactionV5 | ContractTransactionV6>; revokeTransaction: Promise<ContractTransactionV5 | ContractTransactionV6>}>} 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,
account: string,
signer: SignerV5 | SignerV6,
options: CommandOptions = {},
): Promise<{
grantTransaction: Promise<ContractTransactionV5 | ContractTransactionV6>;
revokeTransaction: Promise<ContractTransactionV5 | ContractTransactionV6>;
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
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
RishabhS7 marked this conversation as resolved.

return { grantTransaction, revokeTransaction };
};
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -61,6 +63,8 @@ export {
documentStoreRevokeRole,
documentStoreIssue,
documentStoreRevoke,
getRoleString,
documentStoreTransferOwnership,
DocumentStore__factory,
TransferableDocumentStore__factory,
};