|
|
|
@ -30,7 +30,7 @@ import { |
|
|
|
|
recoverETH, |
|
|
|
|
} from './utils'; |
|
|
|
|
|
|
|
|
|
class Transaction { |
|
|
|
|
export class TransactionBase { |
|
|
|
|
emitter: Emitter; |
|
|
|
|
messenger: Messenger; |
|
|
|
|
txStatus: TxStatus; |
|
|
|
@ -43,225 +43,21 @@ class Transaction { |
|
|
|
|
cxConfirmationCheck: number = 0; |
|
|
|
|
receipt?: TransasctionReceipt; |
|
|
|
|
|
|
|
|
|
private id: string; |
|
|
|
|
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; |
|
|
|
|
id: string; |
|
|
|
|
shardID: number | string; |
|
|
|
|
|
|
|
|
|
// constructor
|
|
|
|
|
constructor( |
|
|
|
|
params?: TxParams | any, |
|
|
|
|
messenger: Messenger = defaultMessenger, |
|
|
|
|
txStatus = TxStatus.INTIALIZED, |
|
|
|
|
) { |
|
|
|
|
constructor(messenger: Messenger, txStatus: TxStatus) { |
|
|
|
|
this.messenger = messenger; |
|
|
|
|
this.txStatus = txStatus; |
|
|
|
|
this.emitter = new Emitter(); |
|
|
|
|
|
|
|
|
|
// 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 ? this.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; |
|
|
|
|
this.id = '0x'; |
|
|
|
|
this.shardID = this.messenger.currentShard; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
setMessenger(messenger: Messenger) { |
|
|
|
|
this.messenger = messenger; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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: this.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 ? this.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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
setTxStatus(txStatus: TxStatus): void { |
|
|
|
|
this.txStatus = txStatus; |
|
|
|
|
} |
|
|
|
@ -302,84 +98,26 @@ class Transaction { |
|
|
|
|
isCxConfirmed(): boolean { |
|
|
|
|
return this.getCxStatus() === TxStatus.CONFIRMED; |
|
|
|
|
} |
|
|
|
|
isCrossShard(): boolean { |
|
|
|
|
return new BN(this.txParams.shardID).toString() !== new BN(this.txParams.toShardID).toString(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
observed() { |
|
|
|
|
return this.emitter; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async sendTransaction(): Promise<[Transaction, string]> { |
|
|
|
|
if (this.rawTransaction === 'tx' || this.rawTransaction === undefined) { |
|
|
|
|
throw new Error('Transaction not signed'); |
|
|
|
|
} |
|
|
|
|
async trackTx(txHash: string, shardID: number | string) { |
|
|
|
|
if (!this.messenger) { |
|
|
|
|
throw new Error('Messenger not found'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// const fromShard = this.shardID;
|
|
|
|
|
// const toShard = this.toShardID;
|
|
|
|
|
// await this.messenger.setShardingProviders();
|
|
|
|
|
// TODO: regex validation for txHash so we don't get garbage
|
|
|
|
|
const res = await this.messenger.send( |
|
|
|
|
RPCMethod.SendRawTransaction, |
|
|
|
|
this.rawTransaction, |
|
|
|
|
RPCMethod.GetTransactionReceipt, |
|
|
|
|
txHash, |
|
|
|
|
this.messenger.chainType, |
|
|
|
|
typeof this.shardID === 'string' ? Number.parseInt(this.shardID, 10) : this.shardID, |
|
|
|
|
typeof shardID === 'string' ? Number.parseInt(shardID, 10) : 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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async trackTx(txHash: string, shardID: number | string = this.shardID) { |
|
|
|
|
if (!this.messenger) { |
|
|
|
|
throw new Error('Messenger not found'); |
|
|
|
|
} |
|
|
|
|
// TODO: regex validation for txHash so we don't get garbage
|
|
|
|
|
const res = await this.messenger.send( |
|
|
|
|
RPCMethod.GetTransactionReceipt, |
|
|
|
|
txHash, |
|
|
|
|
this.messenger.chainType, |
|
|
|
|
typeof shardID === 'string' ? Number.parseInt(shardID, 10) : shardID, |
|
|
|
|
); |
|
|
|
|
if (res.isResult() && res.result !== null) { |
|
|
|
|
this.receipt = res.result; |
|
|
|
|
this.emitReceipt(this.receipt); |
|
|
|
|
this.id = res.result.transactionHash; |
|
|
|
|
this.confirmations += 1; |
|
|
|
|
if (res.isResult() && res.result !== null) { |
|
|
|
|
this.receipt = res.result; |
|
|
|
|
this.emitReceipt(this.receipt); |
|
|
|
|
this.id = res.result.transactionHash; |
|
|
|
|
this.confirmations += 1; |
|
|
|
|
|
|
|
|
|
if (this.receipt) { |
|
|
|
|
if (this.receipt.status && this.receipt.status === '0x1') { |
|
|
|
@ -415,7 +153,7 @@ class Transaction { |
|
|
|
|
txHash: string, |
|
|
|
|
maxAttempts: number = 20, |
|
|
|
|
interval: number = 1000, |
|
|
|
|
shardID: number | string = this.shardID, |
|
|
|
|
shardID: number | string, |
|
|
|
|
) { |
|
|
|
|
if (this.messenger.provider instanceof HttpProvider) { |
|
|
|
|
this.txStatus = TxStatus.PENDING; |
|
|
|
@ -480,8 +218,8 @@ class Transaction { |
|
|
|
|
socketConfirm( |
|
|
|
|
txHash: string, |
|
|
|
|
maxAttempts: number = 20, |
|
|
|
|
shardID: number | string = this.shardID, |
|
|
|
|
): Promise<Transaction> { |
|
|
|
|
shardID: number | string, |
|
|
|
|
): Promise<TransactionBase> { |
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
|
const newHeads = Promise.resolve( |
|
|
|
|
new NewHeaders( |
|
|
|
@ -573,9 +311,7 @@ class Transaction { |
|
|
|
|
RPCMethod.GetBlockByNumber, |
|
|
|
|
[blockNumber, true], |
|
|
|
|
this.messenger.chainPrefix, |
|
|
|
|
typeof this.txParams.shardID === 'string' |
|
|
|
|
? Number.parseInt(this.txParams.shardID, 10) |
|
|
|
|
: this.txParams.shardID, |
|
|
|
|
typeof this.shardID === 'string' ? Number.parseInt(this.shardID, 10) : this.shardID, |
|
|
|
|
); |
|
|
|
|
if (block.isError()) { |
|
|
|
|
throw block.message; |
|
|
|
@ -590,7 +326,7 @@ class Transaction { |
|
|
|
|
txHash: string, |
|
|
|
|
maxAttempts: number = 20, |
|
|
|
|
interval: number = 1000, |
|
|
|
|
toShardID: number | string = this.txParams.toShardID, |
|
|
|
|
toShardID: number | string, |
|
|
|
|
) { |
|
|
|
|
if (this.messenger.provider instanceof HttpProvider) { |
|
|
|
|
const oldBlock = await this.getBlockNumber(toShardID); |
|
|
|
@ -676,8 +412,8 @@ class Transaction { |
|
|
|
|
socketCxConfirm( |
|
|
|
|
txHash: string, |
|
|
|
|
maxAttempts: number = 20, |
|
|
|
|
toShardID: number | string = this.txParams.toShardID, |
|
|
|
|
): Promise<Transaction> { |
|
|
|
|
toShardID: number | string, |
|
|
|
|
): Promise<TransactionBase> { |
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
|
const newHeads = Promise.resolve( |
|
|
|
|
new NewHeaders( |
|
|
|
@ -721,7 +457,7 @@ class Transaction { |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
normalizeAddress(address: string) { |
|
|
|
|
static normalizeAddress(address: string) { |
|
|
|
|
if (address === '0x') { |
|
|
|
|
return '0x'; |
|
|
|
|
} else if ( |
|
|
|
@ -735,4 +471,275 @@ class Transaction { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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 }; |
|
|
|
|