Your First Multisig

Your First Multisig

This tutorial introduces assorted K-of-N multi-signer authentication operations and supplements content from the following tutorials:

  • Your First Transaction

  • Your First Coin

  • Your First Move Module

Try out the above tutorials (which include dependency installations) before moving on to multisig operations.

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/endless

Install the necessary dependencies:

pnpm install

Run the binding example:

pnpm run bindings

Step 3: Generate accounts and fund them

First, we will generate accounts for Alice, Bob, and Chad and fund them:

Fresh accounts are generated for each example run, but the output should resemble:

Fund alice: version 633280
Fund bob: version 633291

=== Account addresses ===
Alice: 0x91c381ec582ee96f96841a8f71b13a9feea83f52441e15a9e8b9d2bcf2ebbbc9
Bob:   0xd92dffdee6345ed5a0b3e3fe72b972dd90ba200bb478693dba4288998bc8343d
Chad:  0x820a4393e98c207b920a098c251c72435b5830b5b938bdb1aee77f83713d359c

=== Authentication keys ===
Alice: 0x91c381ec582ee96f96841a8f71b13a9feea83f52441e15a9e8b9d2bcf2ebbbc9
Bob:   0xd92dffdee6345ed5a0b3e3fe72b972dd90ba200bb478693dba4288998bc8343d
Chad:  0x820a4393e98c207b920a098c251c72435b5830b5b938bdb1aee77f83713d359c

For each user, at this moment, the account address and authentication key are identical.

Step 4: Add Bob's authentication key into Alice's authentication key(list)

Next, Alice adds Bob's authentication key to her list of authentication keys. This involves submitting a transaction that invokes the `add_authentication_key` function within the account module of the system contract. Both Alice and Bob must sign this transaction.

let txn = await endless.transaction.build.multiAgent({
    sender: alice.accountAddress,
    data: {
        function: "0x1::account::add_authentication_key",
        functionArguments: []
    },
    secondarySignerAddresses: [bob.accountAddress],
})

let aliceAuth = alice.signTransactionWithAuthenticator(txn)
let bobAuth = bob.signTransactionWithAuthenticator(txn)
pending = await endless.transaction.submit.multiAgent({
    transaction: txn,
    senderAuthenticator: aliceAuth,
    additionalSignersAuthenticators: [bobAuth],
})
tx_response = await endless.waitForTransaction({ transactionHash: pending.hash })

console.log(`\nAdd Bob's authkey into Alice authkey list: version ${tx_response.version}`)

Step 5: Alice's authentication key (contains Bob's authentication key)

After add authentication key transaction, bob's authentication key is added into Alice's authentication

Alice authentication_key:
0x91c381ec582ee96f96841a8f71b13a9feea83f52441e15a9e8b9d2bcf2ebbbc9,
0xd92dffdee6345ed5a0b3e3fe72b972dd90ba200bb478693dba4288998bc8343d

Bob controlled accounts:
0x91c381ec582ee96f96841a8f71b13a9feea83f52441e15a9e8b9d2bcf2ebbbc9

Step 6: Send coins from Alice to Chad, signed by Bob

We now build an EDS transfer transaction, sender is Alice's account, receiver is Chad, transaction is signed by Bob.

let transferEDSRawTransaction = await endless.transaction.build.simple({
    sender: alice.accountAddress,
    data: {
        function: "0x1::endless_account::transfer",
        functionArguments: [chad.accountAddress, 1000]
    },
})

let multiAuthKeyAccount = new MultiAuthKeyAccount({ sender: alice.accountAddress, signers: [bob] })

pending = await endless.signAndSubmitTransaction({
    transaction: transferEDSRawTransaction,
    signer: multiAuthKeyAccount,
})
tx_response = await endless.waitForTransaction({ transactionHash: pending.hash })

console.log(`\ntransfer EDS from Alice to Chad with Bob auth: version ${tx_response.version}`)

Step 7: remove Bob's authentication key from Alice's authentication key list

We invoke 0x1::account::remove_authentication_key to remove Bob's authenticaion key, restore Alice's authentication key as default. This transaction also must be signed by both Alice and Bob.

txn = await endless.transaction.build.simple({
    sender: alice.accountAddress,
    data: {
        function: "0x1::account::remove_authentication_key",
        functionArguments: [bob.accountAddress.data]
    },
})

aliceAuth = alice.signTransactionWithAuthenticator(txn)
pending = await endless.transaction.submit.simple({
    transaction: txn,
    senderAuthenticator: aliceAuth,
})
tx_response = await endless.waitForTransaction({ transactionHash: pending.hash })

console.log(`\nRemove Bob from Alice's auth key : version ${tx_response.version}`)

Step 8: Check if Alice's authentication key is restored to default

Alice authentication_key:
0x91c381ec582ee96f96841a8f71b13a9feea83f52441e15a9e8b9d2bcf2ebbbc9

Bob controlled accounts:
None

Step 9: Batch add Bob&Chad authkey into Alice's authentication key list, but set Threashold to 2

By adding Bob and Chad auth key to Alice's auth key list, either Bob or Chad can sign transaction on Alice behalf. A required signature count of 2 (Threshold = 2) is enforced for transactions originating from Alice. Verification will fail if fewer than two signatures are present (e.g., only Alice's signature, or only one of Bob's or Chad's)

txn = await endless.transaction.build.multiAgent({
    sender: alice.accountAddress,
    data: {
        function: "0x1::account::batch_add_authentication_key",
        functionArguments: [2]
    },
    secondarySignerAddresses: [bob.accountAddress, chad.accountAddress],
})

aliceAuth = alice.signTransactionWithAuthenticator(txn)
bobAuth = bob.signTransactionWithAuthenticator(txn)
let chadAuth = chad.signTransactionWithAuthenticator(txn)
pending = await endless.transaction.submit.multiAgent({
    transaction: txn,
    senderAuthenticator: aliceAuth,
    additionalSignersAuthenticators: [bobAuth, chadAuth],
})
tx_response = await endless.waitForTransaction({ transactionHash: pending.hash })

console.log(`\nBatch Add auth key, set "AuthThreadhold" to 2: version ${tx_response.version}`)

Step 10: transfer transaction, signed only by Bob, will fail

transferEDSRawTransaction = await endless.transaction.build.simple({
    sender: alice.accountAddress,
    data: {
        function: "0x1::endless_account::transfer",
        functionArguments: [chad.accountAddress, 1000]
    },
})
multiAuthKeyAccount = new MultiAuthKeyAccount({ sender: alice.accountAddress, signers: [bob] })

try {
    pending = await endless.signAndSubmitTransaction({
    transaction: transferEDSRawTransaction,
    signer: multiAuthKeyAccount,
    })
    await endless.waitForTransaction({ transactionHash: pending.hash });
} catch (error) {
    console.log("\nFailed to transfer EDS from Alice to Chad with only by Bob auth, cause `Auth Threshold` is 2")
}

Step 11: transfer transaction, signed by both Bob and Chad, will success

multiAuthKeyAccount = new MultiAuthKeyAccount({ sender: alice.accountAddress, signers: [bob, alice] })
pending = await endless.signAndSubmitTransaction({
    transaction: transferEDSRawTransaction,
    signer: multiAuthKeyAccount,
})
tx_response = await endless.waitForTransaction({ transactionHash: pending.hash })
console.log(`\ntransfer EDS from Alice to Chad with Alice and Bob auth, version ${tx_response.version}`)

The above examples showcase the Endless multi-signature feature, demonstrating how to manage Authentication Keys and generate transaction signatures. This multi-signature implementation, combined with Keyless functionality, offers an emergency escape route.

For example, if a Keyless account (A) is locked due to Web2 service issues, and its authentication key list includes another wallet (AA), then AA can rescue the funds by initiating a transaction on A's behalf, avoiding assets locked.

Last updated