-
-
Save christroutner/ac8810146ee3664c4ee8d6cb8bd66afe to your computer and use it in GitHub Desktop.
const createHash = require('create-hash') | |
const { Avalanche, BinTools, BN } = require('avalanche') | |
const { KeyChain } = require('avalanche/dist/apis/evm') | |
const avm = require('avalanche/dist/apis/avm') | |
const { Signature } = require('avalanche/dist/common/credentials') | |
const ava = new Avalanche('api.avax.network', 443, 'https') | |
const bintools = BinTools.getInstance() | |
const xchain = ava.XChain() | |
const aliceWallet = { | |
address: 'X-avax10ps8jjqmd3s29wuqa7fanpwk9g63yjxdnmawqx', | |
privateKey: 'PrivateKey-hzkJjZ3vh23cMEX7xbKMVSQuZVsehdRnZxyrz1CYNpbVFvdUv', | |
utxos: [ | |
{ | |
txid: '2ns8XVRdy8TRVJJaa9BTNTu2AvpdGweQ3vXfq3WnJVzApbXCH2', | |
outputIdx: '00000001', | |
amount: 100, | |
assetID: '2jgTFB6MM4vwLzUNWFYGPfyeQfpLaEqj4XWku6FoW7vaGrrEd5', | |
typeID: 7 | |
} | |
] | |
} | |
const bobWallet = { | |
address: 'X-avax1wcjw6t2kqafservk445awwyufjqze29y7j33m9', | |
privateKey: 'PrivateKey-GkhJmNAkKqH6us3neA7hCESexVzUPCovKCGFjwpaZsj3LTuGA', | |
utxos: [ | |
{ | |
txid: 'qRTFJsBdBBk5PZatmbXMwKvDGUQAxqLi8jRGXVwqVe8dCqTbW', | |
outputIdx: '00000000', | |
amount: 21000000, | |
assetID: 'FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z', | |
typeID: 7 | |
} | |
] | |
} | |
const addrReferences = {} | |
const parseUtxo = (utxoJSON, address) => { | |
const amount = new BN(utxoJSON.amount) | |
const tokenTransferInput = new avm.SECPTransferInput(amount) | |
tokenTransferInput.addSignatureIdx(0, address) | |
const tokenTxInput = new avm.TransferableInput( | |
bintools.cb58Decode(utxoJSON.txid), | |
Buffer.from(utxoJSON.outputIdx, 'hex'), | |
bintools.cb58Decode(utxoJSON.assetID), | |
tokenTransferInput | |
) | |
return tokenTxInput | |
} | |
const generateOutput = (amount, address, assetID) => { | |
const tokenTransferOutput = new avm.SECPTransferOutput( | |
amount, | |
[address] | |
) | |
return new avm.TransferableOutput( | |
assetID, | |
tokenTransferOutput | |
) | |
} | |
/** | |
* This method assumes that all the utxos have only one associated address | |
* @param {avm.UnsignedTx} tx | |
* @param {KeyChain} keychain | |
* @param {Credential} credentials | |
* @param {Object} reference | |
*/ | |
const partialySignTx = (tx, keychain, credentials = [], reference = {}) => { | |
const txBuffer = tx.toBuffer() | |
const msg = Buffer.from( | |
createHash('sha256').update(txBuffer).digest() | |
) | |
const ins = tx.getTransaction().getIns() | |
for (let i = 0; i < ins.length; i++) { | |
const input = ins[i] | |
const cred = avm.SelectCredentialClass(input.getInput().getCredentialID()) | |
const inputid = bintools.cb58Encode(input.getOutputIdx()) | |
try { | |
const source = xchain.parseAddress(reference[inputid]) | |
const keypair = keychain.getKey(source) | |
const signval = keypair.sign(msg) | |
const sig = new Signature() | |
sig.fromBuffer(signval) | |
cred.addSignature(sig) | |
console.log(`Successfully signed input ${i}, ( ${inputid} signed with ${reference[inputid]} )`) | |
credentials[i] = cred | |
} catch (error) { | |
console.log(`Skipping input ${i}: ${error.message}, ( ${inputid})`) | |
} | |
} | |
console.log(' ') | |
return new avm.Tx(tx, credentials) | |
} | |
// Generates the ANT transaction, ready to broadcast to network. | |
const colaborate = async () => { | |
try { | |
const avaxID = await xchain.getAVAXAssetID() | |
// First side of the transaction - Alice first time | |
// Alice creates an *unsigned* transaction, which she passes to Bob: | |
// - 1 input: the token as an input | |
// - 1 output: 0.1 AVAX to her address | |
const aliceAddressBuffer = xchain.parseAddress(aliceWallet.address) | |
const bobAddressBuffer = xchain.parseAddress(bobWallet.address) | |
let [tokenInput] = aliceWallet.utxos | |
tokenInput = parseUtxo(tokenInput, aliceAddressBuffer) | |
const initialInputs = [tokenInput] | |
const tokenInputId = bintools.cb58Encode(tokenInput.getOutputIdx()) | |
addrReferences[tokenInputId] = aliceWallet.address | |
// get the desired avax outputs for the transaction | |
const avaxToReceive = new BN(0.1) | |
const avaxOutput = generateOutput(avaxToReceive, aliceAddressBuffer, avaxID) | |
const initialOutputs = [avaxOutput] | |
// Build the transcation | |
const partialTx = new avm.BaseTx( | |
ava.getNetworkID(), | |
bintools.cb58Decode(xchain.getBlockchainID()), | |
initialOutputs, | |
initialInputs, | |
Buffer.from('from alice') | |
) | |
// This is what Alice has to send and what Bob will receive | |
const hexString = partialTx.toBuffer() | |
// Second side of the transaction - Bob first time | |
// Bob adds to the transaction before passing it back to Alice: | |
// - 1 input of 0.1 AVAX (plus tx fees), which he signs. | |
// - 1 output of the token, going to his address. | |
// Parse back the transaction from base58 to an object | |
const docodedTx = new avm.BaseTx() | |
docodedTx.fromBuffer(hexString) | |
const finalInputs = docodedTx.getIns() | |
const finalOutputs = docodedTx.getOuts() | |
let [avaxInput] = bobWallet.utxos | |
avaxInput = parseUtxo(avaxInput, bobAddressBuffer) | |
finalInputs.push(avaxInput) | |
const avaxInputId = bintools.cb58Encode(avaxInput.getOutputIdx()) | |
addrReferences[avaxInputId] = bobWallet.address | |
// get the desired token outputs for the transaction | |
const tokensToReceive = new BN(tokenInput.amount) | |
const tokenOutput = generateOutput(tokensToReceive, bobAddressBuffer, avaxID) | |
finalOutputs.push(tokenOutput) | |
// Build the partially signed transcation | |
const wholeTx = new avm.BaseTx( | |
ava.getNetworkID(), | |
bintools.cb58Decode(xchain.getBlockchainID()), | |
finalOutputs, | |
finalInputs, | |
Buffer.from('from bob') | |
) | |
const unsignedTxBob = new avm.UnsignedTx(wholeTx) | |
// Sign bob inputs with his keychain | |
const bobKeyChain = new KeyChain(ava.getHRP(), 'X') | |
bobKeyChain.importKey(bobWallet.privateKey) | |
const signedByBob = partialySignTx(unsignedTxBob, bobKeyChain, [], addrReferences) | |
// Bob sends back the tx with his inputs signed | |
const signedByBobString = bintools.cb58Encode(signedByBob.toBuffer()) | |
// Finally, Alice checks the transaction, before she adds her signature to her input, and then broadcasts the transaction. | |
const partiallySigned = new avm.Tx() | |
partiallySigned.fromBuffer(bintools.cb58Decode(signedByBobString)) | |
// Sign Alice inputs with her keychain, and the previous credentials | |
const aliceKeyChain = new KeyChain(ava.getHRP(), 'X') | |
aliceKeyChain.importKey(aliceWallet.privateKey) | |
const previousCredentials = partiallySigned.getCredentials() | |
const unsignedTxAlice = partiallySigned.getUnsignedTx() | |
const signedByAlice = partialySignTx(unsignedTxAlice, aliceKeyChain, previousCredentials, addrReferences) | |
// this is the fully signed transaction that must be broadcasted | |
return signedByAlice | |
} catch (err) { | |
console.log('Error in send-token.js/sendTokens()') | |
throw err | |
} | |
} | |
colaborate() |
8/23/21 CT: My next steps are study this code by executing each line and analyzing the objects. I will then be able to compare this code to the collaborative transaction examples for BCH and see if the two use cases in those examples translate to AVAX.
Once that is complete, I can move forward with plans to create a trustless, atomic DEX for the AVAX x-chain, as well as an escrow system for buying and selling general goods and services.
The code above was a result of this task:
This is a research task. The deliverable is an example script that shows how to build a collaborative transaction.
A collaborative transaction is one where two or more parties collaborate to build the transaction, by passing partially signed transaction. Here is an example:
Alice and Bob want to trade an (ANT) token for some AVAX. Alice has the token, Bob has the AVAX.
Alice creates an unsigned transaction, which she passes to Bob:
1 input: the token as an input
1 output: 0.1 AVAX to her address
Bob adds to the transaction before passing it back to Alice:
1 input of 0.1 AVAX (plus tx fees), which he signs.
1 output of the token, going to his address.
Alice checks the transaction, before she adds her signature to her input, and then broadcasts the transaction.
In the above example, there are two key pairs, which represent Alice and Bob. They collaborate to form the transaction by passing the transaction data (encoded as hex) back and forth.
The goal of this task is to see if such a transaction can be created. I'm not sure if it can be. You may need to reach out to Gabriel Cardona and the other Avalanche devs for help on their Discord channel. Give me frequent updates on your progress, so that I can help you hunt for the answers.
From MezzMar