UNPKG

@cityofzion/neon-js

Version:

Neon-JS SDK for interacting with NEO blockchain

233 lines 10.7 kB
import { CONST, rpc, sc, tx, u, wallet } from "@cityofzion/neon-core"; import { GASContract } from "./nep17"; import { smartCalculateNetworkFee } from "@cityofzion/neon-api"; /** * Calculate the GAS costs for validation and inclusion of the transaction in a block * @param transaction - the transaction to calculate the network fee for * @param account - * @param config - * * @deprecated use the smartCalculateNetworkFee helper instead. */ export async function calculateNetworkFee(transaction, account, config) { if (transaction.signers.length < 1) { throw new Error("Cannot calculate the network fee without a sender in the transaction."); } const hashes = transaction.getScriptHashesForVerifying(); let networkFeeSize = transaction.headerSize + u.getSerializedSize(transaction.signers) + u.getSerializedSize(transaction.attributes) + u.getSerializedSize(transaction.script) + u.getSerializedSize(hashes.length); const rpcClient = new rpc.RPCClient(config.rpcAddress); let execFeeFactor = 0; try { const response = await rpcClient.invokeFunction(CONST.NATIVE_CONTRACT_HASH.PolicyContract, "getExecFeeFactor"); if (response.state === "FAULT") { throw Error; } execFeeFactor = parseInt(response.stack[0].value); } catch (e) { throw new Error(`Failed to get 'Execution Fee factor' from Policy contract. Error: ${e}`); } let networkFee = 0; hashes.map((hash) => { let witnessScript; if (hash === account.scriptHash && account.contract.script !== undefined) { witnessScript = u.HexString.fromBase64(account.contract.script); } if (witnessScript === undefined && transaction.witnesses.length > 0) { for (const witness of transaction.witnesses) { if (witness.scriptHash === hash) { witnessScript = witness.verificationScript; break; } } } if (witnessScript === undefined) // should get the contract script via RPC getcontractstate // then execute the script with a verification trigger (not yet supported) // and collect the gas consumed throw new Error("Using a smart contract as a witness is not yet supported in neon-js"); else if (sc.isSignatureContract(witnessScript)) { networkFeeSize += 67 + u.getSerializedSize(witnessScript); networkFee = execFeeFactor * (sc.OpCodePrices[sc.OpCode.PUSHDATA1] * 2 + sc.OpCodePrices[sc.OpCode.SYSCALL] + sc.getInteropServicePrice(sc.InteropServiceCode.SYSTEM_CRYPTO_CHECKSIG)); } else if (sc.isMultisigContract(witnessScript)) { const publicKeyCount = wallet.getPublicKeysFromVerificationScript(witnessScript.toString()).length; const signatureCount = wallet.getSigningThresholdFromVerificationScript(witnessScript.toString()); const invocationScriptSize = 66 * signatureCount; networkFeeSize += u.getSerializedSize(invocationScriptSize) + invocationScriptSize + u.getSerializedSize(witnessScript); networkFee += execFeeFactor * sc.OpCodePrices[sc.OpCode.PUSHDATA1] * signatureCount; const builder = new sc.ScriptBuilder(); let pushOpcode = sc.fromHex(builder.emitPush(signatureCount).build().slice(0, 2)); // price for pushing the signature count networkFee += execFeeFactor * sc.OpCodePrices[pushOpcode]; // now do the same for the public keys builder.reset(); pushOpcode = sc.fromHex(builder.emitPush(publicKeyCount).build().slice(0, 2)); // price for pushing the public key count networkFee += execFeeFactor * sc.OpCodePrices[pushOpcode]; networkFee += execFeeFactor * (sc.getInteropServicePrice(sc.InteropServiceCode.SYSTEM_CRYPTO_CHECKMULTISIG) * publicKeyCount); } // else { // future supported contract types} }); try { const response = await rpcClient.invokeFunction(CONST.NATIVE_CONTRACT_HASH.PolicyContract, "getFeePerByte"); if (response.state === "FAULT") { throw Error; } const nativeContractPolicyFeePerByte = parseInt(response.stack[0].value); networkFee += networkFeeSize * nativeContractPolicyFeePerByte; } catch (e) { throw new Error(`Failed to get 'fee per byte' from Policy contract. Error: ${e}`); } return u.BigInteger.fromDecimal(networkFee, 0); } /** * Get the cost of executing the smart contract script * @param script - smart contract script * @param config - * @param signers - signers to set while running the script */ export async function getSystemFee(script, config, signers) { const rpcClient = new rpc.RPCClient(config.rpcAddress); try { const response = await rpcClient.invokeScript(script, signers); if (response.state === "FAULT") { throw Error(`Script execution failed. ExecutionEngine state = FAULT. ${response.exception}`); } return u.BigInteger.fromDecimal(response.gasconsumed, 0); } catch (e) { throw new Error(`Failed to get system fee. ${e}`); } } /** * Set the validUntilBlock field on a transaction * * If `blocksTillExpiry` is provided then the value is used. * If `blocksTillExpiry` is not provided, or the value exceeds the maximum allowed, * then the field is automatically set to the maximum allowed by the network. * @param transaction - the transaction to set the expiry field on * @param config - * @param blocksTillExpiry - number of blocks from the current chain height until the transaction is no longer valid */ export async function setBlockExpiry(transaction, config, blocksTillExpiry) { let blockLifeSpan = tx.Transaction.MAX_TRANSACTION_LIFESPAN; if (blocksTillExpiry && !(blocksTillExpiry > tx.Transaction.MAX_TRANSACTION_LIFESPAN)) blockLifeSpan = blocksTillExpiry; const rpcClient = new rpc.RPCClient(config.rpcAddress); transaction.validUntilBlock = (await rpcClient.getBlockCount()) + blockLifeSpan - 1; } /** * Add system and network fees to a transaction. * Validates that the source Account has sufficient balance * * Note: Witnesses must be present on the transaction. If no witnesses are * present a temporary single signature account witness will be used for * fee calculation. For fee calculation using a multi signature account you * must add the witness yourself. See TransactionBuilder.addEmptyWitnesses() * for reference how this could be done. * @param transaction - the transaction to add network and system fees to * @param config - */ export async function addFees(transaction, config) { if (config.networkFeeOverride && config.prioritisationFee) { throw new Error("networkFeeOverride and prioritisationFee are mutually exclusive"); } if (config.systemFeeOverride) { transaction.systemFee = config.systemFeeOverride; } else { transaction.systemFee = await getSystemFee(transaction.script, config, transaction.signers); } if (config.account === undefined) throw new Error("Cannot determine network fee and validate balances without an account in your config"); if (config.networkFeeOverride) { transaction.networkFee = config.networkFeeOverride; } else { const rpcClient = new rpc.RPCClient(config.rpcAddress); const txClone = new tx.Transaction(transaction); if (txClone.witnesses.length < 1) { txClone.addWitness(new tx.Witness({ invocationScript: "", verificationScript: u.HexString.fromBase64(config.account.contract.script).toString(), })); } transaction.networkFee = await smartCalculateNetworkFee(txClone, rpcClient); } if (config.prioritisationFee) { transaction.networkFee = transaction.networkFee.add(u.BigInteger.fromNumber(config.prioritisationFee)); } const GAS = new GASContract(config); const gasBalance = await GAS.balanceOf(config.account.address); const requiredGAS = parseFloat(transaction.systemFee.add(transaction.networkFee).toDecimal(8)); if (gasBalance < requiredGAS) { throw new Error(`Insufficient GAS. Required: ${requiredGAS} Available: ${gasBalance}`); } } /** * Deploy a smart contract * @param nef - A smart contract in Neo executable file format. Commonly created by a NEO compiler and stored as .NEF on disk * @param manifest - the manifest corresponding to the smart contract * @param config - */ export async function deployContract(nef, manifest, config) { const builder = new sc.ScriptBuilder(); builder.emitContractCall({ scriptHash: CONST.NATIVE_CONTRACT_HASH.ManagementContract, operation: "deploy", callFlags: sc.CallFlags.All, args: [ sc.ContractParam.byteArray(u.HexString.fromHex(nef.serialize(), true)), sc.ContractParam.string(JSON.stringify(manifest.toJson())), ], }); const transaction = new tx.Transaction(); transaction.script = u.HexString.fromHex(builder.build()); await setBlockExpiry(transaction, config, config.blocksTillExpiry); // add a sender if (config.account === undefined) throw new Error("Account in your config cannot be undefined"); transaction.addSigner({ account: config.account.scriptHash, scopes: "CalledByEntry", }); await addFees(transaction, config); transaction.sign(config.account, config.networkMagic); const rpcClient = new rpc.RPCClient(config.rpcAddress); return await rpcClient.sendRawTransaction(transaction); } /** * Get the hash that identifies the contract on the chain matching the specified NEF * @param sender - The sender of the transaction * @param nefChecksum - The checksum of the Neo Executable File. A NEF file is a smart contract commonly created by a NEO compiler and stored as .NEF on disk * @param contractName - The name as indicated in the manifest */ export function getContractHash(sender, nefChecksum, contractName) { const assembledScript = new sc.ScriptBuilder() .emit(sc.OpCode.ABORT) .emitPush(sender) .emitPush(nefChecksum) .emitPush(contractName) .build(); return u.reverseHex(u.hash160(assembledScript)); } //# sourceMappingURL=helpers.js.map