Skip to content
This repository has been archived by the owner on May 24, 2022. It is now read-only.

Commit

Permalink
refactor: Rewrite post$ to take a password and not use signer
Browse files Browse the repository at this point in the history
BREAKING CHANGE:

`post$` now requires `passphrase` in its options.

Non-constant contract method calls from `makeContract` now require `passphrase` in their options.

`post$` now returns `{estimated}?` `{signed}` `{sent}` `{confirmed}` and postRaw$ now returns `{sent}` `{confirmed}`
  • Loading branch information
axelchalon committed Jan 22, 2019
1 parent 7641688 commit 689ae52
Show file tree
Hide file tree
Showing 8 changed files with 52 additions and 41 deletions.
5 changes: 5 additions & 0 deletions packages/api/src/rpc/personal/personal.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ class Personal {
.then(outAddress);
}

signTransaction(options, password) {
return this._provider
.send('personal_signTransaction', inOptions(options), password);
}

sendTransaction (options, password) {
return this._provider
.send('personal_sendTransaction', inOptions(options), password);
Expand Down
16 changes: 10 additions & 6 deletions packages/light.js/docs/guides/tutorial4-send-a-transaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,19 @@ Creating a new transaction is a simple affair. The function `post$` takes as fir
- `gasPrice` The price (in Wei) per unit of gas. Will default to something sensible (in Parity's case, this is the median of the distribution formed from the last several hundred transactions).
- `nonce` The sender nonce of the transaction. Will default to the latest known value.

The second (optional) argument describes options to pass to this function. It is an object with the following keys:
The second argument describes options to pass to this function. It is an object with the following keys:

- `estimate`: Will estimate the amount of gas required for this transaction first.
- `passphrase`: The Parity Ethereum passphrase for your `from` account. If you prefer to sign the transaction yourself, you can use the `postRaw$` function instead.
- `estimate` (optional): Will estimate the amount of gas required for this transaction first.

### Return Value

The value to which the RpcObservable evaluates reflects the ongoing status of the transaction as it moves through the process of getting finalised. It is always an object with a single key/value. It can be:

- `{"estimating": true}` The amount of gas required for this transaction to execute is being determined (only fired if `estimate` is set to `true`).
- `{"estimated": value}` The amount of gas required for this transaction to execute has been determined as value; the user is about to be asked (only fired if `estimate` is set to `true`).
- `{"requested": id}` The user has been asked for approval of the transaction; id is the (numeric) identity of the request.
- `{"signed": hash}` The user has approved the transaction and the transaction has been signed to form a final transaction hash of hash. It is now ready to be sent to the network for inclusion in a new block.
- `{"signed": hash}` The transaction has been signed with the given passphrase. It is now ready to be sent to the network for inclusion in a new block.
- `{"sent": hash}` This is the hash of the transaction.
- `{"confirmed": receipt}` The transaction has been confirmed into a block. The receipt of the transaction is provided as receipt, giving information concerning its operation, including any logs.
- `{"failed": error}` The transaction has failed. Generally this is because the user did not approve the transaction or otherwise signing could not take place. error is a string which contains any details regarding the situation.

Expand All @@ -37,12 +38,15 @@ The value to which the RpcObservable evaluates reflects the ongoing status of th
import { post$ } from '@parity/light.js';

post$({
from: '0x921ceff422ef827110ac9dde154fbae2ac4eec9d',
to: '0x180fbce524fd79b4af8dccf83809acd9bc95fd1a',
value: 100 * 1e15 // value in wei
}, {
passphrase: 'mypassphrase'
}).subscribe(console.log);

// Logs:
// { requested: '0x1' }
// { signed: '0x123...ff' }
// { sent: '0x456...ff' }
// { confirmed: {/* receipt object */} }
```
```
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,17 @@ gavcoinContract
'0x407d73d8a49eeb85d32cf465507dd71d507100c1', // The "to" address
new BigNumber(2.01) // The amount to transfer
],
// This 2nd argument is optional, and is an options object containing the following fields:
// This 2nd argument is an options object containing the following fields:
{
from: "0x...",
passphrase: "mypassphrase",
gasPrice: ...
}
)
.subscribe(transfer => console.log('transfer$', transfer));

// Logs:
// transfer$ { requested: '0x2' }
// transfer$ { signed: '0x123...ff' }
// transfer$ { sent: '0x456...ff' }
// transfer$ { confirmed: {/* receipt object */} }
```
8 changes: 5 additions & 3 deletions packages/light.js/src/rpc/other/makeContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const getContract = memoizee(
);

/**
* Create a contract, givan an api object.
* Create a contract, given an api object.
* Pure function version of {@link makeContract}.
*
* @ignore
Expand Down Expand Up @@ -89,15 +89,17 @@ const makeContractWithApi = memoizee(
]
})({ provider: api.provider })(...args);
} else {
const { estimate, passphrase, ...txFields } = options;

return post$({
to: address,
data: abiEncode(
method.name,
method.inputs.map(({ kind: { type } }: any) => type), // TODO Use @parity/api types
args
),
...options
});
...txFields
}, { estimate, passphrase });
}
};
});
Expand Down
45 changes: 21 additions & 24 deletions packages/light.js/src/rpc/other/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { RpcObservableOptions, Tx, TxStatus } from '../../types';

interface PostOptions extends RpcObservableOptions {
estimate?: boolean;
passphrase: String;
}

function getTransactionReceipt (transactionHash: string, api: any) {
Expand All @@ -37,15 +38,21 @@ function getTransactionReceipt (transactionHash: string, api: any) {
/**
* Post a transaction to the network.
*
* Calls, in this order, `eth_estimateGas`, `parity_postTransaction`,
* `parity_checkRequest` and `eth_getTransactionReceipt` to get the status of
* Calls, in this order, `eth_estimateGas`, `personal_signTransaction`,
* `eth_sendRawTransaction` and `eth_getTransactionReceipt` to get the status of
* the transaction.
*
* @param options? - Options to pass to the {@link RpcObservable}.
* @return - The status of the transaction.
* @param tx - Transaction object
* @param options - Options to pass to the {@link RpcObservable}.
* @param options.passphrase - Passphrase of the account
* @return - The status of the transaction: (estimated), signed, sent, confirmed
*/
export function post$ (tx: Tx, options: PostOptions = {}) {
const { estimate, provider } = options;
export function post$ (tx: Tx, options: PostOptions) {
if (!options || !options.passphrase) {
throw new Error('The passphrase is missing from the options');
}

const { estimate, passphrase, provider } = options;
const api = provider ? createApiFromProvider(provider) : getApi();

const source$ = Observable.create(async (observer: Observer<TxStatus>) => {
Expand All @@ -55,22 +62,11 @@ export function post$ (tx: Tx, options: PostOptions = {}) {
const gas = await api.eth.estimateGas(tx);
observer.next({ estimated: gas });
}
const signerRequestId = await api.parity.postTransaction(tx);
observer.next({ requested: signerRequestId });
const transactionHash = await api.pollMethod(
'parity_checkRequest',
signerRequestId
);
if (tx.condition) {
observer.next({ signed: transactionHash, schedule: tx.condition });
} else {
observer.next({ signed: transactionHash });

const receipt = await getTransactionReceipt(transactionHash, api);
observer.next({ confirmed: receipt });
}
const signedTransaction = await api.personal.signTransaction(tx, passphrase);
observer.next({ signed: signedTransaction.raw });
postRaw$(signedTransaction.raw).subscribe(observer);

observer.complete();
} catch (error) {
observer.next({ failed: error });
observer.error(error);
Expand All @@ -87,17 +83,18 @@ export function post$ (tx: Tx, options: PostOptions = {}) {
* Calls, in this order, `eth_sendRawTransaction` and
* `eth_getTransactionReceipt` to get the status of the transaction.
*
* @param rawTx - Raw transaction
* @param options? - Options to pass to the {@link RpcObservable}.
* @return - The status of the transaction.
* @return - The status of the transaction: sent, confirmed
*/
export function postRaw$ (tx: string, options: PostOptions = {}) {
export function postRaw$ (rawTx: string, options: RpcObservableOptions = {}) {
const { provider } = options;
const api = provider ? createApiFromProvider(provider) : getApi();

const source$ = Observable.create(async (observer: Observer<TxStatus>) => {
try {
const transactionHash = await api.eth.sendRawTransaction(tx);
observer.next({ signed: transactionHash });
const transactionHash = await api.eth.sendRawTransaction(rawTx);
observer.next({ sent: transactionHash });

const receipt = await getTransactionReceipt(transactionHash, api);
observer.next({ confirmed: receipt });
Expand Down
8 changes: 5 additions & 3 deletions packages/light.js/src/rpc/rpc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,22 @@ const testRpc = (name: string, rpc$: RpcObservable<any, any>) =>
setApi(resolveApi());
});

const options = name === 'post$' ? { passphrase: 'passphrase' } : {};

it('should be a function', () => {
expect(typeof rpc$).toBe('function');
});

it('should return an Observable', () => {
expect(isObservable(rpc$({}))).toBe(true);
expect(isObservable(rpc$({}, options))).toBe(true);
});

it('result Observable should be subscribable', () => {
expect(() => rpc$({}).subscribe()).not.toThrow();
expect(() => rpc$({}, options).subscribe()).not.toThrow();
});

it('result Observable should return values', done => {
rpc$({}).subscribe(data => {
rpc$({}, options).subscribe(data => {
expect(data).not.toBeNull();
done();
});
Expand Down
3 changes: 1 addition & 2 deletions packages/light.js/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ export interface TxStatus {
estimating?: boolean;
estimated?: BigNumber;
failed?: Error;
requested?: string;
schedule?: any;
signed?: string;
sent?: string;
}
2 changes: 1 addition & 1 deletion scripts/lerna-publish.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ set -e # Quits if there's an error

git remote set-url origin https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git > /dev/null 2>&1

lerna version patch --yes -m "[ci skip] Publish %s"
lerna version --conventional-commits --yes -m "[ci skip] Publish %s"
lerna publish from-git --yes

0 comments on commit 689ae52

Please sign in to comment.