@trustvc/trustvc 2.10.0 → 2.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,6 +27,7 @@ TrustVC is a comprehensive wrapper library designed to simplify the signing and
27
27
  - [b) Token Registry V5](#b-token-registry-v5)
28
28
  - [8. **Document Builder**](#8-document-builder)
29
29
  - [9. **Document Store**](#9-document-store)
30
+ - [10. **Transaction Cancel**](#10-transaction-cancel)
30
31
 
31
32
  ## Installation
32
33
 
@@ -1107,3 +1108,56 @@ for (const hash of documentHashes) {
1107
1108
  await documentStoreIssue(storeAddress, hash, signer);
1108
1109
  }
1109
1110
  ```
1111
+
1112
+ ---
1113
+
1114
+ ## 10. Transaction Cancel
1115
+
1116
+ TrustVC provides a utility to cancel a pending Ethereum transaction by replacing it with a 0-value transaction to the same address, using the same nonce and a higher gas price (replace-by-fee). This works with both ethers v5 and v6 signers.
1117
+
1118
+ **Reference:** [How to cancel Ethereum pending transactions](https://info.etherscan.com/how-to-cancel-ethereum-pending-transactions/)
1119
+
1120
+ ### cancelTransaction
1121
+
1122
+ #### Description
1123
+
1124
+ Cancels a pending transaction by sending a 0-value transaction to the signer’s address with the same nonce and a higher gas price. You can specify the pending transaction either by **transaction hash** (nonce and gas price are fetched; gas price is doubled) or by **nonce and gas price** explicitly. Transactions that use EIP-1559 (no legacy `gasPrice`) must be cancelled using nonce and gas price.
1125
+
1126
+ #### Parameters
1127
+
1128
+ - **signer** – Signer with a connected provider; type `CancelTransactionSigner` (compatible with ethers v5 and v6 signers).
1129
+ - **options** – `CancelTransactionOptions`:
1130
+ - **transactionHash** (optional): Pending transaction hash (`0x...`). If provided, nonce and gas price are read from the chain and gas price is increased by 100%.
1131
+ - **nonce** (optional): Pending transaction nonce. Must be used together with `gasPrice`.
1132
+ - **gasPrice** (optional): Gas price in wei for the replacement transaction. Must be used together with `nonce`.
1133
+
1134
+ Either `(nonce, gasPrice)` or `transactionHash` must be provided.
1135
+
1136
+ #### Returns
1137
+
1138
+ **Promise<string>** – The replacement transaction hash.
1139
+
1140
+ #### Throws
1141
+
1142
+ - If the signer has no provider.
1143
+ - If neither `(nonce, gasPrice)` nor `transactionHash` is provided.
1144
+ - If `transactionHash` is given but the transaction is not found.
1145
+ - If the transaction uses EIP-1559 (no `gasPrice`); in that case use nonce and gas price explicitly.
1146
+
1147
+ #### Example
1148
+
1149
+ ```ts
1150
+ import { cancelTransaction } from '@trustvc/trustvc';
1151
+
1152
+ // Cancel by transaction hash (gas price is fetched and doubled)
1153
+ const replacementHash = await cancelTransaction(signer, {
1154
+ transactionHash: '0x...',
1155
+ });
1156
+ console.log('Replacement tx:', replacementHash);
1157
+
1158
+ // Or cancel by nonce and gas price (e.g. for EIP-1559 txs)
1159
+ const replacementHash2 = await cancelTransaction(signer, {
1160
+ nonce: '5',
1161
+ gasPrice: '25000000000', // 25 gwei in wei
1162
+ });
1163
+ ```
package/dist/cjs/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  var tokenRegistryV4 = require('./token-registry-v4');
4
4
  var tokenRegistryV5 = require('./token-registry-v5');
5
5
  var documentStore = require('./document-store');
6
+ var transaction = require('./transaction');
6
7
  var tokenRegistryFunctions = require('./token-registry-functions');
7
8
  var core = require('./core');
8
9
  var openAttestation = require('./open-attestation');
@@ -118,6 +119,16 @@ Object.defineProperty(exports, "getRoleString", {
118
119
  enumerable: true,
119
120
  get: function () { return documentStore.getRoleString; }
120
121
  });
122
+ Object.defineProperty(exports, "cancelTransaction", {
123
+ enumerable: true,
124
+ get: function () { return transaction.cancelTransaction; }
125
+ });
126
+ Object.keys(transaction).forEach(function (k) {
127
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
128
+ enumerable: true,
129
+ get: function () { return transaction[k]; }
130
+ });
131
+ });
121
132
  Object.keys(tokenRegistryFunctions).forEach(function (k) {
122
133
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
123
134
  enumerable: true,
@@ -21,24 +21,15 @@ const ENCRYPTION_PARAMETERS = Object.freeze({
21
21
  // Type 1 using the above params without compression
22
22
  });
23
23
  const generateEncryptionKey = /* @__PURE__ */ __name((keyLengthInBits = ENCRYPTION_PARAMETERS.keyLength) => {
24
- if (!Number.isInteger(keyLengthInBits) || ![128, 192, 256].includes(keyLengthInBits)) {
25
- throw new Error("keyLengthInBits must be one of 128, 192, or 256");
26
- }
27
24
  const encryptionKey = forge__default.default.random.getBytesSync(keyLengthInBits / 8);
28
25
  return forge__default.default.util.bytesToHex(encryptionKey);
29
26
  }, "generateEncryptionKey");
30
27
  const encodeDocument = /* @__PURE__ */ __name((document) => {
31
28
  const bytes = forge__default.default.util.encodeUtf8(document);
32
- const standard = forge__default.default.util.encode64(bytes);
33
- const s = standard.replace(/\+/g, "-").replace(/\//g, "_");
34
- const trim = s.endsWith("==") ? 2 : s.endsWith("=") ? 1 : 0;
35
- return trim ? s.slice(0, -trim) : s;
29
+ return forge__default.default.util.encode64(bytes);
36
30
  }, "encodeDocument");
37
31
  const decodeDocument = /* @__PURE__ */ __name((encoded) => {
38
- let normalized = encoded.replace(/-/g, "+").replace(/_/g, "/");
39
- const pad = normalized.length % 4;
40
- if (pad) normalized += "=".repeat(4 - pad);
41
- const decoded = forge__default.default.util.decode64(normalized);
32
+ const decoded = forge__default.default.util.decode64(encoded);
42
33
  return forge__default.default.util.decodeUtf8(decoded);
43
34
  }, "decodeDocument");
44
35
  const {
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ var ethers = require('ethers');
4
+
5
+ var __defProp = Object.defineProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ const GAS_PRICE_SCALE_WHEN_FROM_HASH = 2;
8
+ const cancelTransaction = /* @__PURE__ */ __name(async (signer, options) => {
9
+ if (!signer.provider) {
10
+ throw new Error("Provider is required on signer");
11
+ }
12
+ const { nonce, gasPrice, transactionHash } = options;
13
+ let transactionNonce = nonce;
14
+ let transactionGasPrice = gasPrice;
15
+ if (transactionHash) {
16
+ if (typeof signer.provider.getTransaction !== "function") {
17
+ throw new Error("Provider does not support getTransaction");
18
+ }
19
+ const tx = await signer.provider.getTransaction(transactionHash);
20
+ if (!tx) {
21
+ throw new Error(`Transaction not found: ${transactionHash}`);
22
+ }
23
+ const txNonce = tx.nonce;
24
+ const txGasPrice = tx.gasPrice;
25
+ if (txGasPrice == null) {
26
+ throw new Error(
27
+ "Transaction uses EIP-1559 (no gasPrice). Cancel by providing --nonce and --gas-price explicitly."
28
+ );
29
+ }
30
+ transactionNonce = String(txNonce);
31
+ const scaled = typeof txGasPrice === "bigint" ? txGasPrice * BigInt(GAS_PRICE_SCALE_WHEN_FROM_HASH) : ethers.BigNumber.from(txGasPrice).mul(GAS_PRICE_SCALE_WHEN_FROM_HASH);
32
+ transactionGasPrice = String(scaled);
33
+ }
34
+ if (!transactionNonce || !transactionGasPrice) {
35
+ throw new Error(
36
+ "Provide either (--nonce and --gas-price) or --transaction-hash to cancel the pending transaction"
37
+ );
38
+ }
39
+ const address = await signer.getAddress();
40
+ if (!/^\d+$/.test(transactionNonce)) {
41
+ throw new Error("Invalid nonce: expected a non-negative integer");
42
+ }
43
+ if (!/^\d+$/.test(transactionGasPrice) || transactionGasPrice === "0") {
44
+ throw new Error("Invalid gasPrice: expected a positive integer string in wei");
45
+ }
46
+ const nonceNum = Number(transactionNonce);
47
+ const txResponse = await signer.sendTransaction({
48
+ to: address,
49
+ value: 0,
50
+ nonce: nonceNum,
51
+ gasPrice: transactionGasPrice,
52
+ gasLimit: 21e3
53
+ });
54
+ return txResponse.hash;
55
+ }, "cancelTransaction");
56
+
57
+ exports.cancelTransaction = cancelTransaction;
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ var cancel = require('./cancel');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "cancelTransaction", {
8
+ enumerable: true,
9
+ get: function () { return cancel.cancelTransaction; }
10
+ });
package/dist/esm/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export { v4ComputeInterfaceId, v4ComputeTitleEscrowAddress, v4ContractAddress, v4Contracts, v4EncodeInitParams, v4GetEventFromReceipt, v4RoleHash, v4SupportInterfaceIds, v4Utils } from './token-registry-v4';
2
2
  export { v5ComputeInterfaceId, v5ContractAddress, v5Contracts, v5EncodeInitParams, v5GetEventFromReceipt, v5RoleHash, v5SupportInterfaceIds, v5Utils } from './token-registry-v5';
3
3
  export { DocumentStore__factory, TransferableDocumentStore__factory, deployDocumentStore, documentStoreGrantRole, documentStoreIssue, documentStoreRevoke, documentStoreRevokeRole, documentStoreTransferOwnership, getRoleString } from './document-store';
4
+ export * from './transaction';
5
+ export { cancelTransaction } from './transaction';
4
6
  export * from './token-registry-functions';
5
7
  export * from './core';
6
8
  export * from './open-attestation';
@@ -16,24 +16,15 @@ const ENCRYPTION_PARAMETERS = Object.freeze({
16
16
  // Type 1 using the above params without compression
17
17
  });
18
18
  const generateEncryptionKey = /* @__PURE__ */ __name((keyLengthInBits = ENCRYPTION_PARAMETERS.keyLength) => {
19
- if (!Number.isInteger(keyLengthInBits) || ![128, 192, 256].includes(keyLengthInBits)) {
20
- throw new Error("keyLengthInBits must be one of 128, 192, or 256");
21
- }
22
19
  const encryptionKey = forge.random.getBytesSync(keyLengthInBits / 8);
23
20
  return forge.util.bytesToHex(encryptionKey);
24
21
  }, "generateEncryptionKey");
25
22
  const encodeDocument = /* @__PURE__ */ __name((document) => {
26
23
  const bytes = forge.util.encodeUtf8(document);
27
- const standard = forge.util.encode64(bytes);
28
- const s = standard.replace(/\+/g, "-").replace(/\//g, "_");
29
- const trim = s.endsWith("==") ? 2 : s.endsWith("=") ? 1 : 0;
30
- return trim ? s.slice(0, -trim) : s;
24
+ return forge.util.encode64(bytes);
31
25
  }, "encodeDocument");
32
26
  const decodeDocument = /* @__PURE__ */ __name((encoded) => {
33
- let normalized = encoded.replace(/-/g, "+").replace(/_/g, "/");
34
- const pad = normalized.length % 4;
35
- if (pad) normalized += "=".repeat(4 - pad);
36
- const decoded = forge.util.decode64(normalized);
27
+ const decoded = forge.util.decode64(encoded);
37
28
  return forge.util.decodeUtf8(decoded);
38
29
  }, "decodeDocument");
39
30
  const {
@@ -0,0 +1,55 @@
1
+ import { BigNumber } from 'ethers';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
5
+ const GAS_PRICE_SCALE_WHEN_FROM_HASH = 2;
6
+ const cancelTransaction = /* @__PURE__ */ __name(async (signer, options) => {
7
+ if (!signer.provider) {
8
+ throw new Error("Provider is required on signer");
9
+ }
10
+ const { nonce, gasPrice, transactionHash } = options;
11
+ let transactionNonce = nonce;
12
+ let transactionGasPrice = gasPrice;
13
+ if (transactionHash) {
14
+ if (typeof signer.provider.getTransaction !== "function") {
15
+ throw new Error("Provider does not support getTransaction");
16
+ }
17
+ const tx = await signer.provider.getTransaction(transactionHash);
18
+ if (!tx) {
19
+ throw new Error(`Transaction not found: ${transactionHash}`);
20
+ }
21
+ const txNonce = tx.nonce;
22
+ const txGasPrice = tx.gasPrice;
23
+ if (txGasPrice == null) {
24
+ throw new Error(
25
+ "Transaction uses EIP-1559 (no gasPrice). Cancel by providing --nonce and --gas-price explicitly."
26
+ );
27
+ }
28
+ transactionNonce = String(txNonce);
29
+ const scaled = typeof txGasPrice === "bigint" ? txGasPrice * BigInt(GAS_PRICE_SCALE_WHEN_FROM_HASH) : BigNumber.from(txGasPrice).mul(GAS_PRICE_SCALE_WHEN_FROM_HASH);
30
+ transactionGasPrice = String(scaled);
31
+ }
32
+ if (!transactionNonce || !transactionGasPrice) {
33
+ throw new Error(
34
+ "Provide either (--nonce and --gas-price) or --transaction-hash to cancel the pending transaction"
35
+ );
36
+ }
37
+ const address = await signer.getAddress();
38
+ if (!/^\d+$/.test(transactionNonce)) {
39
+ throw new Error("Invalid nonce: expected a non-negative integer");
40
+ }
41
+ if (!/^\d+$/.test(transactionGasPrice) || transactionGasPrice === "0") {
42
+ throw new Error("Invalid gasPrice: expected a positive integer string in wei");
43
+ }
44
+ const nonceNum = Number(transactionNonce);
45
+ const txResponse = await signer.sendTransaction({
46
+ to: address,
47
+ value: 0,
48
+ nonce: nonceNum,
49
+ gasPrice: transactionGasPrice,
50
+ gasLimit: 21e3
51
+ });
52
+ return txResponse.hash;
53
+ }, "cancelTransaction");
54
+
55
+ export { cancelTransaction };
@@ -0,0 +1 @@
1
+ export { cancelTransaction } from './cancel';
@@ -19,6 +19,7 @@ export { documentStoreTransferOwnership } from './document-store/transferOwnersh
19
19
  export { deployDocumentStore } from './deploy/document-store.js';
20
20
  export { getRoleString } from './document-store/document-store-roles.js';
21
21
  export { DocumentStore__factory, TransferableDocumentStore__factory } from '@trustvc/document-store';
22
+ export { CancelTransactionOptions, CancelTransactionSigner, cancelTransaction } from './transaction/cancel.js';
22
23
  export { nominate, transferBeneficiary, transferHolder, transferOwners } from './token-registry-functions/transfer.js';
23
24
  export { rejectTransferBeneficiary, rejectTransferHolder, rejectTransferOwners } from './token-registry-functions/rejectTransfers.js';
24
25
  export { acceptReturned, rejectReturned, returnToIssuer } from './token-registry-functions/returnToken.js';
@@ -3,10 +3,10 @@ import '@tradetrust-tt/tradetrust';
3
3
  import '@tradetrust-tt/tradetrust/dist/types/shared/utils';
4
4
 
5
5
  /**
6
- * Encrypts a given string with symmetric AES-GCM.
7
- * @param {string} document - Input string to encrypt.
8
- * @param {string} [key] - Optional encryption key in hexadecimal (64 chars for 256-bit). If omitted, a key is generated.
9
- * @returns {IEncryptionResults} Object with cipherText (base64), iv (base64), tag (base64), key (hex), and type.
6
+ * Encrypts a given string with symmetric AES
7
+ * @param {string} document Input string to encrypt
8
+ * @param {string} [key] Optional encryption key in hexadecimal; generated if omitted
9
+ * @returns {IEncryptionResults} Object with cipherText (base64), iv (base64), tag (base64), key (hex), and type (algorithm identifier)
10
10
  */
11
11
  declare const encryptString: (document: string, key?: string) => IEncryptionResults;
12
12
 
@@ -19,23 +19,21 @@ declare const ENCRYPTION_PARAMETERS: Readonly<{
19
19
  readonly version: "OPEN-ATTESTATION-TYPE-1";
20
20
  }>;
21
21
  /**
22
- * Generates a random key represented as a hexadecimal string.
23
- * @param {number} [keyLengthInBits] - Key length in bits.
24
- * @returns {string} Hexadecimal-encoded encryption key.
22
+ * Generates a random key represented as a hexadecimal string
23
+ * @param {number} keyLengthInBits Key length in bits
24
+ * @returns {string} Encryption key as hexadecimal string
25
25
  */
26
- declare const generateEncryptionKey: (keyLengthInBits?: number) => string;
26
+ declare const generateEncryptionKey: (keyLengthInBits?: 256) => string;
27
27
  /**
28
- * Encode document string to URL-safe base64 (base64url: UTF-8 then base64 with +→-, /→_, no padding).
29
- * Safe for use in query strings and JSON without further encoding.
30
- * @param {string} document - Plain text document to encode.
31
- * @returns {string} Base64url-encoded string.
28
+ * Encode document string to base64 (UTF-8 then base64).
29
+ * @param {string} document UTF-8 document string to encode
30
+ * @returns {string} Base64-encoded document
32
31
  */
33
32
  declare const encodeDocument: (document: string) => string;
34
33
  /**
35
- * Decode base64url-encoded document string back to UTF-8.
36
- * Accepts both base64url (no padding, - and _) and standard base64 for backwards compatibility.
37
- * @param {string} encoded - Base64- or base64url-encoded string to decode.
38
- * @returns {string} Decoded UTF-8 plain text.
34
+ * Decode base64-encoded document string back to UTF-8.
35
+ * @param {string} encoded Base64-encoded document string
36
+ * @returns {string} Decoded UTF-8 document string
39
37
  */
40
38
  declare const decodeDocument: (encoded: string) => string;
41
39
  declare const isTransferableAsset: (document: any) => boolean;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Transaction Cancel Module
3
+ *
4
+ * Cancels a pending transaction by replacing it with a 0-value transaction to self
5
+ * using the same nonce and a higher gas price (e.g. 2x when using --transaction-hash).
6
+ * @see https://info.etherscan.com/how-to-cancel-ethereum-pending-transactions/
7
+ */
8
+ interface CancelTransactionOptions {
9
+ /** Pending transaction nonce (use with gasPrice, or use transactionHash instead) */
10
+ nonce?: string;
11
+ /** Gas price higher than the pending transaction (use with nonce) */
12
+ gasPrice?: string;
13
+ /** Pending transaction hash; nonce and gas price will be fetched and gas price increased by 100% */
14
+ transactionHash?: string;
15
+ }
16
+ /** Signer with provider, compatible with ethers v5 and v6. */
17
+ interface CancelTransactionSigner {
18
+ getAddress(): Promise<string>;
19
+ provider: {
20
+ getTransaction?(hash: string): Promise<unknown>;
21
+ } | null;
22
+ sendTransaction(tx: Record<string, unknown>): Promise<{
23
+ hash: string;
24
+ }>;
25
+ }
26
+ /**
27
+ * Cancels a pending transaction by sending a 0-value transaction to self with the same nonce
28
+ * and a higher gas price. Supports both ethers v5 and v6 signers via a unified transaction object.
29
+ * @param {CancelTransactionSigner} signer - Signer with provider (ethers v5 or v6)
30
+ * @param {CancelTransactionOptions} options - Either (nonce + gasPrice) or transactionHash
31
+ * @returns {Promise<string>} The replacement transaction hash
32
+ * @throws If neither (nonce and gasPrice) nor transactionHash is provided
33
+ * @throws If provider is not available on signer
34
+ */
35
+ declare const cancelTransaction: (signer: CancelTransactionSigner, options: CancelTransactionOptions) => Promise<string>;
36
+
37
+ export { type CancelTransactionOptions, type CancelTransactionSigner, cancelTransaction };
@@ -0,0 +1 @@
1
+ export { CancelTransactionOptions, CancelTransactionSigner, cancelTransaction } from './cancel.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trustvc/trustvc",
3
- "version": "2.10.0",
3
+ "version": "2.11.1",
4
4
  "description": "TrustVC library",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",