Javascript SDK of WoopChain protocol
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
sdk/packages/harmony-transaction/src/transaction.ts

293 lines
9.7 KiB

import {
BN,
encode,
arrayify,
hexlify,
stripZeros,
Signature,
splitSignature,
} from '@harmony-js/crypto';
import { add0xToString, numberToHex, ChainType, Unit } from '@harmony-js/utils';
import { Messenger, RPCMethod } from '@harmony-js/network';
import { TxParams, TxStatus } from './types';
import {
recover,
transactionFields,
defaultMessenger,
transactionFieldsETH,
recoverETH,
} from './utils';
import { TransactionBase } from './transactionBase';
class Transaction extends TransactionBase {
private from: string;
private nonce: number | string;
private to: string;
// private shardID: number | string;
private toShardID: number | string;
private gasLimit: BN;
private gasPrice: BN;
private data: string;
private value: BN;
private chainId: number;
private rawTransaction: string;
private unsignedRawTransaction: string;
private signature: Signature;
// constructor
constructor(
params?: TxParams | any,
messenger: Messenger = defaultMessenger,
txStatus = TxStatus.INTIALIZED,
) {
super(messenger, txStatus);
// intialize transaction
this.id = params && params.id ? params.id : '0x';
this.from = params && params.from ? params.from : '0x';
this.nonce = params && params.nonce ? params.nonce : 0;
this.gasPrice =
params && params.gasPrice
? new Unit(params.gasPrice).asWei().toWei()
: new Unit(0).asWei().toWei();
this.gasLimit =
params && params.gasLimit
? new Unit(params.gasLimit).asWei().toWei()
: new Unit(0).asWei().toWei();
this.shardID =
params && params.shardID !== undefined ? params.shardID : this.messenger.currentShard;
this.toShardID =
params && params.toShardID !== undefined ? params.toShardID : this.messenger.currentShard;
this.to = params && params.to ? Transaction.normalizeAddress(params.to) : '0x';
this.value =
params && params.value ? new Unit(params.value).asWei().toWei() : new Unit(0).asWei().toWei();
this.data = params && params.data ? params.data : '0x';
// chainid should change with different network settings
this.chainId = params && params.chainId ? params.chainId : this.messenger.chainId;
this.rawTransaction = params && params.rawTransaction ? params.rawTransaction : '0x';
this.unsignedRawTransaction =
params && params.unsignedRawTransaction ? params.unsignedRawTransaction : '0x';
this.signature =
params && params.signature
? params.signature
: {
r: '',
s: '',
recoveryParam: 0,
v: 0,
};
this.receipt = params && params.receipt ? params.receipt : undefined;
this.cxStatus = this.isCrossShard() ? TxStatus.INTIALIZED : TxStatus.NONE;
}
getRLPUnsigned(): [string, any[]] {
const raw: Array<string | Uint8Array> = [];
// temp setting to be compatible with eth
const fields =
this.messenger.chainType === ChainType.Harmony ? transactionFields : transactionFieldsETH;
fields.forEach((field: any) => {
let value = (<any>this.txParams)[field.name] || [];
value = arrayify(
hexlify(field.transform === 'hex' ? add0xToString(value.toString(16)) : value),
);
// Fixed-width field
if (field.fix === true && field.length && value.length !== field.length && value.length > 0) {
throw new Error(`invalid length for ${field.name}`);
}
// Variable-width (with a maximum)
if (field.fix === false && field.length) {
value = stripZeros(value);
if (value.length > field.length) {
throw new Error(`invalid length for ${field.name}`);
}
}
raw.push(hexlify(value));
});
if (this.txParams.chainId != null && this.txParams.chainId !== 0) {
raw.push(hexlify(this.txParams.chainId));
raw.push('0x');
raw.push('0x');
}
return [encode(raw), raw];
}
getRLPSigned(raw: any[], signature: Signature): string {
// temp setting to be compatible with eth
const rawLength = this.messenger.chainType === ChainType.Harmony ? 11 : 9;
const sig = splitSignature(signature);
let v = 27 + (sig.recoveryParam || 0);
if (raw.length === rawLength) {
raw.pop();
raw.pop();
raw.pop();
v += this.chainId * 2 + 8;
}
raw.push(hexlify(v));
raw.push(stripZeros(arrayify(sig.r) || []));
raw.push(stripZeros(arrayify(sig.s) || []));
return encode(raw);
}
getRawTransaction(): string {
return this.rawTransaction;
}
recover(rawTransaction: string): Transaction {
// temp setting to be compatible with eth
const recovered =
this.messenger.chainType === ChainType.Harmony
? recover(rawTransaction)
: recoverETH(rawTransaction);
this.setParams(recovered);
return this;
}
// use when using eth_sendTransaction
get txPayload() {
return {
from: this.txParams.from || '0x',
to: this.txParams.to || '0x',
shardID: this.txParams.shardID ? numberToHex(this.shardID) : '0x',
toShardID: this.txParams.toShardID ? numberToHex(this.toShardID) : '0x',
gas: this.txParams.gasLimit ? numberToHex(this.txParams.gasLimit) : '0x',
gasPrice: this.txParams.gasPrice ? numberToHex(this.txParams.gasPrice) : '0x',
value: this.txParams.value ? numberToHex(this.txParams.value) : '0x',
data: this.txParams.data || '0x',
nonce: this.txParams.nonce ? numberToHex(this.nonce) : '0x',
};
}
get txParams(): TxParams {
return {
id: this.id || '0x',
from: this.from || '',
nonce: this.nonce || 0,
gasPrice: this.gasPrice || new Unit(0).asWei().toWei(),
gasLimit: this.gasLimit || new Unit(0).asWei().toWei(),
shardID: this.shardID !== undefined ? this.shardID : this.messenger.currentShard,
toShardID: this.toShardID !== undefined ? this.toShardID : this.messenger.currentShard,
to: Transaction.normalizeAddress(this.to) || '0x',
value: this.value || new Unit(0).asWei().toWei(),
data: this.data || '0x',
chainId: this.chainId || 0,
rawTransaction: this.rawTransaction || '0x',
unsignedRawTransaction: this.unsignedRawTransaction || '0x',
signature: this.signature || '0x',
};
}
setParams(params: TxParams) {
this.id = params && params.id ? params.id : '0x';
this.from = params && params.from ? params.from : '0x';
this.nonce = params && params.nonce ? params.nonce : 0;
this.gasPrice =
params && params.gasPrice
? new Unit(params.gasPrice).asWei().toWei()
: new Unit(0).asWei().toWei();
this.gasLimit =
params && params.gasLimit
? new Unit(params.gasLimit).asWei().toWei()
: new Unit(0).asWei().toWei();
this.shardID =
params && params.shardID !== undefined ? params.shardID : this.messenger.currentShard;
this.toShardID =
params && params.toShardID !== undefined ? params.toShardID : this.messenger.currentShard;
this.to = params && params.to ? Transaction.normalizeAddress(params.to) : '0x';
this.value =
params && params.value ? new Unit(params.value).asWei().toWei() : new Unit(0).asWei().toWei();
this.data = params && params.data ? params.data : '0x';
this.chainId = params && params.chainId ? params.chainId : 0;
this.rawTransaction = params && params.rawTransaction ? params.rawTransaction : '0x';
this.unsignedRawTransaction =
params && params.unsignedRawTransaction ? params.unsignedRawTransaction : '0x';
this.signature =
params && params.signature
? params.signature
: {
r: '',
s: '',
recoveryParam: 0,
v: 0,
};
if (this.rawTransaction !== '0x') {
this.setTxStatus(TxStatus.SIGNED);
} else {
this.setTxStatus(TxStatus.INTIALIZED);
}
}
map(fn: any) {
const newParams = fn(this.txParams);
this.setParams(newParams);
return this;
}
isCrossShard(): boolean {
return new BN(this.txParams.shardID).toString() !== new BN(this.txParams.toShardID).toString();
}
async sendTransaction(): Promise<[Transaction, string]> {
if (this.rawTransaction === 'tx' || this.rawTransaction === undefined) {
throw new Error('Transaction not signed');
}
if (!this.messenger) {
throw new Error('Messenger not found');
}
// const fromShard = this.shardID;
// const toShard = this.toShardID;
// await this.messenger.setShardingProviders();
const res = await this.messenger.send(
RPCMethod.SendRawTransaction,
this.rawTransaction,
this.messenger.chainType,
typeof this.shardID === 'string' ? Number.parseInt(this.shardID, 10) : this.shardID,
);
// temporarilly hard coded
if (res.isResult()) {
this.id = res.result;
this.emitTransactionHash(this.id);
this.setTxStatus(TxStatus.PENDING);
// await this.confirm(this.id, 20, 1000);
return [this, res.result];
} else if (res.isError()) {
this.emitConfirm(`transaction failed:${res.error.message}`);
this.setTxStatus(TxStatus.REJECTED);
return [this, `transaction failed:${res.error.message}`];
} else {
this.emitError('transaction failed');
throw new Error('transaction failed');
}
}
async confirm(
txHash: string,
maxAttempts: number = 20,
interval: number = 1000,
shardID: number | string = this.txParams.shardID,
toShardID: number | string = this.txParams.toShardID,
) {
const txConfirmed = await this.txConfirm(txHash, maxAttempts, interval, shardID);
if (!this.isCrossShard()) {
return txConfirmed;
}
if (txConfirmed.isConfirmed()) {
const cxConfirmed = await this.cxConfirm(txHash, maxAttempts, interval, toShardID);
return cxConfirmed;
} else {
return txConfirmed;
}
}
}
export { Transaction };