Basic - Signing with a multi-sig account
This tutorial aims to show how to sign with a multi-sig account. This will be done by sending some assets from a multi-sig account.
A multi-sig Account can be identified by its verification script. It uses the
CHECKMULTISIG
OpCode (ae
in hexstring). Thus, if you see any verification
scripts ending with ae
, it is likely to be a multi-sig Account.
Setup
Here I will assume that you have already sent some assets over to the multi-sig account.
const { default: Neon, api, wallet, tx, rpc } = require("@cityofzion/neon-js");
const neoscan = new api.neoscan.instance(
"https://neoscan-testnet.io/api/main_net"
);
const rpcNodeUrl = "http://seed2.neo.org:20332";
Our multi-sig account in this example is made up of 3 keys with a signing threshold of 2. Do note that the order of keys in the array matters. A different order will generate a totally different address.
const keyA = new wallet.Account(
"7d128a6d096f0c14c3a25a2b0c41cf79661bfcb4a8cc95aaaea28bde4d732344"
);
const keyB = new wallet.Account(
"9ab7e154840daca3a2efadaf0df93cd3a5b51768c632f5433f86909d9b994a69"
);
const keyC = new wallet.Account(
"3edee7036b8fd9cef91de47386b191dd76db2888a553e7736bb02808932a915b"
);
const multisigAcct = wallet.Account.createMultiSig(2, [
keyA.publicKey,
keyB.publicKey,
keyC.publicKey,
]);
console.log("\n\n--- Multi-sig ---");
console.log(`My multi-sig address is ${multisigAcct.address}`);
console.log(
`My multi-sig verificationScript is ${multisigAcct.contract.script}`
);
Construct Transaction
Similar to how we setup a transaction for a normal account transfer, we also do the same for our transfer from a multi-sig account.
var constructTx = neoscan.getBalance(multisigAcct.address).then((balance) => {
const transaction = Neon.create
.contractTx()
.addIntent("NEO", 1, keyA.address)
.addIntent("GAS", 0.00000001, keyB.address)
.calculate(balance);
return transaction;
});
Sign Transaction
The only difference is in the signing of transactions. We need to sign the transaction individually by each key first. Then, we combine the signatures together to form a multi-sig witness. We should only see 1 witness attached to the transaction.
const signTx = constructTx.then((transaction) => {
const txHex = transaction.serialize(false);
// This can be any 2 out of the 3 keys.
const sig1 = wallet.sign(txHex, keyB.privateKey);
const sig2 = wallet.sign(txHex, keyC.privateKey);
const multiSigWitness = tx.Witness.buildMultiSig(
txHex,
[sig1, sig2],
multisigAcct
);
transaction.addWitness(multiSigWitness);
console.log("\n\n--- Transaction ---");
console.log(JSON.stringify(transaction.export(), undefined, 2));
console.log("\n\n--- Transaction hash---");
console.log(transaction.hash);
console.log("\n\n--- Transaction string ---");
console.log(transaction.serialize(true));
return transaction;
});
Send Transaction
We send off the transaction using sendrawtransaction RPC call like any other normal transaction.
const sendTx = signTx
.then((transaction) => {
const client = new rpc.RPCClient(rpcNodeUrl);
return client.sendRawTransaction(transaction.serialize(true));
})
.then((res) => {
console.log("\n\n--- Response ---");
console.log(res);
})
.catch((err) => console.log(err));