UNPKG

@dioxide-js/silas

Version:

RPC utility for Silas

233 lines (200 loc) 6.65 kB
import { AlgOption } from './../utils/address/base' import { encode } from 'base64-arraybuffer' import { sha256 } from 'js-sha256' import base32Encode from 'base32-encode' import { dataview } from '@dioxide-js/misc' import TransactionService from '../api/transactions' import { DIOAddress, Alg, toUint8Array, isValidAddress } from '../utils' import PowDifficulty from '../utils/powDifficulty' import OverviewService from '../api/overview' import { OriginalTxn } from '../api/type' export type SignMethod = (txdata: Uint8Array, privatekey: Uint8Array) => Promise<Uint8Array> export interface TransferDIOParams { to: string amount: string secretKey: Uint8Array | string ttl?: number sender: string } export interface TransferFCAParams { symbol: string to: string amount: string secretKey: Uint8Array | string ttl?: number } export interface TxOption { alg?: Alg apiKey: string showDuration?: boolean n?: number customSign?: SignMethod showTxFlow?: boolean } class Transaction { private txnServices: TransactionService private overViewServices: OverviewService customSign?: SignMethod alg: Alg = 'sm2' showTxFlow?: boolean n = 0 private duration = { compose: 0, sign: 0, verify: 0, computedNonce: 0, all: 0, } constructor(opts: TxOption) { const { alg = 'sm2', showTxFlow = false, apiKey, n, customSign } = opts this.txnServices = new TransactionService({ apiKey }) this.overViewServices = new OverviewService({ apiKey }) this.alg = alg this.customSign = customSign this.showTxFlow = showTxFlow if (typeof n !== 'undefined' && typeof n !== 'number') { throw 'n muse be number' } this.n = n ?? 3 } getTx = async (hash: string) => { return this.txnServices.getTransactionByHash(hash) } private async compose(originalTxn: OriginalTxn) { const ret = await this.txnServices.compose(originalTxn) return ret.TxData } async sign(originalTxn: OriginalTxn, secretKey: Uint8Array | string, option?: AlgOption) { const t0 = Date.now() if (typeof secretKey === 'string') { secretKey = toUint8Array(secretKey) } const dioAddress = new DIOAddress(this.alg, secretKey) const txdata = await this.compose(originalTxn) this.duration.compose = Date.now() - t0 const t1 = Date.now() let pk: Uint8Array | null = null let longPK: Uint8Array | null = null if (dioAddress.alg === 'sm2') { pk = await dioAddress.getPubicKeyFromPrivateKey(secretKey) longPK = dataview.concat(new Uint8Array([4]), pk) } else { longPK = pk = dioAddress.addressToPublicKey(originalTxn.sender) } if (!pk) { throw new Error('pk error') } const dataWithPK = dioAddress.insertPKIntoTxData(txdata, [ { encryptedMethodOrderNumber: dioAddress.methodNum, publicKey: pk }, ]) const raw = encode(dataWithPK) const signedInfo = this.customSign ? await this.customSign(dataWithPK, secretKey) : await dioAddress.sign(dataWithPK, secretKey, option) this.duration.sign = Date.now() - t1 const t2 = Date.now() // const signature = dataview.u8ToHex(signedInfo) // const isValid = await dioAddress.verifySignature(dataWithPK, signature, longPK!, option) this.duration.verify = Date.now() - t2 const t3 = Date.now() // if (!isValid) { // throw new Error('sign error') // } const finalInfo = dataview.concat(dataWithPK, signedInfo) const powDiff = new PowDifficulty({ originTxn: finalInfo.buffer, ttl: originalTxn.ttl, n: this.n, debug: this.showTxFlow, }) const finalInfowithNonce = powDiff.getHashMixinNonnce() this.duration.computedNonce = Date.now() - t3 const hash = base32Encode(sha256.arrayBuffer(finalInfowithNonce), 'Crockford') this.duration.all = Date.now() - t0 if (this.showTxFlow) { console.log('Tx Flow =>', this.duration) } return { composedTxDataWithPK: raw, signature: encode(signedInfo), longPK: encode(longPK!), rawTxData: encode(finalInfowithNonce), hash: hash.toLowerCase(), pk: encode(pk), txFlow: this.duration, nonceTime: powDiff.t, } } async send(originTxn: OriginalTxn, secretKey: Uint8Array | string) { const { rawTxData } = await this.sign(originTxn, secretKey) const ret = await this.txnServices.sendTransaction({ txdata: rawTxData, }) return ret.Hash } async sendRawTx(rawTxData: string) { const ret = await this.txnServices.sendTransaction({ txdata: rawTxData, }) return ret.Hash } async getEstimatedFee(originTxn: OriginalTxn) { const { function: func, args, delegatee, scale = 3, tokens } = originTxn const overview = await this.overViewServices.chainStatus() const avgGasPrice = overview?.AvgGasPrice || 0 const to = args.to || args.To const ret = await this.txnServices.compose({ sender: to, gasprice: avgGasPrice, delegatee: delegatee, function: func, args, tokens, }) const gasLimit = ret.GasOffered.toString() const gasFee = this.calculateGasFee({ average: avgGasPrice, scale: scale, gasLimit: Number(gasLimit), }) return gasFee } calculateGasFee(options: { average: number; scale: number; gasLimit: number }): number | string | bigint { const { average, scale = 3, gasLimit } = options const gasPrice = parseInt(((scale - 1) * 0.25 + 0.5) * average + '', 10) const gasFee = gasPrice * gasLimit return gasFee } async transfer(params: TransferDIOParams) { const { to, amount, secretKey, ttl, sender } = params if (!sender) { throw 'sender unfilled' } if (!isValidAddress(sender)) { throw 'invalid sender' } // const sender = await this.sk2base32Address(secretKey, this.alg) return this.send( { sender, gasprice: 100, function: 'core.coin.transfer', args: { To: to, Amount: amount, }, ttl, }, secretKey, ) } async sk2base32Address(sk: Uint8Array | string, alg: Alg) { if (typeof sk === 'string') { sk = toUint8Array(sk) } const dioAddress = new DIOAddress(alg, sk) const { address } = await dioAddress.generate() return address.toLowerCase() } } export { Transaction }