Skip to content

Commit

Permalink
fix: resolve manual merge
Browse files Browse the repository at this point in the history
  • Loading branch information
shuffledex committed Feb 17, 2020
2 parents 7d7838c + 14a810d commit cf1072f
Show file tree
Hide file tree
Showing 17 changed files with 1,290 additions and 184 deletions.
4 changes: 2 additions & 2 deletions .eslintrc
Expand Up @@ -15,8 +15,8 @@
"FunctionDeclaration": true,
"MethodDefinition": true,
"ClassDeclaration": true,
"ArrowFunctionExpression": true,
"FunctionExpression": true
"ArrowFunctionExpression": false,
"FunctionExpression": false
}
}
]
Expand Down
5 changes: 4 additions & 1 deletion package.json
@@ -1,5 +1,5 @@
{
"name": "typescript-repo-template",
"name": "@polymathnetwork/polymesh-sdk",
"version": "0.0.0",
"description": "Template repository for typescript projects",
"main": "dist/index.js",
Expand All @@ -19,8 +19,10 @@
"@commitlint/config-conventional": "^7.6.0",
"@semantic-release/git": "^8.0.0",
"@types/jest": "^23.3.10",
"@types/lodash": "^4.14.149",
"@types/json-stable-stringify": "^1.0.32",
"@types/node": "^13.1.8",
"@types/sinon": "^7.5.1",
"@typescript-eslint/eslint-plugin": "^2.17.0",
"@typescript-eslint/parser": "^2.17.0",
"@zerollup/ts-transform-paths": "^1.7.11",
Expand Down Expand Up @@ -61,6 +63,7 @@
"@polkadot/ui-settings": "^0.49.1",
"@polymathnetwork/polkadot": "0.101.0-beta.9",
"@types/sinon": "^7.5.1",
"lodash": "^4.17.15",
"json-stable-stringify": "^1.0.1"
}
}
26 changes: 26 additions & 0 deletions src/base/PolymeshError.ts
@@ -0,0 +1,26 @@
import { ErrorCode } from '~/types';

export const ErrorMessagesPerCode: {
[errorCode: string]: string;
} = {
[ErrorCode.TransactionReverted]: 'The transaction execution reverted due to an error',
[ErrorCode.TransactionAborted]:
'The transaction was removed from the transaction pool. This might mean that it was malformed (nonce too large/nonce too small/duplicated or invalid transaction)',
[ErrorCode.TransactionRejectedByUser]: 'The user canceled the transaction signature',
};

/**
* Wraps an error to give more information about its type
*/
export class PolymeshError extends Error {
public code: ErrorCode;

/**
* @hidden
*/
constructor({ message, code }: { message?: string; code: ErrorCode }) {
super(message || ErrorMessagesPerCode[code] || `Unknown error, code: ${code}`);

this.code = code;
}
}
303 changes: 303 additions & 0 deletions src/base/PolymeshTransaction.ts
@@ -0,0 +1,303 @@
import { EventEmitter } from 'events';
import { PostTransactionValue } from './PostTransactionValue';
import {
TransactionSpec,
PolymeshTx,
PostTransactionValueArray,
MapMaybePostTransactionValue,
Extrinsics,
} from '~/types/internal';
import { TransactionStatus, ErrorCode } from '~/types';
import { PolymeshError } from '~/base/PolymeshError';
import { SubmittableResultImpl, TxTag, AddressOrPair } from '@polymathnetwork/polkadot/api/types';
import { DispatchError } from '@polymathnetwork/polkadot/types/interfaces';
import { RegistryError } from '@polymathnetwork/polkadot/types/types';

enum Event {
StatusChange = 'StatusChange',
}

/**
* Wrapper class for a Polymesh Transaction
*/
export class PolymeshTransaction<
ModuleName extends keyof Extrinsics,
TransactionName extends keyof Extrinsics[ModuleName],
Values extends unknown[] = unknown[]
> {
/**
* current status of the transaction
*/
public status: TransactionStatus = TransactionStatus.Idle;

/**
* stores errors thrown while running the transaction (status: `Failed`, `Aborted`)
*/
public error?: PolymeshError;

/**
* stores the transaction receipt (if successful)
*/
public receipt?: SubmittableResultImpl;

/**
* type of transaction represented by this instance for display purposes
*/
public tag: TxTag;

/**
* transaction hash (status: `Running`, `Succeeded`, `Failed`)
*/
public txHash?: string;

/**
* hash of the block where this transaction resides (status: `Succeeded`, `Failed`)
*/
public blockHash?: string;

/**
* arguments with which the transaction will be called
*/
public args: MapMaybePostTransactionValue<ArgsType<PolymeshTx<ModuleName, TransactionName>>>;

/**
* whether this tx failing makes the entire tx queue fail or not
*/
public isCritical: boolean;

/**
* @hidden
*
* underlying transaction to be executed
*/
protected tx: PolymeshTx<ModuleName, TransactionName>;

/**
* @hidden
*
* wrappers for values that will exist after this transaction has executed
*/
private postValues: PostTransactionValueArray<
Values
> = ([] as unknown) as PostTransactionValueArray<Values>;

/**
* @hidden
*
* internal event emitter to handle status changes
*/
private emitter: EventEmitter;

/**
* @hidden
*
* account that will sign the transaction
*/
private signer: AddressOrPair;

/**
* @hidden
*/
constructor(transactionSpec: TransactionSpec<ModuleName, TransactionName, Values>) {
const { postTransactionValues, tag, tx, args, signer, isCritical } = transactionSpec;

if (postTransactionValues) {
this.postValues = postTransactionValues;
}

this.emitter = new EventEmitter();
this.tag = tag;
this.tx = tx;
this.args = args;
this.signer = signer;
this.isCritical = isCritical;
}

/**
* Run the poly transaction and update the transaction status
*/
public async run(): Promise<void> {
try {
const receipt = await this.internalRun();
this.receipt = receipt;

await Promise.all(this.postValues.map(postValue => postValue.run(receipt)));

this.updateStatus(TransactionStatus.Succeeded);
} catch (err) {
const error: PolymeshError = err;

this.error = err;

switch (error.code) {
case ErrorCode.TransactionAborted: {
this.updateStatus(TransactionStatus.Aborted);
break;
}
case ErrorCode.TransactionRejectedByUser: {
this.updateStatus(TransactionStatus.Rejected);
break;
}
case ErrorCode.TransactionReverted:
case ErrorCode.FatalError:
default: {
this.updateStatus(TransactionStatus.Failed);
break;
}
}

throw error;
}
}

/**
* Subscribe to status changes
*
* @param listener - callback function that will be called whenever the status changes
*
* @returns unsubscribe function
*/
public onStatusChange(listener: (transaction: this) => void): () => void {
this.emitter.on(Event.StatusChange, listener);

return (): void => {
this.emitter.removeListener(Event.StatusChange, listener);
};
}

/**
* @hidden
*
* Execute the underlying transaction, updating the status where applicable and
* throwing any pertinent errors
*/
private async internalRun(): Promise<SubmittableResultImpl> {
this.updateStatus(TransactionStatus.Unapproved);

const unwrappedArgs = this.unwrapArgs(this.args);

const { tx } = this;

const gettingReceipt: Promise<SubmittableResultImpl> = new Promise((resolve, reject) => {
const txWithArgs = tx(...unwrappedArgs);
const gettingUnsub = txWithArgs.signAndSend(this.signer, receipt => {
const { status } = receipt;

if (receipt.isCompleted) {
/*
* isCompleted === isFinalized || isError, which means
* no further updates, so we unsubscribe
*/
gettingUnsub.then(unsub => {
unsub();
});

if (receipt.isFinalized) {
// tx included in a block
// TODO @monitz87: replace with event object when it is auto-generated by the polkadot fork
const failed = receipt.findRecord('system', 'ExtrinsicFailed');

this.blockHash = status.asFinalized.toString();

if (failed) {
// get revert message from event
let message: string;
const dispatchError = failed.event.data[0] as DispatchError;

if (dispatchError.isModule) {
// known error
const mod = dispatchError.asModule;

const { section, name, documentation }: RegistryError = mod.registry.findMetaError(
new Uint8Array([mod.index.toNumber(), mod.error.toNumber()])
);
message = `${section}.${name}: ${documentation.join(' ')}`;
} else if (dispatchError.isBadOrigin) {
message = 'Bad origin';
} else if (dispatchError.isCannotLookup) {
message = 'Could not lookup information required to validate the transaction';
} else {
message = 'Unknown error';
}

reject(new PolymeshError({ code: ErrorCode.TransactionReverted, message }));
} else {
resolve(receipt);
}
} else {
// this means receipt.isError === true
reject(new PolymeshError({ code: ErrorCode.TransactionAborted }));
}
}
});

gettingUnsub
.then(() => {
// tx approved by signer
this.updateStatus(TransactionStatus.Running);
this.txHash = txWithArgs.hash.toString();
})
.catch((err: Error) => {
let error;
/* istanbul ignore else */
if (err.message.indexOf('Cancelled') > -1) {
// tx rejected by signer
error = new PolymeshError({ code: ErrorCode.TransactionRejectedByUser });
} else {
// unexpected error
error = new PolymeshError({ code: ErrorCode.FatalError, message: err.message });
}

reject(error);
});
});

return gettingReceipt;
}

/**
* @hidden
*/
private updateStatus(status: TransactionStatus): void {
this.status = status;

/* eslint-disable default-case */
switch (status) {
case TransactionStatus.Unapproved:
case TransactionStatus.Running:
case TransactionStatus.Succeeded: {
this.emitter.emit(Event.StatusChange, this);
return;
}
case TransactionStatus.Rejected:
case TransactionStatus.Aborted:
case TransactionStatus.Failed: {
this.emitter.emit(Event.StatusChange, this, this.error);
}
}
/* eslint-enable default-case */
}

/**
* @hidden
*/
private unwrapArgs<T extends unknown[]>(args: MapMaybePostTransactionValue<T>): T {
return args.map(arg => {
if (arg instanceof PostTransactionValue) {
const { value } = arg;

/* istanbul ignore if: this should never happen unless we're doing something horribly wrong */
if (!value) {
throw new PolymeshError({
code: ErrorCode.FatalError,
message:
'Post Transaction Value accessed before the corresponding transaction was executed',
});
}

return value;
}
return arg;
}) as T;
}
}
Expand Up @@ -5,8 +5,8 @@ import { SubmittableResultImpl } from '@polymathnetwork/polkadot/api/types';
* Represents a value or method that doesn't exist at the moment, but will exist once a certain transaction
* has been run
*/
export class PostTransactionResolver<Value> {
public result?: Value;
export class PostTransactionValue<Value> {
public value?: Value;

private resolver: (receipt: SubmittableResultImpl) => Promise<Value | undefined>;

Expand All @@ -21,6 +21,6 @@ export class PostTransactionResolver<Value> {
public async run(receipt: SubmittableResultImpl): Promise<void> {
const result = await this.resolver(receipt);

this.result = result;
this.value = result;
}
}

0 comments on commit cf1072f

Please sign in to comment.