Endless
  • 🚀README
  • Discovery
    • 🚀Endless Web3 Genesis Cloud
    • 💎Business Model
    • 🎯Vision
    • ✈️Roadmap
    • 🪙Economics
    • 👤Team
      • Yu Xiong
      • Amit Kumar Jaiswal
      • Ned
      • 0xfun
      • Scott Trowbridge
      • Neeraj Sharma LLB
      • Amjad Suleman
      • Binu Paul
      • Eduard Romulus GOEAN
    • ❤️Developer Community
  • Endless Chain
    • Tech Docs
      • Account Address Format
      • Endless Account
      • Endless Coin(EDS)
      • Sponsored Transaction
      • On-Chain Multisig
      • Randomness
      • Safety Transaction
      • Token Locking & Distribution
    • Start
      • Learn about Endless
        • Accounts
        • Resources
        • Events
        • Transactions and States
        • Gas and Storage Fees
        • Computing Transaction Gas
        • Blocks
        • Staking
          • Delegated Staking
        • Governance
        • Endless Blockchain Deep Dive
          • Validator Nodes Overview
          • Fullnodes Overview
          • Node Networks and Synchronization
        • Move - A Web3 Language and Runtime
      • Explore Endless
      • Latest Endless Releases
      • Networks
    • Build
      • Tutorials
        • Your First Transaction
        • Your First Fungible Asset
        • Your First NFT
        • Your First Move Module
        • Your First Multisig
      • Learn the Move Language
        • The Move Book
          • Getting Started
            • Introduction
            • Modules and Scripts
          • Primitive Types
            • Move Tutorial
            • Integers
            • Bool
            • Address
            • Vector
            • Signer
            • References
            • Tuples and Unit
          • Basic Concepts
            • Local Variables and Scope
            • Equality
            • Abort and Assert
            • Conditionals
            • While, For, and Loop
            • Functions
            • Structs and Resources
            • Constants
            • Generics
            • Abilities
            • Uses and Aliases
            • Friends
            • Packages
            • Package Upgrades
            • Unit Tests
          • Global Storage
            • Global Storage - Structure
            • Global Storage - Operators
          • Reference
            • Libraries
            • Move Coding Conventions
        • Advanced Move Guides
          • Objects
            • Creating Objects
            • Configuring objects
            • Using objects
          • Move Scripts
            • Writing Move Scripts
            • Compiling Move Scripts
            • Running Move Scripts
            • Move Scripts Tutorial
          • Resource Accounts
          • Modules on Endless
          • Cryptography
          • Gas Profiling
          • Security
      • Endless Standards
        • Object
        • Endless Fungible Asset Standard
        • Endless Digital Asset Standard
        • Endless Wallet Standard
      • Endless APIs
        • Fullnode Rest API
        • Indexer Restful API
          • Indexer Installation
        • GRPC Transaction Stream
          • Running Locally
          • Custom Processors
            • End-to-End Tutorial
            • Parsing Transactions
          • Self-Hosted Transaction Stream Service
      • Endless SDKs
        • TypeScript SDK
          • Account
          • SDK Configuration
          • Fetch data from chain
          • Transaction Builder
          • HTTP Client
          • Move Types
          • Testing
          • Typescript
        • Rust SDK
        • Go SDK
      • Endless CLI
        • Install the Endless CLI
          • Install On Mac
          • Install On Alibaba Cloud
          • Install On Linux
          • Install On Windows
        • CLI Configuration
        • Use Endless CLI
          • Working With Move Contracts
            • Arguments in JSON Tutorial
          • Trying Things On-Chain
            • Look Up On-Chain Account Info
            • Create Test Accounts
          • Running A Local Network
            • Running a Public Network
          • Managing a Network Node
      • Integrate with Endless
        • Endless Token Overview
        • Application Integration Guide
      • Endless VSCode extension
      • Advanced Builder Guides
        • Develop Locally
          • Running a Local Network
          • Run a Localnet with Validator
    • Nodes
      • Learn about Nodes
      • Run a Validator and VFN
        • Node Requirements
        • Deploy Nodes
          • Using Docker
          • Using AWS
          • Using Azure
          • Using GCP
        • Connect Nodes
          • Connect to a Network
        • Verify Nodes
          • Node Health
          • Validator Leaderboard
      • Run a Public Fullnode
        • PFN Requirements
        • Deploy a PFN
          • Using Pre-compiled Binary
          • Using Docker
          • Using GCP 🚧 (under_construction)
        • Verify a PFN
        • Modify a PFN
          • Upgrade your PFN
          • Generate a PFN Identity
          • Customize PFN Networks
      • Bootstrap a Node
        • Bootstrap from a Snapshot
        • Bootstrap from a Backup
      • Configure a Node
        • State Synchronization
        • Data Pruning
        • Telemetry
        • Locating Node Files
          • Files For Mainnet
          • Files For Testnet
          • Files For Devnet
      • Monitor a Node
        • Node Inspection Service
        • Important Node Metrics
        • Node Health Checker
    • Reference
      • Endless Error Codes
      • Move Reference Documentation
      • Endless Glossary
    • FAQs
  • Endless Bridge
    • Intro to Endless Bridge
    • How to use bridge
    • Liquidity Management
    • Faucet
    • Developer Integration
      • Contract Integration
        • Message Contract
        • Execute Contract
      • Server-Side Integration
        • Message Sender
        • Example of Message Listener Service (Rust)
        • Example of Token Cross-Chain (JS)
  • Endless Wallet
    • User Guide
    • Basic Tutorial
    • FAQs
    • MultiAccount
    • SDK
      • Functions
      • Events
  • GameFi
    • Intro
    • GameFi & Endless
  • Endless Modules
    • Stacks
    • Storage
    • Module List
  • Endless Ecosystem
    • Intro
    • Show Cases
    • App Demo
  • Whitepaper
  • Endless SCAN
    • User Guide
  • MULTI-SIGNATURE
    • Multi-Signature User Guide
  • Regulations
    • Privacy Policy
    • Terms of Service
    • Funding Terms - Disclaimer
Powered by GitBook
On this page
  • On-Chain K-of-N multisig
  • Step 1: Pick an SDK
  • Step 2: Run the example
  • Process flow
  • Pros and Cons
  • On-Chain Multisig
  • Off-Chain Multisig
Export as PDF
  1. Endless Chain
  2. Tech Docs

On-Chain Multisig

With K-of-N multisig authentication, there are a total of N signers for the account, and at least K of those N signatures must be used to authenticate a transaction.

Endless support two method of MultiSig:

  • On-Chain K-of-N multisig

  • Off-Chain K-of-N multisig

Here we describe the operations the On-Chain K-of-N multisig and compares with Off-Chain at the end.

On-Chain K-of-N multisig

Step 1: Pick an SDK

Install your preferred SDK from the below list:

  • TypeScript SDK


Step 2: Run the example

Clone the endless-ts-sdk repo and build it:

git clone https://github.com/endless-labs/endless-ts-sdk.git
cd endless-ts-sdk
pnpm install
pnpm build

Navigate to the Typescript examples directory:

cd examples/typescript/endless

Install the necessary dependencies:

pnpm install
pnpm run onchain_multisig

Process flow

1

First, we will generate accounts for 4 owner accounts and fund them:

owner1 balance: 1000000000
owner2 balance: 1000000000
owner3 balance: 1000000000
owner4 balance: 1000000000
2

First we invoke view function 0x1::multisig_account::get_next_multisig_account_address to get multisig account address

const payload: InputViewFunctionData = {
function: "0x1::multisig_account::get_next_multisig_account_address",
functionArguments: [owner1.accountAddress.toString()],
};
[multisigAddress] = await endless.view<[string]>({ payload });

The Multisig Account addresss is derived from owner1 account address.

We build a transaction to create a new Multisig Account, via invoke "0x1::multisig_account::create_with_owners()" and append owner2,owner3 the owner of multisig account, set Threshold to 2.

owner1, the signer of transaction of "create multisig account", also is the owner of multisig account.

const createMultisig = await endless.transaction.build.simple({
    sender: owner1.accountAddress,
    data: {
        function: "0x1::multisig_account::create_with_owners",
        functionArguments: [
        [owner2.accountAddress, owner3.accountAddress],
        2,
        ["Example"],
        [new MoveString("SDK").bcsToBytes()],
        ],
    },
});

const owner1Authenticator = endless.transaction.sign({ signer: owner1, transaction: createMultisig });

const res = await endless.transaction.submit.simple({
    senderAuthenticator: owner1Authenticator,
    transaction: createMultisig,
});

output like below:

create_with_owners: https://scan.endless.link/txn/D5jRQVcV8B67UpFjjF74XAXyT7xu6uXbQD9WxJJG7enu
Multisig Account Address: FRPXTpbeaWqALuqLrGMiypiN9MVzmh1NAG7hn4oQTd3y
3

Any operation on multisig account is done via interact with "0x1::multisig_account" move module by transaction, eg:

  • create and submit a new transfer EDS multisig transaction

  • vote Appprove for this transaction

  • vote Reject for this transaction

  • execute the transfer EDS multisig transaction

we create a new EDS transfer transaction, from multisig account to recipient, detailed step as below:

  1. generate transaction payload

  2. invoke "0x1::multisig_account::create_transaction" and pass serialized data of payload as input parameter

  3. any owner sign and submit the transaction

create_transaction upload serialized transaction on Endless chain, and get transactionId(value is 1).

owners could refer transactionId to vote.

const createMultiSigTransferTransaction = async () => {
  console.log("Creating a multisig transaction to transfer coins...");

  transactionPayload = await generateTransactionPayload({
    multisigAddress,
    function: "0x1::endless_account::transfer",
    functionArguments: [recipient.accountAddress, 1_000_000],
    endlessConfig: config,
  });

  // Build create_transaction transaction
  const createMultisigTx = await endless.transaction.build.simple({
    sender: owner2.accountAddress,
    data: {
      function: "0x1::multisig_account::create_transaction",
      functionArguments: [multisigAddress, transactionPayload.multiSig.transaction_payload.bcsToBytes()],
    },
  });

  // Owner 2 signs the transaction
  const createMultisigTxAuthenticator = endless.transaction.sign({ signer: owner2, transaction: createMultisigTx });

  // Submit the transaction to chain
  const createMultisigTxResponse = await endless.transaction.submit.simple({
    senderAuthenticator: createMultisigTxAuthenticator,
    transaction: createMultisigTx,
  });
  await endless.waitForTransaction({ transactionHash: createMultisigTxResponse.hash });

  console.log("create_transaction:", scan(createMultisigTxResponse.hash));
};
4

Threshold of multisig is 2, means any transaction, at least 2 owners vote for "Approve" to make tranaction validate. eg:

  • if 2 owners vote for a transcation, one for "Approve", one for "Reject", the transaction execute successfully.

  • if 3 owners vote for a transcation, two for "Approve", one for "Reject", the transaction execute with failure.

const rejectAndApprove = async (aprroveOwner: Account, rejectOwner: Account, transactionId: number): Promise<void> => {
  const rejectTx = await endless.transaction.build.simple({
    sender: aprroveOwner.accountAddress,
    data: {
      function: "0x1::multisig_account::reject_transaction",
      functionArguments: [multisigAddress, transactionId],
    },
  });

  const rejectSenderAuthenticator = endless.transaction.sign({ signer: aprroveOwner, transaction: rejectTx });
  const rejectTxResponse = await endless.transaction.submit.simple({
    senderAuthenticator: rejectSenderAuthenticator,
    transaction: rejectTx,
  });
  await endless.waitForTransaction({ transactionHash: rejectTxResponse.hash });

  console.log("reject_transaction:", scan(rejectTxResponse.hash));

  const approveTx = await endless.transaction.build.simple({
    sender: rejectOwner.accountAddress,
    data: {
      function: "0x1::multisig_account::approve_transaction",
      functionArguments: [multisigAddress, transactionId],
    },
  });

  const approveSenderAuthenticator = endless.transaction.sign({ signer: rejectOwner, transaction: approveTx });
  const approveTxResponse = await endless.transaction.submit.simple({
    senderAuthenticator: approveSenderAuthenticator,
    transaction: approveTx,
  });
  await endless.waitForTransaction({ transactionHash: approveTxResponse.hash });

  console.log("approve_transaction:", scan(approveTxResponse.hash));
};

simular output as below:

transfer but fail:
Creating a multisig transaction to transfer coins...
create_transaction: https://scan.endless.link/txn/9hamxKBTUEVvKzeA94oxx8aLq62rZJ9AY6qaPaV8WNqL
reject_transaction: https://scan.endless.link/txn/cEpCKXL2SwTiJr6fL1hNQwvTACY1dh5ZGVWSqbLRPMR
approve_transaction: https://scan.endless.link/txn/2YZPh5THyvaazUN7RjYjV3z3kC8GhFWZ6hAYxBrpt4Qf
const executeMultiSigTransferTransaction = async () => {
  const rawTransaction = await generateRawTransaction({
    endlessConfig: config,
    sender: owner2.accountAddress,
    payload: transactionPayload,
  });

  const transaction = new SimpleTransaction(rawTransaction);

  const owner2Authenticator = endless.transaction.sign({ signer: owner2, transaction });
  const transferTransactionReponse = await endless.transaction.submit.simple({
    senderAuthenticator: owner2Authenticator,
    transaction,
  });
  await endless.waitForTransaction({ transactionHash: transferTransactionReponse.hash });

  console.log("executeMultiSigTransferTransaction:", scan(transferTransactionReponse.hash));
};

vote count As shown in the code above, owner1 voted in favor, owner3 voted against.

owner2 executed the transaction, effectively casting a vote in favor.

The vote count is 2 in favor, and this transaction can be executed, for the multi-signature governance module

executeMultiSigTransferTransaction: https://scan.endless.link/txn/DfA8L2PC3zP3WLGTBaXQRMrg39sfqcoDrXTpGzE6zupF
recipient balance: 0

The transfer transaction fails due to multisig account balance is zero.

5

0x1::multisig_account module provide owner management functions as add_owner and remove_owner.

const createAddingAnOwnerToMultiSigAccountTransaction = async () => {
  // Generate a transaction payload as it is one of the input arguments create_transaction expects
  const addOwnerTransactionPayload = await generateTransactionPayload({
    multisigAddress,
    function: "0x1::multisig_account::add_owner",
    functionArguments: [owner4.accountAddress],
    endlessConfig: config,
  });

  // Build create_transaction transaction
  const createAddOwnerTransaction = await endless.transaction.build.simple({
    sender: owner2.accountAddress,
    data: {
      function: "0x1::multisig_account::create_transaction",
      functionArguments: [multisigAddress, addOwnerTransactionPayload.multiSig.transaction_payload.bcsToBytes()],
    },
  });
  // Owner 2 signs the transaction
  const createAddOwnerTxAuthenticator = endless.transaction.sign({
    signer: owner2,
    transaction: createAddOwnerTransaction,
  });
  // Submit the transaction to chain
  const createAddOwnerTxResponse = await endless.transaction.submit.simple({
    senderAuthenticator: createAddOwnerTxAuthenticator,
    transaction: createAddOwnerTransaction,
  });
  await endless.waitForTransaction({ transactionHash: createAddOwnerTxResponse.hash });

  console.log("create_transaction:", scan(createAddOwnerTxResponse.hash));
};

we re-use the same process flow: owner1 voted in favor, owner3 voted against, and owner2 executed the transaction

const executeAddingAnOwnerToMultiSigAccountTransaction = async () => {
  const multisigTxExecution3 = await generateRawTransaction({
    endlessConfig: config,
    sender: owner2.accountAddress,
    payload: new TransactionPayloadMultiSig(new MultiSig(AccountAddress.fromString(multisigAddress))),
  });

  const transaction = new SimpleTransaction(multisigTxExecution3);

  const owner2Authenticator3 = endless.transaction.sign({
    signer: owner2,
    transaction,
  });
  const multisigTxExecution3Reponse = await endless.transaction.submit.simple({
    senderAuthenticator: owner2Authenticator3,
    transaction,
  });
  await endless.waitForTransaction({ transactionHash: multisigTxExecution3Reponse.hash });

  console.log("Execution:", scan(multisigTxExecution3Reponse.hash));
};

when execute multisig transaction, we can set transaction payload as new MultiSig(AccountAddress.fromString(multisigAddress)), only specify the multisig account address in the parameters, instead of set transfer traction payload like Step 2. It works because in Step 6. we already upload the AddingOwnerToMultisigAccount to multisig_account module.

specifing the multisig account address in the parameters, indicates that we are specifying the next transaction in the queue within the multisig_account module to be executed, i.e., the AddingOwnerToMultisigAccount transaction.

output as below:

adding an owner:
create_transaction: https://scan.endless.link/txn/Ear1cyydwSbYm1g7LAXPFJkSzGCgNAVEDoR1wvR3anDD
reject_transaction: https://scan.endless.link/txn/AdwWLqhdmSsQzkHBHYsGySFatdSVPGjzsigYrKRSebL9
approve_transaction: https://scan.endless.link/txn/4mgS4uRf2FBvBeyMATPLkXvW25gGHnEH6HftiASkfcK5
Execution: https://scan.endless.link/txn/B1VbZ3gVxo9n5oBevN4sTQzEnovrEn1SnSCMayWCRP8g
Number of Owners: 4
6

We also can modify multisign threshold. At first we set threshold to 2 when create multisign account. now we set threshold to 3

const createChangeSignatureThresholdTransaction = async () => {
  const changeSigThresholdPayload = await generateTransactionPayload({
    multisigAddress,
    function: "0x1::multisig_account::update_signatures_required",
    functionArguments: [3],
    endlessConfig: config,
  });

  // Build create_transaction transaction
  const changeSigThresholdTx = await endless.transaction.build.simple({
    sender: owner2.accountAddress,
    data: {
      function: "0x1::multisig_account::create_transaction",
      functionArguments: [multisigAddress, changeSigThresholdPayload.multiSig.transaction_payload.bcsToBytes()],
    },
  });

  // Owner 2 signs the transaction
  const changeSigThresholdAuthenticator = endless.transaction.sign({
    signer: owner2,
    transaction: changeSigThresholdTx,
  });
  // Submit the transaction to chain
  const changeSigThresholdResponse = await endless.transaction.submit.simple({
    senderAuthenticator: changeSigThresholdAuthenticator,
    transaction: changeSigThresholdTx,
  });
  await endless.waitForTransaction({ transactionHash: changeSigThresholdResponse.hash });

  console.log("changeSigThreshold:", scan(changeSigThresholdResponse.hash));
};

After re-set threshold, we invoke view function num_signatures_required with paramter of multisigAddress to fetch updated threashold

const getSignatureThreshold = async (): Promise<void> => {
  const multisigAccountResource = await endless.getAccountResource<{ num_signatures_required: number }>({
    accountAddress: multisigAddress,
    resourceType: "0x1::multisig_account::MultisigAccount",
  });
  console.log("Signature Threshold:", multisigAccountResource.num_signatures_required);
};
Threshold:
changeSigThreshold: https://scan.endless.link/txn/CNLtLWBVBSS4wVPtvCiF5NQ4R55HEC883D2KpDqdcsrs
reject_transaction: https://scan.endless.link/txn/C8rqvfJFBRjZxeUypqmX14qgUh8YXKHubh7YJmW3Phmk
approve_transaction: https://scan.endless.link/txn/9BtopY8JLjHnMr1JNsRsjGDa5M6boUiPmtE8jGpQXPQg
changeSigThreshold Execution: https://scan.endless.link/txn/8MUyc1d17YPSGtjyyCyRRyAr5FqcgNnRA8y9VDk8dwhC
Signature Threshold: 3

Multisig setup and transactions complete.

Pros and Cons

On-Chain Multisig

On-Chain multisig implement versatile MultiSig based on Move module, which could expand to support different key schemas in one trasaction, and more expandable features, eg. adding Weight to each account to support Weighted-Threashold multisig.

On-Chain multisig account is created on "0x1::multisig_account" module, and dedicated multisig account. it means we cannot convert any pre-exists account with Ed25519 keypair, into an multisig account.

any k-N multisig transaction, go through K transaction committment, K-1 for seperate approve of each owner, 1 transaction is for any owner execute the transaction. the whole process is gas consumption, compared with Off-Chain multisig.

Off-Chain Multisig

Off-Chain is built on the Endless Account authentication key and off-chain multisig service(Dapp). With well-designed Dapp user interface, users can easily collaborate to sign transactions, while the Dapp handles transaction submission to the Endless network, delivering a smoother operational experience.

Compared to on-chain multisig, off-chain multisig significantly reduces gas consumption.

Due to the structural invariance of Endless Account, off-chain multisig is less scalable and less versatile.

For more details, please refer to the following resources:

PreviousSponsored TransactionNextRandomness

Last updated 4 months ago

Run the example:

: A tutorial demonstrating off-chain multisig.

onchain_multisig
Your-First-Mulitisig
Endless Multisig Dapp