From 1ee11e8a14d585af5c341fa5b3ae1c8c549e6e75 Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Thu, 12 Feb 2026 17:34:44 +0700 Subject: [PATCH 01/41] add interface for multisig account wallet --- src/wallet-account-multisig-read-only.js | 68 +++++++++++++ src/wallet-account-multisig.js | 116 +++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 src/wallet-account-multisig-read-only.js create mode 100644 src/wallet-account-multisig.js diff --git a/src/wallet-account-multisig-read-only.js b/src/wallet-account-multisig-read-only.js new file mode 100644 index 0000000..e661f9f --- /dev/null +++ b/src/wallet-account-multisig-read-only.js @@ -0,0 +1,68 @@ +// src/wallet-account-multisig-read-only.js + +'use strict' + +import { NotImplementedError } from './errors.js' + +/** + * A chain-agnostic proposal object representing a pending multisig transaction. + * + * @typedef {Object} MultisigProposal + * @property {string} proposalId - Unique identifier for the created proposal + * @property {number} confirmations - Number of confirmations + * @property {number} threshold - Required threshold for execution + */ + +/** @interface */ +export class IWalletAccountMultisigReadOnly { + + /** + * Returns the address/identifier of the signer associated with this account + * (the individual key, not the multisig address). + * Returns null if no signer is associated. + * + * @returns {Promise} The signer's identifier + */ + async getSignerAddress () { + throw new NotImplementedError('getSignerAddress()') + } + + /** + * Returns the list of owners/co-signers of the multisig wallet. + * The format of each entry is chain-specific (address, pubkey, etc). + * + * @returns {Promise} Array of owner identifiers + */ + async getOwners () { + throw new NotImplementedError('getOwners()') + } + + /** + * Returns the number of required signatures to execute a transaction. + * + * @returns {Promise} The threshold + */ + async getThreshold () { + throw new NotImplementedError('getThreshold()') + } + + /** + * Returns a proposal by its identifier. + * + * @param {string} proposalId - The proposal identifier + * @returns {Promise} The proposal details, or null if not found + */ + async getProposal (proposalId) { + throw new NotImplementedError('getProposal(proposalId)') + } + + /** + * Checks if a proposal has enough signatures to be executed. + * + * @param {string} proposalId - The proposal identifier + * @returns {Promise} True if ready to execute + */ + async isReadyToExecute (proposalId) { + throw new NotImplementedError('isReadyToExecute(proposalId)') + } +} \ No newline at end of file diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js new file mode 100644 index 0000000..835beea --- /dev/null +++ b/src/wallet-account-multisig.js @@ -0,0 +1,116 @@ +// src/wallet-account-multisig.js + +'use strict' + +import { IWalletAccountMultisigReadOnly } from './wallet-account-multisig-read-only.js' +import { NotImplementedError } from './errors.js' + +/** + * @typedef {Object} MultisigResult + * @property {string} proposalId - Unique identifier for the created proposal + * @property {number} confirmations - Number of confirmations + * @property {number} threshold - Required threshold for execution + */ + +/** + * @typedef {Object} MultisigExecuteResult + * @property {string} hash - The finalized on-chain transaction identifier + */ + +/** @interface */ +export class IWalletAccountMultisig extends IWalletAccountMultisigReadOnly { + // ========================================== + // Proposal Lifecycle + // ========================================== + /** + * Creates a new proposal for a transaction. + * The proposer's signature is included automatically. + * + * + * @param {import('./wallet-account-read-only.js').Transaction} tx - The transaction to propose + * @returns {Promise} The proposal result + */ + async propose (tx) { + throw new NotImplementedError('propose(tx)') + } + + /** + * Adds the current signer's approval to an existing proposal. + * + * @param {string} proposalId - The proposal identifier to approve + * @returns {Promise} The approval result + */ + async approve (proposalId) { + throw new NotImplementedError('approve(proposalId)') + } + + /** + * Rejects a pending proposal. + * Behavior is chain-specific: some chains support on-chain rejection, + * others may create a competing proposal or simply record disapproval. + * + * @param {string} proposalId - The proposal identifier to reject + * @returns {Promise} The rejection result + */ + async reject (proposalId) { + throw new NotImplementedError('reject(proposalId)') + } + + /** + * Submits a fully-signed proposal for on-chain execution. + * + * @param {string} proposalId - The proposal identifier to execute + * @returns {Promise} The transaction hash + */ + async execute (proposalId) { + throw new NotImplementedError('execute(proposalId)') + } + + // ========================================== + // Owner Management + // ========================================== + + /** + * Proposes adding a new owner to the multisig. + * throw NotImplementedError if not supported. + * + * @param {string} owner - The new owner's identifier (address/pubkey) + * @param {number} [newThreshold] - Optional new threshold + * @returns {Promise} The proposal result + */ + async addOwner (owner, newThreshold) { + throw new NotImplementedError('addOwner(owner, newThreshold)') + } + + /** + * Proposes removing an owner from the multisig. + * + * @param {string} owner - The owner's identifier to remove + * @param {number} [newThreshold] - Optional new threshold + * @returns {Promise} The proposal result + */ + async removeOwner (owner, newThreshold) { + throw new NotImplementedError('removeOwner(owner, newThreshold)') + } + + /** + * Proposes replacing one owner with another. + * + * @param {string} oldOwner - The owner to replace + * @param {string} newOwner - The replacement owner + * @returns {Promise} The proposal result + */ + async swapOwner (oldOwner, newOwner) { + throw new NotImplementedError('swapOwner(oldOwner, newOwner)') + } + + /** + * Proposes changing the signature threshold. + * + * @param {number} newThreshold - The new threshold + * @returns {Promise} The proposal result + */ + async changeThreshold (newThreshold) { + throw new NotImplementedError('changeThreshold(newThreshold)') + } +} \ No newline at end of file From 88f148e1cb9b4b8184b5d0813baab6e923010732 Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Thu, 12 Feb 2026 17:42:21 +0700 Subject: [PATCH 02/41] build types --- index.js | 8 ++ package-lock.json | 18 ++-- types/index.d.ts | 11 ++- .../wallet-account-multisig-read-only.d.ts | 63 ++++++++++++ types/src/wallet-account-multisig.d.ts | 98 +++++++++++++++++++ 5 files changed, 186 insertions(+), 12 deletions(-) create mode 100644 types/src/wallet-account-multisig-read-only.d.ts create mode 100644 types/src/wallet-account-multisig.d.ts diff --git a/index.js b/index.js index 62fb463..2a7e742 100644 --- a/index.js +++ b/index.js @@ -23,10 +23,18 @@ /** @typedef {import('./src/wallet-account.js').KeyPair} KeyPair */ +/** @typedef {import('./src/wallet-account-multisig-read-only.js').MultisigProposal} MultisigProposal */ +/** @typedef {import('./src/wallet-account-multisig.js').MultisigResult} MultisigResult */ +/** @typedef {import('./src/wallet-account-multisig.js').MultisigExecuteResult} MultisigExecuteResult */ + export { default } from './src/wallet-manager.js' export { default as WalletAccountReadOnly, IWalletAccountReadOnly } from './src/wallet-account-read-only.js' export { IWalletAccount } from './src/wallet-account.js' +export { IWalletAccountMultisigReadOnly } from './src/wallet-account-multisig-read-only.js' + +export { IWalletAccountMultisig } from './src/wallet-account-multisig.js' + export { NotImplementedError } from './src/errors.js' diff --git a/package-lock.json b/package-lock.json index a2c7e45..1d49832 100644 --- a/package-lock.json +++ b/package-lock.json @@ -576,9 +576,9 @@ "license": "Python-2.0" }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -3792,9 +3792,9 @@ } }, "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -5766,9 +5766,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/types/index.d.ts b/types/index.d.ts index 2063d06..2bee684 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,3 +1,8 @@ +export { default } from "./src/wallet-manager.js"; +export { IWalletAccount } from "./src/wallet-account.js"; +export { IWalletAccountMultisigReadOnly } from "./src/wallet-account-multisig-read-only.js"; +export { IWalletAccountMultisig } from "./src/wallet-account-multisig.js"; +export { NotImplementedError } from "./src/errors.js"; export type FeeRates = import("./src/wallet-manager.js").FeeRates; export type WalletConfig = import("./src/wallet-manager.js").WalletConfig; export type Transaction = import("./src/wallet-account-read-only.js").Transaction; @@ -5,7 +10,7 @@ export type TransactionResult = import("./src/wallet-account-read-only.js").Tran export type TransferOptions = import("./src/wallet-account-read-only.js").TransferOptions; export type TransferResult = import("./src/wallet-account-read-only.js").TransferResult; export type KeyPair = import("./src/wallet-account.js").KeyPair; -export { default } from "./src/wallet-manager.js"; +export type MultisigProposal = import("./src/wallet-account-multisig-read-only.js").MultisigProposal; +export type MultisigResult = import("./src/wallet-account-multisig.js").MultisigResult; +export type MultisigExecuteResult = import("./src/wallet-account-multisig.js").MultisigExecuteResult; export { default as WalletAccountReadOnly, IWalletAccountReadOnly } from "./src/wallet-account-read-only.js"; -export { IWalletAccount } from "./src/wallet-account.js"; -export { NotImplementedError } from "./src/errors.js"; diff --git a/types/src/wallet-account-multisig-read-only.d.ts b/types/src/wallet-account-multisig-read-only.d.ts new file mode 100644 index 0000000..a32cb19 --- /dev/null +++ b/types/src/wallet-account-multisig-read-only.d.ts @@ -0,0 +1,63 @@ +/** + * A chain-agnostic proposal object representing a pending multisig transaction. + * + * @typedef {Object} MultisigProposal + * @property {string} proposalId - Unique identifier for the created proposal + * @property {number} confirmations - Number of confirmations + * @property {number} threshold - Required threshold for execution + */ +/** @interface */ +export class IWalletAccountMultisigReadOnly { + /** + * Returns the address/identifier of the signer associated with this account + * (the individual key, not the multisig address). + * Returns null if no signer is associated. + * + * @returns {Promise} The signer's identifier + */ + getSignerAddress(): Promise; + /** + * Returns the list of owners/co-signers of the multisig wallet. + * The format of each entry is chain-specific (address, pubkey, etc). + * + * @returns {Promise} Array of owner identifiers + */ + getOwners(): Promise; + /** + * Returns the number of required signatures to execute a transaction. + * + * @returns {Promise} The threshold + */ + getThreshold(): Promise; + /** + * Returns a proposal by its identifier. + * + * @param {string} proposalId - The proposal identifier + * @returns {Promise} The proposal details, or null if not found + */ + getProposal(proposalId: string): Promise; + /** + * Checks if a proposal has enough signatures to be executed. + * + * @param {string} proposalId - The proposal identifier + * @returns {Promise} True if ready to execute + */ + isReadyToExecute(proposalId: string): Promise; +} +/** + * A chain-agnostic proposal object representing a pending multisig transaction. + */ +export type MultisigProposal = { + /** + * - Unique identifier for the created proposal + */ + proposalId: string; + /** + * - Number of confirmations + */ + confirmations: number; + /** + * - Required threshold for execution + */ + threshold: number; +}; diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts new file mode 100644 index 0000000..e09b4bf --- /dev/null +++ b/types/src/wallet-account-multisig.d.ts @@ -0,0 +1,98 @@ +/** + * @typedef {Object} MultisigResult + * @property {string} proposalId - Unique identifier for the created proposal + * @property {number} confirmations - Number of confirmations + * @property {number} threshold - Required threshold for execution + */ +/** + * @typedef {Object} MultisigExecuteResult + * @property {string} hash - The finalized on-chain transaction identifier + */ +/** @interface */ +export class IWalletAccountMultisig extends IWalletAccountMultisigReadOnly { + /** + * Creates a new proposal for a transaction. + * The proposer's signature is included automatically. + * + * + * @param {import('./wallet-account-read-only.js').Transaction} tx - The transaction to propose + * @returns {Promise} The proposal result + */ + propose(tx: import("./wallet-account-read-only.js").Transaction): Promise; + /** + * Adds the current signer's approval to an existing proposal. + * + * @param {string} proposalId - The proposal identifier to approve + * @returns {Promise} The approval result + */ + approve(proposalId: string): Promise; + /** + * Rejects a pending proposal. + * Behavior is chain-specific: some chains support on-chain rejection, + * others may create a competing proposal or simply record disapproval. + * + * @param {string} proposalId - The proposal identifier to reject + * @returns {Promise} The rejection result + */ + reject(proposalId: string): Promise; + /** + * Submits a fully-signed proposal for on-chain execution. + * + * @param {string} proposalId - The proposal identifier to execute + * @returns {Promise} The transaction hash + */ + execute(proposalId: string): Promise; + /** + * Proposes adding a new owner to the multisig. + * throw NotImplementedError if not supported. + * + * @param {string} owner - The new owner's identifier (address/pubkey) + * @param {number} [newThreshold] - Optional new threshold + * @returns {Promise} The proposal result + */ + addOwner(owner: string, newThreshold?: number): Promise; + /** + * Proposes removing an owner from the multisig. + * + * @param {string} owner - The owner's identifier to remove + * @param {number} [newThreshold] - Optional new threshold + * @returns {Promise} The proposal result + */ + removeOwner(owner: string, newThreshold?: number): Promise; + /** + * Proposes replacing one owner with another. + * + * @param {string} oldOwner - The owner to replace + * @param {string} newOwner - The replacement owner + * @returns {Promise} The proposal result + */ + swapOwner(oldOwner: string, newOwner: string): Promise; + /** + * Proposes changing the signature threshold. + * + * @param {number} newThreshold - The new threshold + * @returns {Promise} The proposal result + */ + changeThreshold(newThreshold: number): Promise; +} +export type MultisigResult = { + /** + * - Unique identifier for the created proposal + */ + proposalId: string; + /** + * - Number of confirmations + */ + confirmations: number; + /** + * - Required threshold for execution + */ + threshold: number; +}; +export type MultisigExecuteResult = { + /** + * - The finalized on-chain transaction identifier + */ + hash: string; +}; +import { IWalletAccountMultisigReadOnly } from './wallet-account-multisig-read-only.js'; From 32ffd8221a3d1bb1db2b03f06904e1e67e75cabc Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Thu, 12 Feb 2026 17:43:15 +0700 Subject: [PATCH 03/41] lint --- src/wallet-account-multisig-read-only.js | 3 +-- src/wallet-account-multisig.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/wallet-account-multisig-read-only.js b/src/wallet-account-multisig-read-only.js index e661f9f..cf7a910 100644 --- a/src/wallet-account-multisig-read-only.js +++ b/src/wallet-account-multisig-read-only.js @@ -15,7 +15,6 @@ import { NotImplementedError } from './errors.js' /** @interface */ export class IWalletAccountMultisigReadOnly { - /** * Returns the address/identifier of the signer associated with this account * (the individual key, not the multisig address). @@ -65,4 +64,4 @@ export class IWalletAccountMultisigReadOnly { async isReadyToExecute (proposalId) { throw new NotImplementedError('isReadyToExecute(proposalId)') } -} \ No newline at end of file +} diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index 835beea..31a0bb9 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -113,4 +113,4 @@ export class IWalletAccountMultisig extends IWalletAccountMultisigReadOnly { async changeThreshold (newThreshold) { throw new NotImplementedError('changeThreshold(newThreshold)') } -} \ No newline at end of file +} From 58d3535b667eb914d6d99957864416f83286b10c Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Fri, 13 Feb 2026 09:44:45 +0700 Subject: [PATCH 04/41] add propose and approve message interface for multisig --- index.js | 3 ++ src/wallet-account-multisig-read-only.js | 19 +++++-- src/wallet-account-multisig.js | 34 ++++++++++++- types/index.d.ts | 2 + .../wallet-account-multisig-read-only.d.ts | 38 ++++++++++++-- types/src/wallet-account-multisig.d.ts | 49 ++++++++++++++++++- 6 files changed, 132 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 2a7e742..eceb23c 100644 --- a/index.js +++ b/index.js @@ -24,8 +24,11 @@ /** @typedef {import('./src/wallet-account.js').KeyPair} KeyPair */ /** @typedef {import('./src/wallet-account-multisig-read-only.js').MultisigProposal} MultisigProposal */ +/** @typedef {import('./src/wallet-account-multisig-read-only.js').MessageInfo} MessageInfo */ + /** @typedef {import('./src/wallet-account-multisig.js').MultisigResult} MultisigResult */ /** @typedef {import('./src/wallet-account-multisig.js').MultisigExecuteResult} MultisigExecuteResult */ +/** @typedef {import('./src/wallet-account-multisig.js').MessageProposal} MessageProposal */ export { default } from './src/wallet-manager.js' diff --git a/src/wallet-account-multisig-read-only.js b/src/wallet-account-multisig-read-only.js index cf7a910..c04a212 100644 --- a/src/wallet-account-multisig-read-only.js +++ b/src/wallet-account-multisig-read-only.js @@ -13,6 +13,15 @@ import { NotImplementedError } from './errors.js' * @property {number} threshold - Required threshold for execution */ +/** + * @typedef {Object} MessageInfo + * @property {string} messageHash - The message hash + * @property {string} message - The original message + * @property {number} confirmations - Number of confirmations + * @property {number} threshold - Required threshold + * @property {string | null} combinedSignature - Final combined signature when threshold is met + */ + /** @interface */ export class IWalletAccountMultisigReadOnly { /** @@ -56,12 +65,12 @@ export class IWalletAccountMultisigReadOnly { } /** - * Checks if a proposal has enough signatures to be executed. + * Returns a message proposal by its hash. * - * @param {string} proposalId - The proposal identifier - * @returns {Promise} True if ready to execute + * @param {string} messageHash - The message hash + * @returns {Promise} The message info or null if not found */ - async isReadyToExecute (proposalId) { - throw new NotImplementedError('isReadyToExecute(proposalId)') + async getMessage (messageHash) { + throw new NotImplementedError('getMessage(messageHash)') } } diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index 31a0bb9..07c53b9 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -2,7 +2,7 @@ 'use strict' -import { IWalletAccountMultisigReadOnly } from './wallet-account-multisig-read-only.js' +import { IWalletAccount } from './wallet-account.js' import { NotImplementedError } from './errors.js' /** @@ -17,8 +17,38 @@ import { NotImplementedError } from './errors.js' * @property {string} hash - The finalized on-chain transaction identifier */ +/** + * @typedef {Object} MessageProposal + * @property {string} messageHash - Unique identifier for the message proposal + * @property {string} signature - This signer's signature + * @property {number} confirmations - Number of confirmations + * @property {number} threshold - Required threshold + * @property {string | null} combinedSignature - Final combined signature when threshold is met + */ + /** @interface */ -export class IWalletAccountMultisig extends IWalletAccountMultisigReadOnly { +export class IWalletAccountMultisig extends IWalletAccount { + /** + * Proposes signing a message with the multisig. + * The proposer's signature is included automatically. + * + * @param {string} message - The message to sign + * @returns {Promise} The message proposal result + */ + async proposeMessage (message) { + throw new NotImplementedError('proposeMessage(message)') + } + + /** + * Approves an existing message proposal. + * + * @param {string} messageHash - The message hash to approve + * @returns {Promise} The approval result + */ + async approveMessage (messageHash) { + throw new NotImplementedError('approveMessage(messageHash)') + } + // ========================================== // Proposal Lifecycle // ========================================== diff --git a/types/index.d.ts b/types/index.d.ts index 2bee684..dc4f747 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -11,6 +11,8 @@ export type TransferOptions = import("./src/wallet-account-read-only.js").Transf export type TransferResult = import("./src/wallet-account-read-only.js").TransferResult; export type KeyPair = import("./src/wallet-account.js").KeyPair; export type MultisigProposal = import("./src/wallet-account-multisig-read-only.js").MultisigProposal; +export type MessageInfo = import("./src/wallet-account-multisig-read-only.js").MessageInfo; export type MultisigResult = import("./src/wallet-account-multisig.js").MultisigResult; export type MultisigExecuteResult = import("./src/wallet-account-multisig.js").MultisigExecuteResult; +export type MessageProposal = import("./src/wallet-account-multisig.js").MessageProposal; export { default as WalletAccountReadOnly, IWalletAccountReadOnly } from "./src/wallet-account-read-only.js"; diff --git a/types/src/wallet-account-multisig-read-only.d.ts b/types/src/wallet-account-multisig-read-only.d.ts index a32cb19..8ba64a2 100644 --- a/types/src/wallet-account-multisig-read-only.d.ts +++ b/types/src/wallet-account-multisig-read-only.d.ts @@ -6,6 +6,14 @@ * @property {number} confirmations - Number of confirmations * @property {number} threshold - Required threshold for execution */ +/** + * @typedef {Object} MessageInfo + * @property {string} messageHash - The message hash + * @property {string} message - The original message + * @property {number} confirmations - Number of confirmations + * @property {number} threshold - Required threshold + * @property {string | null} combinedSignature - Final combined signature when threshold is met + */ /** @interface */ export class IWalletAccountMultisigReadOnly { /** @@ -37,12 +45,12 @@ export class IWalletAccountMultisigReadOnly { */ getProposal(proposalId: string): Promise; /** - * Checks if a proposal has enough signatures to be executed. + * Returns a message proposal by its hash. * - * @param {string} proposalId - The proposal identifier - * @returns {Promise} True if ready to execute + * @param {string} messageHash - The message hash + * @returns {Promise} The message info or null if not found */ - isReadyToExecute(proposalId: string): Promise; + getMessage(messageHash: string): Promise; } /** * A chain-agnostic proposal object representing a pending multisig transaction. @@ -61,3 +69,25 @@ export type MultisigProposal = { */ threshold: number; }; +export type MessageInfo = { + /** + * - The message hash + */ + messageHash: string; + /** + * - The original message + */ + message: string; + /** + * - Number of confirmations + */ + confirmations: number; + /** + * - Required threshold + */ + threshold: number; + /** + * - Final combined signature when threshold is met + */ + combinedSignature: string | null; +}; diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts index e09b4bf..14f637d 100644 --- a/types/src/wallet-account-multisig.d.ts +++ b/types/src/wallet-account-multisig.d.ts @@ -8,8 +8,31 @@ * @typedef {Object} MultisigExecuteResult * @property {string} hash - The finalized on-chain transaction identifier */ +/** + * @typedef {Object} MessageProposal + * @property {string} messageHash - Unique identifier for the message proposal + * @property {string} signature - This signer's signature + * @property {number} confirmations - Number of confirmations + * @property {number} threshold - Required threshold + * @property {string | null} combinedSignature - Final combined signature when threshold is met + */ /** @interface */ -export class IWalletAccountMultisig extends IWalletAccountMultisigReadOnly { +export class IWalletAccountMultisig extends IWalletAccount { + /** + * Proposes signing a message with the multisig. + * The proposer's signature is included automatically. + * + * @param {string} message - The message to sign + * @returns {Promise} The message proposal result + */ + proposeMessage(message: string): Promise; + /** + * Approves an existing message proposal. + * + * @param {string} messageHash - The message hash to approve + * @returns {Promise} The approval result + */ + approveMessage(messageHash: string): Promise; /** * Creates a new proposal for a transaction. * The proposer's signature is included automatically. @@ -95,4 +118,26 @@ export type MultisigExecuteResult = { */ hash: string; }; -import { IWalletAccountMultisigReadOnly } from './wallet-account-multisig-read-only.js'; +export type MessageProposal = { + /** + * - Unique identifier for the message proposal + */ + messageHash: string; + /** + * - This signer's signature + */ + signature: string; + /** + * - Number of confirmations + */ + confirmations: number; + /** + * - Required threshold + */ + threshold: number; + /** + * - Final combined signature when threshold is met + */ + combinedSignature: string | null; +}; +import { IWalletAccount } from './wallet-account.js'; From 8773e06a2656f45608a692835d122188eb8f560e Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Fri, 13 Feb 2026 10:47:45 +0700 Subject: [PATCH 05/41] add MultisigInfo object --- index.js | 1 + src/wallet-account-multisig-read-only.js | 22 +++++++++++++++++++--- types/index.d.ts | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index eceb23c..9dc9b89 100644 --- a/index.js +++ b/index.js @@ -23,6 +23,7 @@ /** @typedef {import('./src/wallet-account.js').KeyPair} KeyPair */ +/** @typedef {import('./src/wallet-account-multisig-read-only.js').MultisigInfo} MultisigInfo */ /** @typedef {import('./src/wallet-account-multisig-read-only.js').MultisigProposal} MultisigProposal */ /** @typedef {import('./src/wallet-account-multisig-read-only.js').MessageInfo} MessageInfo */ diff --git a/src/wallet-account-multisig-read-only.js b/src/wallet-account-multisig-read-only.js index c04a212..5c1181c 100644 --- a/src/wallet-account-multisig-read-only.js +++ b/src/wallet-account-multisig-read-only.js @@ -5,7 +5,15 @@ import { NotImplementedError } from './errors.js' /** - * A chain-agnostic proposal object representing a pending multisig transaction. + * @typedef {Object} MultisigInfo + * @property {string} address - The multisig address + * @property {string[]} owners - Array of owner identifiers + * @property {number} threshold - Required number of signatures + * @property {boolean} [isCreated] - Whether the multisig wallet has been created + */ + +/** + * A proposal object representing a pending multisig transaction. * * @typedef {Object} MultisigProposal * @property {string} proposalId - Unique identifier for the created proposal @@ -25,8 +33,7 @@ import { NotImplementedError } from './errors.js' /** @interface */ export class IWalletAccountMultisigReadOnly { /** - * Returns the address/identifier of the signer associated with this account - * (the individual key, not the multisig address). + * Returns the address/identifier of the signer associated with this account wallet * Returns null if no signer is associated. * * @returns {Promise} The signer's identifier @@ -35,6 +42,15 @@ export class IWalletAccountMultisigReadOnly { throw new NotImplementedError('getSignerAddress()') } + /** + * Returns the multisig wallet info. + * + * @returns {Promise} The multisig info + */ + async getMultisigInfo () { + throw new NotImplementedError('getMultisigInfo()') + } + /** * Returns the list of owners/co-signers of the multisig wallet. * The format of each entry is chain-specific (address, pubkey, etc). diff --git a/types/index.d.ts b/types/index.d.ts index dc4f747..884e182 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -10,6 +10,7 @@ export type TransactionResult = import("./src/wallet-account-read-only.js").Tran export type TransferOptions = import("./src/wallet-account-read-only.js").TransferOptions; export type TransferResult = import("./src/wallet-account-read-only.js").TransferResult; export type KeyPair = import("./src/wallet-account.js").KeyPair; +export type MultisigInfo = import("./src/wallet-account-multisig-read-only.js").MultisigInfo; export type MultisigProposal = import("./src/wallet-account-multisig-read-only.js").MultisigProposal; export type MessageInfo = import("./src/wallet-account-multisig-read-only.js").MessageInfo; export type MultisigResult = import("./src/wallet-account-multisig.js").MultisigResult; From 7f89ab54787df13e050c99f8bfce68e278839ea3 Mon Sep 17 00:00:00 2001 From: Jonathan Dunne Date: Sat, 14 Feb 2026 22:16:20 +1100 Subject: [PATCH 06/41] fix: IWalletAccountMultisigReadOnly as an interface Co-authored-by: Davide Casale --- types/src/wallet-account-multisig-read-only.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/src/wallet-account-multisig-read-only.d.ts b/types/src/wallet-account-multisig-read-only.d.ts index 8ba64a2..46505b3 100644 --- a/types/src/wallet-account-multisig-read-only.d.ts +++ b/types/src/wallet-account-multisig-read-only.d.ts @@ -15,7 +15,7 @@ * @property {string | null} combinedSignature - Final combined signature when threshold is met */ /** @interface */ -export class IWalletAccountMultisigReadOnly { +export interface IWalletAccountMultisigReadOnly { /** * Returns the address/identifier of the signer associated with this account * (the individual key, not the multisig address). From 95f8f3e876d8b69f270a980cb50545bf797fda9f Mon Sep 17 00:00:00 2001 From: Jonathan Dunne Date: Sat, 14 Feb 2026 22:18:35 +1100 Subject: [PATCH 07/41] fix: IWalletAccountMultisig as an interface Co-authored-by: Davide Casale --- types/src/wallet-account-multisig.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts index 14f637d..6544f8c 100644 --- a/types/src/wallet-account-multisig.d.ts +++ b/types/src/wallet-account-multisig.d.ts @@ -17,7 +17,7 @@ * @property {string | null} combinedSignature - Final combined signature when threshold is met */ /** @interface */ -export class IWalletAccountMultisig extends IWalletAccount { +export interface IWalletAccountMultisig extends IWalletAccount { /** * Proposes signing a message with the multisig. * The proposer's signature is included automatically. From 206d1e9aa302c09fa786217b0c1feaadcae270c2 Mon Sep 17 00:00:00 2001 From: Sunday <38057360+quocle108@users.noreply.github.com> Date: Mon, 16 Feb 2026 06:53:04 +0700 Subject: [PATCH 08/41] Update src/wallet-account-multisig-read-only.js Co-authored-by: Davide Casale --- src/wallet-account-multisig-read-only.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/wallet-account-multisig-read-only.js b/src/wallet-account-multisig-read-only.js index 5c1181c..99ac523 100644 --- a/src/wallet-account-multisig-read-only.js +++ b/src/wallet-account-multisig-read-only.js @@ -1,4 +1,16 @@ -// src/wallet-account-multisig-read-only.js +// Copyright 2024 Tether Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. 'use strict' From 69851edd92ab3eb2b170d8bbea4f9722e78f9f57 Mon Sep 17 00:00:00 2001 From: Sunday <38057360+quocle108@users.noreply.github.com> Date: Mon, 16 Feb 2026 06:53:40 +0700 Subject: [PATCH 09/41] Update src/wallet-account-multisig.js Co-authored-by: Davide Casale --- src/wallet-account-multisig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index 07c53b9..865ea57 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -57,7 +57,7 @@ export class IWalletAccountMultisig extends IWalletAccount { * The proposer's signature is included automatically. * * - * @param {import('./wallet-account-read-only.js').Transaction} tx - The transaction to propose + * @param {Transaction} tx - The transaction to propose * @returns {Promise} The proposal result */ async propose (tx) { From 8980e74cc2039969a16b1706edc3521562e10893 Mon Sep 17 00:00:00 2001 From: Sunday <38057360+quocle108@users.noreply.github.com> Date: Mon, 16 Feb 2026 06:53:54 +0700 Subject: [PATCH 10/41] Update src/wallet-account-multisig.js Co-authored-by: Davide Casale --- src/wallet-account-multisig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index 865ea57..b94ed75 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -27,7 +27,7 @@ import { NotImplementedError } from './errors.js' */ /** @interface */ -export class IWalletAccountMultisig extends IWalletAccount { +export class IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { /** * Proposes signing a message with the multisig. * The proposer's signature is included automatically. From eb43fd4f6e2c52dbbfb830aa9e346f8998aa2e6e Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Mon, 16 Feb 2026 09:01:45 +0700 Subject: [PATCH 11/41] refactor: update multisig interface hierarchy and naming --- index.js | 10 +- src/wallet-account-multisig.js | 25 ++-- ...s => wallet-account-read-only-multisig.js} | 43 ++----- types/index.d.ts | 10 +- types/src/wallet-account-multisig.d.ts | 29 +---- .../wallet-account-read-only-multisig.d.ts | 107 ++++++++++++++++++ 6 files changed, 151 insertions(+), 73 deletions(-) rename src/{wallet-account-multisig-read-only.js => wallet-account-read-only-multisig.js} (64%) create mode 100644 types/src/wallet-account-read-only-multisig.d.ts diff --git a/index.js b/index.js index 9dc9b89..a7fd4dd 100644 --- a/index.js +++ b/index.js @@ -23,11 +23,11 @@ /** @typedef {import('./src/wallet-account.js').KeyPair} KeyPair */ -/** @typedef {import('./src/wallet-account-multisig-read-only.js').MultisigInfo} MultisigInfo */ -/** @typedef {import('./src/wallet-account-multisig-read-only.js').MultisigProposal} MultisigProposal */ -/** @typedef {import('./src/wallet-account-multisig-read-only.js').MessageInfo} MessageInfo */ +/** @typedef {import('./src/wallet-account-read-only-multisig.js').MultisigInfo} MultisigInfo */ +/** @typedef {import('./src/wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ +/** @typedef {import('./src/wallet-account-read-only-multisig.js').MessageInfo} MessageInfo */ -/** @typedef {import('./src/wallet-account-multisig.js').MultisigResult} MultisigResult */ +/** @typedef {import('./src/wallet-account-multisig.js').MultisigProposal} MultisigResult */ /** @typedef {import('./src/wallet-account-multisig.js').MultisigExecuteResult} MultisigExecuteResult */ /** @typedef {import('./src/wallet-account-multisig.js').MessageProposal} MessageProposal */ @@ -37,7 +37,7 @@ export { default as WalletAccountReadOnly, IWalletAccountReadOnly } from './src/ export { IWalletAccount } from './src/wallet-account.js' -export { IWalletAccountMultisigReadOnly } from './src/wallet-account-multisig-read-only.js' +export { IWalletAccountReadOnlyMultisig } from './src/wallet-account-read-only-multisig.js' export { IWalletAccountMultisig } from './src/wallet-account-multisig.js' diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index b94ed75..c009ce2 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -1,16 +1,24 @@ -// src/wallet-account-multisig.js +// Copyright 2024 Tether Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. 'use strict' import { IWalletAccount } from './wallet-account.js' import { NotImplementedError } from './errors.js' -/** - * @typedef {Object} MultisigResult - * @property {string} proposalId - Unique identifier for the created proposal - * @property {number} confirmations - Number of confirmations - * @property {number} threshold - Required threshold for execution - */ +/** @typedef {import('./wallet-account-read-only.js').Transaction} Transaction */ +/** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ /** * @typedef {Object} MultisigExecuteResult @@ -27,7 +35,7 @@ import { NotImplementedError } from './errors.js' */ /** @interface */ -export class IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { +export class IWalletAccountMultisig extends IWalletAccount { /** * Proposes signing a message with the multisig. * The proposer's signature is included automatically. @@ -56,7 +64,6 @@ export class IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * Creates a new proposal for a transaction. * The proposer's signature is included automatically. * - * * @param {Transaction} tx - The transaction to propose * @returns {Promise} The proposal result */ diff --git a/src/wallet-account-multisig-read-only.js b/src/wallet-account-read-only-multisig.js similarity index 64% rename from src/wallet-account-multisig-read-only.js rename to src/wallet-account-read-only-multisig.js index 99ac523..b5ba784 100644 --- a/src/wallet-account-multisig-read-only.js +++ b/src/wallet-account-read-only-multisig.js @@ -14,6 +14,7 @@ 'use strict' +import { IWalletAccountReadOnly } from './wallet-account-read-only.js' import { NotImplementedError } from './errors.js' /** @@ -25,7 +26,6 @@ import { NotImplementedError } from './errors.js' */ /** - * A proposal object representing a pending multisig transaction. * * @typedef {Object} MultisigProposal * @property {string} proposalId - Unique identifier for the created proposal @@ -43,7 +43,7 @@ import { NotImplementedError } from './errors.js' */ /** @interface */ -export class IWalletAccountMultisigReadOnly { +export class IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { /** * Returns the address/identifier of the signer associated with this account wallet * Returns null if no signer is associated. @@ -64,41 +64,22 @@ export class IWalletAccountMultisigReadOnly { } /** - * Returns the list of owners/co-signers of the multisig wallet. - * The format of each entry is chain-specific (address, pubkey, etc). + * Returns a list of proposals by their identifiers. * - * @returns {Promise} Array of owner identifiers + * @param {string[]} proposalIds - The list of proposal identifiers + * @returns {Promise<(MultisigProposal | null)[]>} The proposal details, or null for proposals not found */ - async getOwners () { - throw new NotImplementedError('getOwners()') + async getProposals (proposalIds) { + throw new NotImplementedError('getProposals(proposalIds)') } /** - * Returns the number of required signatures to execute a transaction. + * Returns a list of message proposals by their hashes. * - * @returns {Promise} The threshold + * @param {string[]} messageHashes - The list of message hashes + * @returns {Promise<(MessageInfo | null)[]>} The message details, or null for messages not found */ - async getThreshold () { - throw new NotImplementedError('getThreshold()') - } - - /** - * Returns a proposal by its identifier. - * - * @param {string} proposalId - The proposal identifier - * @returns {Promise} The proposal details, or null if not found - */ - async getProposal (proposalId) { - throw new NotImplementedError('getProposal(proposalId)') - } - - /** - * Returns a message proposal by its hash. - * - * @param {string} messageHash - The message hash - * @returns {Promise} The message info or null if not found - */ - async getMessage (messageHash) { - throw new NotImplementedError('getMessage(messageHash)') + async getMessages (messageHashes) { + throw new NotImplementedError('getMessages(messageHashes)') } } diff --git a/types/index.d.ts b/types/index.d.ts index 884e182..eb61199 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,6 +1,6 @@ export { default } from "./src/wallet-manager.js"; export { IWalletAccount } from "./src/wallet-account.js"; -export { IWalletAccountMultisigReadOnly } from "./src/wallet-account-multisig-read-only.js"; +export { IWalletAccountReadOnlyMultisig } from "./src/wallet-account-read-only-multisig.js"; export { IWalletAccountMultisig } from "./src/wallet-account-multisig.js"; export { NotImplementedError } from "./src/errors.js"; export type FeeRates = import("./src/wallet-manager.js").FeeRates; @@ -10,10 +10,10 @@ export type TransactionResult = import("./src/wallet-account-read-only.js").Tran export type TransferOptions = import("./src/wallet-account-read-only.js").TransferOptions; export type TransferResult = import("./src/wallet-account-read-only.js").TransferResult; export type KeyPair = import("./src/wallet-account.js").KeyPair; -export type MultisigInfo = import("./src/wallet-account-multisig-read-only.js").MultisigInfo; -export type MultisigProposal = import("./src/wallet-account-multisig-read-only.js").MultisigProposal; -export type MessageInfo = import("./src/wallet-account-multisig-read-only.js").MessageInfo; -export type MultisigResult = import("./src/wallet-account-multisig.js").MultisigResult; +export type MultisigInfo = import("./src/wallet-account-read-only-multisig.js").MultisigInfo; +export type MultisigProposal = import("./src/wallet-account-read-only-multisig.js").MultisigProposal; +export type MessageInfo = import("./src/wallet-account-read-only-multisig.js").MessageInfo; +export type MultisigResult = import("./src/wallet-account-multisig.js").MultisigProposal; export type MultisigExecuteResult = import("./src/wallet-account-multisig.js").MultisigExecuteResult; export type MessageProposal = import("./src/wallet-account-multisig.js").MessageProposal; export { default as WalletAccountReadOnly, IWalletAccountReadOnly } from "./src/wallet-account-read-only.js"; diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts index 6544f8c..602db5b 100644 --- a/types/src/wallet-account-multisig.d.ts +++ b/types/src/wallet-account-multisig.d.ts @@ -1,9 +1,5 @@ -/** - * @typedef {Object} MultisigResult - * @property {string} proposalId - Unique identifier for the created proposal - * @property {number} confirmations - Number of confirmations - * @property {number} threshold - Required threshold for execution - */ +/** @typedef {import('./wallet-account-read-only.js').Transaction} Transaction */ +/** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ /** * @typedef {Object} MultisigExecuteResult * @property {string} hash - The finalized on-chain transaction identifier @@ -37,11 +33,10 @@ export interface IWalletAccountMultisig extends IWalletAccount { * Creates a new proposal for a transaction. * The proposer's signature is included automatically. * - * - * @param {import('./wallet-account-read-only.js').Transaction} tx - The transaction to propose + * @param {Transaction} tx - The transaction to propose * @returns {Promise} The proposal result */ - propose(tx: import("./wallet-account-read-only.js").Transaction): Promise; + propose(tx: Transaction): Promise; /** * Adds the current signer's approval to an existing proposal. * @@ -98,20 +93,8 @@ export interface IWalletAccountMultisig extends IWalletAccount { */ changeThreshold(newThreshold: number): Promise; } -export type MultisigResult = { - /** - * - Unique identifier for the created proposal - */ - proposalId: string; - /** - * - Number of confirmations - */ - confirmations: number; - /** - * - Required threshold for execution - */ - threshold: number; -}; +export type Transaction = import("./wallet-account-read-only.js").Transaction; +export type MultisigResult = import("./wallet-account-read-only-multisig.js").MultisigProposal; export type MultisigExecuteResult = { /** * - The finalized on-chain transaction identifier diff --git a/types/src/wallet-account-read-only-multisig.d.ts b/types/src/wallet-account-read-only-multisig.d.ts new file mode 100644 index 0000000..86ac627 --- /dev/null +++ b/types/src/wallet-account-read-only-multisig.d.ts @@ -0,0 +1,107 @@ +/** + * @typedef {Object} MultisigInfo + * @property {string} address - The multisig address + * @property {string[]} owners - Array of owner identifiers + * @property {number} threshold - Required number of signatures + * @property {boolean} [isCreated] - Whether the multisig wallet has been created + */ +/** + * + * @typedef {Object} MultisigProposal + * @property {string} proposalId - Unique identifier for the created proposal + * @property {number} confirmations - Number of confirmations + * @property {number} threshold - Required threshold for execution + */ +/** + * @typedef {Object} MessageInfo + * @property {string} messageHash - The message hash + * @property {string} message - The original message + * @property {number} confirmations - Number of confirmations + * @property {number} threshold - Required threshold + * @property {string | null} combinedSignature - Final combined signature when threshold is met + */ +/** @interface */ +export interface IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { + /** + * Returns the address/identifier of the signer associated with this account wallet + * Returns null if no signer is associated. + * + * @returns {Promise} The signer's identifier + */ + getSignerAddress(): Promise; + /** + * Returns the multisig wallet info. + * + * @returns {Promise} The multisig info + */ + getMultisigInfo(): Promise; + /** + * Returns a list of proposals by their identifiers. + * + * @param {string[]} proposalIds - The list of proposal identifiers + * @returns {Promise<(MultisigProposal | null)[]>} The proposal details, or null for proposals not found + */ + getProposals(proposalIds: string[]): Promise<(MultisigProposal | null)[]>; + /** + * Returns a list of message proposals by their hashes. + * + * @param {string[]} messageHashes - The list of message hashes + * @returns {Promise<(MessageInfo | null)[]>} The message details, or null for messages not found + */ + getMessages(messageHashes: string[]): Promise<(MessageInfo | null)[]>; +} +export type MultisigInfo = { + /** + * - The multisig address + */ + address: string; + /** + * - Array of owner identifiers + */ + owners: string[]; + /** + * - Required number of signatures + */ + threshold: number; + /** + * - Whether the multisig wallet has been created + */ + isCreated?: boolean; +}; +export type MultisigProposal = { + /** + * - Unique identifier for the created proposal + */ + proposalId: string; + /** + * - Number of confirmations + */ + confirmations: number; + /** + * - Required threshold for execution + */ + threshold: number; +}; +export type MessageInfo = { + /** + * - The message hash + */ + messageHash: string; + /** + * - The original message + */ + message: string; + /** + * - Number of confirmations + */ + confirmations: number; + /** + * - Required threshold + */ + threshold: number; + /** + * - Final combined signature when threshold is met + */ + combinedSignature: string | null; +}; +import { IWalletAccountReadOnly } from './wallet-account-read-only.js'; From 3fbf34efc415620ca5103c9216b0e39033fb9e17 Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Mon, 16 Feb 2026 09:09:49 +0700 Subject: [PATCH 12/41] remove redundant file --- index.js | 2 +- .../wallet-account-multisig-read-only.d.ts | 93 ------------------- 2 files changed, 1 insertion(+), 94 deletions(-) delete mode 100644 types/src/wallet-account-multisig-read-only.d.ts diff --git a/index.js b/index.js index a7fd4dd..7a382cb 100644 --- a/index.js +++ b/index.js @@ -26,8 +26,8 @@ /** @typedef {import('./src/wallet-account-read-only-multisig.js').MultisigInfo} MultisigInfo */ /** @typedef {import('./src/wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ /** @typedef {import('./src/wallet-account-read-only-multisig.js').MessageInfo} MessageInfo */ +/** @typedef {import('./src/wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ -/** @typedef {import('./src/wallet-account-multisig.js').MultisigProposal} MultisigResult */ /** @typedef {import('./src/wallet-account-multisig.js').MultisigExecuteResult} MultisigExecuteResult */ /** @typedef {import('./src/wallet-account-multisig.js').MessageProposal} MessageProposal */ diff --git a/types/src/wallet-account-multisig-read-only.d.ts b/types/src/wallet-account-multisig-read-only.d.ts deleted file mode 100644 index 46505b3..0000000 --- a/types/src/wallet-account-multisig-read-only.d.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * A chain-agnostic proposal object representing a pending multisig transaction. - * - * @typedef {Object} MultisigProposal - * @property {string} proposalId - Unique identifier for the created proposal - * @property {number} confirmations - Number of confirmations - * @property {number} threshold - Required threshold for execution - */ -/** - * @typedef {Object} MessageInfo - * @property {string} messageHash - The message hash - * @property {string} message - The original message - * @property {number} confirmations - Number of confirmations - * @property {number} threshold - Required threshold - * @property {string | null} combinedSignature - Final combined signature when threshold is met - */ -/** @interface */ -export interface IWalletAccountMultisigReadOnly { - /** - * Returns the address/identifier of the signer associated with this account - * (the individual key, not the multisig address). - * Returns null if no signer is associated. - * - * @returns {Promise} The signer's identifier - */ - getSignerAddress(): Promise; - /** - * Returns the list of owners/co-signers of the multisig wallet. - * The format of each entry is chain-specific (address, pubkey, etc). - * - * @returns {Promise} Array of owner identifiers - */ - getOwners(): Promise; - /** - * Returns the number of required signatures to execute a transaction. - * - * @returns {Promise} The threshold - */ - getThreshold(): Promise; - /** - * Returns a proposal by its identifier. - * - * @param {string} proposalId - The proposal identifier - * @returns {Promise} The proposal details, or null if not found - */ - getProposal(proposalId: string): Promise; - /** - * Returns a message proposal by its hash. - * - * @param {string} messageHash - The message hash - * @returns {Promise} The message info or null if not found - */ - getMessage(messageHash: string): Promise; -} -/** - * A chain-agnostic proposal object representing a pending multisig transaction. - */ -export type MultisigProposal = { - /** - * - Unique identifier for the created proposal - */ - proposalId: string; - /** - * - Number of confirmations - */ - confirmations: number; - /** - * - Required threshold for execution - */ - threshold: number; -}; -export type MessageInfo = { - /** - * - The message hash - */ - messageHash: string; - /** - * - The original message - */ - message: string; - /** - * - Number of confirmations - */ - confirmations: number; - /** - * - Required threshold - */ - threshold: number; - /** - * - Final combined signature when threshold is met - */ - combinedSignature: string | null; -}; From c9b9fc49ae5cd424688930a4e07443c355066cab Mon Sep 17 00:00:00 2001 From: Sunday <38057360+quocle108@users.noreply.github.com> Date: Thu, 19 Feb 2026 07:05:29 +0700 Subject: [PATCH 13/41] Update src/wallet-account-multisig.js Co-authored-by: Davide Casale --- src/wallet-account-multisig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index c009ce2..dedc3ed 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -21,7 +21,7 @@ import { NotImplementedError } from './errors.js' /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ /** - * @typedef {Object} MultisigExecuteResult + * @typedef {Object} MultisigTransactionResult * @property {string} hash - The finalized on-chain transaction identifier */ From e062b0c0753e58908c0d29320f8dfeab7d18cc7e Mon Sep 17 00:00:00 2001 From: Sunday <38057360+quocle108@users.noreply.github.com> Date: Thu, 19 Feb 2026 07:06:38 +0700 Subject: [PATCH 14/41] Update src/wallet-account-multisig.js Co-authored-by: Davide Casale --- src/wallet-account-multisig.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index dedc3ed..d800ee0 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -109,11 +109,11 @@ export class IWalletAccountMultisig extends IWalletAccount { /** * Proposes adding a new owner to the multisig. - * throw NotImplementedError if not supported. * - * @param {string} owner - The new owner's identifier (address/pubkey) - * @param {number} [newThreshold] - Optional new threshold - * @returns {Promise} The proposal result + * @param {string} owner - The new owner's identifier (address/pubkey). + * @param {number} [newThreshold] - Optional new threshold. + * @returns {Promise} The proposal result. + * @throws {Error} If the operation is not supported. */ async addOwner (owner, newThreshold) { throw new NotImplementedError('addOwner(owner, newThreshold)') From 5ffcb43f724f27f94db71209d0fe617e3db1cf49 Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Thu, 19 Feb 2026 07:47:57 +0700 Subject: [PATCH 15/41] update type and desc --- index.js | 1 + src/wallet-account-multisig.js | 18 +++++++--- types/index.d.ts | 3 +- types/src/wallet-account-multisig.d.ts | 35 +++++++++++++------ .../wallet-account-read-only-multisig.d.ts | 2 +- 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index 7a382cb..4352317 100644 --- a/index.js +++ b/index.js @@ -30,6 +30,7 @@ /** @typedef {import('./src/wallet-account-multisig.js').MultisigExecuteResult} MultisigExecuteResult */ /** @typedef {import('./src/wallet-account-multisig.js').MessageProposal} MessageProposal */ +/** @typedef {import('./src/wallet-account-multisig.js').MultisigOptions} MultisigOptions */ export { default } from './src/wallet-manager.js' diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index d800ee0..7e2872d 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -34,6 +34,11 @@ import { NotImplementedError } from './errors.js' * @property {string | null} combinedSignature - Final combined signature when threshold is met */ +/** + * @typedef {Object} MultisigOptions + * @property {number} threshold - The number of approvals required to execute a transaction. + */ + /** @interface */ export class IWalletAccountMultisig extends IWalletAccount { /** @@ -97,7 +102,7 @@ export class IWalletAccountMultisig extends IWalletAccount { * Submits a fully-signed proposal for on-chain execution. * * @param {string} proposalId - The proposal identifier to execute - * @returns {Promise} The transaction hash + * @returns {Promise} The transaction hash */ async execute (proposalId) { throw new NotImplementedError('execute(proposalId)') @@ -111,11 +116,11 @@ export class IWalletAccountMultisig extends IWalletAccount { * Proposes adding a new owner to the multisig. * * @param {string} owner - The new owner's identifier (address/pubkey). - * @param {number} [newThreshold] - Optional new threshold. + * @param {MultisigOptions} [options] - The new multisig options. * @returns {Promise} The proposal result. * @throws {Error} If the operation is not supported. */ - async addOwner (owner, newThreshold) { + async addOwner (owner, options) { throw new NotImplementedError('addOwner(owner, newThreshold)') } @@ -123,10 +128,11 @@ export class IWalletAccountMultisig extends IWalletAccount { * Proposes removing an owner from the multisig. * * @param {string} owner - The owner's identifier to remove - * @param {number} [newThreshold] - Optional new threshold + * @param {MultisigOptions} [options] - The new multisig options. * @returns {Promise} The proposal result + * @throws {Error} If the operation is not supported. */ - async removeOwner (owner, newThreshold) { + async removeOwner (owner, options) { throw new NotImplementedError('removeOwner(owner, newThreshold)') } @@ -136,6 +142,7 @@ export class IWalletAccountMultisig extends IWalletAccount { * @param {string} oldOwner - The owner to replace * @param {string} newOwner - The replacement owner * @returns {Promise} The proposal result + * @throws {Error} If the operation is not supported. */ async swapOwner (oldOwner, newOwner) { throw new NotImplementedError('swapOwner(oldOwner, newOwner)') @@ -146,6 +153,7 @@ export class IWalletAccountMultisig extends IWalletAccount { * * @param {number} newThreshold - The new threshold * @returns {Promise} The proposal result + * @throws {Error} If the operation is not supported. */ async changeThreshold (newThreshold) { throw new NotImplementedError('changeThreshold(newThreshold)') diff --git a/types/index.d.ts b/types/index.d.ts index eb61199..6ba1f04 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -13,7 +13,8 @@ export type KeyPair = import("./src/wallet-account.js").KeyPair; export type MultisigInfo = import("./src/wallet-account-read-only-multisig.js").MultisigInfo; export type MultisigProposal = import("./src/wallet-account-read-only-multisig.js").MultisigProposal; export type MessageInfo = import("./src/wallet-account-read-only-multisig.js").MessageInfo; -export type MultisigResult = import("./src/wallet-account-multisig.js").MultisigProposal; +export type MultisigResult = import("./src/wallet-account-read-only-multisig.js").MultisigProposal; export type MultisigExecuteResult = import("./src/wallet-account-multisig.js").MultisigExecuteResult; export type MessageProposal = import("./src/wallet-account-multisig.js").MessageProposal; +export type MultisigOptions = import("./src/wallet-account-multisig.js").MultisigOptions; export { default as WalletAccountReadOnly, IWalletAccountReadOnly } from "./src/wallet-account-read-only.js"; diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts index 602db5b..e716b99 100644 --- a/types/src/wallet-account-multisig.d.ts +++ b/types/src/wallet-account-multisig.d.ts @@ -1,7 +1,7 @@ /** @typedef {import('./wallet-account-read-only.js').Transaction} Transaction */ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ /** - * @typedef {Object} MultisigExecuteResult + * @typedef {Object} MultisigTransactionResult * @property {string} hash - The finalized on-chain transaction identifier */ /** @@ -12,6 +12,10 @@ * @property {number} threshold - Required threshold * @property {string | null} combinedSignature - Final combined signature when threshold is met */ +/** + * @typedef {Object} MultisigOptions + * @property {number} threshold - The number of approvals required to execute a transaction. + */ /** @interface */ export interface IWalletAccountMultisig extends IWalletAccount { /** @@ -57,32 +61,34 @@ export interface IWalletAccountMultisig extends IWalletAccount { * Submits a fully-signed proposal for on-chain execution. * * @param {string} proposalId - The proposal identifier to execute - * @returns {Promise} The transaction hash + * @returns {Promise} The transaction hash */ - execute(proposalId: string): Promise; + execute(proposalId: string): Promise; /** * Proposes adding a new owner to the multisig. - * throw NotImplementedError if not supported. * - * @param {string} owner - The new owner's identifier (address/pubkey) - * @param {number} [newThreshold] - Optional new threshold - * @returns {Promise} The proposal result + * @param {string} owner - The new owner's identifier (address/pubkey). + * @param {MultisigOptions} [options] - The new multisig options. + * @returns {Promise} The proposal result. + * @throws {Error} If the operation is not supported. */ - addOwner(owner: string, newThreshold?: number): Promise; + addOwner(owner: string, options?: MultisigOptions): Promise; /** * Proposes removing an owner from the multisig. * * @param {string} owner - The owner's identifier to remove - * @param {number} [newThreshold] - Optional new threshold + * @param {MultisigOptions} [options] - The new multisig options. * @returns {Promise} The proposal result + * @throws {Error} If the operation is not supported. */ - removeOwner(owner: string, newThreshold?: number): Promise; + removeOwner(owner: string, options?: MultisigOptions): Promise; /** * Proposes replacing one owner with another. * * @param {string} oldOwner - The owner to replace * @param {string} newOwner - The replacement owner * @returns {Promise} The proposal result + * @throws {Error} If the operation is not supported. */ swapOwner(oldOwner: string, newOwner: string): Promise; /** @@ -90,12 +96,13 @@ export interface IWalletAccountMultisig extends IWalletAccount { * * @param {number} newThreshold - The new threshold * @returns {Promise} The proposal result + * @throws {Error} If the operation is not supported. */ changeThreshold(newThreshold: number): Promise; } export type Transaction = import("./wallet-account-read-only.js").Transaction; export type MultisigResult = import("./wallet-account-read-only-multisig.js").MultisigProposal; -export type MultisigExecuteResult = { +export type MultisigTransactionResult = { /** * - The finalized on-chain transaction identifier */ @@ -123,4 +130,10 @@ export type MessageProposal = { */ combinedSignature: string | null; }; +export type MultisigOptions = { + /** + * - The number of approvals required to execute a transaction. + */ + threshold: number; +}; import { IWalletAccount } from './wallet-account.js'; diff --git a/types/src/wallet-account-read-only-multisig.d.ts b/types/src/wallet-account-read-only-multisig.d.ts index 86ac627..2fd6ed3 100644 --- a/types/src/wallet-account-read-only-multisig.d.ts +++ b/types/src/wallet-account-read-only-multisig.d.ts @@ -21,7 +21,7 @@ * @property {string | null} combinedSignature - Final combined signature when threshold is met */ /** @interface */ -export interface IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { +export class IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { /** * Returns the address/identifier of the signer associated with this account wallet * Returns null if no signer is associated. From e6de41c2c591bb76c22f4316e2393494e91ab196 Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Mon, 23 Feb 2026 10:36:31 +0700 Subject: [PATCH 16/41] fix: IWalletAccountMultisig extends both IWalletAccount and IWalletAccountReadOnlyMultisig --- src/wallet-account-multisig.js | 6 +++++- types/src/wallet-account-multisig.d.ts | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index 7e2872d..18dbbd6 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -39,7 +39,11 @@ import { NotImplementedError } from './errors.js' * @property {number} threshold - The number of approvals required to execute a transaction. */ -/** @interface */ +/** + * @interface + * @extends {IWalletAccount} + * @extends {IWalletAccountReadOnlyMultisig} + */ export class IWalletAccountMultisig extends IWalletAccount { /** * Proposes signing a message with the multisig. diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts index e716b99..716f3ff 100644 --- a/types/src/wallet-account-multisig.d.ts +++ b/types/src/wallet-account-multisig.d.ts @@ -17,7 +17,7 @@ * @property {number} threshold - The number of approvals required to execute a transaction. */ /** @interface */ -export interface IWalletAccountMultisig extends IWalletAccount { +export interface IWalletAccountMultisig extends IWalletAccount, IWalletAccountReadOnlyMultisig { /** * Proposes signing a message with the multisig. * The proposer's signature is included automatically. @@ -137,3 +137,4 @@ export type MultisigOptions = { threshold: number; }; import { IWalletAccount } from './wallet-account.js'; +import { IWalletAccountReadOnlyMultisig } from './wallet-account-read-only-multisig.js'; From a06073a2b04dc2140c03c0f25c9d4d2e29dce317 Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Mon, 23 Feb 2026 11:00:56 +0700 Subject: [PATCH 17/41] feat: override sendTransaction and transfer in IWalletAccountMultisig --- src/wallet-account-multisig.js | 38 +++++++++++++++++- types/src/wallet-account-multisig.d.ts | 55 +++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index 18dbbd6..f0b7420 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -18,11 +18,21 @@ import { IWalletAccount } from './wallet-account.js' import { NotImplementedError } from './errors.js' /** @typedef {import('./wallet-account-read-only.js').Transaction} Transaction */ +/** @typedef {import('./wallet-account-read-only.js').TransferOptions} TransferOptions */ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ /** * @typedef {Object} MultisigTransactionResult - * @property {string} hash - The finalized on-chain transaction identifier + * @property {string} hash - The transaction hash (proposal hash if not executed, on-chain hash if executed) + * @property {bigint} fee - The transaction fee + * @property {number} confirmations - Current number of confirmations + * @property {number} threshold - Required threshold for execution + * @property {boolean} executed - Whether the transaction was executed on-chain + */ + +/** + * @typedef {Object} MultisigSendOptions + * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met */ /** @@ -45,6 +55,32 @@ import { NotImplementedError } from './errors.js' * @extends {IWalletAccountReadOnlyMultisig} */ export class IWalletAccountMultisig extends IWalletAccount { + /** + * Proposes sending a transaction. + * The transaction will be sent automatically once the approval threshold is met + * if autoExecute option is enabled. + * + * @param {Transaction} tx - The transaction + * @param {MultisigSendOptions} [options] - The multisig send options + * @returns {Promise} The transaction result + */ + async sendTransaction (tx, options) { + throw new NotImplementedError('sendTransaction(tx)') + } + + /** + * Proposes transferring a token to another address. + * The transfer will be executed automatically once the approval threshold is met + * if autoExecute option is enabled. + * + * @param {TransferOptions} options - The transfer options + * @param {MultisigSendOptions} [sendOptions] - The multisig send options + * @returns {Promise} The transfer result + */ + async transfer (options, sendOptions) { + throw new NotImplementedError('transfer(options)') + } + /** * Proposes signing a message with the multisig. * The proposer's signature is included automatically. diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts index 716f3ff..2053eba 100644 --- a/types/src/wallet-account-multisig.d.ts +++ b/types/src/wallet-account-multisig.d.ts @@ -2,7 +2,15 @@ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ /** * @typedef {Object} MultisigTransactionResult - * @property {string} hash - The finalized on-chain transaction identifier + * @property {string} hash - The transaction hash (proposal hash if not executed, on-chain hash if executed) + * @property {bigint} fee - The transaction fee + * @property {number} confirmations - Current number of confirmations + * @property {number} threshold - Required threshold for execution + * @property {boolean} executed - Whether the transaction was executed on-chain + */ +/** + * @typedef {Object} MultisigSendOptions + * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met */ /** * @typedef {Object} MessageProposal @@ -18,6 +26,26 @@ */ /** @interface */ export interface IWalletAccountMultisig extends IWalletAccount, IWalletAccountReadOnlyMultisig { + /** + * Proposes sending a transaction. + * The transaction will be sent automatically once the approval threshold is met + * if autoExecute option is enabled. + * + * @param {Transaction} tx - The transaction + * @param {MultisigSendOptions} [options] - The multisig send options + * @returns {Promise} The transaction result + */ + sendTransaction(tx: Transaction, options?: MultisigSendOptions): Promise; + /** + * Proposes transferring a token to another address. + * The transfer will be executed automatically once the approval threshold is met + * if autoExecute option is enabled. + * + * @param {TransferOptions} options - The transfer options + * @param {MultisigSendOptions} [sendOptions] - The multisig send options + * @returns {Promise} The transfer result + */ + transfer(options: TransferOptions, sendOptions?: MultisigSendOptions): Promise; /** * Proposes signing a message with the multisig. * The proposer's signature is included automatically. @@ -101,12 +129,35 @@ export interface IWalletAccountMultisig extends IWalletAccount, IWalletAccountRe changeThreshold(newThreshold: number): Promise; } export type Transaction = import("./wallet-account-read-only.js").Transaction; +export type TransferOptions = import("./wallet-account-read-only.js").TransferOptions; export type MultisigResult = import("./wallet-account-read-only-multisig.js").MultisigProposal; export type MultisigTransactionResult = { /** - * - The finalized on-chain transaction identifier + * - The transaction hash (proposal hash if not executed, on-chain hash if executed) */ hash: string; + /** + * - The transaction fee + */ + fee: bigint; + /** + * - Current number of confirmations + */ + confirmations: number; + /** + * - Required threshold for execution + */ + threshold: number; + /** + * - Whether the transaction was executed on-chain + */ + executed: boolean; +}; +export type MultisigSendOptions = { + /** + * - If true, automatically execute the transaction when the approval threshold is met + */ + autoExecute?: boolean; }; export type MessageProposal = { /** From 44ff4711c6023f6e8285f72b54d8e8310413b96e Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Mon, 23 Feb 2026 11:08:00 +0700 Subject: [PATCH 18/41] update types --- index.js | 3 ++- types/index.d.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 4352317..fb19a38 100644 --- a/index.js +++ b/index.js @@ -28,7 +28,8 @@ /** @typedef {import('./src/wallet-account-read-only-multisig.js').MessageInfo} MessageInfo */ /** @typedef {import('./src/wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ -/** @typedef {import('./src/wallet-account-multisig.js').MultisigExecuteResult} MultisigExecuteResult */ +/** @typedef {import('./src/wallet-account-multisig.js').MultisigTransactionResult} MultisigTransactionResult */ +/** @typedef {import('./src/wallet-account-multisig.js').MultisigSendOptions} MultisigSendOptions */ /** @typedef {import('./src/wallet-account-multisig.js').MessageProposal} MessageProposal */ /** @typedef {import('./src/wallet-account-multisig.js').MultisigOptions} MultisigOptions */ diff --git a/types/index.d.ts b/types/index.d.ts index 6ba1f04..effdfee 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -14,7 +14,8 @@ export type MultisigInfo = import("./src/wallet-account-read-only-multisig.js"). export type MultisigProposal = import("./src/wallet-account-read-only-multisig.js").MultisigProposal; export type MessageInfo = import("./src/wallet-account-read-only-multisig.js").MessageInfo; export type MultisigResult = import("./src/wallet-account-read-only-multisig.js").MultisigProposal; -export type MultisigExecuteResult = import("./src/wallet-account-multisig.js").MultisigExecuteResult; +export type MultisigTransactionResult = import("./src/wallet-account-multisig.js").MultisigTransactionResult; +export type MultisigSendOptions = import("./src/wallet-account-multisig.js").MultisigSendOptions; export type MessageProposal = import("./src/wallet-account-multisig.js").MessageProposal; export type MultisigOptions = import("./src/wallet-account-multisig.js").MultisigOptions; export { default as WalletAccountReadOnly, IWalletAccountReadOnly } from "./src/wallet-account-read-only.js"; From 4ddea45806b8522eecd5e4a9b0f441a9e453cd7f Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Mon, 23 Feb 2026 11:10:02 +0700 Subject: [PATCH 19/41] use interface instead of class in type --- types/src/wallet-account-multisig.d.ts | 10 +++++++--- types/src/wallet-account-read-only-multisig.d.ts | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts index 2053eba..311ffa7 100644 --- a/types/src/wallet-account-multisig.d.ts +++ b/types/src/wallet-account-multisig.d.ts @@ -1,4 +1,5 @@ /** @typedef {import('./wallet-account-read-only.js').Transaction} Transaction */ +/** @typedef {import('./wallet-account-read-only.js').TransferOptions} TransferOptions */ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ /** * @typedef {Object} MultisigTransactionResult @@ -24,8 +25,12 @@ * @typedef {Object} MultisigOptions * @property {number} threshold - The number of approvals required to execute a transaction. */ -/** @interface */ -export interface IWalletAccountMultisig extends IWalletAccount, IWalletAccountReadOnlyMultisig { +/** + * @interface + * @extends {IWalletAccount} + * @extends {IWalletAccountReadOnlyMultisig} + */ +export interface IWalletAccountMultisig extends IWalletAccount { /** * Proposes sending a transaction. * The transaction will be sent automatically once the approval threshold is met @@ -188,4 +193,3 @@ export type MultisigOptions = { threshold: number; }; import { IWalletAccount } from './wallet-account.js'; -import { IWalletAccountReadOnlyMultisig } from './wallet-account-read-only-multisig.js'; diff --git a/types/src/wallet-account-read-only-multisig.d.ts b/types/src/wallet-account-read-only-multisig.d.ts index 2fd6ed3..86ac627 100644 --- a/types/src/wallet-account-read-only-multisig.d.ts +++ b/types/src/wallet-account-read-only-multisig.d.ts @@ -21,7 +21,7 @@ * @property {string | null} combinedSignature - Final combined signature when threshold is met */ /** @interface */ -export class IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { +export interface IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { /** * Returns the address/identifier of the signer associated with this account wallet * Returns null if no signer is associated. From ca897ac51597e1986bc392a9f7af2cb8ad085682 Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Mon, 23 Feb 2026 11:33:55 +0700 Subject: [PATCH 20/41] update execute multisig type --- index.js | 1 + src/wallet-account-multisig.js | 9 +++++++-- types/src/wallet-account-multisig.d.ts | 21 ++++++++++++++++----- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index fb19a38..892eb4c 100644 --- a/index.js +++ b/index.js @@ -29,6 +29,7 @@ /** @typedef {import('./src/wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ /** @typedef {import('./src/wallet-account-multisig.js').MultisigTransactionResult} MultisigTransactionResult */ +/** @typedef {import('./src/wallet-account-multisig.js').MultisigExecuteResult} MultisigExecuteResult */ /** @typedef {import('./src/wallet-account-multisig.js').MultisigSendOptions} MultisigSendOptions */ /** @typedef {import('./src/wallet-account-multisig.js').MessageProposal} MessageProposal */ /** @typedef {import('./src/wallet-account-multisig.js').MultisigOptions} MultisigOptions */ diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index f0b7420..af4d6a7 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -24,12 +24,17 @@ import { NotImplementedError } from './errors.js' /** * @typedef {Object} MultisigTransactionResult * @property {string} hash - The transaction hash (proposal hash if not executed, on-chain hash if executed) - * @property {bigint} fee - The transaction fee + * @property {bigint} fee - The estimated transaction fee * @property {number} confirmations - Current number of confirmations * @property {number} threshold - Required threshold for execution * @property {boolean} executed - Whether the transaction was executed on-chain */ +/** + * @typedef {Object} MultisigExecuteResult + * @property {string} hash - The on-chain transaction hash + */ + /** * @typedef {Object} MultisigSendOptions * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met @@ -142,7 +147,7 @@ export class IWalletAccountMultisig extends IWalletAccount { * Submits a fully-signed proposal for on-chain execution. * * @param {string} proposalId - The proposal identifier to execute - * @returns {Promise} The transaction hash + * @returns {Promise} The execution result */ async execute (proposalId) { throw new NotImplementedError('execute(proposalId)') diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts index 311ffa7..6124d17 100644 --- a/types/src/wallet-account-multisig.d.ts +++ b/types/src/wallet-account-multisig.d.ts @@ -4,11 +4,15 @@ /** * @typedef {Object} MultisigTransactionResult * @property {string} hash - The transaction hash (proposal hash if not executed, on-chain hash if executed) - * @property {bigint} fee - The transaction fee + * @property {bigint} fee - The estimated transaction fee * @property {number} confirmations - Current number of confirmations * @property {number} threshold - Required threshold for execution * @property {boolean} executed - Whether the transaction was executed on-chain */ +/** + * @typedef {Object} MultisigExecuteResult + * @property {string} hash - The on-chain transaction hash + */ /** * @typedef {Object} MultisigSendOptions * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met @@ -30,7 +34,7 @@ * @extends {IWalletAccount} * @extends {IWalletAccountReadOnlyMultisig} */ -export interface IWalletAccountMultisig extends IWalletAccount { +export interface IWalletAccountMultisig extends IWalletAccount, IWalletAccountReadOnlyMultisig { /** * Proposes sending a transaction. * The transaction will be sent automatically once the approval threshold is met @@ -94,9 +98,9 @@ export interface IWalletAccountMultisig extends IWalletAccount { * Submits a fully-signed proposal for on-chain execution. * * @param {string} proposalId - The proposal identifier to execute - * @returns {Promise} The transaction hash + * @returns {Promise} The execution result */ - execute(proposalId: string): Promise; + execute(proposalId: string): Promise; /** * Proposes adding a new owner to the multisig. * @@ -142,7 +146,7 @@ export type MultisigTransactionResult = { */ hash: string; /** - * - The transaction fee + * - The estimated transaction fee */ fee: bigint; /** @@ -158,6 +162,12 @@ export type MultisigTransactionResult = { */ executed: boolean; }; +export type MultisigExecuteResult = { + /** + * - The on-chain transaction hash + */ + hash: string; +}; export type MultisigSendOptions = { /** * - If true, automatically execute the transaction when the approval threshold is met @@ -193,3 +203,4 @@ export type MultisigOptions = { threshold: number; }; import { IWalletAccount } from './wallet-account.js'; +import { IWalletAccountReadOnlyMultisig } from './wallet-account-read-only-multisig.js'; From 864fe1379a8eaaf46b3d1eb210aef705e7b8f899 Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Mon, 23 Feb 2026 16:35:34 +0700 Subject: [PATCH 21/41] update MultisigTransactionResult type and desc --- index.js | 1 - src/wallet-account-multisig.js | 12 ++++------ types/src/wallet-account-multisig.d.ts | 31 +++++++++++--------------- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/index.js b/index.js index 892eb4c..fb19a38 100644 --- a/index.js +++ b/index.js @@ -29,7 +29,6 @@ /** @typedef {import('./src/wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ /** @typedef {import('./src/wallet-account-multisig.js').MultisigTransactionResult} MultisigTransactionResult */ -/** @typedef {import('./src/wallet-account-multisig.js').MultisigExecuteResult} MultisigExecuteResult */ /** @typedef {import('./src/wallet-account-multisig.js').MultisigSendOptions} MultisigSendOptions */ /** @typedef {import('./src/wallet-account-multisig.js').MessageProposal} MessageProposal */ /** @typedef {import('./src/wallet-account-multisig.js').MultisigOptions} MultisigOptions */ diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index af4d6a7..5c30cca 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -23,18 +23,14 @@ import { NotImplementedError } from './errors.js' /** * @typedef {Object} MultisigTransactionResult - * @property {string} hash - The transaction hash (proposal hash if not executed, on-chain hash if executed) - * @property {bigint} fee - The estimated transaction fee + * @property {string} proposalId - The proposal identifier + * @property {string} [hash] - The on-chain transaction hash + * @property {bigint} [fee] - The transaction fee * @property {number} confirmations - Current number of confirmations * @property {number} threshold - Required threshold for execution * @property {boolean} executed - Whether the transaction was executed on-chain */ -/** - * @typedef {Object} MultisigExecuteResult - * @property {string} hash - The on-chain transaction hash - */ - /** * @typedef {Object} MultisigSendOptions * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met @@ -147,7 +143,7 @@ export class IWalletAccountMultisig extends IWalletAccount { * Submits a fully-signed proposal for on-chain execution. * * @param {string} proposalId - The proposal identifier to execute - * @returns {Promise} The execution result + * @returns {Promise} The execution result */ async execute (proposalId) { throw new NotImplementedError('execute(proposalId)') diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts index 6124d17..ad34da1 100644 --- a/types/src/wallet-account-multisig.d.ts +++ b/types/src/wallet-account-multisig.d.ts @@ -3,16 +3,13 @@ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ /** * @typedef {Object} MultisigTransactionResult - * @property {string} hash - The transaction hash (proposal hash if not executed, on-chain hash if executed) - * @property {bigint} fee - The estimated transaction fee + * @property {string} proposalId - The proposal identifier + * @property {string} [hash] - The on-chain transaction hash + * @property {bigint} [fee] - The transaction fee * @property {number} confirmations - Current number of confirmations * @property {number} threshold - Required threshold for execution * @property {boolean} executed - Whether the transaction was executed on-chain */ -/** - * @typedef {Object} MultisigExecuteResult - * @property {string} hash - The on-chain transaction hash - */ /** * @typedef {Object} MultisigSendOptions * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met @@ -98,9 +95,9 @@ export interface IWalletAccountMultisig extends IWalletAccount, IWalletAccountRe * Submits a fully-signed proposal for on-chain execution. * * @param {string} proposalId - The proposal identifier to execute - * @returns {Promise} The execution result + * @returns {Promise} The execution result */ - execute(proposalId: string): Promise; + execute(proposalId: string): Promise; /** * Proposes adding a new owner to the multisig. * @@ -142,13 +139,17 @@ export type TransferOptions = import("./wallet-account-read-only.js").TransferOp export type MultisigResult = import("./wallet-account-read-only-multisig.js").MultisigProposal; export type MultisigTransactionResult = { /** - * - The transaction hash (proposal hash if not executed, on-chain hash if executed) + * - The proposal identifier + */ + proposalId: string; + /** + * - The on-chain transaction hash */ - hash: string; + hash?: string; /** - * - The estimated transaction fee + * - The transaction fee */ - fee: bigint; + fee?: bigint; /** * - Current number of confirmations */ @@ -162,12 +163,6 @@ export type MultisigTransactionResult = { */ executed: boolean; }; -export type MultisigExecuteResult = { - /** - * - The on-chain transaction hash - */ - hash: string; -}; export type MultisigSendOptions = { /** * - If true, automatically execute the transaction when the approval threshold is met From 14116118606628dc5389c25258ab7139c87df873 Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Mon, 23 Feb 2026 21:46:48 +0700 Subject: [PATCH 22/41] revert: requires fee and hash for MultisigTransactionResult --- index.js | 1 + src/wallet-account-multisig.js | 12 +++++++--- types/index.d.ts | 1 + types/src/wallet-account-multisig.d.ts | 31 +++++++++++++++++++------- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index fb19a38..892eb4c 100644 --- a/index.js +++ b/index.js @@ -29,6 +29,7 @@ /** @typedef {import('./src/wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ /** @typedef {import('./src/wallet-account-multisig.js').MultisigTransactionResult} MultisigTransactionResult */ +/** @typedef {import('./src/wallet-account-multisig.js').MultisigExecuteResult} MultisigExecuteResult */ /** @typedef {import('./src/wallet-account-multisig.js').MultisigSendOptions} MultisigSendOptions */ /** @typedef {import('./src/wallet-account-multisig.js').MessageProposal} MessageProposal */ /** @typedef {import('./src/wallet-account-multisig.js').MultisigOptions} MultisigOptions */ diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index 5c30cca..99a91a1 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -24,13 +24,19 @@ import { NotImplementedError } from './errors.js' /** * @typedef {Object} MultisigTransactionResult * @property {string} proposalId - The proposal identifier - * @property {string} [hash] - The on-chain transaction hash - * @property {bigint} [fee] - The transaction fee + * @property {string} hash - The transaction hash or proposal identifier + * @property {bigint} fee - The transaction fee or estimated fee * @property {number} confirmations - Current number of confirmations * @property {number} threshold - Required threshold for execution * @property {boolean} executed - Whether the transaction was executed on-chain */ +/** + * @typedef {Object} MultisigExecuteResult + * @property {string} hash - The transaction hash + * @property {bigint} [fee] - The transaction fee + */ + /** * @typedef {Object} MultisigSendOptions * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met @@ -143,7 +149,7 @@ export class IWalletAccountMultisig extends IWalletAccount { * Submits a fully-signed proposal for on-chain execution. * * @param {string} proposalId - The proposal identifier to execute - * @returns {Promise} The execution result + * @returns {Promise} The execution result */ async execute (proposalId) { throw new NotImplementedError('execute(proposalId)') diff --git a/types/index.d.ts b/types/index.d.ts index effdfee..b678d1c 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -15,6 +15,7 @@ export type MultisigProposal = import("./src/wallet-account-read-only-multisig.j export type MessageInfo = import("./src/wallet-account-read-only-multisig.js").MessageInfo; export type MultisigResult = import("./src/wallet-account-read-only-multisig.js").MultisigProposal; export type MultisigTransactionResult = import("./src/wallet-account-multisig.js").MultisigTransactionResult; +export type MultisigExecuteResult = import("./src/wallet-account-multisig.js").MultisigExecuteResult; export type MultisigSendOptions = import("./src/wallet-account-multisig.js").MultisigSendOptions; export type MessageProposal = import("./src/wallet-account-multisig.js").MessageProposal; export type MultisigOptions = import("./src/wallet-account-multisig.js").MultisigOptions; diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts index ad34da1..fcde6f4 100644 --- a/types/src/wallet-account-multisig.d.ts +++ b/types/src/wallet-account-multisig.d.ts @@ -4,12 +4,17 @@ /** * @typedef {Object} MultisigTransactionResult * @property {string} proposalId - The proposal identifier - * @property {string} [hash] - The on-chain transaction hash - * @property {bigint} [fee] - The transaction fee + * @property {string} hash - The transaction hash or proposal identifier + * @property {bigint} fee - The transaction fee or estimated fee * @property {number} confirmations - Current number of confirmations * @property {number} threshold - Required threshold for execution * @property {boolean} executed - Whether the transaction was executed on-chain */ +/** + * @typedef {Object} MultisigExecuteResult + * @property {string} hash - The transaction hash + * @property {bigint} [fee] - The transaction fee + */ /** * @typedef {Object} MultisigSendOptions * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met @@ -95,9 +100,9 @@ export interface IWalletAccountMultisig extends IWalletAccount, IWalletAccountRe * Submits a fully-signed proposal for on-chain execution. * * @param {string} proposalId - The proposal identifier to execute - * @returns {Promise} The execution result + * @returns {Promise} The execution result */ - execute(proposalId: string): Promise; + execute(proposalId: string): Promise; /** * Proposes adding a new owner to the multisig. * @@ -143,13 +148,13 @@ export type MultisigTransactionResult = { */ proposalId: string; /** - * - The on-chain transaction hash + * - The transaction hash or proposal identifier */ - hash?: string; + hash: string; /** - * - The transaction fee + * - The transaction fee or estimated fee */ - fee?: bigint; + fee: bigint; /** * - Current number of confirmations */ @@ -163,6 +168,16 @@ export type MultisigTransactionResult = { */ executed: boolean; }; +export type MultisigExecuteResult = { + /** + * - The transaction hash + */ + hash: string; + /** + * - The transaction fee + */ + fee?: bigint; +}; export type MultisigSendOptions = { /** * - If true, automatically execute the transaction when the approval threshold is met From 9392415902b50e96354a79d17ea6243be38a6876 Mon Sep 17 00:00:00 2001 From: Sunday <38057360+quocle108@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:00:18 +0700 Subject: [PATCH 23/41] Update src/wallet-account-multisig.js Co-authored-by: Jonathan Dunne --- src/wallet-account-multisig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index 99a91a1..06f97c6 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -39,7 +39,7 @@ import { NotImplementedError } from './errors.js' /** * @typedef {Object} MultisigSendOptions - * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met + * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met. Only takes effect if this signer's approval is the final one required. Otherwise the transaction must be executed manually via `execute()`. */ /** From 4ec73ba08afbcd3b9761da21deb122ea751217c7 Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Wed, 25 Feb 2026 09:00:25 +0700 Subject: [PATCH 24/41] correct desc for hash in MultisigTransactionResult --- src/wallet-account-multisig.js | 2 +- types/src/wallet-account-multisig.d.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index 06f97c6..6b36212 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -24,7 +24,7 @@ import { NotImplementedError } from './errors.js' /** * @typedef {Object} MultisigTransactionResult * @property {string} proposalId - The proposal identifier - * @property {string} hash - The transaction hash or proposal identifier + * @property {string} hash - The transaction hash * @property {bigint} fee - The transaction fee or estimated fee * @property {number} confirmations - Current number of confirmations * @property {number} threshold - Required threshold for execution diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts index fcde6f4..85ec8ae 100644 --- a/types/src/wallet-account-multisig.d.ts +++ b/types/src/wallet-account-multisig.d.ts @@ -4,7 +4,7 @@ /** * @typedef {Object} MultisigTransactionResult * @property {string} proposalId - The proposal identifier - * @property {string} hash - The transaction hash or proposal identifier + * @property {string} hash - The transaction hash * @property {bigint} fee - The transaction fee or estimated fee * @property {number} confirmations - Current number of confirmations * @property {number} threshold - Required threshold for execution @@ -148,7 +148,7 @@ export type MultisigTransactionResult = { */ proposalId: string; /** - * - The transaction hash or proposal identifier + * - The proposal identifier */ hash: string; /** From aa4c57cd9b52d7a6e13cf92e503218230015fb32 Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Fri, 6 Mar 2026 09:49:07 +0700 Subject: [PATCH 25/41] update function naming for multisig module --- src/wallet-account-multisig.js | 28 +++++++------------------- types/src/wallet-account-multisig.d.ts | 22 +++++++------------- 2 files changed, 14 insertions(+), 36 deletions(-) diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js index 6b36212..3cbc824 100644 --- a/src/wallet-account-multisig.js +++ b/src/wallet-account-multisig.js @@ -39,7 +39,7 @@ import { NotImplementedError } from './errors.js' /** * @typedef {Object} MultisigSendOptions - * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met. Only takes effect if this signer's approval is the final one required. Otherwise the transaction must be executed manually via `execute()`. + * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met. Only takes effect if this signer's approval is the final one required. Otherwise the transaction must be executed manually via `executeTx()`. */ /** @@ -109,28 +109,14 @@ export class IWalletAccountMultisig extends IWalletAccount { throw new NotImplementedError('approveMessage(messageHash)') } - // ========================================== - // Proposal Lifecycle - // ========================================== - /** - * Creates a new proposal for a transaction. - * The proposer's signature is included automatically. - * - * @param {Transaction} tx - The transaction to propose - * @returns {Promise} The proposal result - */ - async propose (tx) { - throw new NotImplementedError('propose(tx)') - } - /** * Adds the current signer's approval to an existing proposal. * * @param {string} proposalId - The proposal identifier to approve * @returns {Promise} The approval result */ - async approve (proposalId) { - throw new NotImplementedError('approve(proposalId)') + async approveTx (proposalId) { + throw new NotImplementedError('approveTx(proposalId)') } /** @@ -141,8 +127,8 @@ export class IWalletAccountMultisig extends IWalletAccount { * @param {string} proposalId - The proposal identifier to reject * @returns {Promise} The rejection result */ - async reject (proposalId) { - throw new NotImplementedError('reject(proposalId)') + async rejectTx (proposalId) { + throw new NotImplementedError('rejectTx(proposalId)') } /** @@ -151,8 +137,8 @@ export class IWalletAccountMultisig extends IWalletAccount { * @param {string} proposalId - The proposal identifier to execute * @returns {Promise} The execution result */ - async execute (proposalId) { - throw new NotImplementedError('execute(proposalId)') + async executeTx (proposalId) { + throw new NotImplementedError('executeTx(proposalId)') } // ========================================== diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts index 85ec8ae..1b727d6 100644 --- a/types/src/wallet-account-multisig.d.ts +++ b/types/src/wallet-account-multisig.d.ts @@ -17,7 +17,7 @@ */ /** * @typedef {Object} MultisigSendOptions - * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met + * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met. Only takes effect if this signer's approval is the final one required. Otherwise the transaction must be executed manually via `executeTx()`. */ /** * @typedef {Object} MessageProposal @@ -72,21 +72,13 @@ export interface IWalletAccountMultisig extends IWalletAccount, IWalletAccountRe * @returns {Promise} The approval result */ approveMessage(messageHash: string): Promise; - /** - * Creates a new proposal for a transaction. - * The proposer's signature is included automatically. - * - * @param {Transaction} tx - The transaction to propose - * @returns {Promise} The proposal result - */ - propose(tx: Transaction): Promise; /** * Adds the current signer's approval to an existing proposal. * * @param {string} proposalId - The proposal identifier to approve * @returns {Promise} The approval result */ - approve(proposalId: string): Promise; + approveTx(proposalId: string): Promise; /** * Rejects a pending proposal. * Behavior is chain-specific: some chains support on-chain rejection, @@ -95,14 +87,14 @@ export interface IWalletAccountMultisig extends IWalletAccount, IWalletAccountRe * @param {string} proposalId - The proposal identifier to reject * @returns {Promise} The rejection result */ - reject(proposalId: string): Promise; + rejectTx(proposalId: string): Promise; /** * Submits a fully-signed proposal for on-chain execution. * * @param {string} proposalId - The proposal identifier to execute * @returns {Promise} The execution result */ - execute(proposalId: string): Promise; + executeTx(proposalId: string): Promise; /** * Proposes adding a new owner to the multisig. * @@ -148,7 +140,7 @@ export type MultisigTransactionResult = { */ proposalId: string; /** - * - The proposal identifier + * - The transaction hash */ hash: string; /** @@ -180,7 +172,7 @@ export type MultisigExecuteResult = { }; export type MultisigSendOptions = { /** - * - If true, automatically execute the transaction when the approval threshold is met + * - If true, automatically execute the transaction when the approval threshold is met. Only takes effect if this signer's approval is the final one required. Otherwise the transaction must be executed manually via `executeTx()`. */ autoExecute?: boolean; }; @@ -212,5 +204,5 @@ export type MultisigOptions = { */ threshold: number; }; -import { IWalletAccount } from './wallet-account.js'; import { IWalletAccountReadOnlyMultisig } from './wallet-account-read-only-multisig.js'; +import { IWalletAccount } from './wallet-account.js'; From f8bced577b77eb9490c0da17fbf8a4b177f0fa3d Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Wed, 11 Mar 2026 06:56:23 +0100 Subject: [PATCH 26/41] Refactor code. --- index.js | 15 -- package.json | 4 + src/multisig/index.js | 27 +++ src/multisig/wallet-account-multisig.js | 180 +++++++++++++++ .../wallet-account-read-only-multisig.js | 86 ++++++++ src/wallet-account-multisig.js | 194 ---------------- src/wallet-account-read-only-multisig.js | 85 ------- types/src/multisig/index.d.ts | 9 + .../src/multisig/wallet-account-multisig.d.ts | 159 +++++++++++++ .../wallet-account-read-only-multisig.d.ts | 86 ++++++++ types/src/wallet-account-multisig.d.ts | 208 ------------------ .../wallet-account-read-only-multisig.d.ts | 107 --------- 12 files changed, 551 insertions(+), 609 deletions(-) create mode 100644 src/multisig/index.js create mode 100644 src/multisig/wallet-account-multisig.js create mode 100644 src/multisig/wallet-account-read-only-multisig.js delete mode 100644 src/wallet-account-multisig.js delete mode 100644 src/wallet-account-read-only-multisig.js create mode 100644 types/src/multisig/index.d.ts create mode 100644 types/src/multisig/wallet-account-multisig.d.ts create mode 100644 types/src/multisig/wallet-account-read-only-multisig.d.ts delete mode 100644 types/src/wallet-account-multisig.d.ts delete mode 100644 types/src/wallet-account-read-only-multisig.d.ts diff --git a/index.js b/index.js index 892eb4c..62fb463 100644 --- a/index.js +++ b/index.js @@ -23,25 +23,10 @@ /** @typedef {import('./src/wallet-account.js').KeyPair} KeyPair */ -/** @typedef {import('./src/wallet-account-read-only-multisig.js').MultisigInfo} MultisigInfo */ -/** @typedef {import('./src/wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ -/** @typedef {import('./src/wallet-account-read-only-multisig.js').MessageInfo} MessageInfo */ -/** @typedef {import('./src/wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ - -/** @typedef {import('./src/wallet-account-multisig.js').MultisigTransactionResult} MultisigTransactionResult */ -/** @typedef {import('./src/wallet-account-multisig.js').MultisigExecuteResult} MultisigExecuteResult */ -/** @typedef {import('./src/wallet-account-multisig.js').MultisigSendOptions} MultisigSendOptions */ -/** @typedef {import('./src/wallet-account-multisig.js').MessageProposal} MessageProposal */ -/** @typedef {import('./src/wallet-account-multisig.js').MultisigOptions} MultisigOptions */ - export { default } from './src/wallet-manager.js' export { default as WalletAccountReadOnly, IWalletAccountReadOnly } from './src/wallet-account-read-only.js' export { IWalletAccount } from './src/wallet-account.js' -export { IWalletAccountReadOnlyMultisig } from './src/wallet-account-read-only-multisig.js' - -export { IWalletAccountMultisig } from './src/wallet-account-multisig.js' - export { NotImplementedError } from './src/errors.js' diff --git a/package.json b/package.json index 5cb9975..c7bc85a 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,10 @@ "types": "./types/src/protocols/index.d.ts", "default": "./src/protocols/index.js" }, + "./multisig": { + "types": "./types/src/multisig/index.d.ts", + "default": "./src/multisig/index.js" + }, "./package": { "default": "./package.json" } diff --git a/src/multisig/index.js b/src/multisig/index.js new file mode 100644 index 0000000..6dee4a6 --- /dev/null +++ b/src/multisig/index.js @@ -0,0 +1,27 @@ +// Copyright 2024 Tether Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +'use strict' + +/** @typedef {import('./wallet-account-read-only-multisig.js').MultisigInfo} MultisigInfo */ +/** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ +/** @typedef {import('./wallet-account-read-only-multisig.js').MultisigMessage} MultisigMessage */ + +/** @typedef {import('./wallet-account-multisig.js').MultisigTransactionResult} MultisigTransactionResult */ +/** @typedef {import('./wallet-account-multisig.js').MultisigTransactionOptions} MultisigTransactionOptions */ +/** @typedef {import('./wallet-account-multisig.js').MultisigMessageProposal} MultisigMessageProposal */ +/** @typedef {import('./wallet-account-multisig.js').MultisigOptions} MultisigOptions */ + +export { IWalletAccountReadOnlyMultisig } from './wallet-account-read-only-multisig.js' + +export { IWalletAccountMultisig } from './wallet-account-multisig.js' diff --git a/src/multisig/wallet-account-multisig.js b/src/multisig/wallet-account-multisig.js new file mode 100644 index 0000000..1474fc1 --- /dev/null +++ b/src/multisig/wallet-account-multisig.js @@ -0,0 +1,180 @@ +// Copyright 2024 Tether Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +'use strict' + +import { NotImplementedError } from '../errors.js' + +/** @typedef {import('../wallet-account.js').IWalletAccount} IWalletAccount */ + +/** @typedef {import('../wallet-account-read-only.js').Transaction} Transaction */ +/** @typedef {import('../wallet-account-read-only.js').TransferOptions} TransferOptions */ +/** @typedef {import('../wallet-account-read-only.js').TransactionResult} TransactionResult */ + +/** @typedef {import('./wallet-account-read-only-multisig.js').IWalletAccountReadOnlyMultisig} IWalletAccountReadOnlyMultisig */ +/** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ + +/** + * @typedef {Object} MultisigTransactionResult + * @property {string} proposalId - The proposal's id. + * @property {string} hash - The transaction's hash. + * @property {bigint} fee - The gas cost. + * @property {number} confirmations - The current number of confirmations. + * @property {number} threshold - The minimum amount of confirmations to execute the transaction. + * @property {boolean} executed - True if the transaction has already been executed. + */ + +/** + * @typedef {Object} MultisigTransactionOptions + * @property {boolean} [autoExecute] - If true, automatically executes the transaction when the approval threshold is met (only takes effect if this signer's approval is the last one required). + */ + +/** + * @typedef {Object} MultisigMessageProposal + * @property {string} messageHash - The message's hash. + * @property {string} signature - The signature of the caller. + * @property {number} confirmations - The current number of confirmations. + * @property {number} threshold - The minimum amount of confirmations to sign the message. + * @property {string | null} combinedSignature - The final combined signature when the threshold is met. + */ + +/** + * @typedef {Object} MultisigOptions + * @property {number} threshold - The new amount of approvals required to execute a transaction. + */ + +/** + * @interface + * @extends {IWalletAccount} + * @extends {IWalletAccountReadOnlyMultisig} + */ +export class IWalletAccountMultisig { + /** + * Proposes sending a transaction. + * + * @param {Transaction} tx - The transaction. + * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. + * @returns {Promise} The transaction's result. + */ + async sendTransaction (tx, transactionOptions) { + throw new NotImplementedError('sendTransaction(tx, transactionOptions)') + } + + /** + * Proposes transferring a token to another address. + * + * @param {TransferOptions} options - The transfer's options. + * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. + * @returns {Promise} The transfer's result. + */ + async transfer (options, transactionOptions) { + throw new NotImplementedError('transfer(options, transactionOptions)') + } + + /** + * Proposes signing a message. + * + * @param {string} message - The message to sign. + * @returns {Promise} The multisig message proposal. + */ + async proposeMessage (message) { + throw new NotImplementedError('proposeMessage(message)') + } + + /** + * Approves an existing message proposal. + * + * @param {string} messageHash - The message's hash. + * @returns {Promise} The multisig message proposal. + */ + async approveMessage (messageHash) { + throw new NotImplementedError('approveMessage(messageHash)') + } + + /** + * Approves a pending proposal. + * + * @param {string} proposalId - The proposal's id. + * @returns {Promise} The multisig proposal. + */ + async approveTx (proposalId) { + throw new NotImplementedError('approveTx(proposalId)') + } + + /** + * Rejects a pending proposal. + * + * @param {string} proposalId - The proposal's id. + * @returns {Promise} The multisig proposal. + */ + async rejectTx (proposalId) { + throw new NotImplementedError('rejectTx(proposalId)') + } + + /** + * Submits a proposal for on-chain execution. + * + * @param {string} proposalId - The proposal's id. + * @returns {Promise} The transaction's result. + */ + async executeTx (proposalId) { + throw new NotImplementedError('executeTx(proposalId)') + } + + /** + * Proposes adding a new owner to the multisig wallet account. + * + * @param {string} owner - The owner's address. + * @param {MultisigOptions} [options] - The multisig options. + * @returns {Promise} The multisig proposal. + * @throws {Error} If the operation is not supported. + */ + async addOwner (owner, options) { + throw new NotImplementedError('addOwner(owner, newThreshold)') + } + + /** + * Proposes removing an owner from the multisig wallet account. + * + * @param {string} owner - The owner's address. + * @param {MultisigOptions} [options] - The multisig options. + * @returns {Promise} The multisig proposal. + * @throws {Error} If the operation is not supported. + */ + async removeOwner (owner, options) { + throw new NotImplementedError('removeOwner(owner, newThreshold)') + } + + /** + * Proposes replacing an owner with a different one. + * + * @param {string} oldOwner - The old owner. + * @param {string} newOwner - The new owner. + * @returns {Promise} The multisig proposal. + * @throws {Error} If the operation is not supported. + */ + async swapOwner (oldOwner, newOwner) { + throw new NotImplementedError('swapOwner(oldOwner, newOwner)') + } + + /** + * Proposes changing the signature threshold. + * + * @param {number} newThreshold - The new threshold. + * @returns {Promise} The multisig proposal. + * @throws {Error} If the operation is not supported. + */ + async changeThreshold (newThreshold) { + throw new NotImplementedError('changeThreshold(newThreshold)') + } +} diff --git a/src/multisig/wallet-account-read-only-multisig.js b/src/multisig/wallet-account-read-only-multisig.js new file mode 100644 index 0000000..199a9ef --- /dev/null +++ b/src/multisig/wallet-account-read-only-multisig.js @@ -0,0 +1,86 @@ +// Copyright 2024 Tether Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +'use strict' + +import { IWalletAccountReadOnly } from '../wallet-account-read-only.js' + +import { NotImplementedError } from '../errors.js' + +/** + * @typedef {Object} MultisigInfo + * @property {string} address - The multisig wallet account address. + * @property {string[]} owners - The owners of the multisig wallet account. + * @property {number} threshold - The minimum amount of signatures to execute a transaction. + * @property {boolean} [isCreated] - True if the multisig wallet account has already been created. + */ + +/** + * + * @typedef {Object} MultisigProposal + * @property {string} proposalId - The proposal's id. + * @property {number} confirmations - The current number of confirmations. + * @property {number} threshold - The minimum amount of confirmations to execute the transaction. + */ + +/** + * @typedef {Object} MultisigMessage + * @property {string} messageHash - The message's hash. + * @property {string} message - The original message. + * @property {number} confirmations - The current number of confirmations. + * @property {number} threshold - The minimum amount of confirmations to sign the message. + * @property {string | null} combinedSignature - The final combined signature when the threshold is met. + */ + +/** @interface */ +export class IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { + /** + * Returns the address of the signer associated with this wallet account. + * + * @returns {Promise} The signer's address, or null if no signer is associated yet. + */ + async getSignerAddress () { + throw new NotImplementedError('getSignerAddress()') + } + + /** + * Returns the multisig wallet account info. + * + * @returns {Promise} The info. + */ + async getMultisigInfo () { + throw new NotImplementedError('getMultisigInfo()') + } + + /** + * Returns a list of proposals by their identifiers. + * + * @param {string[]} proposalIds - The list of proposal identifiers. + * @returns {Promise<(MultisigProposal | null)[]>} For each proposal id, the proposal details or + * null if the proposal has not been found. + */ + async getProposals (proposalIds) { + throw new NotImplementedError('getProposals(proposalIds)') + } + + /** + * Returns a list of message proposals by their hashes. + * + * @param {string[]} messageHashes - The list of message hashes + * @returns {Promise<(MultisigMessage | null)[]>} For each message hash, the message details or + * null if the message has not been found. + */ + async getMessages (messageHashes) { + throw new NotImplementedError('getMessages(messageHashes)') + } +} diff --git a/src/wallet-account-multisig.js b/src/wallet-account-multisig.js deleted file mode 100644 index 3cbc824..0000000 --- a/src/wallet-account-multisig.js +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2024 Tether Operations Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict' - -import { IWalletAccount } from './wallet-account.js' -import { NotImplementedError } from './errors.js' - -/** @typedef {import('./wallet-account-read-only.js').Transaction} Transaction */ -/** @typedef {import('./wallet-account-read-only.js').TransferOptions} TransferOptions */ -/** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ - -/** - * @typedef {Object} MultisigTransactionResult - * @property {string} proposalId - The proposal identifier - * @property {string} hash - The transaction hash - * @property {bigint} fee - The transaction fee or estimated fee - * @property {number} confirmations - Current number of confirmations - * @property {number} threshold - Required threshold for execution - * @property {boolean} executed - Whether the transaction was executed on-chain - */ - -/** - * @typedef {Object} MultisigExecuteResult - * @property {string} hash - The transaction hash - * @property {bigint} [fee] - The transaction fee - */ - -/** - * @typedef {Object} MultisigSendOptions - * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met. Only takes effect if this signer's approval is the final one required. Otherwise the transaction must be executed manually via `executeTx()`. - */ - -/** - * @typedef {Object} MessageProposal - * @property {string} messageHash - Unique identifier for the message proposal - * @property {string} signature - This signer's signature - * @property {number} confirmations - Number of confirmations - * @property {number} threshold - Required threshold - * @property {string | null} combinedSignature - Final combined signature when threshold is met - */ - -/** - * @typedef {Object} MultisigOptions - * @property {number} threshold - The number of approvals required to execute a transaction. - */ - -/** - * @interface - * @extends {IWalletAccount} - * @extends {IWalletAccountReadOnlyMultisig} - */ -export class IWalletAccountMultisig extends IWalletAccount { - /** - * Proposes sending a transaction. - * The transaction will be sent automatically once the approval threshold is met - * if autoExecute option is enabled. - * - * @param {Transaction} tx - The transaction - * @param {MultisigSendOptions} [options] - The multisig send options - * @returns {Promise} The transaction result - */ - async sendTransaction (tx, options) { - throw new NotImplementedError('sendTransaction(tx)') - } - - /** - * Proposes transferring a token to another address. - * The transfer will be executed automatically once the approval threshold is met - * if autoExecute option is enabled. - * - * @param {TransferOptions} options - The transfer options - * @param {MultisigSendOptions} [sendOptions] - The multisig send options - * @returns {Promise} The transfer result - */ - async transfer (options, sendOptions) { - throw new NotImplementedError('transfer(options)') - } - - /** - * Proposes signing a message with the multisig. - * The proposer's signature is included automatically. - * - * @param {string} message - The message to sign - * @returns {Promise} The message proposal result - */ - async proposeMessage (message) { - throw new NotImplementedError('proposeMessage(message)') - } - - /** - * Approves an existing message proposal. - * - * @param {string} messageHash - The message hash to approve - * @returns {Promise} The approval result - */ - async approveMessage (messageHash) { - throw new NotImplementedError('approveMessage(messageHash)') - } - - /** - * Adds the current signer's approval to an existing proposal. - * - * @param {string} proposalId - The proposal identifier to approve - * @returns {Promise} The approval result - */ - async approveTx (proposalId) { - throw new NotImplementedError('approveTx(proposalId)') - } - - /** - * Rejects a pending proposal. - * Behavior is chain-specific: some chains support on-chain rejection, - * others may create a competing proposal or simply record disapproval. - * - * @param {string} proposalId - The proposal identifier to reject - * @returns {Promise} The rejection result - */ - async rejectTx (proposalId) { - throw new NotImplementedError('rejectTx(proposalId)') - } - - /** - * Submits a fully-signed proposal for on-chain execution. - * - * @param {string} proposalId - The proposal identifier to execute - * @returns {Promise} The execution result - */ - async executeTx (proposalId) { - throw new NotImplementedError('executeTx(proposalId)') - } - - // ========================================== - // Owner Management - // ========================================== - - /** - * Proposes adding a new owner to the multisig. - * - * @param {string} owner - The new owner's identifier (address/pubkey). - * @param {MultisigOptions} [options] - The new multisig options. - * @returns {Promise} The proposal result. - * @throws {Error} If the operation is not supported. - */ - async addOwner (owner, options) { - throw new NotImplementedError('addOwner(owner, newThreshold)') - } - - /** - * Proposes removing an owner from the multisig. - * - * @param {string} owner - The owner's identifier to remove - * @param {MultisigOptions} [options] - The new multisig options. - * @returns {Promise} The proposal result - * @throws {Error} If the operation is not supported. - */ - async removeOwner (owner, options) { - throw new NotImplementedError('removeOwner(owner, newThreshold)') - } - - /** - * Proposes replacing one owner with another. - * - * @param {string} oldOwner - The owner to replace - * @param {string} newOwner - The replacement owner - * @returns {Promise} The proposal result - * @throws {Error} If the operation is not supported. - */ - async swapOwner (oldOwner, newOwner) { - throw new NotImplementedError('swapOwner(oldOwner, newOwner)') - } - - /** - * Proposes changing the signature threshold. - * - * @param {number} newThreshold - The new threshold - * @returns {Promise} The proposal result - * @throws {Error} If the operation is not supported. - */ - async changeThreshold (newThreshold) { - throw new NotImplementedError('changeThreshold(newThreshold)') - } -} diff --git a/src/wallet-account-read-only-multisig.js b/src/wallet-account-read-only-multisig.js deleted file mode 100644 index b5ba784..0000000 --- a/src/wallet-account-read-only-multisig.js +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2024 Tether Operations Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict' - -import { IWalletAccountReadOnly } from './wallet-account-read-only.js' -import { NotImplementedError } from './errors.js' - -/** - * @typedef {Object} MultisigInfo - * @property {string} address - The multisig address - * @property {string[]} owners - Array of owner identifiers - * @property {number} threshold - Required number of signatures - * @property {boolean} [isCreated] - Whether the multisig wallet has been created - */ - -/** - * - * @typedef {Object} MultisigProposal - * @property {string} proposalId - Unique identifier for the created proposal - * @property {number} confirmations - Number of confirmations - * @property {number} threshold - Required threshold for execution - */ - -/** - * @typedef {Object} MessageInfo - * @property {string} messageHash - The message hash - * @property {string} message - The original message - * @property {number} confirmations - Number of confirmations - * @property {number} threshold - Required threshold - * @property {string | null} combinedSignature - Final combined signature when threshold is met - */ - -/** @interface */ -export class IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { - /** - * Returns the address/identifier of the signer associated with this account wallet - * Returns null if no signer is associated. - * - * @returns {Promise} The signer's identifier - */ - async getSignerAddress () { - throw new NotImplementedError('getSignerAddress()') - } - - /** - * Returns the multisig wallet info. - * - * @returns {Promise} The multisig info - */ - async getMultisigInfo () { - throw new NotImplementedError('getMultisigInfo()') - } - - /** - * Returns a list of proposals by their identifiers. - * - * @param {string[]} proposalIds - The list of proposal identifiers - * @returns {Promise<(MultisigProposal | null)[]>} The proposal details, or null for proposals not found - */ - async getProposals (proposalIds) { - throw new NotImplementedError('getProposals(proposalIds)') - } - - /** - * Returns a list of message proposals by their hashes. - * - * @param {string[]} messageHashes - The list of message hashes - * @returns {Promise<(MessageInfo | null)[]>} The message details, or null for messages not found - */ - async getMessages (messageHashes) { - throw new NotImplementedError('getMessages(messageHashes)') - } -} diff --git a/types/src/multisig/index.d.ts b/types/src/multisig/index.d.ts new file mode 100644 index 0000000..82125d9 --- /dev/null +++ b/types/src/multisig/index.d.ts @@ -0,0 +1,9 @@ +export { IWalletAccountReadOnlyMultisig } from "./wallet-account-read-only-multisig.js"; +export { IWalletAccountMultisig } from "./wallet-account-multisig.js"; +export type MultisigInfo = import("./wallet-account-read-only-multisig.js").MultisigInfo; +export type MultisigProposal = import("./wallet-account-read-only-multisig.js").MultisigProposal; +export type MultisigMessage = import("./wallet-account-read-only-multisig.js").MultisigMessage; +export type MultisigTransactionResult = import("./wallet-account-multisig.js").MultisigTransactionResult; +export type MultisigTransactionOptions = import("./wallet-account-multisig.js").MultisigTransactionOptions; +export type MultisigMessageProposal = import("./wallet-account-multisig.js").MultisigMessageProposal; +export type MultisigOptions = import("./wallet-account-multisig.js").MultisigOptions; diff --git a/types/src/multisig/wallet-account-multisig.d.ts b/types/src/multisig/wallet-account-multisig.d.ts new file mode 100644 index 0000000..308acfc --- /dev/null +++ b/types/src/multisig/wallet-account-multisig.d.ts @@ -0,0 +1,159 @@ +/** + * @interface + * @extends {IWalletAccount} + * @extends {IWalletAccountReadOnlyMultisig} + */ +export interface IWalletAccountMultisig extends IWalletAccount, IWalletAccountReadOnlyMultisig { + /** + * Proposes sending a transaction. + * + * @param {Transaction} tx - The transaction. + * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. + * @returns {Promise} The transaction's result. + */ + sendTransaction(tx: Transaction, transactionOptions?: MultisigTransactionOptions): Promise; + /** + * Proposes transferring a token to another address. + * + * @param {TransferOptions} options - The transfer's options. + * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. + * @returns {Promise} The transfer's result. + */ + transfer(options: TransferOptions, transactionOptions?: MultisigTransactionOptions): Promise; + /** + * Proposes signing a message. + * + * @param {string} message - The message to sign. + * @returns {Promise} The multisig message proposal. + */ + proposeMessage(message: string): Promise; + /** + * Approves an existing message proposal. + * + * @param {string} messageHash - The message's hash. + * @returns {Promise} The multisig message proposal. + */ + approveMessage(messageHash: string): Promise; + /** + * Approves a pending proposal. + * + * @param {string} proposalId - The proposal's id. + * @returns {Promise} The multisig proposal. + */ + approveTx(proposalId: string): Promise; + /** + * Rejects a pending proposal. + * + * @param {string} proposalId - The proposal's id. + * @returns {Promise} The multisig proposal. + */ + rejectTx(proposalId: string): Promise; + /** + * Submits a proposal for on-chain execution. + * + * @param {string} proposalId - The proposal's id. + * @returns {Promise} The transaction's result. + */ + executeTx(proposalId: string): Promise; + /** + * Proposes adding a new owner to the multisig wallet account. + * + * @param {string} owner - The owner's address. + * @param {MultisigOptions} [options] - The multisig options. + * @returns {Promise} The multisig proposal. + * @throws {Error} If the operation is not supported. + */ + addOwner(owner: string, options?: MultisigOptions): Promise; + /** + * Proposes removing an owner from the multisig wallet account. + * + * @param {string} owner - The owner's address. + * @param {MultisigOptions} [options] - The multisig options. + * @returns {Promise} The multisig proposal. + * @throws {Error} If the operation is not supported. + */ + removeOwner(owner: string, options?: MultisigOptions): Promise; + /** + * Proposes replacing an owner with a different one. + * + * @param {string} oldOwner - The old owner. + * @param {string} newOwner - The new owner. + * @returns {Promise} The multisig proposal. + * @throws {Error} If the operation is not supported. + */ + swapOwner(oldOwner: string, newOwner: string): Promise; + /** + * Proposes changing the signature threshold. + * + * @param {number} newThreshold - The new threshold. + * @returns {Promise} The multisig proposal. + * @throws {Error} If the operation is not supported. + */ + changeThreshold(newThreshold: number): Promise; +} +export type IWalletAccount = import("../wallet-account.js").IWalletAccount; +export type Transaction = import("../wallet-account-read-only.js").Transaction; +export type TransferOptions = import("../wallet-account-read-only.js").TransferOptions; +export type TransactionResult = import("../wallet-account-read-only.js").TransactionResult; +export type IWalletAccountReadOnlyMultisig = import("./wallet-account-read-only-multisig.js").IWalletAccountReadOnlyMultisig; +export type MultisigProposal = import("./wallet-account-read-only-multisig.js").MultisigProposal; +export type MultisigTransactionResult = { + /** + * - The proposal's id. + */ + proposalId: string; + /** + * - The transaction's hash. + */ + hash: string; + /** + * - The gas cost. + */ + fee: bigint; + /** + * - The current number of confirmations. + */ + confirmations: number; + /** + * - The minimum amount of confirmations to execute the transaction. + */ + threshold: number; + /** + * - True if the transaction has already been executed. + */ + executed: boolean; +}; +export type MultisigTransactionOptions = { + /** + * - If true, automatically executes the transaction when the approval threshold is met (only takes effect if this signer's approval is the last one required). + */ + autoExecute?: boolean; +}; +export type MultisigMessageProposal = { + /** + * - The message's hash. + */ + messageHash: string; + /** + * - The signature of the caller. + */ + signature: string; + /** + * - The current number of confirmations. + */ + confirmations: number; + /** + * - The minimum amount of confirmations to sign the message. + */ + threshold: number; + /** + * - The final combined signature when the threshold is met. + */ + combinedSignature: string | null; +}; +export type MultisigOptions = { + /** + * - The new amount of approvals required to execute a transaction. + */ + threshold: number; +}; diff --git a/types/src/multisig/wallet-account-read-only-multisig.d.ts b/types/src/multisig/wallet-account-read-only-multisig.d.ts new file mode 100644 index 0000000..bf53d8c --- /dev/null +++ b/types/src/multisig/wallet-account-read-only-multisig.d.ts @@ -0,0 +1,86 @@ +/** @interface */ +export interface IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { + /** + * Returns the address of the signer associated with this wallet account. + * + * @returns {Promise} The signer's address, or null if no signer is associated yet. + */ + getSignerAddress(): Promise; + /** + * Returns the multisig wallet account info. + * + * @returns {Promise} The info. + */ + getMultisigInfo(): Promise; + /** + * Returns a list of proposals by their identifiers. + * + * @param {string[]} proposalIds - The list of proposal identifiers. + * @returns {Promise<(MultisigProposal | null)[]>} For each proposal id, the proposal details or + * null if the proposal has not been found. + */ + getProposals(proposalIds: string[]): Promise<(MultisigProposal | null)[]>; + /** + * Returns a list of message proposals by their hashes. + * + * @param {string[]} messageHashes - The list of message hashes + * @returns {Promise<(MultisigMessage | null)[]>} For each message hash, the message details or + * null if the message has not been found. + */ + getMessages(messageHashes: string[]): Promise<(MultisigMessage | null)[]>; +} +export type MultisigInfo = { + /** + * - The multisig wallet account address. + */ + address: string; + /** + * - The owners of the multisig wallet account. + */ + owners: string[]; + /** + * - The minimum amount of signatures to execute a transaction. + */ + threshold: number; + /** + * - True if the multisig wallet account has already been created. + */ + isCreated?: boolean; +}; +export type MultisigProposal = { + /** + * - The proposal's id. + */ + proposalId: string; + /** + * - The current number of confirmations. + */ + confirmations: number; + /** + * - The minimum amount of confirmations to execute the transaction. + */ + threshold: number; +}; +export type MultisigMessage = { + /** + * - The message's hash. + */ + messageHash: string; + /** + * - The original message. + */ + message: string; + /** + * - The current number of confirmations. + */ + confirmations: number; + /** + * - The minimum amount of confirmations to sign the message. + */ + threshold: number; + /** + * - The final combined signature when the threshold is met. + */ + combinedSignature: string | null; +}; +import { IWalletAccountReadOnly } from '../wallet-account-read-only.js'; diff --git a/types/src/wallet-account-multisig.d.ts b/types/src/wallet-account-multisig.d.ts deleted file mode 100644 index 1b727d6..0000000 --- a/types/src/wallet-account-multisig.d.ts +++ /dev/null @@ -1,208 +0,0 @@ -/** @typedef {import('./wallet-account-read-only.js').Transaction} Transaction */ -/** @typedef {import('./wallet-account-read-only.js').TransferOptions} TransferOptions */ -/** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigResult */ -/** - * @typedef {Object} MultisigTransactionResult - * @property {string} proposalId - The proposal identifier - * @property {string} hash - The transaction hash - * @property {bigint} fee - The transaction fee or estimated fee - * @property {number} confirmations - Current number of confirmations - * @property {number} threshold - Required threshold for execution - * @property {boolean} executed - Whether the transaction was executed on-chain - */ -/** - * @typedef {Object} MultisigExecuteResult - * @property {string} hash - The transaction hash - * @property {bigint} [fee] - The transaction fee - */ -/** - * @typedef {Object} MultisigSendOptions - * @property {boolean} [autoExecute] - If true, automatically execute the transaction when the approval threshold is met. Only takes effect if this signer's approval is the final one required. Otherwise the transaction must be executed manually via `executeTx()`. - */ -/** - * @typedef {Object} MessageProposal - * @property {string} messageHash - Unique identifier for the message proposal - * @property {string} signature - This signer's signature - * @property {number} confirmations - Number of confirmations - * @property {number} threshold - Required threshold - * @property {string | null} combinedSignature - Final combined signature when threshold is met - */ -/** - * @typedef {Object} MultisigOptions - * @property {number} threshold - The number of approvals required to execute a transaction. - */ -/** - * @interface - * @extends {IWalletAccount} - * @extends {IWalletAccountReadOnlyMultisig} - */ -export interface IWalletAccountMultisig extends IWalletAccount, IWalletAccountReadOnlyMultisig { - /** - * Proposes sending a transaction. - * The transaction will be sent automatically once the approval threshold is met - * if autoExecute option is enabled. - * - * @param {Transaction} tx - The transaction - * @param {MultisigSendOptions} [options] - The multisig send options - * @returns {Promise} The transaction result - */ - sendTransaction(tx: Transaction, options?: MultisigSendOptions): Promise; - /** - * Proposes transferring a token to another address. - * The transfer will be executed automatically once the approval threshold is met - * if autoExecute option is enabled. - * - * @param {TransferOptions} options - The transfer options - * @param {MultisigSendOptions} [sendOptions] - The multisig send options - * @returns {Promise} The transfer result - */ - transfer(options: TransferOptions, sendOptions?: MultisigSendOptions): Promise; - /** - * Proposes signing a message with the multisig. - * The proposer's signature is included automatically. - * - * @param {string} message - The message to sign - * @returns {Promise} The message proposal result - */ - proposeMessage(message: string): Promise; - /** - * Approves an existing message proposal. - * - * @param {string} messageHash - The message hash to approve - * @returns {Promise} The approval result - */ - approveMessage(messageHash: string): Promise; - /** - * Adds the current signer's approval to an existing proposal. - * - * @param {string} proposalId - The proposal identifier to approve - * @returns {Promise} The approval result - */ - approveTx(proposalId: string): Promise; - /** - * Rejects a pending proposal. - * Behavior is chain-specific: some chains support on-chain rejection, - * others may create a competing proposal or simply record disapproval. - * - * @param {string} proposalId - The proposal identifier to reject - * @returns {Promise} The rejection result - */ - rejectTx(proposalId: string): Promise; - /** - * Submits a fully-signed proposal for on-chain execution. - * - * @param {string} proposalId - The proposal identifier to execute - * @returns {Promise} The execution result - */ - executeTx(proposalId: string): Promise; - /** - * Proposes adding a new owner to the multisig. - * - * @param {string} owner - The new owner's identifier (address/pubkey). - * @param {MultisigOptions} [options] - The new multisig options. - * @returns {Promise} The proposal result. - * @throws {Error} If the operation is not supported. - */ - addOwner(owner: string, options?: MultisigOptions): Promise; - /** - * Proposes removing an owner from the multisig. - * - * @param {string} owner - The owner's identifier to remove - * @param {MultisigOptions} [options] - The new multisig options. - * @returns {Promise} The proposal result - * @throws {Error} If the operation is not supported. - */ - removeOwner(owner: string, options?: MultisigOptions): Promise; - /** - * Proposes replacing one owner with another. - * - * @param {string} oldOwner - The owner to replace - * @param {string} newOwner - The replacement owner - * @returns {Promise} The proposal result - * @throws {Error} If the operation is not supported. - */ - swapOwner(oldOwner: string, newOwner: string): Promise; - /** - * Proposes changing the signature threshold. - * - * @param {number} newThreshold - The new threshold - * @returns {Promise} The proposal result - * @throws {Error} If the operation is not supported. - */ - changeThreshold(newThreshold: number): Promise; -} -export type Transaction = import("./wallet-account-read-only.js").Transaction; -export type TransferOptions = import("./wallet-account-read-only.js").TransferOptions; -export type MultisigResult = import("./wallet-account-read-only-multisig.js").MultisigProposal; -export type MultisigTransactionResult = { - /** - * - The proposal identifier - */ - proposalId: string; - /** - * - The transaction hash - */ - hash: string; - /** - * - The transaction fee or estimated fee - */ - fee: bigint; - /** - * - Current number of confirmations - */ - confirmations: number; - /** - * - Required threshold for execution - */ - threshold: number; - /** - * - Whether the transaction was executed on-chain - */ - executed: boolean; -}; -export type MultisigExecuteResult = { - /** - * - The transaction hash - */ - hash: string; - /** - * - The transaction fee - */ - fee?: bigint; -}; -export type MultisigSendOptions = { - /** - * - If true, automatically execute the transaction when the approval threshold is met. Only takes effect if this signer's approval is the final one required. Otherwise the transaction must be executed manually via `executeTx()`. - */ - autoExecute?: boolean; -}; -export type MessageProposal = { - /** - * - Unique identifier for the message proposal - */ - messageHash: string; - /** - * - This signer's signature - */ - signature: string; - /** - * - Number of confirmations - */ - confirmations: number; - /** - * - Required threshold - */ - threshold: number; - /** - * - Final combined signature when threshold is met - */ - combinedSignature: string | null; -}; -export type MultisigOptions = { - /** - * - The number of approvals required to execute a transaction. - */ - threshold: number; -}; -import { IWalletAccountReadOnlyMultisig } from './wallet-account-read-only-multisig.js'; -import { IWalletAccount } from './wallet-account.js'; diff --git a/types/src/wallet-account-read-only-multisig.d.ts b/types/src/wallet-account-read-only-multisig.d.ts deleted file mode 100644 index 86ac627..0000000 --- a/types/src/wallet-account-read-only-multisig.d.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * @typedef {Object} MultisigInfo - * @property {string} address - The multisig address - * @property {string[]} owners - Array of owner identifiers - * @property {number} threshold - Required number of signatures - * @property {boolean} [isCreated] - Whether the multisig wallet has been created - */ -/** - * - * @typedef {Object} MultisigProposal - * @property {string} proposalId - Unique identifier for the created proposal - * @property {number} confirmations - Number of confirmations - * @property {number} threshold - Required threshold for execution - */ -/** - * @typedef {Object} MessageInfo - * @property {string} messageHash - The message hash - * @property {string} message - The original message - * @property {number} confirmations - Number of confirmations - * @property {number} threshold - Required threshold - * @property {string | null} combinedSignature - Final combined signature when threshold is met - */ -/** @interface */ -export interface IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { - /** - * Returns the address/identifier of the signer associated with this account wallet - * Returns null if no signer is associated. - * - * @returns {Promise} The signer's identifier - */ - getSignerAddress(): Promise; - /** - * Returns the multisig wallet info. - * - * @returns {Promise} The multisig info - */ - getMultisigInfo(): Promise; - /** - * Returns a list of proposals by their identifiers. - * - * @param {string[]} proposalIds - The list of proposal identifiers - * @returns {Promise<(MultisigProposal | null)[]>} The proposal details, or null for proposals not found - */ - getProposals(proposalIds: string[]): Promise<(MultisigProposal | null)[]>; - /** - * Returns a list of message proposals by their hashes. - * - * @param {string[]} messageHashes - The list of message hashes - * @returns {Promise<(MessageInfo | null)[]>} The message details, or null for messages not found - */ - getMessages(messageHashes: string[]): Promise<(MessageInfo | null)[]>; -} -export type MultisigInfo = { - /** - * - The multisig address - */ - address: string; - /** - * - Array of owner identifiers - */ - owners: string[]; - /** - * - Required number of signatures - */ - threshold: number; - /** - * - Whether the multisig wallet has been created - */ - isCreated?: boolean; -}; -export type MultisigProposal = { - /** - * - Unique identifier for the created proposal - */ - proposalId: string; - /** - * - Number of confirmations - */ - confirmations: number; - /** - * - Required threshold for execution - */ - threshold: number; -}; -export type MessageInfo = { - /** - * - The message hash - */ - messageHash: string; - /** - * - The original message - */ - message: string; - /** - * - Number of confirmations - */ - confirmations: number; - /** - * - Required threshold - */ - threshold: number; - /** - * - Final combined signature when threshold is met - */ - combinedSignature: string | null; -}; -import { IWalletAccountReadOnly } from './wallet-account-read-only.js'; From 922dd4d48709cc184e985441b3ba2a42c142c9e3 Mon Sep 17 00:00:00 2001 From: rickalon Date: Thu, 11 Jun 2026 06:44:49 -0400 Subject: [PATCH 27/41] feat(multisig): add chain-agnostic IMultisigTransport interface Adds IMultisigTransport to src/multisig/: a transport contract for sharing transaction and message proposals (and their confirmations) between the owners of a distributed multisig account. Proposal/message payloads are opaque so the same transport can back account-abstraction, UTXO (PSBT) or other multisig wallets; each chain's package interprets them. Exported from the @tetherto/wdk-wallet/multisig subpath alongside the existing multisig interfaces. Consumed by @tetherto/wdk-protocol-multisig-safe, which previously defined this interface locally. --- src/multisig/i-multisig-transport.js | 120 +++++++++++++++++++ src/multisig/index.js | 6 + types/src/multisig/i-multisig-transport.d.ts | 118 ++++++++++++++++++ types/src/multisig/index.d.ts | 4 + 4 files changed, 248 insertions(+) create mode 100644 src/multisig/i-multisig-transport.js create mode 100644 types/src/multisig/i-multisig-transport.d.ts diff --git a/src/multisig/i-multisig-transport.js b/src/multisig/i-multisig-transport.js new file mode 100644 index 0000000..c637902 --- /dev/null +++ b/src/multisig/i-multisig-transport.js @@ -0,0 +1,120 @@ +// Copyright 2024 Tether Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +'use strict' + +import { NotImplementedError } from '../errors.js' + +/** + * A message proposal to share with the other owners for them to confirm. + * + * @typedef {Object} MultisigTransportMessageInput + * @property {string} message - The message to sign. + * @property {string} signature - The submitting owner's signature over the message. + */ + +/** + * A shared transaction proposal returned by the transport. The concrete payload is + * chain-specific and opaque to the transport: an implementation persists whatever the + * chain's execution layer produced and returns it intact, alongside the owner + * confirmations collected so far. + * + * @typedef {Object} MultisigTransportProposal + * @property {unknown[]} confirmations - The owner confirmations (signatures) collected so far. + */ + +/** + * A shared message proposal returned by the transport, alongside the owner confirmations + * collected so far. As with proposals, any further fields are chain-specific. + * + * @typedef {Object} MultisigTransportMessage + * @property {unknown[]} confirmations - The owner confirmations (signatures) collected so far. + */ + +/** + * Transport for sharing multisig calldata between the owners of a multisig account. + * + * A transport distributes transaction proposals and message proposals (and their + * confirmations) amongst the owners of a multisig account, so that signers running on + * separate machines can coordinate without a shared process. It is chain-agnostic: the + * proposal and message payloads are opaque to the transport and interpreted by each + * chain's multisig package, so the same transport can back account-abstraction, UTXO + * (PSBT) or other multisig wallets. Plug in a custom backend (a hosted service, a + * database, a peer-to-peer channel, etc.) by implementing this interface. + * + * @interface + */ +export class IMultisigTransport { + /** + * Submits a new transaction proposal so the other owners can confirm it. + * + * @param {Record} proposal - The signed transaction proposal to share. Opaque to the transport, which must persist it so {@link getProposal} can return it intact. + * @returns {Promise} + */ + async submitProposal (proposal) { + throw new NotImplementedError('submitProposal(proposal)') + } + + /** + * Returns a transaction proposal by its identifier. + * + * @param {string} proposalId - The proposal's identifier. + * @returns {Promise} The proposal, or null if it has not been found. + */ + async getProposal (proposalId) { + throw new NotImplementedError('getProposal(proposalId)') + } + + /** + * Adds an owner's confirmation (signature) to an existing transaction proposal. + * + * @param {string} proposalId - The proposal's identifier. + * @param {string} signature - The owner's signature over the proposal. + * @returns {Promise} + */ + async confirmProposal (proposalId, signature) { + throw new NotImplementedError('confirmProposal(proposalId, signature)') + } + + /** + * Submits a new message proposal so the other owners can confirm it. + * + * @param {string} accountAddress - The multisig account's address. + * @param {MultisigTransportMessageInput} message - The message proposal to share. + * @returns {Promise} + */ + async submitMessage (accountAddress, message) { + throw new NotImplementedError('submitMessage(accountAddress, message)') + } + + /** + * Returns a message proposal by its hash. + * + * @param {string} messageHash - The message's hash. + * @returns {Promise} The message, or null if it has not been found. + */ + async getMessage (messageHash) { + throw new NotImplementedError('getMessage(messageHash)') + } + + /** + * Adds an owner's confirmation (signature) to an existing message proposal. + * + * @param {string} messageHash - The message's hash. + * @param {string} signature - The owner's signature over the message. + * @returns {Promise} + */ + async confirmMessage (messageHash, signature) { + throw new NotImplementedError('confirmMessage(messageHash, signature)') + } +} diff --git a/src/multisig/index.js b/src/multisig/index.js index 6dee4a6..6c05a5e 100644 --- a/src/multisig/index.js +++ b/src/multisig/index.js @@ -22,6 +22,12 @@ /** @typedef {import('./wallet-account-multisig.js').MultisigMessageProposal} MultisigMessageProposal */ /** @typedef {import('./wallet-account-multisig.js').MultisigOptions} MultisigOptions */ +/** @typedef {import('./i-multisig-transport.js').MultisigTransportProposal} MultisigTransportProposal */ +/** @typedef {import('./i-multisig-transport.js').MultisigTransportMessage} MultisigTransportMessage */ +/** @typedef {import('./i-multisig-transport.js').MultisigTransportMessageInput} MultisigTransportMessageInput */ + export { IWalletAccountReadOnlyMultisig } from './wallet-account-read-only-multisig.js' export { IWalletAccountMultisig } from './wallet-account-multisig.js' + +export { IMultisigTransport } from './i-multisig-transport.js' diff --git a/types/src/multisig/i-multisig-transport.d.ts b/types/src/multisig/i-multisig-transport.d.ts new file mode 100644 index 0000000..53e5cb8 --- /dev/null +++ b/types/src/multisig/i-multisig-transport.d.ts @@ -0,0 +1,118 @@ +/** + * A message proposal to share with the other owners for them to confirm. + * + * @typedef {Object} MultisigTransportMessageInput + * @property {string} message - The message to sign. + * @property {string} signature - The submitting owner's signature over the message. + */ +/** + * A shared transaction proposal returned by the transport. The concrete payload is + * chain-specific and opaque to the transport: an implementation persists whatever the + * chain's execution layer produced and returns it intact, alongside the owner + * confirmations collected so far. + * + * @typedef {Object} MultisigTransportProposal + * @property {unknown[]} confirmations - The owner confirmations (signatures) collected so far. + */ +/** + * A shared message proposal returned by the transport, alongside the owner confirmations + * collected so far. As with proposals, any further fields are chain-specific. + * + * @typedef {Object} MultisigTransportMessage + * @property {unknown[]} confirmations - The owner confirmations (signatures) collected so far. + */ +/** + * Transport for sharing multisig calldata between the owners of a multisig account. + * + * A transport distributes transaction proposals and message proposals (and their + * confirmations) amongst the owners of a multisig account, so that signers running on + * separate machines can coordinate without a shared process. It is chain-agnostic: the + * proposal and message payloads are opaque to the transport and interpreted by each + * chain's multisig package, so the same transport can back account-abstraction, UTXO + * (PSBT) or other multisig wallets. Plug in a custom backend (a hosted service, a + * database, a peer-to-peer channel, etc.) by implementing this interface. + * + * @interface + */ +export interface IMultisigTransport { + /** + * Submits a new transaction proposal so the other owners can confirm it. + * + * @param {Record} proposal - The signed transaction proposal to share. Opaque to the transport, which must persist it so {@link getProposal} can return it intact. + * @returns {Promise} + */ + submitProposal(proposal: Record): Promise; + /** + * Returns a transaction proposal by its identifier. + * + * @param {string} proposalId - The proposal's identifier. + * @returns {Promise} The proposal, or null if it has not been found. + */ + getProposal(proposalId: string): Promise; + /** + * Adds an owner's confirmation (signature) to an existing transaction proposal. + * + * @param {string} proposalId - The proposal's identifier. + * @param {string} signature - The owner's signature over the proposal. + * @returns {Promise} + */ + confirmProposal(proposalId: string, signature: string): Promise; + /** + * Submits a new message proposal so the other owners can confirm it. + * + * @param {string} accountAddress - The multisig account's address. + * @param {MultisigTransportMessageInput} message - The message proposal to share. + * @returns {Promise} + */ + submitMessage(accountAddress: string, message: MultisigTransportMessageInput): Promise; + /** + * Returns a message proposal by its hash. + * + * @param {string} messageHash - The message's hash. + * @returns {Promise} The message, or null if it has not been found. + */ + getMessage(messageHash: string): Promise; + /** + * Adds an owner's confirmation (signature) to an existing message proposal. + * + * @param {string} messageHash - The message's hash. + * @param {string} signature - The owner's signature over the message. + * @returns {Promise} + */ + confirmMessage(messageHash: string, signature: string): Promise; +} +/** + * A message proposal to share with the other owners for them to confirm. + */ +export type MultisigTransportMessageInput = { + /** + * - The message to sign. + */ + message: string; + /** + * - The submitting owner's signature over the message. + */ + signature: string; +}; +/** + * A shared transaction proposal returned by the transport. The concrete payload is + * chain-specific and opaque to the transport: an implementation persists whatever the + * chain's execution layer produced and returns it intact, alongside the owner + * confirmations collected so far. + */ +export type MultisigTransportProposal = { + /** + * - The owner confirmations (signatures) collected so far. + */ + confirmations: unknown[]; +}; +/** + * A shared message proposal returned by the transport, alongside the owner confirmations + * collected so far. As with proposals, any further fields are chain-specific. + */ +export type MultisigTransportMessage = { + /** + * - The owner confirmations (signatures) collected so far. + */ + confirmations: unknown[]; +}; diff --git a/types/src/multisig/index.d.ts b/types/src/multisig/index.d.ts index 82125d9..6ba4ff0 100644 --- a/types/src/multisig/index.d.ts +++ b/types/src/multisig/index.d.ts @@ -1,5 +1,6 @@ export { IWalletAccountReadOnlyMultisig } from "./wallet-account-read-only-multisig.js"; export { IWalletAccountMultisig } from "./wallet-account-multisig.js"; +export { IMultisigTransport } from "./i-multisig-transport.js"; export type MultisigInfo = import("./wallet-account-read-only-multisig.js").MultisigInfo; export type MultisigProposal = import("./wallet-account-read-only-multisig.js").MultisigProposal; export type MultisigMessage = import("./wallet-account-read-only-multisig.js").MultisigMessage; @@ -7,3 +8,6 @@ export type MultisigTransactionResult = import("./wallet-account-multisig.js").M export type MultisigTransactionOptions = import("./wallet-account-multisig.js").MultisigTransactionOptions; export type MultisigMessageProposal = import("./wallet-account-multisig.js").MultisigMessageProposal; export type MultisigOptions = import("./wallet-account-multisig.js").MultisigOptions; +export type MultisigTransportProposal = import("./i-multisig-transport.js").MultisigTransportProposal; +export type MultisigTransportMessage = import("./i-multisig-transport.js").MultisigTransportMessage; +export type MultisigTransportMessageInput = import("./i-multisig-transport.js").MultisigTransportMessageInput; From 50b4c9a693bbca21e12cafaf33c548db18128d94 Mon Sep 17 00:00:00 2001 From: rickalon Date: Mon, 15 Jun 2026 14:40:23 -0400 Subject: [PATCH 28/41] refactor(multisig): apply ISP so multisig interfaces don't extend IWalletAccount Per the team decision on PR #32: a multisig sendTransaction proposes (it does not execute), so the inherited IWalletAccount { hash, fee } contract cannot be honored across chains (off-chain proposal id on Safe/Bitcoin, proposal-not-execution on Solana). Decouple the two families and model the multisig flow directly. - Add internal IWalletAccountReadOnlyBase (getAddress, verify, getBalance, getTokenBalance, getTransactionReceipt); IWalletAccountReadOnly and IWalletAccountReadOnlyMultisig both extend it. Not exported from the entry point. - IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig only (no longer IWalletAccount). Rename sendTransaction->propose, transfer->proposeTransfer, approveTx->approveProposal, rejectTx->rejectProposal, executeTx->executeProposal. - propose returns MultisigProposalResult { proposalId, fee, confirmations, threshold, executed } (no fake on-chain hash); executeProposal returns the on-chain { hash, fee }. - Move owner management into a separate optional IMultisigOwnerManagement interface; chains with an immutable owner set (e.g. Bitcoin script multisig) don't implement it. - Re-add index/path/keyPair to IWalletAccountReadOnlyMultisig (previously inherited via IWalletAccount). Types hand-maintained to match the repo's interface-style .d.ts. --- src/multisig/i-multisig-owner-management.js | 76 ++++++++++++++ src/multisig/index.js | 8 +- src/multisig/wallet-account-multisig.js | 98 +++++-------------- .../wallet-account-read-only-multisig.js | 48 ++++++++- src/wallet-account-read-only-base.js | 77 +++++++++++++++ src/wallet-account-read-only.js | 54 +--------- .../multisig/i-multisig-owner-management.d.ts | 48 +++++++++ types/src/multisig/index.d.ts | 7 +- .../src/multisig/wallet-account-multisig.d.ts | 84 ++++------------ .../wallet-account-read-only-multisig.d.ts | 36 ++++++- types/src/wallet-account-read-only-base.d.ts | 46 +++++++++ types/src/wallet-account-read-only.d.ts | 38 +------ 12 files changed, 383 insertions(+), 237 deletions(-) create mode 100644 src/multisig/i-multisig-owner-management.js create mode 100644 src/wallet-account-read-only-base.js create mode 100644 types/src/multisig/i-multisig-owner-management.d.ts create mode 100644 types/src/wallet-account-read-only-base.d.ts diff --git a/src/multisig/i-multisig-owner-management.js b/src/multisig/i-multisig-owner-management.js new file mode 100644 index 0000000..04170b3 --- /dev/null +++ b/src/multisig/i-multisig-owner-management.js @@ -0,0 +1,76 @@ +// Copyright 2024 Tether Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +'use strict' + +import { NotImplementedError } from '../errors.js' + +/** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ + +/** + * @typedef {Object} MultisigOptions + * @property {number} threshold - The new amount of approvals required to execute a transaction. + */ + +/** + * Optional owner-management surface for multisig accounts whose owner set is mutable + * (e.g. account-abstraction wallets). Chains whose owner set is fixed at creation — + * such as Bitcoin script multisig, where the participants are committed in the redeem + * script — do not implement this interface. + * + * @interface + */ +export class IMultisigOwnerManagement { + /** + * Proposes adding a new owner to the multisig wallet account. + * + * @param {string} owner - The owner's address. + * @param {MultisigOptions} [options] - The multisig options. + * @returns {Promise} The multisig proposal. + */ + async addOwner (owner, options) { + throw new NotImplementedError('addOwner(owner, options)') + } + + /** + * Proposes removing an owner from the multisig wallet account. + * + * @param {string} owner - The owner's address. + * @param {MultisigOptions} [options] - The multisig options. + * @returns {Promise} The multisig proposal. + */ + async removeOwner (owner, options) { + throw new NotImplementedError('removeOwner(owner, options)') + } + + /** + * Proposes replacing an owner with a different one. + * + * @param {string} oldOwner - The old owner. + * @param {string} newOwner - The new owner. + * @returns {Promise} The multisig proposal. + */ + async swapOwner (oldOwner, newOwner) { + throw new NotImplementedError('swapOwner(oldOwner, newOwner)') + } + + /** + * Proposes changing the signature threshold. + * + * @param {number} newThreshold - The new threshold. + * @returns {Promise} The multisig proposal. + */ + async changeThreshold (newThreshold) { + throw new NotImplementedError('changeThreshold(newThreshold)') + } +} diff --git a/src/multisig/index.js b/src/multisig/index.js index 6c05a5e..c45ff3f 100644 --- a/src/multisig/index.js +++ b/src/multisig/index.js @@ -16,11 +16,13 @@ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigInfo} MultisigInfo */ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigMessage} MultisigMessage */ +/** @typedef {import('./wallet-account-read-only-multisig.js').MultisigExecuteQuote} MultisigExecuteQuote */ +/** @typedef {import('../wallet-account.js').KeyPair} KeyPair */ -/** @typedef {import('./wallet-account-multisig.js').MultisigTransactionResult} MultisigTransactionResult */ +/** @typedef {import('./wallet-account-multisig.js').MultisigProposalResult} MultisigProposalResult */ /** @typedef {import('./wallet-account-multisig.js').MultisigTransactionOptions} MultisigTransactionOptions */ /** @typedef {import('./wallet-account-multisig.js').MultisigMessageProposal} MultisigMessageProposal */ -/** @typedef {import('./wallet-account-multisig.js').MultisigOptions} MultisigOptions */ +/** @typedef {import('./i-multisig-owner-management.js').MultisigOptions} MultisigOptions */ /** @typedef {import('./i-multisig-transport.js').MultisigTransportProposal} MultisigTransportProposal */ /** @typedef {import('./i-multisig-transport.js').MultisigTransportMessage} MultisigTransportMessage */ @@ -30,4 +32,6 @@ export { IWalletAccountReadOnlyMultisig } from './wallet-account-read-only-multi export { IWalletAccountMultisig } from './wallet-account-multisig.js' +export { IMultisigOwnerManagement } from './i-multisig-owner-management.js' + export { IMultisigTransport } from './i-multisig-transport.js' diff --git a/src/multisig/wallet-account-multisig.js b/src/multisig/wallet-account-multisig.js index 1474fc1..6a84e98 100644 --- a/src/multisig/wallet-account-multisig.js +++ b/src/multisig/wallet-account-multisig.js @@ -15,8 +15,6 @@ import { NotImplementedError } from '../errors.js' -/** @typedef {import('../wallet-account.js').IWalletAccount} IWalletAccount */ - /** @typedef {import('../wallet-account-read-only.js').Transaction} Transaction */ /** @typedef {import('../wallet-account-read-only.js').TransferOptions} TransferOptions */ /** @typedef {import('../wallet-account-read-only.js').TransactionResult} TransactionResult */ @@ -25,13 +23,11 @@ import { NotImplementedError } from '../errors.js' /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ /** - * @typedef {Object} MultisigTransactionResult - * @property {string} proposalId - The proposal's id. - * @property {string} hash - The transaction's hash. - * @property {bigint} fee - The gas cost. + * @typedef {Object} MultisigProposalResult + * @property {string} proposalId - The proposal's id, used to approve, reject, query, quote or execute it. * @property {number} confirmations - The current number of confirmations. * @property {number} threshold - The minimum amount of confirmations to execute the transaction. - * @property {boolean} executed - True if the transaction has already been executed. + * @property {boolean} executed - True if the transaction has already been executed (e.g. via `autoExecute`). */ /** @@ -48,37 +44,34 @@ import { NotImplementedError } from '../errors.js' * @property {string | null} combinedSignature - The final combined signature when the threshold is met. */ -/** - * @typedef {Object} MultisigOptions - * @property {number} threshold - The new amount of approvals required to execute a transaction. - */ - /** * @interface - * @extends {IWalletAccount} * @extends {IWalletAccountReadOnlyMultisig} */ export class IWalletAccountMultisig { /** - * Proposes sending a transaction. + * Proposes sending a transaction for the other owners to approve. Does not execute on-chain: + * the returned proposal must be approved up to the threshold and then executed via + * {@link executeProposal}. * * @param {Transaction} tx - The transaction. * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. - * @returns {Promise} The transaction's result. + * @returns {Promise} The proposal's result. */ - async sendTransaction (tx, transactionOptions) { - throw new NotImplementedError('sendTransaction(tx, transactionOptions)') + async propose (tx, transactionOptions) { + throw new NotImplementedError('propose(tx, transactionOptions)') } /** - * Proposes transferring a token to another address. + * Proposes transferring a token to another address for the other owners to approve. Does not + * execute on-chain; see {@link propose}. * * @param {TransferOptions} options - The transfer's options. * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. - * @returns {Promise} The transfer's result. + * @returns {Promise} The proposal's result. */ - async transfer (options, transactionOptions) { - throw new NotImplementedError('transfer(options, transactionOptions)') + async proposeTransfer (options, transactionOptions) { + throw new NotImplementedError('proposeTransfer(options, transactionOptions)') } /** @@ -107,8 +100,8 @@ export class IWalletAccountMultisig { * @param {string} proposalId - The proposal's id. * @returns {Promise} The multisig proposal. */ - async approveTx (proposalId) { - throw new NotImplementedError('approveTx(proposalId)') + async approveProposal (proposalId) { + throw new NotImplementedError('approveProposal(proposalId)') } /** @@ -117,64 +110,17 @@ export class IWalletAccountMultisig { * @param {string} proposalId - The proposal's id. * @returns {Promise} The multisig proposal. */ - async rejectTx (proposalId) { - throw new NotImplementedError('rejectTx(proposalId)') + async rejectProposal (proposalId) { + throw new NotImplementedError('rejectProposal(proposalId)') } /** - * Submits a proposal for on-chain execution. + * Submits an approved proposal for on-chain execution. * * @param {string} proposalId - The proposal's id. - * @returns {Promise} The transaction's result. - */ - async executeTx (proposalId) { - throw new NotImplementedError('executeTx(proposalId)') - } - - /** - * Proposes adding a new owner to the multisig wallet account. - * - * @param {string} owner - The owner's address. - * @param {MultisigOptions} [options] - The multisig options. - * @returns {Promise} The multisig proposal. - * @throws {Error} If the operation is not supported. - */ - async addOwner (owner, options) { - throw new NotImplementedError('addOwner(owner, newThreshold)') - } - - /** - * Proposes removing an owner from the multisig wallet account. - * - * @param {string} owner - The owner's address. - * @param {MultisigOptions} [options] - The multisig options. - * @returns {Promise} The multisig proposal. - * @throws {Error} If the operation is not supported. - */ - async removeOwner (owner, options) { - throw new NotImplementedError('removeOwner(owner, newThreshold)') - } - - /** - * Proposes replacing an owner with a different one. - * - * @param {string} oldOwner - The old owner. - * @param {string} newOwner - The new owner. - * @returns {Promise} The multisig proposal. - * @throws {Error} If the operation is not supported. - */ - async swapOwner (oldOwner, newOwner) { - throw new NotImplementedError('swapOwner(oldOwner, newOwner)') - } - - /** - * Proposes changing the signature threshold. - * - * @param {number} newThreshold - The new threshold. - * @returns {Promise} The multisig proposal. - * @throws {Error} If the operation is not supported. + * @returns {Promise} The on-chain transaction's result. */ - async changeThreshold (newThreshold) { - throw new NotImplementedError('changeThreshold(newThreshold)') + async executeProposal (proposalId) { + throw new NotImplementedError('executeProposal(proposalId)') } } diff --git a/src/multisig/wallet-account-read-only-multisig.js b/src/multisig/wallet-account-read-only-multisig.js index 199a9ef..dc90271 100644 --- a/src/multisig/wallet-account-read-only-multisig.js +++ b/src/multisig/wallet-account-read-only-multisig.js @@ -13,10 +13,12 @@ // limitations under the License. 'use strict' -import { IWalletAccountReadOnly } from '../wallet-account-read-only.js' +import { IWalletAccountReadOnlyBase } from '../wallet-account-read-only-base.js' import { NotImplementedError } from '../errors.js' +/** @typedef {import('../wallet-account.js').KeyPair} KeyPair */ + /** * @typedef {Object} MultisigInfo * @property {string} address - The multisig wallet account address. @@ -42,8 +44,40 @@ import { NotImplementedError } from '../errors.js' * @property {string | null} combinedSignature - The final combined signature when the threshold is met. */ +/** + * @typedef {Object} MultisigExecuteQuote + * @property {bigint} fee - The estimated gas cost of executing the proposal on-chain. + */ + /** @interface */ -export class IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { +export class IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnlyBase { + /** + * The derivation path's index of the signer associated with this account. + * + * @type {number} + */ + get index () { + throw new NotImplementedError('index') + } + + /** + * The derivation path of the signer associated with this account (see [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)). + * + * @type {string} + */ + get path () { + throw new NotImplementedError('path') + } + + /** + * The key pair of the signer associated with this account. + * + * @type {KeyPair} + */ + get signerKeyPair () { + throw new NotImplementedError('signerKeyPair') + } + /** * Returns the address of the signer associated with this wallet account. * @@ -83,4 +117,14 @@ export class IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { async getMessages (messageHashes) { throw new NotImplementedError('getMessages(messageHashes)') } + + /** + * Quotes the on-chain cost of executing a pending proposal. + * + * @param {string} proposalId - The proposal's id. + * @returns {Promise} The execution cost estimate. + */ + async quoteExecuteProposal (proposalId) { + throw new NotImplementedError('quoteExecuteProposal(proposalId)') + } } diff --git a/src/wallet-account-read-only-base.js b/src/wallet-account-read-only-base.js new file mode 100644 index 0000000..586198a --- /dev/null +++ b/src/wallet-account-read-only-base.js @@ -0,0 +1,77 @@ +// Copyright 2024 Tether Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +'use strict' + +import { NotImplementedError } from './errors.js' + +/** + * The read-only members shared by every wallet account, single-signer or multisig. + * + * This is an internal base interface: it is not exported from the package entry point. + * Consumers use {@link IWalletAccountReadOnly} or {@link IWalletAccountReadOnlyMultisig}, + * which both extend it. + * + * @interface + */ +export class IWalletAccountReadOnlyBase { + /** + * Returns the account's address. + * + * @returns {Promise} The account's address. + */ + async getAddress () { + throw new NotImplementedError('getAddress()') + } + + /** + * Verifies a message's signature. + * + * @param {string} message - The original message. + * @param {string} signature - The signature to verify. + * @returns {Promise} True if the signature is valid. + * @throws {Error} If the read-only wallet account class is not able to provide an implementation for the method. + */ + async verify (message, signature) { + throw new NotImplementedError('verify(message, signature)') + } + + /** + * Returns the account's native token balance. + * + * @returns {Promise} The native token balance. + */ + async getBalance () { + throw new NotImplementedError('getBalance()') + } + + /** + * Returns the account balance for a specific token. + * + * @param {string} tokenAddress - The smart contract address of the token. + * @returns {Promise} The token balance. + */ + async getTokenBalance (tokenAddress) { + throw new NotImplementedError('getTokenBalance(tokenAddress)') + } + + /** + * Returns a transaction's receipt. + * + * @param {string} hash - The transaction's hash. + * @returns {Promise} The receipt, or null if the transaction has not been included in a block yet. + */ + async getTransactionReceipt (hash) { + throw new NotImplementedError('getTransactionReceipt(hash)') + } +} diff --git a/src/wallet-account-read-only.js b/src/wallet-account-read-only.js index 3129cd9..8544a7c 100644 --- a/src/wallet-account-read-only.js +++ b/src/wallet-account-read-only.js @@ -15,6 +15,8 @@ import { NotImplementedError } from './errors.js' +import { IWalletAccountReadOnlyBase } from './wallet-account-read-only-base.js' + /** * @typedef {Object} Transaction * @property {string} to - The transaction's recipient. @@ -41,47 +43,7 @@ import { NotImplementedError } from './errors.js' */ /** @interface */ -export class IWalletAccountReadOnly { - /** - * Returns the account's address. - * - * @returns {Promise} The account's address. - */ - async getAddress () { - throw new NotImplementedError('getAddress()') - } - - /** - * Verifies a message's signature. - * - * @param {string} message - The original message. - * @param {string} signature - The signature to verify. - * @returns {Promise} True if the signature is valid. - * @throws {Error} If the read-only wallet account class is not able to provide an implementation for the method. - */ - async verify (message, signature) { - throw new NotImplementedError('verify(message, signature)') - } - - /** - * Returns the account's native token balance. - * - * @returns {Promise} The native token balance. - */ - async getBalance () { - throw new NotImplementedError('getBalance()') - } - - /** - * Returns the account balance for a specific token. - * - * @param {string} tokenAddress - The smart contract address of the token. - * @returns {Promise} The token balance. - */ - async getTokenBalance (tokenAddress) { - throw new NotImplementedError('getTokenBalance(tokenAddress)') - } - +export class IWalletAccountReadOnly extends IWalletAccountReadOnlyBase { /** * Quotes the costs of a send transaction operation. * @@ -101,16 +63,6 @@ export class IWalletAccountReadOnly { async quoteTransfer (options) { throw new NotImplementedError('quoteTransfer(options)') } - - /** - * Returns a transaction's receipt. - * - * @param {string} hash - The transaction's hash. - * @returns {Promise} The receipt, or null if the transaction has not been included in a block yet. - */ - async getTransactionReceipt (hash) { - throw new NotImplementedError('getTransactionReceipt(hash)') - } } /** diff --git a/types/src/multisig/i-multisig-owner-management.d.ts b/types/src/multisig/i-multisig-owner-management.d.ts new file mode 100644 index 0000000..f73beca --- /dev/null +++ b/types/src/multisig/i-multisig-owner-management.d.ts @@ -0,0 +1,48 @@ +/** + * Optional owner-management surface for multisig accounts whose owner set is mutable + * (e.g. account-abstraction wallets). Chains whose owner set is fixed at creation — + * such as Bitcoin script multisig, where the participants are committed in the redeem + * script — do not implement this interface. + * + * @interface + */ +export interface IMultisigOwnerManagement { + /** + * Proposes adding a new owner to the multisig wallet account. + * + * @param {string} owner - The owner's address. + * @param {MultisigOptions} [options] - The multisig options. + * @returns {Promise} The multisig proposal. + */ + addOwner(owner: string, options?: MultisigOptions): Promise; + /** + * Proposes removing an owner from the multisig wallet account. + * + * @param {string} owner - The owner's address. + * @param {MultisigOptions} [options] - The multisig options. + * @returns {Promise} The multisig proposal. + */ + removeOwner(owner: string, options?: MultisigOptions): Promise; + /** + * Proposes replacing an owner with a different one. + * + * @param {string} oldOwner - The old owner. + * @param {string} newOwner - The new owner. + * @returns {Promise} The multisig proposal. + */ + swapOwner(oldOwner: string, newOwner: string): Promise; + /** + * Proposes changing the signature threshold. + * + * @param {number} newThreshold - The new threshold. + * @returns {Promise} The multisig proposal. + */ + changeThreshold(newThreshold: number): Promise; +} +export type MultisigProposal = import("./wallet-account-read-only-multisig.js").MultisigProposal; +export type MultisigOptions = { + /** + * - The new amount of approvals required to execute a transaction. + */ + threshold: number; +}; diff --git a/types/src/multisig/index.d.ts b/types/src/multisig/index.d.ts index 6ba4ff0..1b4c0d7 100644 --- a/types/src/multisig/index.d.ts +++ b/types/src/multisig/index.d.ts @@ -1,13 +1,16 @@ export { IWalletAccountReadOnlyMultisig } from "./wallet-account-read-only-multisig.js"; export { IWalletAccountMultisig } from "./wallet-account-multisig.js"; +export { IMultisigOwnerManagement } from "./i-multisig-owner-management.js"; export { IMultisigTransport } from "./i-multisig-transport.js"; export type MultisigInfo = import("./wallet-account-read-only-multisig.js").MultisigInfo; export type MultisigProposal = import("./wallet-account-read-only-multisig.js").MultisigProposal; export type MultisigMessage = import("./wallet-account-read-only-multisig.js").MultisigMessage; -export type MultisigTransactionResult = import("./wallet-account-multisig.js").MultisigTransactionResult; +export type MultisigExecuteQuote = import("./wallet-account-read-only-multisig.js").MultisigExecuteQuote; +export type KeyPair = import("../wallet-account.js").KeyPair; +export type MultisigProposalResult = import("./wallet-account-multisig.js").MultisigProposalResult; export type MultisigTransactionOptions = import("./wallet-account-multisig.js").MultisigTransactionOptions; export type MultisigMessageProposal = import("./wallet-account-multisig.js").MultisigMessageProposal; -export type MultisigOptions = import("./wallet-account-multisig.js").MultisigOptions; +export type MultisigOptions = import("./i-multisig-owner-management.js").MultisigOptions; export type MultisigTransportProposal = import("./i-multisig-transport.js").MultisigTransportProposal; export type MultisigTransportMessage = import("./i-multisig-transport.js").MultisigTransportMessage; export type MultisigTransportMessageInput = import("./i-multisig-transport.js").MultisigTransportMessageInput; diff --git a/types/src/multisig/wallet-account-multisig.d.ts b/types/src/multisig/wallet-account-multisig.d.ts index 308acfc..e0121e1 100644 --- a/types/src/multisig/wallet-account-multisig.d.ts +++ b/types/src/multisig/wallet-account-multisig.d.ts @@ -1,25 +1,27 @@ /** * @interface - * @extends {IWalletAccount} * @extends {IWalletAccountReadOnlyMultisig} */ -export interface IWalletAccountMultisig extends IWalletAccount, IWalletAccountReadOnlyMultisig { +export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { /** - * Proposes sending a transaction. + * Proposes sending a transaction for the other owners to approve. Does not execute on-chain: + * the returned proposal must be approved up to the threshold and then executed via + * {@link executeProposal}. * * @param {Transaction} tx - The transaction. * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. - * @returns {Promise} The transaction's result. + * @returns {Promise} The proposal's result. */ - sendTransaction(tx: Transaction, transactionOptions?: MultisigTransactionOptions): Promise; + propose(tx: Transaction, transactionOptions?: MultisigTransactionOptions): Promise; /** - * Proposes transferring a token to another address. + * Proposes transferring a token to another address for the other owners to approve. Does not + * execute on-chain; see {@link propose}. * * @param {TransferOptions} options - The transfer's options. * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. - * @returns {Promise} The transfer's result. + * @returns {Promise} The proposal's result. */ - transfer(options: TransferOptions, transactionOptions?: MultisigTransactionOptions): Promise; + proposeTransfer(options: TransferOptions, transactionOptions?: MultisigTransactionOptions): Promise; /** * Proposes signing a message. * @@ -40,76 +42,32 @@ export interface IWalletAccountMultisig extends IWalletAccount, IWalletAccountRe * @param {string} proposalId - The proposal's id. * @returns {Promise} The multisig proposal. */ - approveTx(proposalId: string): Promise; + approveProposal(proposalId: string): Promise; /** * Rejects a pending proposal. * * @param {string} proposalId - The proposal's id. * @returns {Promise} The multisig proposal. */ - rejectTx(proposalId: string): Promise; + rejectProposal(proposalId: string): Promise; /** - * Submits a proposal for on-chain execution. + * Submits an approved proposal for on-chain execution. * * @param {string} proposalId - The proposal's id. - * @returns {Promise} The transaction's result. + * @returns {Promise} The on-chain transaction's result. */ - executeTx(proposalId: string): Promise; - /** - * Proposes adding a new owner to the multisig wallet account. - * - * @param {string} owner - The owner's address. - * @param {MultisigOptions} [options] - The multisig options. - * @returns {Promise} The multisig proposal. - * @throws {Error} If the operation is not supported. - */ - addOwner(owner: string, options?: MultisigOptions): Promise; - /** - * Proposes removing an owner from the multisig wallet account. - * - * @param {string} owner - The owner's address. - * @param {MultisigOptions} [options] - The multisig options. - * @returns {Promise} The multisig proposal. - * @throws {Error} If the operation is not supported. - */ - removeOwner(owner: string, options?: MultisigOptions): Promise; - /** - * Proposes replacing an owner with a different one. - * - * @param {string} oldOwner - The old owner. - * @param {string} newOwner - The new owner. - * @returns {Promise} The multisig proposal. - * @throws {Error} If the operation is not supported. - */ - swapOwner(oldOwner: string, newOwner: string): Promise; - /** - * Proposes changing the signature threshold. - * - * @param {number} newThreshold - The new threshold. - * @returns {Promise} The multisig proposal. - * @throws {Error} If the operation is not supported. - */ - changeThreshold(newThreshold: number): Promise; + executeProposal(proposalId: string): Promise; } -export type IWalletAccount = import("../wallet-account.js").IWalletAccount; export type Transaction = import("../wallet-account-read-only.js").Transaction; export type TransferOptions = import("../wallet-account-read-only.js").TransferOptions; export type TransactionResult = import("../wallet-account-read-only.js").TransactionResult; export type IWalletAccountReadOnlyMultisig = import("./wallet-account-read-only-multisig.js").IWalletAccountReadOnlyMultisig; export type MultisigProposal = import("./wallet-account-read-only-multisig.js").MultisigProposal; -export type MultisigTransactionResult = { +export type MultisigProposalResult = { /** - * - The proposal's id. + * - The proposal's id, used to approve, reject, query, quote or execute it. */ proposalId: string; - /** - * - The transaction's hash. - */ - hash: string; - /** - * - The gas cost. - */ - fee: bigint; /** * - The current number of confirmations. */ @@ -119,7 +77,7 @@ export type MultisigTransactionResult = { */ threshold: number; /** - * - True if the transaction has already been executed. + * - True if the transaction has already been executed (e.g. via `autoExecute`). */ executed: boolean; }; @@ -151,9 +109,3 @@ export type MultisigMessageProposal = { */ combinedSignature: string | null; }; -export type MultisigOptions = { - /** - * - The new amount of approvals required to execute a transaction. - */ - threshold: number; -}; diff --git a/types/src/multisig/wallet-account-read-only-multisig.d.ts b/types/src/multisig/wallet-account-read-only-multisig.d.ts index bf53d8c..510af95 100644 --- a/types/src/multisig/wallet-account-read-only-multisig.d.ts +++ b/types/src/multisig/wallet-account-read-only-multisig.d.ts @@ -1,5 +1,23 @@ /** @interface */ -export interface IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { +export interface IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnlyBase { + /** + * The derivation path's index of the signer associated with this account. + * + * @type {number} + */ + get index(): number; + /** + * The derivation path of the signer associated with this account (see [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)). + * + * @type {string} + */ + get path(): string; + /** + * The key pair of the signer associated with this account. + * + * @type {KeyPair} + */ + get signerKeyPair(): KeyPair; /** * Returns the address of the signer associated with this wallet account. * @@ -28,7 +46,15 @@ export interface IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnly { * null if the message has not been found. */ getMessages(messageHashes: string[]): Promise<(MultisigMessage | null)[]>; + /** + * Quotes the on-chain cost of executing a pending proposal. + * + * @param {string} proposalId - The proposal's id. + * @returns {Promise} The execution cost estimate. + */ + quoteExecuteProposal(proposalId: string): Promise; } +export type KeyPair = import("../wallet-account.js").KeyPair; export type MultisigInfo = { /** * - The multisig wallet account address. @@ -83,4 +109,10 @@ export type MultisigMessage = { */ combinedSignature: string | null; }; -import { IWalletAccountReadOnly } from '../wallet-account-read-only.js'; +export type MultisigExecuteQuote = { + /** + * - The estimated gas cost of executing the proposal on-chain. + */ + fee: bigint; +}; +import { IWalletAccountReadOnlyBase } from '../wallet-account-read-only-base.js'; diff --git a/types/src/wallet-account-read-only-base.d.ts b/types/src/wallet-account-read-only-base.d.ts new file mode 100644 index 0000000..5349476 --- /dev/null +++ b/types/src/wallet-account-read-only-base.d.ts @@ -0,0 +1,46 @@ +/** + * The read-only members shared by every wallet account, single-signer or multisig. + * + * This is an internal base interface: it is not exported from the package entry point. + * Consumers use {@link IWalletAccountReadOnly} or {@link IWalletAccountReadOnlyMultisig}, + * which both extend it. + * + * @interface + */ +export interface IWalletAccountReadOnlyBase { + /** + * Returns the account's address. + * + * @returns {Promise} The account's address. + */ + getAddress(): Promise; + /** + * Verifies a message's signature. + * + * @param {string} message - The original message. + * @param {string} signature - The signature to verify. + * @returns {Promise} True if the signature is valid. + * @throws {Error} If the read-only wallet account class is not able to provide an implementation for the method. + */ + verify(message: string, signature: string): Promise; + /** + * Returns the account's native token balance. + * + * @returns {Promise} The native token balance. + */ + getBalance(): Promise; + /** + * Returns the account balance for a specific token. + * + * @param {string} tokenAddress - The smart contract address of the token. + * @returns {Promise} The token balance. + */ + getTokenBalance(tokenAddress: string): Promise; + /** + * Returns a transaction's receipt. + * + * @param {string} hash - The transaction's hash. + * @returns {Promise} The receipt, or null if the transaction has not been included in a block yet. + */ + getTransactionReceipt(hash: string): Promise; +} diff --git a/types/src/wallet-account-read-only.d.ts b/types/src/wallet-account-read-only.d.ts index 4fb0d79..f9557c4 100644 --- a/types/src/wallet-account-read-only.d.ts +++ b/types/src/wallet-account-read-only.d.ts @@ -1,33 +1,5 @@ /** @interface */ -export interface IWalletAccountReadOnly { - /** - * Returns the account's address. - * - * @returns {Promise} The account's address. - */ - getAddress(): Promise; - /** - * Verifies a message's signature. - * - * @param {string} message - The original message. - * @param {string} signature - The signature to verify. - * @returns {Promise} True if the signature is valid. - * @throws {Error} If the read-only wallet account class is not able to provide an implementation for the method. - */ - verify(message: string, signature: string): Promise; - /** - * Returns the account's native token balance. - * - * @returns {Promise} The native token balance. - */ - getBalance(): Promise; - /** - * Returns the account balance for a specific token. - * - * @param {string} tokenAddress - The smart contract address of the token. - * @returns {Promise} The token balance. - */ - getTokenBalance(tokenAddress: string): Promise; +export interface IWalletAccountReadOnly extends IWalletAccountReadOnlyBase { /** * Quotes the costs of a send transaction operation. * @@ -42,13 +14,6 @@ export interface IWalletAccountReadOnly { * @returns {Promise>} The transfer's quotes. */ quoteTransfer(options: TransferOptions): Promise>; - /** - * Returns a transaction's receipt. - * - * @param {string} hash - The transaction's hash. - * @returns {Promise} The receipt, or null if the transaction has not been included in a block yet. - */ - getTransactionReceipt(hash: string): Promise; } /** * @abstract @@ -170,3 +135,4 @@ export type TransferResult = { */ fee: bigint; }; +import { IWalletAccountReadOnlyBase } from './wallet-account-read-only-base.js'; From 5d004500641639921470466abd286caadd8aa6d3 Mon Sep 17 00:00:00 2001 From: rickalon Date: Tue, 16 Jun 2026 09:47:49 -0400 Subject: [PATCH 29/41] refactor(multisig): move signer-key members to the writable interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A read-only account has no private key, so signerKeyPair/index/path cannot be honored on IWalletAccountReadOnlyMultisig — same reason single-signer keeps them on IWalletAccount, not IWalletAccountReadOnly. Move them to IWalletAccountMultisig; the read-only interface keeps the keyless getSignerAddress(). --- src/multisig/wallet-account-multisig.js | 29 +++++++++++++++++++ .../wallet-account-read-only-multisig.js | 29 ------------------- .../src/multisig/wallet-account-multisig.d.ts | 19 ++++++++++++ .../wallet-account-read-only-multisig.d.ts | 19 ------------ 4 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/multisig/wallet-account-multisig.js b/src/multisig/wallet-account-multisig.js index 6a84e98..795bcd0 100644 --- a/src/multisig/wallet-account-multisig.js +++ b/src/multisig/wallet-account-multisig.js @@ -19,6 +19,8 @@ import { NotImplementedError } from '../errors.js' /** @typedef {import('../wallet-account-read-only.js').TransferOptions} TransferOptions */ /** @typedef {import('../wallet-account-read-only.js').TransactionResult} TransactionResult */ +/** @typedef {import('../wallet-account.js').KeyPair} KeyPair */ + /** @typedef {import('./wallet-account-read-only-multisig.js').IWalletAccountReadOnlyMultisig} IWalletAccountReadOnlyMultisig */ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ @@ -49,6 +51,33 @@ import { NotImplementedError } from '../errors.js' * @extends {IWalletAccountReadOnlyMultisig} */ export class IWalletAccountMultisig { + /** + * The derivation path's index of the signer associated with this account. + * + * @type {number} + */ + get index () { + throw new NotImplementedError('index') + } + + /** + * The derivation path of the signer associated with this account (see [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)). + * + * @type {string} + */ + get path () { + throw new NotImplementedError('path') + } + + /** + * The key pair of the signer associated with this account. + * + * @type {KeyPair} + */ + get signerKeyPair () { + throw new NotImplementedError('signerKeyPair') + } + /** * Proposes sending a transaction for the other owners to approve. Does not execute on-chain: * the returned proposal must be approved up to the threshold and then executed via diff --git a/src/multisig/wallet-account-read-only-multisig.js b/src/multisig/wallet-account-read-only-multisig.js index dc90271..2d2653b 100644 --- a/src/multisig/wallet-account-read-only-multisig.js +++ b/src/multisig/wallet-account-read-only-multisig.js @@ -17,8 +17,6 @@ import { IWalletAccountReadOnlyBase } from '../wallet-account-read-only-base.js' import { NotImplementedError } from '../errors.js' -/** @typedef {import('../wallet-account.js').KeyPair} KeyPair */ - /** * @typedef {Object} MultisigInfo * @property {string} address - The multisig wallet account address. @@ -51,33 +49,6 @@ import { NotImplementedError } from '../errors.js' /** @interface */ export class IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnlyBase { - /** - * The derivation path's index of the signer associated with this account. - * - * @type {number} - */ - get index () { - throw new NotImplementedError('index') - } - - /** - * The derivation path of the signer associated with this account (see [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)). - * - * @type {string} - */ - get path () { - throw new NotImplementedError('path') - } - - /** - * The key pair of the signer associated with this account. - * - * @type {KeyPair} - */ - get signerKeyPair () { - throw new NotImplementedError('signerKeyPair') - } - /** * Returns the address of the signer associated with this wallet account. * diff --git a/types/src/multisig/wallet-account-multisig.d.ts b/types/src/multisig/wallet-account-multisig.d.ts index e0121e1..f8776db 100644 --- a/types/src/multisig/wallet-account-multisig.d.ts +++ b/types/src/multisig/wallet-account-multisig.d.ts @@ -3,6 +3,24 @@ * @extends {IWalletAccountReadOnlyMultisig} */ export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { + /** + * The derivation path's index of the signer associated with this account. + * + * @type {number} + */ + get index(): number; + /** + * The derivation path of the signer associated with this account (see [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)). + * + * @type {string} + */ + get path(): string; + /** + * The key pair of the signer associated with this account. + * + * @type {KeyPair} + */ + get signerKeyPair(): KeyPair; /** * Proposes sending a transaction for the other owners to approve. Does not execute on-chain: * the returned proposal must be approved up to the threshold and then executed via @@ -63,6 +81,7 @@ export type TransferOptions = import("../wallet-account-read-only.js").TransferO export type TransactionResult = import("../wallet-account-read-only.js").TransactionResult; export type IWalletAccountReadOnlyMultisig = import("./wallet-account-read-only-multisig.js").IWalletAccountReadOnlyMultisig; export type MultisigProposal = import("./wallet-account-read-only-multisig.js").MultisigProposal; +export type KeyPair = import("../wallet-account.js").KeyPair; export type MultisigProposalResult = { /** * - The proposal's id, used to approve, reject, query, quote or execute it. diff --git a/types/src/multisig/wallet-account-read-only-multisig.d.ts b/types/src/multisig/wallet-account-read-only-multisig.d.ts index 510af95..24469e1 100644 --- a/types/src/multisig/wallet-account-read-only-multisig.d.ts +++ b/types/src/multisig/wallet-account-read-only-multisig.d.ts @@ -1,23 +1,5 @@ /** @interface */ export interface IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnlyBase { - /** - * The derivation path's index of the signer associated with this account. - * - * @type {number} - */ - get index(): number; - /** - * The derivation path of the signer associated with this account (see [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)). - * - * @type {string} - */ - get path(): string; - /** - * The key pair of the signer associated with this account. - * - * @type {KeyPair} - */ - get signerKeyPair(): KeyPair; /** * Returns the address of the signer associated with this wallet account. * @@ -54,7 +36,6 @@ export interface IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnlyBa */ quoteExecuteProposal(proposalId: string): Promise; } -export type KeyPair = import("../wallet-account.js").KeyPair; export type MultisigInfo = { /** * - The multisig wallet account address. From 24ec0021dfbb27863e32cc8b6065305c25bb195f Mon Sep 17 00:00:00 2001 From: rickalon Date: Thu, 18 Jun 2026 08:06:47 -0400 Subject: [PATCH 30/41] refactor(multisig): make IMultisigTransport generic in the payload types Add type parameters with defaults so concrete transports can declare their specific payload shapes while staying backward-compatible: IMultisigTransport, TMessage = MultisigTransportMessageInput> submitProposal/submitMessage take TProposal/TMessage. Impls that don't supply type arguments fall back to the opaque defaults, so existing transports are unaffected and custom transports remain pluggable via parameter contravariance. get/confirm stay non-generic (chain-agnostic confirmations view + string ids). --- src/multisig/i-multisig-transport.js | 6 ++++-- types/src/multisig/i-multisig-transport.d.ts | 12 +++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/multisig/i-multisig-transport.js b/src/multisig/i-multisig-transport.js index c637902..b0cb481 100644 --- a/src/multisig/i-multisig-transport.js +++ b/src/multisig/i-multisig-transport.js @@ -53,12 +53,14 @@ import { NotImplementedError } from '../errors.js' * database, a peer-to-peer channel, etc.) by implementing this interface. * * @interface + * @template [TProposal=Record] + * @template [TMessage=MultisigTransportMessageInput] */ export class IMultisigTransport { /** * Submits a new transaction proposal so the other owners can confirm it. * - * @param {Record} proposal - The signed transaction proposal to share. Opaque to the transport, which must persist it so {@link getProposal} can return it intact. + * @param {TProposal} proposal - The signed transaction proposal to share. Opaque to the transport, which must persist it so {@link getProposal} can return it intact. * @returns {Promise} */ async submitProposal (proposal) { @@ -90,7 +92,7 @@ export class IMultisigTransport { * Submits a new message proposal so the other owners can confirm it. * * @param {string} accountAddress - The multisig account's address. - * @param {MultisigTransportMessageInput} message - The message proposal to share. + * @param {TMessage} message - The message proposal to share. * @returns {Promise} */ async submitMessage (accountAddress, message) { diff --git a/types/src/multisig/i-multisig-transport.d.ts b/types/src/multisig/i-multisig-transport.d.ts index 53e5cb8..e25c3c2 100644 --- a/types/src/multisig/i-multisig-transport.d.ts +++ b/types/src/multisig/i-multisig-transport.d.ts @@ -33,15 +33,17 @@ * database, a peer-to-peer channel, etc.) by implementing this interface. * * @interface + * @template [TProposal=Record] + * @template [TMessage=MultisigTransportMessageInput] */ -export interface IMultisigTransport { +export interface IMultisigTransport, TMessage = MultisigTransportMessageInput> { /** * Submits a new transaction proposal so the other owners can confirm it. * - * @param {Record} proposal - The signed transaction proposal to share. Opaque to the transport, which must persist it so {@link getProposal} can return it intact. + * @param {TProposal} proposal - The signed transaction proposal to share. Opaque to the transport, which must persist it so {@link getProposal} can return it intact. * @returns {Promise} */ - submitProposal(proposal: Record): Promise; + submitProposal(proposal: TProposal): Promise; /** * Returns a transaction proposal by its identifier. * @@ -61,10 +63,10 @@ export interface IMultisigTransport { * Submits a new message proposal so the other owners can confirm it. * * @param {string} accountAddress - The multisig account's address. - * @param {MultisigTransportMessageInput} message - The message proposal to share. + * @param {TMessage} message - The message proposal to share. * @returns {Promise} */ - submitMessage(accountAddress: string, message: MultisigTransportMessageInput): Promise; + submitMessage(accountAddress: string, message: TMessage): Promise; /** * Returns a message proposal by its hash. * From 8a6e2128e7c0e916429e18fe774d9abe50dfc0eb Mon Sep 17 00:00:00 2001 From: rickalon Date: Thu, 18 Jun 2026 14:27:05 -0400 Subject: [PATCH 31/41] =?UTF-8?q?refactor(multisig):=20address=20review=20?= =?UTF-8?q?=E2=80=94=20generic=20fee=20wording=20+=20messageHash->messageI?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MultisigExecuteQuote.fee: 'gas cost' -> 'cost' (gas is EVM-specific; the interface is chain-agnostic). - Rename messageHash -> messageId (and messageHashes -> messageIds) across the message-proposal flow (MultisigMessage, MultisigMessageProposal, getMessages, getMessage/confirmMessage/approveMessage), for consistency with proposalId and to stay generic (an id may be a hash or otherwise). --- src/multisig/i-multisig-transport.js | 12 ++++++------ src/multisig/wallet-account-multisig.js | 8 ++++---- src/multisig/wallet-account-read-only-multisig.js | 10 +++++----- types/src/multisig/i-multisig-transport.d.ts | 8 ++++---- types/src/multisig/wallet-account-multisig.d.ts | 6 +++--- .../multisig/wallet-account-read-only-multisig.d.ts | 8 ++++---- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/multisig/i-multisig-transport.js b/src/multisig/i-multisig-transport.js index b0cb481..96c419c 100644 --- a/src/multisig/i-multisig-transport.js +++ b/src/multisig/i-multisig-transport.js @@ -102,21 +102,21 @@ export class IMultisigTransport { /** * Returns a message proposal by its hash. * - * @param {string} messageHash - The message's hash. + * @param {string} messageId - The message's hash. * @returns {Promise} The message, or null if it has not been found. */ - async getMessage (messageHash) { - throw new NotImplementedError('getMessage(messageHash)') + async getMessage (messageId) { + throw new NotImplementedError('getMessage(messageId)') } /** * Adds an owner's confirmation (signature) to an existing message proposal. * - * @param {string} messageHash - The message's hash. + * @param {string} messageId - The message's hash. * @param {string} signature - The owner's signature over the message. * @returns {Promise} */ - async confirmMessage (messageHash, signature) { - throw new NotImplementedError('confirmMessage(messageHash, signature)') + async confirmMessage (messageId, signature) { + throw new NotImplementedError('confirmMessage(messageId, signature)') } } diff --git a/src/multisig/wallet-account-multisig.js b/src/multisig/wallet-account-multisig.js index 795bcd0..241d700 100644 --- a/src/multisig/wallet-account-multisig.js +++ b/src/multisig/wallet-account-multisig.js @@ -39,7 +39,7 @@ import { NotImplementedError } from '../errors.js' /** * @typedef {Object} MultisigMessageProposal - * @property {string} messageHash - The message's hash. + * @property {string} messageId - The message's hash. * @property {string} signature - The signature of the caller. * @property {number} confirmations - The current number of confirmations. * @property {number} threshold - The minimum amount of confirmations to sign the message. @@ -116,11 +116,11 @@ export class IWalletAccountMultisig { /** * Approves an existing message proposal. * - * @param {string} messageHash - The message's hash. + * @param {string} messageId - The message's hash. * @returns {Promise} The multisig message proposal. */ - async approveMessage (messageHash) { - throw new NotImplementedError('approveMessage(messageHash)') + async approveMessage (messageId) { + throw new NotImplementedError('approveMessage(messageId)') } /** diff --git a/src/multisig/wallet-account-read-only-multisig.js b/src/multisig/wallet-account-read-only-multisig.js index 2d2653b..c48c820 100644 --- a/src/multisig/wallet-account-read-only-multisig.js +++ b/src/multisig/wallet-account-read-only-multisig.js @@ -35,7 +35,7 @@ import { NotImplementedError } from '../errors.js' /** * @typedef {Object} MultisigMessage - * @property {string} messageHash - The message's hash. + * @property {string} messageId - The message's hash. * @property {string} message - The original message. * @property {number} confirmations - The current number of confirmations. * @property {number} threshold - The minimum amount of confirmations to sign the message. @@ -44,7 +44,7 @@ import { NotImplementedError } from '../errors.js' /** * @typedef {Object} MultisigExecuteQuote - * @property {bigint} fee - The estimated gas cost of executing the proposal on-chain. + * @property {bigint} fee - The estimated cost of executing the proposal on-chain. */ /** @interface */ @@ -81,12 +81,12 @@ export class IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnlyBase { /** * Returns a list of message proposals by their hashes. * - * @param {string[]} messageHashes - The list of message hashes + * @param {string[]} messageIds - The list of message hashes * @returns {Promise<(MultisigMessage | null)[]>} For each message hash, the message details or * null if the message has not been found. */ - async getMessages (messageHashes) { - throw new NotImplementedError('getMessages(messageHashes)') + async getMessages (messageIds) { + throw new NotImplementedError('getMessages(messageIds)') } /** diff --git a/types/src/multisig/i-multisig-transport.d.ts b/types/src/multisig/i-multisig-transport.d.ts index e25c3c2..04467e6 100644 --- a/types/src/multisig/i-multisig-transport.d.ts +++ b/types/src/multisig/i-multisig-transport.d.ts @@ -70,18 +70,18 @@ export interface IMultisigTransport, TMessag /** * Returns a message proposal by its hash. * - * @param {string} messageHash - The message's hash. + * @param {string} messageId - The message's hash. * @returns {Promise} The message, or null if it has not been found. */ - getMessage(messageHash: string): Promise; + getMessage(messageId: string): Promise; /** * Adds an owner's confirmation (signature) to an existing message proposal. * - * @param {string} messageHash - The message's hash. + * @param {string} messageId - The message's hash. * @param {string} signature - The owner's signature over the message. * @returns {Promise} */ - confirmMessage(messageHash: string, signature: string): Promise; + confirmMessage(messageId: string, signature: string): Promise; } /** * A message proposal to share with the other owners for them to confirm. diff --git a/types/src/multisig/wallet-account-multisig.d.ts b/types/src/multisig/wallet-account-multisig.d.ts index f8776db..2be7c3a 100644 --- a/types/src/multisig/wallet-account-multisig.d.ts +++ b/types/src/multisig/wallet-account-multisig.d.ts @@ -50,10 +50,10 @@ export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { /** * Approves an existing message proposal. * - * @param {string} messageHash - The message's hash. + * @param {string} messageId - The message's hash. * @returns {Promise} The multisig message proposal. */ - approveMessage(messageHash: string): Promise; + approveMessage(messageId: string): Promise; /** * Approves a pending proposal. * @@ -110,7 +110,7 @@ export type MultisigMessageProposal = { /** * - The message's hash. */ - messageHash: string; + messageId: string; /** * - The signature of the caller. */ diff --git a/types/src/multisig/wallet-account-read-only-multisig.d.ts b/types/src/multisig/wallet-account-read-only-multisig.d.ts index 24469e1..dfd08df 100644 --- a/types/src/multisig/wallet-account-read-only-multisig.d.ts +++ b/types/src/multisig/wallet-account-read-only-multisig.d.ts @@ -23,11 +23,11 @@ export interface IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnlyBa /** * Returns a list of message proposals by their hashes. * - * @param {string[]} messageHashes - The list of message hashes + * @param {string[]} messageIds - The list of message hashes * @returns {Promise<(MultisigMessage | null)[]>} For each message hash, the message details or * null if the message has not been found. */ - getMessages(messageHashes: string[]): Promise<(MultisigMessage | null)[]>; + getMessages(messageIds: string[]): Promise<(MultisigMessage | null)[]>; /** * Quotes the on-chain cost of executing a pending proposal. * @@ -72,7 +72,7 @@ export type MultisigMessage = { /** * - The message's hash. */ - messageHash: string; + messageId: string; /** * - The original message. */ @@ -92,7 +92,7 @@ export type MultisigMessage = { }; export type MultisigExecuteQuote = { /** - * - The estimated gas cost of executing the proposal on-chain. + * - The estimated cost of executing the proposal on-chain. */ fee: bigint; }; From eb4b117750c6aa62a50f5670dfb99e2cf8e35447 Mon Sep 17 00:00:00 2001 From: rickalon Date: Fri, 19 Jun 2026 09:56:43 -0400 Subject: [PATCH 32/41] feat(multisig): add toTransportJson helper for hand-serializing transports Transports that serialize proposals/messages themselves (rather than delegating to an SDK) can use toTransportJson to convert native BigInt values into JSON-safe strings before JSON.stringify. Exported from @tetherto/wdk-wallet/multisig; SafeTxServiceTransport is unaffected (api-kit keeps native bigint). --- src/multisig/i-multisig-transport.js | 4 ++ src/multisig/index.js | 2 + src/multisig/transport-serialization.js | 44 +++++++++++++++++++ tests/transport-serialization.test.js | 41 +++++++++++++++++ types/src/multisig/i-multisig-transport.d.ts | 4 ++ types/src/multisig/index.d.ts | 1 + .../src/multisig/transport-serialization.d.ts | 13 ++++++ 7 files changed, 109 insertions(+) create mode 100644 src/multisig/transport-serialization.js create mode 100644 tests/transport-serialization.test.js create mode 100644 types/src/multisig/transport-serialization.d.ts diff --git a/src/multisig/i-multisig-transport.js b/src/multisig/i-multisig-transport.js index 96c419c..2195891 100644 --- a/src/multisig/i-multisig-transport.js +++ b/src/multisig/i-multisig-transport.js @@ -52,6 +52,10 @@ import { NotImplementedError } from '../errors.js' * (PSBT) or other multisig wallets. Plug in a custom backend (a hosted service, a * database, a peer-to-peer channel, etc.) by implementing this interface. * + * Implementations that serialize the payloads themselves (rather than handing them to an + * SDK that already does it) can pass them through {@link toTransportJson} to convert native + * values such as BigInt into JSON-safe forms before persisting or transmitting them. + * * @interface * @template [TProposal=Record] * @template [TMessage=MultisigTransportMessageInput] diff --git a/src/multisig/index.js b/src/multisig/index.js index c45ff3f..c79a423 100644 --- a/src/multisig/index.js +++ b/src/multisig/index.js @@ -35,3 +35,5 @@ export { IWalletAccountMultisig } from './wallet-account-multisig.js' export { IMultisigOwnerManagement } from './i-multisig-owner-management.js' export { IMultisigTransport } from './i-multisig-transport.js' + +export { toTransportJson } from './transport-serialization.js' diff --git a/src/multisig/transport-serialization.js b/src/multisig/transport-serialization.js new file mode 100644 index 0000000..db8c343 --- /dev/null +++ b/src/multisig/transport-serialization.js @@ -0,0 +1,44 @@ +// Copyright 2024 Tether Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +'use strict' + +/** + * Recursively converts a transport payload into a JSON-safe value by turning every BigInt into + * its decimal string form, so the payload survives JSON.stringify. + * + * Transports that serialize the proposal/message themselves (rather than delegating to an SDK + * that handles it) can use this instead of reimplementing the conversion. Non-JSON-native values + * other than BigInt (e.g. byte arrays) should be normalized by the transport for its chain's + * encoding before calling this. + * + * @param {unknown} value - The value to convert (object, array, or primitive). + * @returns {unknown} A JSON-safe copy with every BigInt converted to a decimal string. + */ +export function toTransportJson (value) { + if (typeof value === 'bigint') { + return value.toString() + } + + if (Array.isArray(value)) { + return value.map(toTransportJson) + } + + if (value !== null && typeof value === 'object') { + return Object.fromEntries( + Object.entries(value).map(([key, entry]) => [key, toTransportJson(entry)]) + ) + } + + return value +} diff --git a/tests/transport-serialization.test.js b/tests/transport-serialization.test.js new file mode 100644 index 0000000..c38af97 --- /dev/null +++ b/tests/transport-serialization.test.js @@ -0,0 +1,41 @@ +import { describe, expect, test } from '@jest/globals' + +import { toTransportJson } from '../src/multisig/index.js' + +describe('toTransportJson', () => { + test('converts a top-level BigInt to its decimal string', () => { + expect(toTransportJson(10n)).toBe('10') + }) + + test('converts nested BigInts inside objects and arrays', () => { + const payload = { + maxFeePerGas: 1000000000n, + nested: { callGasLimit: 50000n }, + values: [1n, 2n, { preVerificationGas: 21000n }] + } + + expect(toTransportJson(payload)).toEqual({ + maxFeePerGas: '1000000000', + nested: { callGasLimit: '50000' }, + values: ['1', '2', { preVerificationGas: '21000' }] + }) + }) + + test('leaves JSON-native values untouched', () => { + const payload = { to: '0xabc', value: 0, ok: true, missing: null } + + expect(toTransportJson(payload)).toEqual(payload) + }) + + test('produces output that survives JSON.stringify', () => { + expect(JSON.stringify(toTransportJson({ gas: 42n }))).toBe('{"gas":"42"}') + }) + + test('does not mutate the input object', () => { + const payload = { gas: 7n } + + toTransportJson(payload) + + expect(payload.gas).toBe(7n) + }) +}) diff --git a/types/src/multisig/i-multisig-transport.d.ts b/types/src/multisig/i-multisig-transport.d.ts index 04467e6..b07fce6 100644 --- a/types/src/multisig/i-multisig-transport.d.ts +++ b/types/src/multisig/i-multisig-transport.d.ts @@ -32,6 +32,10 @@ * (PSBT) or other multisig wallets. Plug in a custom backend (a hosted service, a * database, a peer-to-peer channel, etc.) by implementing this interface. * + * Implementations that serialize the payloads themselves (rather than handing them to an + * SDK that already does it) can pass them through {@link toTransportJson} to convert native + * values such as BigInt into JSON-safe forms before persisting or transmitting them. + * * @interface * @template [TProposal=Record] * @template [TMessage=MultisigTransportMessageInput] diff --git a/types/src/multisig/index.d.ts b/types/src/multisig/index.d.ts index 1b4c0d7..18b886e 100644 --- a/types/src/multisig/index.d.ts +++ b/types/src/multisig/index.d.ts @@ -2,6 +2,7 @@ export { IWalletAccountReadOnlyMultisig } from "./wallet-account-read-only-multi export { IWalletAccountMultisig } from "./wallet-account-multisig.js"; export { IMultisigOwnerManagement } from "./i-multisig-owner-management.js"; export { IMultisigTransport } from "./i-multisig-transport.js"; +export { toTransportJson } from "./transport-serialization.js"; export type MultisigInfo = import("./wallet-account-read-only-multisig.js").MultisigInfo; export type MultisigProposal = import("./wallet-account-read-only-multisig.js").MultisigProposal; export type MultisigMessage = import("./wallet-account-read-only-multisig.js").MultisigMessage; diff --git a/types/src/multisig/transport-serialization.d.ts b/types/src/multisig/transport-serialization.d.ts new file mode 100644 index 0000000..90ae55a --- /dev/null +++ b/types/src/multisig/transport-serialization.d.ts @@ -0,0 +1,13 @@ +/** + * Recursively converts a transport payload into a JSON-safe value by turning every BigInt into + * its decimal string form, so the payload survives JSON.stringify. + * + * Transports that serialize the proposal/message themselves (rather than delegating to an SDK + * that handles it) can use this instead of reimplementing the conversion. Non-JSON-native values + * other than BigInt (e.g. byte arrays) should be normalized by the transport for its chain's + * encoding before calling this. + * + * @param {unknown} value - The value to convert (object, array, or primitive). + * @returns {unknown} A JSON-safe copy with every BigInt converted to a decimal string. + */ +export function toTransportJson(value: unknown): unknown; From 98b1b7b99cc01f60a9df40ba10827998b29d116d Mon Sep 17 00:00:00 2001 From: rickalon Date: Fri, 19 Jun 2026 10:08:11 -0400 Subject: [PATCH 33/41] refactor(multisig): add proposal status enum and unify the propose return type MultisigProposal gains a 'status: pending | executed' field, and propose/proposeTransfer now return MultisigProposal instead of the separate MultisigProposalResult (dropped, along with its 'executed' boolean). Addresses review #3 (status enum) and #5 (reuse the read-only proposal type). --- src/multisig/index.js | 1 - src/multisig/wallet-account-multisig.js | 12 ++------- .../wallet-account-read-only-multisig.js | 1 + types/src/multisig/index.d.ts | 1 - .../src/multisig/wallet-account-multisig.d.ts | 26 +++---------------- .../wallet-account-read-only-multisig.d.ts | 4 +++ 6 files changed, 11 insertions(+), 34 deletions(-) diff --git a/src/multisig/index.js b/src/multisig/index.js index c79a423..980298c 100644 --- a/src/multisig/index.js +++ b/src/multisig/index.js @@ -19,7 +19,6 @@ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigExecuteQuote} MultisigExecuteQuote */ /** @typedef {import('../wallet-account.js').KeyPair} KeyPair */ -/** @typedef {import('./wallet-account-multisig.js').MultisigProposalResult} MultisigProposalResult */ /** @typedef {import('./wallet-account-multisig.js').MultisigTransactionOptions} MultisigTransactionOptions */ /** @typedef {import('./wallet-account-multisig.js').MultisigMessageProposal} MultisigMessageProposal */ /** @typedef {import('./i-multisig-owner-management.js').MultisigOptions} MultisigOptions */ diff --git a/src/multisig/wallet-account-multisig.js b/src/multisig/wallet-account-multisig.js index 241d700..ebc2903 100644 --- a/src/multisig/wallet-account-multisig.js +++ b/src/multisig/wallet-account-multisig.js @@ -24,14 +24,6 @@ import { NotImplementedError } from '../errors.js' /** @typedef {import('./wallet-account-read-only-multisig.js').IWalletAccountReadOnlyMultisig} IWalletAccountReadOnlyMultisig */ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ -/** - * @typedef {Object} MultisigProposalResult - * @property {string} proposalId - The proposal's id, used to approve, reject, query, quote or execute it. - * @property {number} confirmations - The current number of confirmations. - * @property {number} threshold - The minimum amount of confirmations to execute the transaction. - * @property {boolean} executed - True if the transaction has already been executed (e.g. via `autoExecute`). - */ - /** * @typedef {Object} MultisigTransactionOptions * @property {boolean} [autoExecute] - If true, automatically executes the transaction when the approval threshold is met (only takes effect if this signer's approval is the last one required). @@ -85,7 +77,7 @@ export class IWalletAccountMultisig { * * @param {Transaction} tx - The transaction. * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. - * @returns {Promise} The proposal's result. + * @returns {Promise} The created proposal; its `status` is `'executed'` when `autoExecute` ran to completion, otherwise `'pending'`. */ async propose (tx, transactionOptions) { throw new NotImplementedError('propose(tx, transactionOptions)') @@ -97,7 +89,7 @@ export class IWalletAccountMultisig { * * @param {TransferOptions} options - The transfer's options. * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. - * @returns {Promise} The proposal's result. + * @returns {Promise} The created proposal; its `status` is `'executed'` when `autoExecute` ran to completion, otherwise `'pending'`. */ async proposeTransfer (options, transactionOptions) { throw new NotImplementedError('proposeTransfer(options, transactionOptions)') diff --git a/src/multisig/wallet-account-read-only-multisig.js b/src/multisig/wallet-account-read-only-multisig.js index c48c820..fdf972f 100644 --- a/src/multisig/wallet-account-read-only-multisig.js +++ b/src/multisig/wallet-account-read-only-multisig.js @@ -31,6 +31,7 @@ import { NotImplementedError } from '../errors.js' * @property {string} proposalId - The proposal's id. * @property {number} confirmations - The current number of confirmations. * @property {number} threshold - The minimum amount of confirmations to execute the transaction. + * @property {'pending' | 'executed'} status - The proposal's lifecycle state: `'pending'` while it still awaits confirmations or on-chain execution, `'executed'` once it has been executed on-chain. */ /** diff --git a/types/src/multisig/index.d.ts b/types/src/multisig/index.d.ts index 18b886e..7359a9f 100644 --- a/types/src/multisig/index.d.ts +++ b/types/src/multisig/index.d.ts @@ -8,7 +8,6 @@ export type MultisigProposal = import("./wallet-account-read-only-multisig.js"). export type MultisigMessage = import("./wallet-account-read-only-multisig.js").MultisigMessage; export type MultisigExecuteQuote = import("./wallet-account-read-only-multisig.js").MultisigExecuteQuote; export type KeyPair = import("../wallet-account.js").KeyPair; -export type MultisigProposalResult = import("./wallet-account-multisig.js").MultisigProposalResult; export type MultisigTransactionOptions = import("./wallet-account-multisig.js").MultisigTransactionOptions; export type MultisigMessageProposal = import("./wallet-account-multisig.js").MultisigMessageProposal; export type MultisigOptions = import("./i-multisig-owner-management.js").MultisigOptions; diff --git a/types/src/multisig/wallet-account-multisig.d.ts b/types/src/multisig/wallet-account-multisig.d.ts index 2be7c3a..bb2fc4e 100644 --- a/types/src/multisig/wallet-account-multisig.d.ts +++ b/types/src/multisig/wallet-account-multisig.d.ts @@ -28,18 +28,18 @@ export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * * @param {Transaction} tx - The transaction. * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. - * @returns {Promise} The proposal's result. + * @returns {Promise} The created proposal; its `status` is `'executed'` when `autoExecute` ran to completion, otherwise `'pending'`. */ - propose(tx: Transaction, transactionOptions?: MultisigTransactionOptions): Promise; + propose(tx: Transaction, transactionOptions?: MultisigTransactionOptions): Promise; /** * Proposes transferring a token to another address for the other owners to approve. Does not * execute on-chain; see {@link propose}. * * @param {TransferOptions} options - The transfer's options. * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. - * @returns {Promise} The proposal's result. + * @returns {Promise} The created proposal; its `status` is `'executed'` when `autoExecute` ran to completion, otherwise `'pending'`. */ - proposeTransfer(options: TransferOptions, transactionOptions?: MultisigTransactionOptions): Promise; + proposeTransfer(options: TransferOptions, transactionOptions?: MultisigTransactionOptions): Promise; /** * Proposes signing a message. * @@ -82,24 +82,6 @@ export type TransactionResult = import("../wallet-account-read-only.js").Transac export type IWalletAccountReadOnlyMultisig = import("./wallet-account-read-only-multisig.js").IWalletAccountReadOnlyMultisig; export type MultisigProposal = import("./wallet-account-read-only-multisig.js").MultisigProposal; export type KeyPair = import("../wallet-account.js").KeyPair; -export type MultisigProposalResult = { - /** - * - The proposal's id, used to approve, reject, query, quote or execute it. - */ - proposalId: string; - /** - * - The current number of confirmations. - */ - confirmations: number; - /** - * - The minimum amount of confirmations to execute the transaction. - */ - threshold: number; - /** - * - True if the transaction has already been executed (e.g. via `autoExecute`). - */ - executed: boolean; -}; export type MultisigTransactionOptions = { /** * - If true, automatically executes the transaction when the approval threshold is met (only takes effect if this signer's approval is the last one required). diff --git a/types/src/multisig/wallet-account-read-only-multisig.d.ts b/types/src/multisig/wallet-account-read-only-multisig.d.ts index dfd08df..9a449b0 100644 --- a/types/src/multisig/wallet-account-read-only-multisig.d.ts +++ b/types/src/multisig/wallet-account-read-only-multisig.d.ts @@ -67,6 +67,10 @@ export type MultisigProposal = { * - The minimum amount of confirmations to execute the transaction. */ threshold: number; + /** + * - The proposal's lifecycle state: `'pending'` while it still awaits confirmations or on-chain execution, `'executed'` once it has been executed on-chain. + */ + status: 'pending' | 'executed'; }; export type MultisigMessage = { /** From bf9d98026899765623f9f1ffe575397993f49c15 Mon Sep 17 00:00:00 2001 From: rickalon Date: Mon, 22 Jun 2026 08:53:52 -0400 Subject: [PATCH 34/41] refactor(multisig): clean up root type barrel and use the extends keyword - Root types/index.d.ts no longer re-exports multisig types from deleted root-level paths (./src/wallet-account-*-multisig.js) using pre-restructure names; multisig is exposed via the /multisig subpath, matching index.js. - IWalletAccountMultisig uses the real 'extends IWalletAccountReadOnlyMultisig' keyword (with a runtime import) instead of the redundant @extends JSDoc, matching its sibling read-only interface (review: jonathunne). --- src/multisig/wallet-account-multisig.js | 10 ++++------ types/index.d.ts | 11 ----------- types/src/multisig/wallet-account-multisig.d.ts | 5 +---- 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/multisig/wallet-account-multisig.js b/src/multisig/wallet-account-multisig.js index ebc2903..dbda353 100644 --- a/src/multisig/wallet-account-multisig.js +++ b/src/multisig/wallet-account-multisig.js @@ -13,6 +13,8 @@ // limitations under the License. 'use strict' +import { IWalletAccountReadOnlyMultisig } from './wallet-account-read-only-multisig.js' + import { NotImplementedError } from '../errors.js' /** @typedef {import('../wallet-account-read-only.js').Transaction} Transaction */ @@ -21,7 +23,6 @@ import { NotImplementedError } from '../errors.js' /** @typedef {import('../wallet-account.js').KeyPair} KeyPair */ -/** @typedef {import('./wallet-account-read-only-multisig.js').IWalletAccountReadOnlyMultisig} IWalletAccountReadOnlyMultisig */ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ /** @@ -38,11 +39,8 @@ import { NotImplementedError } from '../errors.js' * @property {string | null} combinedSignature - The final combined signature when the threshold is met. */ -/** - * @interface - * @extends {IWalletAccountReadOnlyMultisig} - */ -export class IWalletAccountMultisig { +/** @interface */ +export class IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { /** * The derivation path's index of the signer associated with this account. * diff --git a/types/index.d.ts b/types/index.d.ts index b678d1c..e6f8d80 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,7 +1,5 @@ export { default } from "./src/wallet-manager.js"; export { IWalletAccount } from "./src/wallet-account.js"; -export { IWalletAccountReadOnlyMultisig } from "./src/wallet-account-read-only-multisig.js"; -export { IWalletAccountMultisig } from "./src/wallet-account-multisig.js"; export { NotImplementedError } from "./src/errors.js"; export type FeeRates = import("./src/wallet-manager.js").FeeRates; export type WalletConfig = import("./src/wallet-manager.js").WalletConfig; @@ -10,13 +8,4 @@ export type TransactionResult = import("./src/wallet-account-read-only.js").Tran export type TransferOptions = import("./src/wallet-account-read-only.js").TransferOptions; export type TransferResult = import("./src/wallet-account-read-only.js").TransferResult; export type KeyPair = import("./src/wallet-account.js").KeyPair; -export type MultisigInfo = import("./src/wallet-account-read-only-multisig.js").MultisigInfo; -export type MultisigProposal = import("./src/wallet-account-read-only-multisig.js").MultisigProposal; -export type MessageInfo = import("./src/wallet-account-read-only-multisig.js").MessageInfo; -export type MultisigResult = import("./src/wallet-account-read-only-multisig.js").MultisigProposal; -export type MultisigTransactionResult = import("./src/wallet-account-multisig.js").MultisigTransactionResult; -export type MultisigExecuteResult = import("./src/wallet-account-multisig.js").MultisigExecuteResult; -export type MultisigSendOptions = import("./src/wallet-account-multisig.js").MultisigSendOptions; -export type MessageProposal = import("./src/wallet-account-multisig.js").MessageProposal; -export type MultisigOptions = import("./src/wallet-account-multisig.js").MultisigOptions; export { default as WalletAccountReadOnly, IWalletAccountReadOnly } from "./src/wallet-account-read-only.js"; diff --git a/types/src/multisig/wallet-account-multisig.d.ts b/types/src/multisig/wallet-account-multisig.d.ts index bb2fc4e..191d5c6 100644 --- a/types/src/multisig/wallet-account-multisig.d.ts +++ b/types/src/multisig/wallet-account-multisig.d.ts @@ -1,7 +1,4 @@ -/** - * @interface - * @extends {IWalletAccountReadOnlyMultisig} - */ +/** @interface */ export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { /** * The derivation path's index of the signer associated with this account. From a31604e991d18ff481074fe567612ca0b17bd0d1 Mon Sep 17 00:00:00 2001 From: rickalon Date: Wed, 24 Jun 2026 08:26:32 -0400 Subject: [PATCH 35/41] refactor(multisig): make the transport read methods generic too Adds TProposalResponse/TMessageResponse template params (defaulting to the opaque MultisigTransportProposal/MultisigTransportMessage) so getProposal/getMessage mirror the already-generic write side. Implementations can advertise the concrete response shape they return, giving direct consumers real type-checking instead of opaque payloads; unparameterized usage is unchanged. --- src/multisig/i-multisig-transport.js | 6 ++++-- types/src/multisig/i-multisig-transport.d.ts | 12 +++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/multisig/i-multisig-transport.js b/src/multisig/i-multisig-transport.js index 2195891..3274a16 100644 --- a/src/multisig/i-multisig-transport.js +++ b/src/multisig/i-multisig-transport.js @@ -59,6 +59,8 @@ import { NotImplementedError } from '../errors.js' * @interface * @template [TProposal=Record] * @template [TMessage=MultisigTransportMessageInput] + * @template [TProposalResponse=MultisigTransportProposal] + * @template [TMessageResponse=MultisigTransportMessage] */ export class IMultisigTransport { /** @@ -75,7 +77,7 @@ export class IMultisigTransport { * Returns a transaction proposal by its identifier. * * @param {string} proposalId - The proposal's identifier. - * @returns {Promise} The proposal, or null if it has not been found. + * @returns {Promise} The proposal, or null if it has not been found. */ async getProposal (proposalId) { throw new NotImplementedError('getProposal(proposalId)') @@ -107,7 +109,7 @@ export class IMultisigTransport { * Returns a message proposal by its hash. * * @param {string} messageId - The message's hash. - * @returns {Promise} The message, or null if it has not been found. + * @returns {Promise} The message, or null if it has not been found. */ async getMessage (messageId) { throw new NotImplementedError('getMessage(messageId)') diff --git a/types/src/multisig/i-multisig-transport.d.ts b/types/src/multisig/i-multisig-transport.d.ts index b07fce6..532eba2 100644 --- a/types/src/multisig/i-multisig-transport.d.ts +++ b/types/src/multisig/i-multisig-transport.d.ts @@ -39,8 +39,10 @@ * @interface * @template [TProposal=Record] * @template [TMessage=MultisigTransportMessageInput] + * @template [TProposalResponse=MultisigTransportProposal] + * @template [TMessageResponse=MultisigTransportMessage] */ -export interface IMultisigTransport, TMessage = MultisigTransportMessageInput> { +export interface IMultisigTransport, TMessage = MultisigTransportMessageInput, TProposalResponse = MultisigTransportProposal, TMessageResponse = MultisigTransportMessage> { /** * Submits a new transaction proposal so the other owners can confirm it. * @@ -52,9 +54,9 @@ export interface IMultisigTransport, TMessag * Returns a transaction proposal by its identifier. * * @param {string} proposalId - The proposal's identifier. - * @returns {Promise} The proposal, or null if it has not been found. + * @returns {Promise} The proposal, or null if it has not been found. */ - getProposal(proposalId: string): Promise; + getProposal(proposalId: string): Promise; /** * Adds an owner's confirmation (signature) to an existing transaction proposal. * @@ -75,9 +77,9 @@ export interface IMultisigTransport, TMessag * Returns a message proposal by its hash. * * @param {string} messageId - The message's hash. - * @returns {Promise} The message, or null if it has not been found. + * @returns {Promise} The message, or null if it has not been found. */ - getMessage(messageId: string): Promise; + getMessage(messageId: string): Promise; /** * Adds an owner's confirmation (signature) to an existing message proposal. * From d1ab5ae75bb9f82ea8d291328988010459a133a2 Mon Sep 17 00:00:00 2001 From: rickalon Date: Thu, 25 Jun 2026 10:11:44 -0400 Subject: [PATCH 36/41] feat(multisig): extend toTransportJson to encode byte arrays as hex toTransportJson now also converts Uint8Array/Buffer to 0x-prefixed hex (in addition to BigInt->decimal-string), so payloads that carry raw bytes survive JSON.stringify. Documents the conversion as one-way (consumers restore types per field). --- src/multisig/transport-serialization.js | 32 +++++++++++++++---- tests/transport-serialization.test.js | 16 ++++++++++ .../src/multisig/transport-serialization.d.ts | 14 ++++---- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/multisig/transport-serialization.js b/src/multisig/transport-serialization.js index db8c343..63f4bb9 100644 --- a/src/multisig/transport-serialization.js +++ b/src/multisig/transport-serialization.js @@ -14,22 +14,40 @@ 'use strict' /** - * Recursively converts a transport payload into a JSON-safe value by turning every BigInt into - * its decimal string form, so the payload survives JSON.stringify. + * Converts a byte array to a 0x-prefixed lowercase hex string. * - * Transports that serialize the proposal/message themselves (rather than delegating to an SDK - * that handles it) can use this instead of reimplementing the conversion. Non-JSON-native values - * other than BigInt (e.g. byte arrays) should be normalized by the transport for its chain's - * encoding before calling this. + * @param {Uint8Array} bytes - The byte array to encode. + * @returns {string} The 0x-prefixed hex encoding. + */ +function bytesToHex (bytes) { + let hex = '0x' + for (const byte of bytes) { + hex += byte.toString(16).padStart(2, '0') + } + return hex +} + +/** + * Recursively converts a value into a JSON-safe form so it survives JSON.stringify: every BigInt + * becomes its decimal string and every byte array (Uint8Array, including Buffer) becomes a + * 0x-prefixed lowercase hex string. Arrays and plain objects are converted entry by entry; all + * other values are returned unchanged. + * + * The conversion is one-way: there is no generic inverse, so a consumer that needs the original + * types back restores them per field (it knows which fields are amounts, byte strings, etc.). * * @param {unknown} value - The value to convert (object, array, or primitive). - * @returns {unknown} A JSON-safe copy with every BigInt converted to a decimal string. + * @returns {unknown} A JSON-safe copy of the value. */ export function toTransportJson (value) { if (typeof value === 'bigint') { return value.toString() } + if (value instanceof Uint8Array) { + return bytesToHex(value) + } + if (Array.isArray(value)) { return value.map(toTransportJson) } diff --git a/tests/transport-serialization.test.js b/tests/transport-serialization.test.js index c38af97..31cb89c 100644 --- a/tests/transport-serialization.test.js +++ b/tests/transport-serialization.test.js @@ -27,6 +27,22 @@ describe('toTransportJson', () => { expect(toTransportJson(payload)).toEqual(payload) }) + test('converts a Uint8Array to a 0x-prefixed hex string', () => { + expect(toTransportJson(new Uint8Array([0, 1, 171, 255]))).toBe('0x0001abff') + }) + + test('converts a Buffer and nested byte arrays to hex', () => { + const payload = { + data: Buffer.from([222, 173]), + nested: { signature: new Uint8Array([1, 2, 3]) } + } + + expect(toTransportJson(payload)).toEqual({ + data: '0xdead', + nested: { signature: '0x010203' } + }) + }) + test('produces output that survives JSON.stringify', () => { expect(JSON.stringify(toTransportJson({ gas: 42n }))).toBe('{"gas":"42"}') }) diff --git a/types/src/multisig/transport-serialization.d.ts b/types/src/multisig/transport-serialization.d.ts index 90ae55a..5a8e8e5 100644 --- a/types/src/multisig/transport-serialization.d.ts +++ b/types/src/multisig/transport-serialization.d.ts @@ -1,13 +1,13 @@ /** - * Recursively converts a transport payload into a JSON-safe value by turning every BigInt into - * its decimal string form, so the payload survives JSON.stringify. + * Recursively converts a value into a JSON-safe form so it survives JSON.stringify: every BigInt + * becomes its decimal string and every byte array (Uint8Array, including Buffer) becomes a + * 0x-prefixed lowercase hex string. Arrays and plain objects are converted entry by entry; all + * other values are returned unchanged. * - * Transports that serialize the proposal/message themselves (rather than delegating to an SDK - * that handles it) can use this instead of reimplementing the conversion. Non-JSON-native values - * other than BigInt (e.g. byte arrays) should be normalized by the transport for its chain's - * encoding before calling this. + * The conversion is one-way: there is no generic inverse, so a consumer that needs the original + * types back restores them per field (it knows which fields are amounts, byte strings, etc.). * * @param {unknown} value - The value to convert (object, array, or primitive). - * @returns {unknown} A JSON-safe copy with every BigInt converted to a decimal string. + * @returns {unknown} A JSON-safe copy of the value. */ export function toTransportJson(value: unknown): unknown; From e99ef9a0f62136bc2ffe1699811d76aed1472cad Mon Sep 17 00:00:00 2001 From: rickalon Date: Fri, 10 Jul 2026 10:28:05 -0400 Subject: [PATCH 37/41] feat(multisig): adopt the shared error taxonomy in the interface contracts Adds SignerError, ValueError and NoSuchElementError (verbatim from the taxonomy agreed on #46) to the core errors module and exports them, and documents them via @throws on the multisig interface contracts (propose/approve/reject/execute/quoteExecuteProposal + owner management). Interface bodies still throw NotImplementedError; @throws documents the contract implementations must honor. UnsupportedOperationError is deferred with the optional-method (abstract-base) work. Mirrors #46's agreed definitions so the shared errors module reconciles cleanly when both land. --- index.js | 2 +- src/errors.js | 41 +++++++++++++++++++ src/multisig/i-multisig-owner-management.js | 6 +++ src/multisig/wallet-account-multisig.js | 14 +++++++ .../wallet-account-read-only-multisig.js | 3 ++ types/index.d.ts | 2 +- types/src/errors.d.ts | 26 ++++++++++++ .../multisig/i-multisig-owner-management.d.ts | 4 ++ .../src/multisig/wallet-account-multisig.d.ts | 10 +++++ .../wallet-account-read-only-multisig.d.ts | 1 + 10 files changed, 107 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 62fb463..10e5391 100644 --- a/index.js +++ b/index.js @@ -29,4 +29,4 @@ export { default as WalletAccountReadOnly, IWalletAccountReadOnly } from './src/ export { IWalletAccount } from './src/wallet-account.js' -export { NotImplementedError } from './src/errors.js' +export { NotImplementedError, SignerError, ValueError, NoSuchElementError } from './src/errors.js' diff --git a/src/errors.js b/src/errors.js index fc9bcbf..46ec468 100644 --- a/src/errors.js +++ b/src/errors.js @@ -25,3 +25,44 @@ export class NotImplementedError extends Error { this.name = 'NotImplementedError' } } + +export class SignerError extends Error { + /** + * Create a new signer error. + * + * @param {string} message - The error's message. + */ + constructor (message) { + super(message) + + this.name = 'SignerError' + } +} + +export class ValueError extends Error { + /** + * Create a new value error. Thrown when an argument has the correct type but + * violates a validation rule. + * + * @param {string} message - The error's message. + */ + constructor (message) { + super(message) + + this.name = 'ValueError' + } +} + +export class NoSuchElementError extends Error { + /** + * Create a new no such element error. Thrown when a lookup finds no element for + * the given identifier. + * + * @param {string} message - The error's message. + */ + constructor (message) { + super(message) + + this.name = 'NoSuchElementError' + } +} diff --git a/src/multisig/i-multisig-owner-management.js b/src/multisig/i-multisig-owner-management.js index 04170b3..04d5dc8 100644 --- a/src/multisig/i-multisig-owner-management.js +++ b/src/multisig/i-multisig-owner-management.js @@ -17,6 +17,8 @@ import { NotImplementedError } from '../errors.js' /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ +/** @typedef {import('../errors.js').SignerError} SignerError */ + /** * @typedef {Object} MultisigOptions * @property {number} threshold - The new amount of approvals required to execute a transaction. @@ -37,6 +39,7 @@ export class IMultisigOwnerManagement { * @param {string} owner - The owner's address. * @param {MultisigOptions} [options] - The multisig options. * @returns {Promise} The multisig proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ async addOwner (owner, options) { throw new NotImplementedError('addOwner(owner, options)') @@ -48,6 +51,7 @@ export class IMultisigOwnerManagement { * @param {string} owner - The owner's address. * @param {MultisigOptions} [options] - The multisig options. * @returns {Promise} The multisig proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ async removeOwner (owner, options) { throw new NotImplementedError('removeOwner(owner, options)') @@ -59,6 +63,7 @@ export class IMultisigOwnerManagement { * @param {string} oldOwner - The old owner. * @param {string} newOwner - The new owner. * @returns {Promise} The multisig proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ async swapOwner (oldOwner, newOwner) { throw new NotImplementedError('swapOwner(oldOwner, newOwner)') @@ -69,6 +74,7 @@ export class IMultisigOwnerManagement { * * @param {number} newThreshold - The new threshold. * @returns {Promise} The multisig proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ async changeThreshold (newThreshold) { throw new NotImplementedError('changeThreshold(newThreshold)') diff --git a/src/multisig/wallet-account-multisig.js b/src/multisig/wallet-account-multisig.js index dbda353..efb988c 100644 --- a/src/multisig/wallet-account-multisig.js +++ b/src/multisig/wallet-account-multisig.js @@ -25,6 +25,10 @@ import { NotImplementedError } from '../errors.js' /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ +/** @typedef {import('../errors.js').SignerError} SignerError */ +/** @typedef {import('../errors.js').NoSuchElementError} NoSuchElementError */ +/** @typedef {import('../errors.js').ValueError} ValueError */ + /** * @typedef {Object} MultisigTransactionOptions * @property {boolean} [autoExecute] - If true, automatically executes the transaction when the approval threshold is met (only takes effect if this signer's approval is the last one required). @@ -76,6 +80,7 @@ export class IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * @param {Transaction} tx - The transaction. * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. * @returns {Promise} The created proposal; its `status` is `'executed'` when `autoExecute` ran to completion, otherwise `'pending'`. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ async propose (tx, transactionOptions) { throw new NotImplementedError('propose(tx, transactionOptions)') @@ -88,6 +93,7 @@ export class IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * @param {TransferOptions} options - The transfer's options. * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. * @returns {Promise} The created proposal; its `status` is `'executed'` when `autoExecute` ran to completion, otherwise `'pending'`. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ async proposeTransfer (options, transactionOptions) { throw new NotImplementedError('proposeTransfer(options, transactionOptions)') @@ -98,6 +104,7 @@ export class IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * * @param {string} message - The message to sign. * @returns {Promise} The multisig message proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ async proposeMessage (message) { throw new NotImplementedError('proposeMessage(message)') @@ -108,6 +115,8 @@ export class IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * * @param {string} messageId - The message's hash. * @returns {Promise} The multisig message proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. + * @throws {NoSuchElementError} If no message exists for the given id. */ async approveMessage (messageId) { throw new NotImplementedError('approveMessage(messageId)') @@ -118,6 +127,8 @@ export class IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * * @param {string} proposalId - The proposal's id. * @returns {Promise} The multisig proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. + * @throws {NoSuchElementError} If no proposal exists for the given id. */ async approveProposal (proposalId) { throw new NotImplementedError('approveProposal(proposalId)') @@ -128,6 +139,7 @@ export class IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * * @param {string} proposalId - The proposal's id. * @returns {Promise} The multisig proposal. + * @throws {NoSuchElementError} If no proposal exists for the given id. */ async rejectProposal (proposalId) { throw new NotImplementedError('rejectProposal(proposalId)') @@ -138,6 +150,8 @@ export class IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * * @param {string} proposalId - The proposal's id. * @returns {Promise} The on-chain transaction's result. + * @throws {NoSuchElementError} If no proposal exists for the given id. + * @throws {ValueError} If the proposal has not reached the approval threshold. */ async executeProposal (proposalId) { throw new NotImplementedError('executeProposal(proposalId)') diff --git a/src/multisig/wallet-account-read-only-multisig.js b/src/multisig/wallet-account-read-only-multisig.js index fdf972f..b1967a8 100644 --- a/src/multisig/wallet-account-read-only-multisig.js +++ b/src/multisig/wallet-account-read-only-multisig.js @@ -17,6 +17,8 @@ import { IWalletAccountReadOnlyBase } from '../wallet-account-read-only-base.js' import { NotImplementedError } from '../errors.js' +/** @typedef {import('../errors.js').NoSuchElementError} NoSuchElementError */ + /** * @typedef {Object} MultisigInfo * @property {string} address - The multisig wallet account address. @@ -95,6 +97,7 @@ export class IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnlyBase { * * @param {string} proposalId - The proposal's id. * @returns {Promise} The execution cost estimate. + * @throws {NoSuchElementError} If no proposal exists for the given id. */ async quoteExecuteProposal (proposalId) { throw new NotImplementedError('quoteExecuteProposal(proposalId)') diff --git a/types/index.d.ts b/types/index.d.ts index e6f8d80..fa0b64d 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,6 +1,6 @@ export { default } from "./src/wallet-manager.js"; export { IWalletAccount } from "./src/wallet-account.js"; -export { NotImplementedError } from "./src/errors.js"; +export { NotImplementedError, SignerError, ValueError, NoSuchElementError } from "./src/errors.js"; export type FeeRates = import("./src/wallet-manager.js").FeeRates; export type WalletConfig = import("./src/wallet-manager.js").WalletConfig; export type Transaction = import("./src/wallet-account-read-only.js").Transaction; diff --git a/types/src/errors.d.ts b/types/src/errors.d.ts index ac2f75a..7979578 100644 --- a/types/src/errors.d.ts +++ b/types/src/errors.d.ts @@ -6,3 +6,29 @@ export class NotImplementedError extends Error { */ constructor(methodName: string); } +export class SignerError extends Error { + /** + * Create a new signer error. + * + * @param {string} message - The error's message. + */ + constructor(message: string); +} +export class ValueError extends Error { + /** + * Create a new value error. Thrown when an argument has the correct type but + * violates a validation rule. + * + * @param {string} message - The error's message. + */ + constructor(message: string); +} +export class NoSuchElementError extends Error { + /** + * Create a new no such element error. Thrown when a lookup finds no element for + * the given identifier. + * + * @param {string} message - The error's message. + */ + constructor(message: string); +} diff --git a/types/src/multisig/i-multisig-owner-management.d.ts b/types/src/multisig/i-multisig-owner-management.d.ts index f73beca..ff680f9 100644 --- a/types/src/multisig/i-multisig-owner-management.d.ts +++ b/types/src/multisig/i-multisig-owner-management.d.ts @@ -13,6 +13,7 @@ export interface IMultisigOwnerManagement { * @param {string} owner - The owner's address. * @param {MultisigOptions} [options] - The multisig options. * @returns {Promise} The multisig proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ addOwner(owner: string, options?: MultisigOptions): Promise; /** @@ -21,6 +22,7 @@ export interface IMultisigOwnerManagement { * @param {string} owner - The owner's address. * @param {MultisigOptions} [options] - The multisig options. * @returns {Promise} The multisig proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ removeOwner(owner: string, options?: MultisigOptions): Promise; /** @@ -29,6 +31,7 @@ export interface IMultisigOwnerManagement { * @param {string} oldOwner - The old owner. * @param {string} newOwner - The new owner. * @returns {Promise} The multisig proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ swapOwner(oldOwner: string, newOwner: string): Promise; /** @@ -36,6 +39,7 @@ export interface IMultisigOwnerManagement { * * @param {number} newThreshold - The new threshold. * @returns {Promise} The multisig proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ changeThreshold(newThreshold: number): Promise; } diff --git a/types/src/multisig/wallet-account-multisig.d.ts b/types/src/multisig/wallet-account-multisig.d.ts index 191d5c6..7192420 100644 --- a/types/src/multisig/wallet-account-multisig.d.ts +++ b/types/src/multisig/wallet-account-multisig.d.ts @@ -26,6 +26,7 @@ export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * @param {Transaction} tx - The transaction. * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. * @returns {Promise} The created proposal; its `status` is `'executed'` when `autoExecute` ran to completion, otherwise `'pending'`. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ propose(tx: Transaction, transactionOptions?: MultisigTransactionOptions): Promise; /** @@ -35,6 +36,7 @@ export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * @param {TransferOptions} options - The transfer's options. * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. * @returns {Promise} The created proposal; its `status` is `'executed'` when `autoExecute` ran to completion, otherwise `'pending'`. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ proposeTransfer(options: TransferOptions, transactionOptions?: MultisigTransactionOptions): Promise; /** @@ -42,6 +44,7 @@ export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * * @param {string} message - The message to sign. * @returns {Promise} The multisig message proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. */ proposeMessage(message: string): Promise; /** @@ -49,6 +52,8 @@ export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * * @param {string} messageId - The message's hash. * @returns {Promise} The multisig message proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. + * @throws {NoSuchElementError} If no message exists for the given id. */ approveMessage(messageId: string): Promise; /** @@ -56,6 +61,8 @@ export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * * @param {string} proposalId - The proposal's id. * @returns {Promise} The multisig proposal. + * @throws {SignerError} If the signer is not an owner of the multisig account. + * @throws {NoSuchElementError} If no proposal exists for the given id. */ approveProposal(proposalId: string): Promise; /** @@ -63,6 +70,7 @@ export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * * @param {string} proposalId - The proposal's id. * @returns {Promise} The multisig proposal. + * @throws {NoSuchElementError} If no proposal exists for the given id. */ rejectProposal(proposalId: string): Promise; /** @@ -70,6 +78,8 @@ export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * * @param {string} proposalId - The proposal's id. * @returns {Promise} The on-chain transaction's result. + * @throws {NoSuchElementError} If no proposal exists for the given id. + * @throws {ValueError} If the proposal has not reached the approval threshold. */ executeProposal(proposalId: string): Promise; } diff --git a/types/src/multisig/wallet-account-read-only-multisig.d.ts b/types/src/multisig/wallet-account-read-only-multisig.d.ts index 9a449b0..b82fb7c 100644 --- a/types/src/multisig/wallet-account-read-only-multisig.d.ts +++ b/types/src/multisig/wallet-account-read-only-multisig.d.ts @@ -33,6 +33,7 @@ export interface IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnlyBa * * @param {string} proposalId - The proposal's id. * @returns {Promise} The execution cost estimate. + * @throws {NoSuchElementError} If no proposal exists for the given id. */ quoteExecuteProposal(proposalId: string): Promise; } From 97f30a14740e7c8246beaae7577ec1e87b609d8f Mon Sep 17 00:00:00 2001 From: rickalon Date: Fri, 10 Jul 2026 10:33:52 -0400 Subject: [PATCH 38/41] docs(multisig): clarify why MultisigInfo.isCreated is optional States that isCreated is omitted when the implementation cannot determine account creation without an on-chain lookup, so the optionality is self-explaining (optional-field audit). --- src/multisig/wallet-account-read-only-multisig.js | 2 +- types/src/multisig/wallet-account-read-only-multisig.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/multisig/wallet-account-read-only-multisig.js b/src/multisig/wallet-account-read-only-multisig.js index b1967a8..bec4012 100644 --- a/src/multisig/wallet-account-read-only-multisig.js +++ b/src/multisig/wallet-account-read-only-multisig.js @@ -24,7 +24,7 @@ import { NotImplementedError } from '../errors.js' * @property {string} address - The multisig wallet account address. * @property {string[]} owners - The owners of the multisig wallet account. * @property {number} threshold - The minimum amount of signatures to execute a transaction. - * @property {boolean} [isCreated] - True if the multisig wallet account has already been created. + * @property {boolean} [isCreated] - True if the multisig wallet account has already been created; omitted when the implementation cannot determine it without an on-chain lookup. */ /** diff --git a/types/src/multisig/wallet-account-read-only-multisig.d.ts b/types/src/multisig/wallet-account-read-only-multisig.d.ts index b82fb7c..f31b8c7 100644 --- a/types/src/multisig/wallet-account-read-only-multisig.d.ts +++ b/types/src/multisig/wallet-account-read-only-multisig.d.ts @@ -51,7 +51,7 @@ export type MultisigInfo = { */ threshold: number; /** - * - True if the multisig wallet account has already been created. + * - True if the multisig wallet account has already been created; omitted when the implementation cannot determine it without an on-chain lookup. */ isCreated?: boolean; }; From 02554d1d8a9f7ee1ce4d3970f6ebc859f20d730d Mon Sep 17 00:00:00 2001 From: rickalon Date: Fri, 10 Jul 2026 11:58:10 -0400 Subject: [PATCH 39/41] refactor(multisig): align quoteExecuteProposal return with the quote/execute convention Drops the bespoke MultisigExecuteQuote typedef and returns Omit from quoteExecuteProposal, matching how quoteSendTransaction/quoteTransfer relate to their execute counterparts in the base wallet. Makes 'execute return extends quote return' explicit (executeProposal returns the full TransactionResult) and removes a redundant type. Type-only; the type is unreleased (new in this PR). --- src/multisig/index.js | 1 - src/multisig/wallet-account-read-only-multisig.js | 9 +++------ types/src/multisig/index.d.ts | 1 - .../multisig/wallet-account-read-only-multisig.d.ts | 11 +++-------- 4 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/multisig/index.js b/src/multisig/index.js index 980298c..d76ed4b 100644 --- a/src/multisig/index.js +++ b/src/multisig/index.js @@ -16,7 +16,6 @@ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigInfo} MultisigInfo */ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigProposal} MultisigProposal */ /** @typedef {import('./wallet-account-read-only-multisig.js').MultisigMessage} MultisigMessage */ -/** @typedef {import('./wallet-account-read-only-multisig.js').MultisigExecuteQuote} MultisigExecuteQuote */ /** @typedef {import('../wallet-account.js').KeyPair} KeyPair */ /** @typedef {import('./wallet-account-multisig.js').MultisigTransactionOptions} MultisigTransactionOptions */ diff --git a/src/multisig/wallet-account-read-only-multisig.js b/src/multisig/wallet-account-read-only-multisig.js index bec4012..644b443 100644 --- a/src/multisig/wallet-account-read-only-multisig.js +++ b/src/multisig/wallet-account-read-only-multisig.js @@ -19,6 +19,8 @@ import { NotImplementedError } from '../errors.js' /** @typedef {import('../errors.js').NoSuchElementError} NoSuchElementError */ +/** @typedef {import('../wallet-account-read-only.js').TransactionResult} TransactionResult */ + /** * @typedef {Object} MultisigInfo * @property {string} address - The multisig wallet account address. @@ -45,11 +47,6 @@ import { NotImplementedError } from '../errors.js' * @property {string | null} combinedSignature - The final combined signature when the threshold is met. */ -/** - * @typedef {Object} MultisigExecuteQuote - * @property {bigint} fee - The estimated cost of executing the proposal on-chain. - */ - /** @interface */ export class IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnlyBase { /** @@ -96,7 +93,7 @@ export class IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnlyBase { * Quotes the on-chain cost of executing a pending proposal. * * @param {string} proposalId - The proposal's id. - * @returns {Promise} The execution cost estimate. + * @returns {Promise>} The execution cost estimate. * @throws {NoSuchElementError} If no proposal exists for the given id. */ async quoteExecuteProposal (proposalId) { diff --git a/types/src/multisig/index.d.ts b/types/src/multisig/index.d.ts index 7359a9f..2df091c 100644 --- a/types/src/multisig/index.d.ts +++ b/types/src/multisig/index.d.ts @@ -6,7 +6,6 @@ export { toTransportJson } from "./transport-serialization.js"; export type MultisigInfo = import("./wallet-account-read-only-multisig.js").MultisigInfo; export type MultisigProposal = import("./wallet-account-read-only-multisig.js").MultisigProposal; export type MultisigMessage = import("./wallet-account-read-only-multisig.js").MultisigMessage; -export type MultisigExecuteQuote = import("./wallet-account-read-only-multisig.js").MultisigExecuteQuote; export type KeyPair = import("../wallet-account.js").KeyPair; export type MultisigTransactionOptions = import("./wallet-account-multisig.js").MultisigTransactionOptions; export type MultisigMessageProposal = import("./wallet-account-multisig.js").MultisigMessageProposal; diff --git a/types/src/multisig/wallet-account-read-only-multisig.d.ts b/types/src/multisig/wallet-account-read-only-multisig.d.ts index f31b8c7..6485af8 100644 --- a/types/src/multisig/wallet-account-read-only-multisig.d.ts +++ b/types/src/multisig/wallet-account-read-only-multisig.d.ts @@ -32,10 +32,10 @@ export interface IWalletAccountReadOnlyMultisig extends IWalletAccountReadOnlyBa * Quotes the on-chain cost of executing a pending proposal. * * @param {string} proposalId - The proposal's id. - * @returns {Promise} The execution cost estimate. + * @returns {Promise>} The execution cost estimate. * @throws {NoSuchElementError} If no proposal exists for the given id. */ - quoteExecuteProposal(proposalId: string): Promise; + quoteExecuteProposal(proposalId: string): Promise>; } export type MultisigInfo = { /** @@ -95,10 +95,5 @@ export type MultisigMessage = { */ combinedSignature: string | null; }; -export type MultisigExecuteQuote = { - /** - * - The estimated cost of executing the proposal on-chain. - */ - fee: bigint; -}; +export type TransactionResult = import("../wallet-account-read-only.js").TransactionResult; import { IWalletAccountReadOnlyBase } from '../wallet-account-read-only-base.js'; From 1a3461fa2f8baf9efcfedaeaf6d01084b163045c Mon Sep 17 00:00:00 2001 From: rickalon Date: Mon, 20 Jul 2026 10:59:09 -0400 Subject: [PATCH 40/41] docs: simplify ValueError description to match the agreed wording Syncs the ValueError doc to the wording agreed on the reference PR ('Thrown when an argument fails validation'), keeping the shared error class definition byte-identical. --- src/errors.js | 3 +-- types/src/errors.d.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/errors.js b/src/errors.js index 46ec468..6945f4f 100644 --- a/src/errors.js +++ b/src/errors.js @@ -41,8 +41,7 @@ export class SignerError extends Error { export class ValueError extends Error { /** - * Create a new value error. Thrown when an argument has the correct type but - * violates a validation rule. + * Create a new value error. Thrown when an argument fails validation. * * @param {string} message - The error's message. */ diff --git a/types/src/errors.d.ts b/types/src/errors.d.ts index 7979578..c606ba5 100644 --- a/types/src/errors.d.ts +++ b/types/src/errors.d.ts @@ -16,8 +16,7 @@ export class SignerError extends Error { } export class ValueError extends Error { /** - * Create a new value error. Thrown when an argument has the correct type but - * violates a validation rule. + * Create a new value error. Thrown when an argument fails validation. * * @param {string} message - The error's message. */ From 5c8c611bb5e1d83b11f01a76a884317ecb87b95b Mon Sep 17 00:00:00 2001 From: rickalon Date: Tue, 21 Jul 2026 12:03:39 -0400 Subject: [PATCH 41/41] refactor(multisig): make propose the generic surface; drop proposeTransfer and MultisigInfo.isCreated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the interface design requirements: the multisig write surface is propose/proposeMessage/approve*/reject*/execute* only — transfer is not a separate interface member (propose is the generic replacement; concrete classes can still offer transfer via propose). Also removes MultisigInfo.isCreated, which fails the common-field criteria (not universal across multisig chains, and never actually undefined in practice). --- src/multisig/wallet-account-multisig.js | 14 -------------- src/multisig/wallet-account-read-only-multisig.js | 1 - types/src/multisig/wallet-account-multisig.d.ts | 11 ----------- .../wallet-account-read-only-multisig.d.ts | 4 ---- 4 files changed, 30 deletions(-) diff --git a/src/multisig/wallet-account-multisig.js b/src/multisig/wallet-account-multisig.js index efb988c..bc7b851 100644 --- a/src/multisig/wallet-account-multisig.js +++ b/src/multisig/wallet-account-multisig.js @@ -18,7 +18,6 @@ import { IWalletAccountReadOnlyMultisig } from './wallet-account-read-only-multi import { NotImplementedError } from '../errors.js' /** @typedef {import('../wallet-account-read-only.js').Transaction} Transaction */ -/** @typedef {import('../wallet-account-read-only.js').TransferOptions} TransferOptions */ /** @typedef {import('../wallet-account-read-only.js').TransactionResult} TransactionResult */ /** @typedef {import('../wallet-account.js').KeyPair} KeyPair */ @@ -86,19 +85,6 @@ export class IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { throw new NotImplementedError('propose(tx, transactionOptions)') } - /** - * Proposes transferring a token to another address for the other owners to approve. Does not - * execute on-chain; see {@link propose}. - * - * @param {TransferOptions} options - The transfer's options. - * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. - * @returns {Promise} The created proposal; its `status` is `'executed'` when `autoExecute` ran to completion, otherwise `'pending'`. - * @throws {SignerError} If the signer is not an owner of the multisig account. - */ - async proposeTransfer (options, transactionOptions) { - throw new NotImplementedError('proposeTransfer(options, transactionOptions)') - } - /** * Proposes signing a message. * diff --git a/src/multisig/wallet-account-read-only-multisig.js b/src/multisig/wallet-account-read-only-multisig.js index 644b443..e8e6f8d 100644 --- a/src/multisig/wallet-account-read-only-multisig.js +++ b/src/multisig/wallet-account-read-only-multisig.js @@ -26,7 +26,6 @@ import { NotImplementedError } from '../errors.js' * @property {string} address - The multisig wallet account address. * @property {string[]} owners - The owners of the multisig wallet account. * @property {number} threshold - The minimum amount of signatures to execute a transaction. - * @property {boolean} [isCreated] - True if the multisig wallet account has already been created; omitted when the implementation cannot determine it without an on-chain lookup. */ /** diff --git a/types/src/multisig/wallet-account-multisig.d.ts b/types/src/multisig/wallet-account-multisig.d.ts index 7192420..5d865bc 100644 --- a/types/src/multisig/wallet-account-multisig.d.ts +++ b/types/src/multisig/wallet-account-multisig.d.ts @@ -29,16 +29,6 @@ export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { * @throws {SignerError} If the signer is not an owner of the multisig account. */ propose(tx: Transaction, transactionOptions?: MultisigTransactionOptions): Promise; - /** - * Proposes transferring a token to another address for the other owners to approve. Does not - * execute on-chain; see {@link propose}. - * - * @param {TransferOptions} options - The transfer's options. - * @param {MultisigTransactionOptions} [transactionOptions] - The multisig transaction's options. - * @returns {Promise} The created proposal; its `status` is `'executed'` when `autoExecute` ran to completion, otherwise `'pending'`. - * @throws {SignerError} If the signer is not an owner of the multisig account. - */ - proposeTransfer(options: TransferOptions, transactionOptions?: MultisigTransactionOptions): Promise; /** * Proposes signing a message. * @@ -84,7 +74,6 @@ export interface IWalletAccountMultisig extends IWalletAccountReadOnlyMultisig { executeProposal(proposalId: string): Promise; } export type Transaction = import("../wallet-account-read-only.js").Transaction; -export type TransferOptions = import("../wallet-account-read-only.js").TransferOptions; export type TransactionResult = import("../wallet-account-read-only.js").TransactionResult; export type IWalletAccountReadOnlyMultisig = import("./wallet-account-read-only-multisig.js").IWalletAccountReadOnlyMultisig; export type MultisigProposal = import("./wallet-account-read-only-multisig.js").MultisigProposal; diff --git a/types/src/multisig/wallet-account-read-only-multisig.d.ts b/types/src/multisig/wallet-account-read-only-multisig.d.ts index 6485af8..4f5ff7b 100644 --- a/types/src/multisig/wallet-account-read-only-multisig.d.ts +++ b/types/src/multisig/wallet-account-read-only-multisig.d.ts @@ -50,10 +50,6 @@ export type MultisigInfo = { * - The minimum amount of signatures to execute a transaction. */ threshold: number; - /** - * - True if the multisig wallet account has already been created; omitted when the implementation cannot determine it without an on-chain lookup. - */ - isCreated?: boolean; }; export type MultisigProposal = { /**