From e42d02d25c8d605b318ce28147acbb82bb33a1d6 Mon Sep 17 00:00:00 2001 From: zhixian Date: Sat, 27 Jun 2020 15:20:40 +0800 Subject: [PATCH] refactor(transaction, signer): add WitnessArgs to transaction with WitnessArgs, developers can get more flexibility in transaction design. BREAKING CHANGE: required parameter 'witnessArgs' is added to Transaction's contructor. the default values can be found in static attribute WITNESS_ARGS of Builder. --- package.json | 2 +- src/builders/builder.ts | 12 ++++++++++++ src/builders/simple-builder.ts | 27 ++++++++++----------------- src/core.ts | 2 +- src/models/transaction.spec.ts | 6 +++++- src/models/transaction.ts | 29 +++++++++++++++++++---------- src/signers/eth-signer.ts | 1 + src/signers/signer.ts | 12 +++++++----- 8 files changed, 56 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index e4eb291..c91ecba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lay2/pw-core", - "version": "0.2.5", + "version": "0.2.6", "description": "the javascript sdk of pw-sdk", "main": "build/main/index.js", "typings": "build/main/index.d.ts", diff --git a/src/builders/builder.ts b/src/builders/builder.ts index b46648e..9bac401 100644 --- a/src/builders/builder.ts +++ b/src/builders/builder.ts @@ -7,6 +7,18 @@ const FEE_BASE = 1000; export abstract class Builder { static readonly MIN_FEE_RATE = 1000; static readonly MIN_CHANGE = new Amount('61', AmountUnit.ckb); + static readonly WITNESS_ARGS = { + Secp256k1: { + lock: '0x' + '0'.repeat(130), + input_type: '', + output_type: '', + }, + Secp256r1: { + lock: '0x' + '0'.repeat(600), + input_type: '', + output_type: '', + }, + }; static calcFee( tx: Transaction, diff --git a/src/builders/simple-builder.ts b/src/builders/simple-builder.ts index 0fbb55d..3094014 100644 --- a/src/builders/simple-builder.ts +++ b/src/builders/simple-builder.ts @@ -20,9 +20,9 @@ export class SimpleBuilder extends Builder { super(feeRate, collector); } - async build(): Promise { + async build(fee: Amount = new Amount('0')): Promise { const outputCell = new Cell(this.amount, this.address.toLockScript()); - const neededAmount = this.amount.add(Builder.MIN_CHANGE); + const neededAmount = this.amount.add(Builder.MIN_CHANGE).add(fee); let inputSum = new Amount('0'); const inputCells: Cell[] = []; @@ -51,27 +51,20 @@ export class SimpleBuilder extends Builder { ); const tx = new Transaction( - new RawTransaction(inputCells, [outputCell, changeCell]) + new RawTransaction(inputCells, [outputCell, changeCell]), + [Builder.WITNESS_ARGS.Secp256k1] ); this.fee = Builder.calcFee(tx); - if (this.fee.add(Builder.MIN_CHANGE).gt(changeCell.capacity)) { - // TODO: collect more cells and recalculate fee, until input capacity is - // enough or no more available unspent cells. - throw new Error( - `input capacity not enough, need ${outputCell.capacity - .add(this.fee) - .toString(AmountUnit.ckb)}, got ${inputSum.toString(AmountUnit.ckb)}` - ); + if (changeCell.capacity.gte(Builder.MIN_CHANGE.add(this.fee))) { + changeCell.capacity = changeCell.capacity.sub(this.fee); + tx.raw.outputs.pop(); + tx.raw.outputs.push(changeCell); + return tx; } - // sub fee from changeCell - changeCell.capacity = changeCell.capacity.sub(this.fee); - tx.raw.outputs.pop(); - tx.raw.outputs.push(changeCell); - - return tx; + return this.build(this.fee); } getCollector() { diff --git a/src/core.ts b/src/core.ts index b5df700..2ef8240 100644 --- a/src/core.ts +++ b/src/core.ts @@ -98,7 +98,7 @@ export default class PWCore { feeRate?: number ): Promise { const simpleBuilder = new SimpleBuilder(address, amount, feeRate); - const ethSigner = new EthSigner(address.addressString); + const ethSigner = new EthSigner(PWCore.provider.address.addressString); return this.sendTransaction(simpleBuilder, ethSigner); } diff --git a/src/models/transaction.spec.ts b/src/models/transaction.spec.ts index e14c113..11ff6b4 100644 --- a/src/models/transaction.spec.ts +++ b/src/models/transaction.spec.ts @@ -10,6 +10,7 @@ import PWCore, { } from '..'; import { DummyCollector } from '../collectors/dummy-collector'; import { DummyProvider } from '../providers/dummy-provider'; +import { Builder } from '../builders'; const test = anyTest as TestInterface<{ tx: Transaction }>; @@ -44,7 +45,10 @@ test.before(async (t) => { const outputs = cells.slice(1); const cellDeps = [new CellDep(DepType.depGroup, outPoint4)]; - t.context.tx = new Transaction(new RawTransaction(inputs, outputs, cellDeps)); + t.context.tx = new Transaction( + new RawTransaction(inputs, outputs, cellDeps), + [Builder.WITNESS_ARGS.Secp256k1] + ); }); test('validate', (t) => { diff --git a/src/models/transaction.ts b/src/models/transaction.ts index 9f69583..bebfec0 100644 --- a/src/models/transaction.ts +++ b/src/models/transaction.ts @@ -1,23 +1,32 @@ -import { CKBModel } from '../interfaces'; +import { CKBModel, WitnessArgs } from '../interfaces'; import { ECDSA_WITNESS_LEN } from '../constants'; -import { validators, normalizers, transformers } from 'ckb-js-toolkit'; -// import { SerializeTransaction } from 'ckb-js-toolkit-contrib/src/blockchain'; -// import { signer } from 'ckb-js-toolkit-contrib/src'; -import { SerializeTransaction } from '@ckb-lumos/types/lib/core'; +import { validators, normalizers, transformers, Reader } from 'ckb-js-toolkit'; +import { + SerializeTransaction, + SerializeWitnessArgs, +} from '@ckb-lumos/types/lib/core'; import { RawTransaction } from '.'; export class Transaction implements CKBModel { - // TODO: add WitnessArgs to adapt different transaction structures public witnesses: string[]; constructor( public raw: RawTransaction, - witnessesLength: number[] = [ECDSA_WITNESS_LEN] + public witnessArgs: WitnessArgs[], + witnessLengths: number[] = [ECDSA_WITNESS_LEN] ) { - // fill witnesses with actural length to make tx size accurate this.witnesses = raw.inputs.map((_) => '0x'); - for (let i = 0; i < witnessesLength.length; i++) { - this.witnesses[i] = '0x' + '0'.repeat(witnessesLength[i] - 2); + for (let i = 0; i < witnessLengths.length; i++) { + this.witnesses[i] = '0x' + '0'.repeat(witnessLengths[i] - 2); + } + if (!Array.isArray(witnessArgs)) + throw new Error('[Transaction] - witnessArgs must be an Array!'); + for (let i = 0; i < witnessArgs.length; i++) { + this.witnesses[i] = new Reader( + SerializeWitnessArgs( + normalizers.NormalizeWitnessArgs(this.witnessArgs[i]) + ) + ).serializeJson(); } } diff --git a/src/signers/eth-signer.ts b/src/signers/eth-signer.ts index 6a0dbe6..921d41c 100644 --- a/src/signers/eth-signer.ts +++ b/src/signers/eth-signer.ts @@ -63,6 +63,7 @@ export class EthSigner extends Signer { reject(result.error); } result = result.result; + let v = Number.parseInt(result.slice(-2), 16); if (v >= 27) v -= 27; result = result.slice(0, -2) + v.toString(16).padStart(2, '0'); resolve([result]); diff --git a/src/signers/signer.ts b/src/signers/signer.ts index 0e85f97..d4923f3 100644 --- a/src/signers/signer.ts +++ b/src/signers/signer.ts @@ -18,17 +18,19 @@ export abstract class Signer { protected abstract async signMessages(messages: Message[]): Promise; async sign(tx: Transaction): Promise { - tx.witnesses[0] = `0x5500000010000000550000005500000041000000${'0'.repeat( - 130 - )}`; + console.log('[signer] tx before: ', tx); const messages = this.toMessages(tx); const witnesses = await this.signMessages(messages); witnesses[0] = new Reader( SerializeWitnessArgs( - normalizers.NormalizeWitnessArgs({ lock: witnesses[0] }) + normalizers.NormalizeWitnessArgs({ + ...tx.witnessArgs[0], + lock: witnesses[0], + }) ) ).serializeJson(); - FillSignedWitnesses(tx, messages, witnesses); + tx = FillSignedWitnesses(tx, messages, witnesses); + console.log('[signer] tx after: ', tx); return tx; }