Skip to content
This repository has been archived by the owner on Apr 6, 2020. It is now read-only.

Commit

Permalink
Add transfer of NEP5 tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
ixje committed Dec 11, 2017
1 parent f9f78e5 commit 1e59217
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 7 deletions.
8 changes: 4 additions & 4 deletions app/api/crypto/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ export function buildClaimTransactionData(claims, amountToRecipient, publicKeyEn
* @param {Buffer} accountAddressHash - own account address hash
* @param {Buffer} destinationAddressHash - recipient address hash
*/
function buildOutputDataFrom(amount, totalValueOfInputs, assetId, accountAddressHash, destinationAddressHash) {
export function buildOutputDataFrom(amount, totalValueOfInputs, assetId, accountAddressHash, destinationAddressHash) {
// if we send our complete balance we need just 1 output entry
// otherwise we need a second output to return the remainder of the balance to ourselves
const OUTPUT_COUNT = amount == totalValueOfInputs ? 0x01 : 0x02
Expand Down Expand Up @@ -420,7 +420,7 @@ function getOutputEntryFrom(assetId, amount, scripthash) {
* @param {number|string} amountToSend
* @returns {Object} {{Buffer}inputs: Buffer of input data, {number}totalValueOfInputs: sum of UTXO values}
*/
function buildInputDataFrom(UTXOs, amountToSend) {
export function buildInputDataFrom(UTXOs, amountToSend) {
let orderedUTXOs = UTXOs
for (let i = 0; i < orderedUTXOs.length - 1; i++) {
for (let j = 0; j < orderedUTXOs.length - 1 - i; j++) {
Expand Down Expand Up @@ -500,7 +500,7 @@ function getInputEntryFrom(utxo) {
* @param {Buffer} signatureScript
* @returns {Buffer} Hashed output
*/
function getHash(signatureScript) {
export function getHash(signatureScript) {
let ProgramHexString = CryptoJS.enc.Hex.parse(signatureScript.toString('hex'))
let ProgramSha256 = CryptoJS.SHA256(ProgramHexString)
return new Buffer(CryptoJS.RIPEMD160(ProgramSha256).toString(), 'hex')
Expand All @@ -511,7 +511,7 @@ function getHash(signatureScript) {
* @param {Buffer} publicKeyEncoded - encoded public key
* @returns {Buffer} script
*/
function createSignatureScript(publicKeyEncoded) {
export function createSignatureScript(publicKeyEncoded) {
let script = new Buffer('21' + publicKeyEncoded.toString('hex') + 'ac', 'hex')
return script
}
Expand Down
118 changes: 117 additions & 1 deletion app/api/crypto/nep5.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isValidPublicAddress } from './index'
import { isValidPublicAddress, getHash, createSignatureScript, buildInputDataFrom, buildOutputDataFrom } from './index'
import bs58 from 'bs58'
import { reverse } from './utils'

Expand Down Expand Up @@ -49,3 +49,119 @@ export function getTokenBalanceScript(token, address) {
throw new Error('Invalid public address')
}
}

/**
* Build the raw script data for transfering a specific amount of a specific token from one address to another.
* @param {String} token NEP5 token scripthash (hex string).
* @param {*} fromAddr The encoded public key of the address from which the assets are to be send.
* @param {*} toAddr The address to which the assets are going to be send.
* @param {*} amount The number of tokens to send.
* @returns {Buffer} constructed raw script data.
*/

function buildTokenTransferScript(token, fromAddr, toAddr, amount) {
if (!isValidPublicAddress(toAddr)) {
throw new Error('Invalid destination address')
}

let fromAddrScriptHash = getHash(createSignatureScript(fromAddr)) // own pub key hash
let toAddrScriptHash = bs58.decode(toAddr).slice(1, 21)

const OPCODE_PUSH1 = 0x51
const SCRIPTHASH_LEN = 0x14
const OPCODE_PUSH1_LEN3 = 0x53 // script parameters length
const OPCODE_PACK = 0xc1
const OPERATION_LENGTH = 8
const OPERATION = Buffer.from('transfer', 'utf8')
const OPCODE_APPCALL = 0x67
let token_scripthash_buffer = reverse(Buffer.from(token, 'hex'))

let offset = 0
let data

if (amount > 0 && amount <= 16) {
const AMOUNT_TO_SENT = OPCODE_PUSH1 - 1 + amount
data = Buffer.alloc(75)

data.writeUInt8(AMOUNT_TO_SENT, offset)
offset += 1
} else if (amount > 16) {
number = amount.toString(16)
number = number.length % 2 ? '0' + number : number
const AMOUNT_TO_SENT = reverse(Buffer.from(number, 'hex'))
const len_amount = AMOUNT_TO_SENT.length

// need to allocate more space
data = Buffer.alloc(75 + len_amount)

data.writeUInt8(len_amount, offset)
offset += 1

data.fill(AMOUNT_TO_SENT, offset, offset + AMOUNT_TO_SENT.length)
offset += AMOUNT_TO_SENT.length
} else {
throw Errro('Invalid transfer amount')
}

// construct script
data.writeUInt8(SCRIPTHASH_LEN, offset)
offset += 1

data.fill(toAddrScriptHash, offset, offset + SCRIPTHASH_LEN)
offset += SCRIPTHASH_LEN

data.writeUInt8(SCRIPTHASH_LEN, offset)
offset += 1

data.fill(fromAddrScriptHash, offset, offset + SCRIPTHASH_LEN)
offset += SCRIPTHASH_LEN

data.writeUInt8(OPCODE_PUSH1_LEN3, offset)
data.writeUInt8(OPCODE_PACK, offset + 1)
data.writeUInt8(OPERATION_LENGTH, offset + 2)
offset += 3

data.fill(OPERATION, offset, offset + OPERATION.length)
offset += OPERATION.length

data.writeUInt8(OPCODE_APPCALL, offset)
offset += 1

data.fill(token_scripthash_buffer, offset)

return data
}

/**
* Constructs an InvocationTransaction based on the given params. It is currently fixed to 'transfer' operation calls only.
* @param {UTXO[]} UTXOs - A list of Unspend Transactions records belonging to a specific address.
* @param {string} assetId - Unique string identify the asset type 'NEO/GAS' (Uint256.toHex()) - should always be GAS for this call
* @param {string} publicKeyEncoded - The encoded public key of the address from which the assets are to be send.
* @param {string} destinationAddress - The address to which the assets are going to be send.
* @param {number} amount - The amount of tokens to send.
* @param {String} token scripthash (hex string).
*/
export function buildInvocationTransactionData(UTXOs, assetId, publicKeyEncoded, destinationAddress, amount, token) {
if (!isValidPublicAddress(destinationAddress)) {
throw new Error('Invalid destination address')
}
let destinationAddressHash = bs58.decode(destinationAddress).slice(1, 21)
let accountAddressHash = getHash(createSignatureScript(publicKeyEncoded)) // own pub key hash

const INVOCATION_TRANSACTION_TYPE = 0xd1
const INVOCATION_VERSION = 0x01
const transfer_script = buildTokenTransferScript(token, publicKeyEncoded, destinationAddress, amount)
const transfer_script_len = transfer_script.length

const HEADER = Buffer.from([INVOCATION_TRANSACTION_TYPE, INVOCATION_VERSION, transfer_script_len])
const gas_value = Buffer.from('0000000000000000', 'hex') // Fixed8(0) no gas value
const TRANSACTION_ATTRIBUTES = Buffer.from('00', 'hex') // no attributes

const gasAmount = 0.00000001

let { inputs, totalValueOfInputs } = buildInputDataFrom(UTXOs, gasAmount)
let outputs = buildOutputDataFrom(gasAmount, totalValueOfInputs, assetId, accountAddressHash, accountAddressHash) // send to self

// final result = unsigned raw transaction
return Buffer.concat([HEADER, transfer_script, gas_value, TRANSACTION_ATTRIBUTES, inputs, outputs])
}
26 changes: 24 additions & 2 deletions app/api/network/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
buildRawTransaction,
signTransactionData
} from '../crypto'
import { getTokenBalanceScript } from '../crypto/nep5'
import { getTokenBalanceScript, buildInvocationTransactionData } from '../crypto/nep5'
import { reverse } from '../crypto/utils'

export function getBalance(address) {
Expand Down Expand Up @@ -144,7 +144,6 @@ export function claimAllGAS(fromWif) {
const totalClaim = response['total_claim']

const txData = buildClaimTransactionData(claims, totalClaim, account.publicKeyEncoded)
console.log(txData.toString('hex'))
const signature = signTransactionData(txData, account.privateKey)
const rawTXData = buildRawTransaction(txData, signature, account.publicKeyEncoded)
return queryRPC('sendrawtransaction', [rawTXData.toString('hex')], 2)
Expand Down Expand Up @@ -208,3 +207,26 @@ export function getTokenBalance(token, address) {
return value
})
}

/**
*
* @param {string} destinationAddress - The destination address.
* @param {string} WIF - The WIF of the originating address.
* @param {string} token - token scripthash (hex string).
* @param {number} amount - of tokens to sent
* @return {Promise<Response>} RPC Response
*/
export function SendNEP5Asset(destinationAddress, WIF, token, amount) {
let assetId = getAssetId('Gas')
const fromAccount = getAccountFromWIF(WIF)

return getBalance(fromAccount.address).then(response => {
const UTXOs = response.unspent['Gas']
const txData = buildInvocationTransactionData(UTXOs, assetId, fromAccount.publicKeyEncoded, destinationAddress, amount, token)
console.log(txData.toString('hex'))
const signature = signTransactionData(txData, fromAccount.privateKey)
const rawTXData = buildRawTransaction(txData, signature, fromAccount.publicKeyEncoded)

return queryRPC('sendrawtransaction', [rawTXData.toString('hex')], 4)
})
}

0 comments on commit 1e59217

Please sign in to comment.