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
111 changes: 110 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@ trustvc document-store issue

# Revoke a document hash from the store
trustvc document-store revoke

# Grant a role to an account
trustvc document-store grant-role

# Revoke a role from an account
trustvc document-store revoke-role

# Transfer ownership of the document store
trustvc document-store transfer-ownership
```

### Token Registry & Title Escrow
Expand Down Expand Up @@ -206,6 +215,9 @@ trustvc title-escrow reject-transfer-owner-holder
| **Document Store** | [`document-store deploy`](#document-store-deploy) | Deploy document store contracts |
| | [`document-store issue`](#document-store-issue) | Issue document hashes |
| | [`document-store revoke`](#document-store-revoke) | Revoke document hashes |
| | [`document-store grant-role`](#document-store-grant-role) | Grant roles to accounts |
| | [`document-store revoke-role`](#document-store-revoke-role) | Revoke roles from accounts |
| | [`document-store transfer-ownership`](#document-store-transfer-ownership) | Transfer document store ownership |
| **Wallet** | [`wallet create`](#wallet-create) | Create a new encrypted wallet file |
| | [`wallet encrypt`](#wallet-encrypt) | Encrypt a wallet using a private key |
| | [`wallet decrypt`](#wallet-decrypt) | Decrypt an encrypted wallet file |
Expand Down Expand Up @@ -634,6 +646,100 @@ Transaction receipt confirming the hash revocation.

</details>

<details>
<summary><h4 id="document-store-grant-role">document-store grant-role</h4></summary>

Grants a role (ISSUER_ROLE, REVOKER_ROLE, or DEFAULT_ADMIN_ROLE) to an account in a deployed document store.

**Usage:**

```sh
trustvc document-store grant-role
```

**Interactive Prompts:**

- Path to document file (or manual input for document store address)
- Role to grant (ISSUER_ROLE, REVOKER_ROLE, DEFAULT_ADMIN_ROLE)
- Account address to grant the role to
- Wallet/private key option
- Dry-run option (estimate gas before execution)

**Output:**
Transaction receipt with hash, block number, gas used, and explorer link.

**Supported Networks:**

- Ethereum (Mainnet, Sepolia)
- Polygon (Mainnet, Amoy Testnet)
- XDC Network (Mainnet, Apothem Testnet)
- Stability (Mainnet, Testnet)
- Astron (Mainnet, Testnet)

</details>

<details>
<summary><h4 id="document-store-revoke-role">document-store revoke-role</h4></summary>

Revokes a role (ISSUER_ROLE, REVOKER_ROLE, or DEFAULT_ADMIN_ROLE) from an account in a deployed document store.

**Usage:**

```sh
trustvc document-store revoke-role
```

**Interactive Prompts:**

- Path to document file (or manual input for document store address)
- Role to revoke (ISSUER_ROLE, REVOKER_ROLE, DEFAULT_ADMIN_ROLE)
- Account address to revoke the role from
- Wallet/private key option
- Dry-run option (estimate gas before execution)

**Output:**
Transaction receipt with hash, block number, gas used, and explorer link.

**Supported Networks:**

- Ethereum (Mainnet, Sepolia)
- Polygon (Mainnet, Amoy Testnet)
- XDC Network (Mainnet, Apothem Testnet)
- Stability (Mainnet, Testnet)
- Astron (Mainnet, Testnet)

</details>

<details>
<summary><h4 id="document-store-transfer-ownership">document-store transfer-ownership</h4></summary>

Transfers ownership of a document store contract to a new owner. This grants DEFAULT_ADMIN_ROLE to the new owner and revokes it from the current owner.

**Usage:**

```sh
trustvc document-store transfer-ownership
```

**Interactive Prompts:**

- Path to document file (or manual input for document store address)
- New owner address
- Wallet/private key option

**Output:**
Transaction receipts for both grant and revoke operations with hashes, block numbers, gas used, and explorer links.

**Supported Networks:**

- Ethereum (Mainnet, Sepolia)
- Polygon (Mainnet, Amoy Testnet)
- XDC Network (Mainnet, Apothem Testnet)
- Stability (Mainnet, Testnet)
- Astron (Mainnet, Testnet)

</details>

<details>
<summary><h4 id="wallet-create">wallet create</h4></summary>

Expand Down Expand Up @@ -1167,7 +1273,10 @@ src/commands/
├── document-store/
│ ├── deploy.ts # Deploy document store contracts
│ ├── issue.ts # Issue document hashes
│ └── revoke.ts # Revoke document hashes
│ ├── revoke.ts # Revoke document hashes
│ ├── grant-role.ts # Grant roles to accounts
│ ├── revoke-role.ts # Revoke roles from accounts
│ └── transfer-ownership.ts # Transfer document store ownership
├── title-escrow/
│ ├── transfer-holder.ts # Transfer holder
│ ├── nominate-transfer-owner.ts # Nominate beneficiary
Expand Down
192 changes: 192 additions & 0 deletions src/commands/document-store/grant-role.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import signale, { error, info, success } from 'signale';
import { select } from '@inquirer/prompts';
import {
displayTransactionPrice,
getErrorMessage,
getEtherscanAddress,
NetworkCmdName,
promptWalletSelection,
getWalletOrSigner,
canEstimateGasPrice,
getGasFees,
performDryRunWithConfirmation,
promptAddress,
promptAndReadDocument,
extractOADocumentInfo,
NetworkAndWalletSignerOption,
GasPriceScale,
} from '../../utils';
import { connectToDocumentStore, waitForTransaction } from '../helpers';
import { documentStoreGrantRole } from '@trustvc/trustvc';
import { Provider, id as keccak256Hash } from 'ethers';

// Define the command type for grant-role
type DocumentStoreGrantRoleCommand = NetworkAndWalletSignerOption &
GasPriceScale & {
documentStoreAddress: string;
role: string;
account: string;
};

export const command = 'grant-role';

export const describe = 'Grants a role to a document store deployed on the blockchain';

export const handler = async (): Promise<void> => {
try {
const answers = await promptForInputs();
if (!answers) return;

await grantRoleToDocumentStore(answers);
} catch (err: unknown) {
error(err instanceof Error ? err.message : String(err));
}
};

// Prompt user for all required inputs
export const promptForInputs = async (): Promise<DocumentStoreGrantRoleCommand> => {
// Extract document information using utility function
const document = await promptAndReadDocument();

const { documentStoreAddress, network } = await extractOADocumentInfo(document);

// Role selection
const role = await select({
message: 'Select the role to grant:',
choices: [
{
name: 'Issuer Role',
value: 'ISSUER_ROLE',
description: 'Allows the account to issue documents',
},
{
name: 'Revoker Role',
value: 'REVOKER_ROLE',
description: 'Allows the account to revoke documents',
},
{
name: 'Default Admin Role',
value: 'DEFAULT_ADMIN_ROLE',
description: 'Allows the account to manage roles',
},
],
default: 'ISSUER_ROLE',
});

// Account address to grant the role to
const account = (await promptAddress('account', 'address to grant the role to')) as string;

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

// Build the result object with proper typing
const baseResult = {
documentStoreAddress,
role, // This will be converted to role hash in the execution function
account,
network,
maxPriorityFeePerGasScale: 1,
};

// Add wallet-specific properties based on selected wallet type
if (encryptedWalletPath) {
return {
...baseResult,
encryptedWalletPath,
} as DocumentStoreGrantRoleCommand;
} else if (keyFile) {
return {
...baseResult,
keyFile,
} as DocumentStoreGrantRoleCommand;
} else if (key) {
return {
...baseResult,
key,
} as DocumentStoreGrantRoleCommand;
}

// For environment variable case (when all wallet options are undefined)
return baseResult as DocumentStoreGrantRoleCommand;
};

// Grant role to document store with the provided inputs
export const grantRoleToDocumentStore = async ({
documentStoreAddress,
role,
account,
network,
...rest
}: DocumentStoreGrantRoleCommand) => {
try {
info(`Granting ${role} to ${account} on document store ${documentStoreAddress}`);

const wallet = await getWalletOrSigner({ network, ...rest });

// Get the role hash from the role name using keccak256
// For AccessControl contracts, roles are stored as keccak256 hashes
// DEFAULT_ADMIN_ROLE is 0x00...00, others are keccak256("ROLE_NAME")
const roleHash =
role === 'DEFAULT_ADMIN_ROLE'
? '0x0000000000000000000000000000000000000000000000000000000000000000'
: keccak256Hash(role);

// Automatic dry run for Ethereum and Polygon networks
const shouldProceed = await performDryRunWithConfirmation({
network,
getTransactionCallback: async () => {
const documentStore = await connectToDocumentStore({
address: documentStoreAddress,
wallet,
});
// Populate the transaction for gas estimation
const tx = await documentStore.grantRole.populateTransaction(roleHash, account);

// Ensure the transaction has a 'from' address for proper gas estimation
return {
...tx,
from: await wallet.getAddress(),
};
},
});

if (!shouldProceed) {
process.exit(0);
}

let transaction;

// Execute transaction with appropriate gas settings based on network capabilities
if (canEstimateGasPrice(network)) {
// Ensure provider is available for gas estimation
if (!wallet.provider) {
throw new Error('Provider is required for gas estimation');
}

// Get current gas fees from the network
const gasFees = await getGasFees({ provider: wallet.provider, ...rest });
// Execute grant role with EIP-1559 gas parameters
transaction = await documentStoreGrantRole(documentStoreAddress, roleHash, account, wallet, {
maxFeePerGas: gasFees.maxFeePerGas?.toString(),
maxPriorityFeePerGas: gasFees.maxPriorityFeePerGas?.toString(),
});
} else {
// Execute grant role without gas estimation (for networks that don't support it)
transaction = await documentStoreGrantRole(documentStoreAddress, roleHash, account, wallet);
}

signale.await(`Waiting for transaction ${transaction.hash} to be mined`);

const receipt = await waitForTransaction(transaction, wallet.provider as Provider);

displayTransactionPrice(receipt, network as NetworkCmdName);
const { hash: transactionHash } = transaction;

success(`Role ${role} has been granted to ${account} on ${documentStoreAddress}`);
info(`Find more details at ${getEtherscanAddress({ network })}/tx/${transactionHash}`);

return documentStoreAddress;
} catch (e) {
error(getErrorMessage(e));
}
};
3 changes: 2 additions & 1 deletion src/commands/document-store/issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,11 @@ export const issueToken = async ({
// Execute mint without gas estimation (for networks that don't support it)
transaction = await documentStoreIssue(documentStoreAddress, documentHash, wallet);
}
const receipt = await waitForTransaction(transaction, wallet.provider as Provider);

signale.await(`Waiting for transaction ${transaction.hash} to be mined`);

const receipt = await waitForTransaction(transaction, wallet.provider as Provider);

displayTransactionPrice(receipt, network as NetworkCmdName);
const { hash: transactionHash } = transaction;

Expand Down
Loading