Here we propose the example of simple transaction from default account(ed25519) to any other account
Keypair creation process
Generate keypair
import {crypto} from'web1337';let mnemonic =""; // mnemonic should be empty in case you generate a new pairlet mnemonicPassword ="HelloKlyntar"; // set the password for your mnemoniclet bip44Path = [44,7331,0,0]; // 7331 is KLY ID and the next values - derivation pathlet keypair =awaitcrypto.ed25519.generateDefaultEd25519Keypair(mnemonic,mnemonicPassword,bip44Path);console.log(JSON.parse(keypair));
Output:
{mnemonic:'refuse enroll glance tree laptop hill void lobster oxygen regular salad hour ice knife lazy gap decrease hundred garden waste move hundred kitchen tissue',bip44Path: [ 44, 7331, 0, 0 ],pub:'n2XZqcPUeXjcn9Gt5iAT4aEx8uLTKwThTPNWf8gAgbS',prv:'MC4CAQAwBQYDK2VwBCIEIH7Pttf8d85IAkB0M3c2BcEthzckDnSSq4Gg8NsQI1xj'}
Tricks with BIP-44
By default, you get the new mnemonic and key pair with m/44'/7331'/0'/0' path. But, to generate many accounts from a single seed do this:
Get the mnemonic from the first generation
Change the path to 1,2,3... to build the HD chain of accounts
For example:
Get the first keypair in future chain:
import {crypto} from'web1337';let mnemonic =""; // mnemonic should be empty in case you generate a new pairlet mnemonicPassword ="HelloKlyntar"; // DO NOT USE IN REAL USE CASESlet bip44Path = [44,7331,0,0]; // 7331 is KLY ID and the next values - derivation pathlet keypair =awaitcrypto.ed25519.generateDefaultEd25519Keypair(mnemonic,mnemonicPassword,bip44Path);console.log(JSON.parse(keypair));
For the first pair in future chain of accounts we don't set the mnemonic and BIP-44 path. Mnemonic will be randomly generated and path will be m/44'/7331'/0'/0'. The last parameter is the password that will be used to get the seed from your mnemonic.
That's why - choose the password with the high entropy, ommiting typical passwords from well known wordlists to make brute force impossible. Also, DON'T SHARE YOUR MNEMONIC PHRASE - YOU WILL LOST CONTROL OF YOUR ACCOUNT.
Now, to build the chain, use this snippet:
import {crypto} from'web1337';constfirstKeypairInChain= { mnemonic:'slam lab chief wire inquiry transfer trigger object segment jeans lawn trumpet tuition amount penalty provide frost tortoise display shiver desert vintage erase vague', bip44Path: [ 44,7331,0,0 ], pub:'E64Sp6NQsHHYhujdrTvGj8dkkhu9ZiDubddm7TW6NmYP', prv:'MC4CAQAwBQYDK2VwBCIEIMWqtM5c1FAUA4wtb26RUlAUqNYTEE7ALLcw6KvJELrJ'};console.log('0 in chain => ',JSON.parse(awaitcrypto.ed25519.generateDefaultEd25519Keypair(firstKeypairInChain.mnemonic,'HelloKlyntar',[44,7331,0,0])));console.log('1 in chain => ',JSON.parse(awaitcrypto.ed25519.generateDefaultEd25519Keypair(firstKeypairInChain.mnemonic,'HelloKlyntar',[44,7331,0,1])));console.log('2 in chain => ',JSON.parse(awaitcrypto.ed25519.generateDefaultEd25519Keypair(firstKeypairInChain.mnemonic,'HelloKlyntar',[44,7331,0,2])));
Example of simplest transaction - from default account to default account
let web1337 =newWeb1337({ chainID:'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', workflowVersion:0, nodeURL:'http://localhost:7332'}); // need node endpoint to return the correct nonce. If you know your nonce - you can omit itconstkeypair= { pub:"9GQ46rqY238rk2neSwgidap9ww5zbAN4dyqyC7j5ZnBK", prv:"MC4CAQAwBQYDK2VwBCIEILdhTMVYFz2GP8+uKUA+1FnZTEdN8eHFzbb8400cpEU9",};constshardID="shard_0";constpayload= { to:"Ai1Pk9RzmgeiSAptc1W69NAmSB52GqjNqGdGQGgqxQA1", amount:13.37, shard: shardID};constfee=0.03;constnonce=awaitweb1337.getAccount(shardID,keypair.pub).then(account=>account.nonce+1);consttxType="TX";let tx =web1337.createEd25519Transaction(shardID,txType,keypair.pub,keypair.prv,nonce,fee,payload);console.log(tx);
let sendStatus =awaitweb1337.sendTransaction(tx);console.log(sendStatus);// After that - you can check the tx receipt// TxID - it's a BLAKE3 hash of transaction signaturelet receipt =awaitweb1337.getTransactionReceiptById(web1337.BLAKE3(tx.sig));console.log(receipt);
shard - context of transaction. Account of sender and recipient also binded to this shard
blockID - the block where tx is
order - position of this tx in block
isOk - status to check if tx successfully processed or not
Optional fields:
reason - in case tx failed you can use this reason to understand why
createdContractAddress - only in txs where contract was deployed. It might be WASM or EVM contract
extraDataToReceipt - optional object with extra data (for example - result of contract call)
Check explorer:
If you check this tx in explorer you should see:
Ed25519 => BLS(multisig address) transaction
A transfer transaction to a multisig address is literally 1 step more complicated. So, when you are going to send something to a multisig address, depending on whether the recipient's account already exists on the network, you need to specify an additional rev_t field that indicates the reverse threshold.
What is reverse threshold?
Imagine that you and your 3 friends are going to manage some resources together, whether it be native KLY coins or some tokens. For this, it is obviously worth using a multisig address.
Let's assume that you agree that the decision is considered accepted if the voting threshold of 3/4 is reached (3 out of 4 friends agree to spend coins or call some kind of smart contract). So, if the threshold is 3/4, then the reverse threshold in this case will be 1 (because 4-3 = 1).
Here are some examples for other cases:
Reverse threshold = 3 for a situation where you need 7/10 agreements
Reverse threshold = 2 for a situation where you need 3/5 agreements
And so on
The reverse threshold was introduced in response to the ability of BLS signatures and public keys to aggregation. Since the situation is often such that
T>T−N
where:
N is the number of sides of the multi-signature
T is the threshold
and when checking the signature we need to know whether the threshold has been reached, then we need to do this:
Present the consenting parties as an aggregated public key and an aggregated BLS signature
In a separate array, present the public BLS keys of those who do not agree with the decision (or could not vote for some reason)
Going back to the 4 friends example, if we have a threshold of 3/4, then it makes more sense to aggregate 3 signatures and 3 public keys into 1 and separately present 1 public key of the one who disagrees than to provide 3 separate keys and signatures from 4.
Let's look at a specific example
You and 3 your friends generate multisig pairs(public + private key) locally. Let's do it with Web1337:
When you need to send something to multisig account you need to set the reverse threshold if account still not exists or use rev_t property of already existed account
So, if account 0xb89c4bf0b9dab0224201d06d46ed6cb49b94f34f8dc8feb0d7bad77caab5b41fc16531dce9ba2cba5784359d2b701cc4 still not in state - use this template:
let web1337 =newWeb1337({ chainID:'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', workflowVersion:0, nodeURL:'http://localhost:7332'}); // need node endpoint to return the correct nonce. If you know your nonce - you can omit itconstkeypair= { pub:"9GQ46rqY238rk2neSwgidap9ww5zbAN4dyqyC7j5ZnBK", prv:"MC4CAQAwBQYDK2VwBCIEILdhTMVYFz2GP8+uKUA+1FnZTEdN8eHFzbb8400cpEU9",};// In our example with 4 friends, since we want 3/4 agreements// to use account, the reverse threshold will be 4-3=1// Use the formula rev_t = N-T where N - number of sides, T-thresholdconstreverseThreshold=1;constshardID="shard_0";constpayload= { to:"0xb89c4bf0b9dab0224201d06d46ed6cb49b94f34f8dc8feb0d7bad77caab5b41fc16531dce9ba2cba5784359d2b701cc4", amount:13.37, shard: shardID, rev_t: reverseThreshold};constfee=0.03;constnonce=awaitweb1337.getAccount(shardID,keypair.pub).then(account=>account.nonce+1);consttxType="TX";let tx =web1337.createEd25519Transaction(shardID,txType,keypair.pub,keypair.prv,nonce,fee,payload);console.log(tx);
As you see, new multisig account is created and binded to shard where sender sends KLY. Also, the rev_t is set to 1 what means that the number of dissenting sides can be 1.
In case account was already in state - get the rev_t from information about account:
And then, use the value of rev_t to build the transaction as above
Ed25519 => TBLS(thresholdsig address) transaction
In this transaction you send something to TBLS root public key which controled by group of N members
We'll talk more about TBLS in the next parts. Just now you need to know the 48-byte root public key
let web1337 =newWeb1337({ chainID:'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', workflowVersion:0, nodeURL:'http://localhost:7332'}); // need node endpoint to return the correct nonce. If you know your nonce - you can omit itconstkeypair= { pub:"9GQ46rqY238rk2neSwgidap9ww5zbAN4dyqyC7j5ZnBK", prv:"MC4CAQAwBQYDK2VwBCIEILdhTMVYFz2GP8+uKUA+1FnZTEdN8eHFzbb8400cpEU9",};constshardID="shard_0";constpayload= { to:"b89c4bf0b9dab0224201d06d46ed6cb49b94f34f8dc8feb0d7bad77caab5b41fc16531dce9ba2cba5784359d2b701cc4", amount:13.37, shard:shardID};constfee=0.03;constnonce=awaitweb1337.getAccount(shardID,keypair.pub).then(account=>account.nonce+1);consttxType="TX";let tx =web1337.createEd25519Transaction(shardID,txType,keypair.pub,keypair.prv,nonce,fee,payload);console.log(tx);
In this transaction you send your assets to the BLAKE3 hash of public key of some post-quantum signatures schemes like DIlithium or BLISS (we support 2 algorithms)
NOW ATTENTION:
In case you fund a NEW post-quantum account you need to add the post-quantum pubkey of recipient to payload
By default, you recipient have locally keypair like this:
let blissKeyPair = { pub: '001b4609d500e31a0a188911900aac07fb06f91566038104e90c01031707d6154701701a15046d07f5089f0c730c8515e712c90b5a130d10081bca0ab40c8f0027101501870ccb17041d691bac0c30162d11ff0566198710f308cd08b30be804261a040c530cb8042e16841623069200b9175410a5016a171e1ceb10f813261bae0acc06be176214471d7013530d92180a0dbd15c800fd09f700ed0a8616141a14095b08a71c3317031d78106602ef1c1f1a53097016df192905b50ad40b5c1d1c027e026b0ecb115417ae1b6f1c1101c60d3f1c12016010a309f8183411840d7d12d414071a5b10d1162111f712951b36066209500e1d137d1dbf055417e6075c0ce307460ff9040715b51d0000cc11cf1bd1194c0a2d19e901191c5306040c8002be0d19024f10b31b19152912fe06900de21b2e10110ab111f80b6403ac1b8505221bac09830e3501a1175705c7138e1db6035c09871c4c121706b70b560ac70c001d2305b0107117ef02c1178a13f010bb193004ca02bc035e036f109419770a2017f11dd00cb3016405b41604091206c61603085208fa0df0130912cb14cd187914b009e306440a3018ca0c5810c305400507103b1113016c0ead00100e3f02b6003410981cdf04c50d0213d61984110c0ba700ae0c8912f618a01a231bc81066010a1d051242103013ac05c30dae14030f890e1117b319a002a707f30923',
prv:'d112525a9435c29d732592e9ec90eba2ae7b1ae2c0d3ac9b6d6ce662ca5140abb02bc846c7f54f955a84fed543107c9180366d025324aac2d253ec60515cf9ee', address:'1826d3782d53b127c53129fe67f4a3e3c1140feb2af36a0517077297a6e867e5'};
Case 1: His account already in state and has balance
In case recipient account already exists, just ask him for address.
In this case your payload of transaction will look like this:
let payload = { to:"1826d3782d53b127c53129fe67f4a3e3c1140feb2af36a0517077297a6e867e5", amount:13.37, shard:shardID}
Case 2: Recipient account will be new and didn't exists before tx
So, if you fund new account the payload of your transaction should be:
let payload = { to:"1826d3782d53b127c53129fe67f4a3e3c1140feb2af36a0517077297a6e867e5", amount:13.37, shard:shardID, pqcPub:"001b4609d500e31a0a188911900aac07fb06f91566038104e90c01031707d6154701701a15046d07f5089f0c730c8515e712c90b5a130d10081bca0ab40c8f0027101501870ccb17041d691bac0c30162d11ff0566198710f308cd08b30be804261a040c530cb8042e16841623069200b9175410a5016a171e1ceb10f813261bae0acc06be176214471d7013530d92180a0dbd15c800fd09f700ed0a8616141a14095b08a71c3317031d78106602ef1c1f1a53097016df192905b50ad40b5c1d1c027e026b0ecb115417ae1b6f1c1101c60d3f1c12016010a309f8183411840d7d12d414071a5b10d1162111f712951b36066209500e1d137d1dbf055417e6075c0ce307460ff9040715b51d0000cc11cf1bd1194c0a2d19e901191c5306040c8002be0d19024f10b31b19152912fe06900de21b2e10110ab111f80b6403ac1b8505221bac09830e3501a1175705c7138e1db6035c09871c4c121706b70b560ac70c001d2305b0107117ef02c1178a13f010bb193004ca02bc035e036f109419770a2017f11dd00cb3016405b41604091206c61603085208fa0df0130912cb14cd187914b009e306440a3018ca0c5810c305400507103b1113016c0ead00100e3f02b6003410981cdf04c50d0213d61984110c0ba700ae0c8912f618a01a231bc81066010a1d051242103013ac05c30dae14030f890e1117b319a002a707f30923"
}
In case PQC account of recipient do not exist - include additional field pqcPub to payload to let the network to create pubkey:address pair in state
We'll talk about PQC accounts later. Just now, as a sender you just need to know only the address of recipient - it's 256-bit BLAKE3 hash of public key
let web1337 =newWeb1337({ chainID:'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', workflowVersion:0, nodeURL:'http://localhost:7332'}); // need node endpoint to return the correct nonce. If you know your nonce - you can omit itconstkeypair= { pub:"9GQ46rqY238rk2neSwgidap9ww5zbAN4dyqyC7j5ZnBK", prv:"MC4CAQAwBQYDK2VwBCIEILdhTMVYFz2GP8+uKUA+1FnZTEdN8eHFzbb8400cpEU9",};constshardID="shard_0";constpayload= { to:"4218fb0aaace62c4bfafbdd9adb05b99a9bf1a33eeae074215a51cb644b9a85c", amount:13.37, shard:shardID};constfee=0.03;constnonce=awaitweb1337.getAccount(shardID,keypair.pub).then(account=>account.nonce+1);consttxType="TX";let tx =web1337.createEd25519Transaction(shardID,txType,keypair.pub,keypair.prv,nonce,fee,payload);console.log(tx);