Skip to content

Instantly share code, notes, and snippets.

@yesil
Forked from christroutner/avax-colaborative-tx.js
Created August 23, 2021 18:55
Show Gist options
  • Save yesil/d06769c9f5bb4de18d1b840722d79553 to your computer and use it in GitHub Desktop.
Save yesil/d06769c9f5bb4de18d1b840722d79553 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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment