# 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 <a href="#step-1-pick-an-sdk" id="step-1-pick-an-sdk"></a>

Install your preferred SDK from the below list:

* TypeScript SDK

***

### Step 2: Run the example <a href="#step-2-run-the-example" id="step-2-run-the-example"></a>

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

```shell
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:

```shell
cd examples/typescript/endless
```

Install the necessary dependencies:

```shell
pnpm install
```

Run the [`onchain_multisig`](https://github.com/endless-labs/endless-ts-sdk/blob/main/examples/typescript/onchain_multisig.ts) example:

```shell
pnpm run onchain_multisig
```

### Process flow

{% stepper %}
{% step %}
First, we will generate accounts for 4 owner accounts and fund them:

```bash
owner1 balance: 1000000000
owner2 balance: 1000000000
owner3 balance: 1000000000
owner4 balance: 1000000000
```

{% endstep %}

{% step %}
First we invoke view function `0x1::multisig_account::get_next_multisig_account_address` to get `multisig account` address

```js
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.

{% hint style="info" %}
`owner1`, the signer of transaction of "create multisig account", also is the owner of `multisig account`.
{% endhint %}

```js
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:

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

{% endstep %}

{% step %}
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

{% hint style="info" %}
`create_transaction` upload serialized transaction on Endless chain, and get `transactionId`(value is 1).

owners could refer `transactionId` to vote.
{% endhint %}

```js
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));
};
```

{% endstep %}

{% step %}
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.

```js
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:

```shell
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
```

```js
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

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

The transfer transaction fails due to multisig account balance is zero.
{% endstep %}

{% step %}
`0x1::multisig_account` module provide owner management functions as `add_owner` and `remove_owner`.

```js
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

```js
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));
};
```

{% hint style="info" %}
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.
{% endhint %}

output as below:

```bash
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
```

{% endstep %}

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

```js
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

```js
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);
};
```

```bash
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.
```

{% endstep %}
{% endstepper %}

## 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:

* [Your-First-Mulitisig](https://docs.endless.link/endless/devbuild/build/tutorials/your-first-multisig): A tutorial demonstrating off-chain multisig.
* [Endless Multisig Dapp](https://signature.endless.link/)
