diff --git a/src/ae/aens.ts b/src/ae/aens.ts index 695e7bf7ca..0929e58a45 100644 --- a/src/ae/aens.ts +++ b/src/ae/aens.ts @@ -366,5 +366,5 @@ export async function aensBid ( nameFee: BigNumber, options: Omit[2], 'nameFee' | 'VSN'> ): ReturnType { - return await aensClaim(name, 0, { ...options, nameFee, VSN: 2 }) + return await aensClaim(name, 0, { ...options, nameFee }) } diff --git a/src/chain.ts b/src/chain.ts index ae5bb67246..08df377289 100644 --- a/src/chain.ts +++ b/src/chain.ts @@ -428,8 +428,8 @@ export async function getName ( export async function resolveName ( nameOrId: AensName | EncodedData, key: string, - { verify = true, resolveByNode, onNode }: - { verify?: boolean, resolveByNode: boolean, onNode: Node } + { verify = true, resolveByNode = false, onNode }: + { verify?: boolean, resolveByNode?: boolean, onNode: Node } ): Promise> { try { const id = nameOrId as EncodedData diff --git a/src/channel/handlers.ts b/src/channel/handlers.ts index ed11834cfd..fa99dad474 100644 --- a/src/channel/handlers.ts +++ b/src/channel/handlers.ts @@ -43,17 +43,17 @@ import { import Channel from '.' import { TX_TYPE } from '../tx/builder/schema' -async function appendSignature ( +export async function appendSignature ( tx: EncodedData<'tx'>, signFn: SignTx ): Promise | number | null> { const { signatures, encodedTx } = unpackTx(tx, TX_TYPE.signed).tx const result = await signFn(encode(encodedTx.rlpEncoded, 'tx')) if (typeof result === 'string') { - const { tx: signedTx, txType } = unpackTx(result, TX_TYPE.signed) + const { tx: signedTx } = unpackTx(result, TX_TYPE.signed) return buildTx({ signatures: signatures.concat(signedTx.signatures), encodedTx: signedTx.encodedTx.rlpEncoded - }, txType).tx + }, TX_TYPE.signed).tx } return result } diff --git a/src/contract/ga.ts b/src/contract/ga.ts index 014c98da8e..65e3bfac5d 100644 --- a/src/contract/ga.ts +++ b/src/contract/ga.ts @@ -151,7 +151,7 @@ export async function createMetaTx ( } // @ts-expect-error createMetaTx needs to be integrated into tx builder const { fee } = await prepareTxParams(TX_TYPE.gaMeta, { ...params, onNode }) - const { rlpEncoded: metaTxRlp } = buildTx({ ...params, fee }, TX_TYPE.gaMeta, { vsn: 2 }) + const { rlpEncoded: metaTxRlp } = buildTx({ ...params, fee }, TX_TYPE.gaMeta) return wrapInEmptySignedTx(metaTxRlp).tx } diff --git a/src/tx/builder/field-types.ts b/src/tx/builder/field-types.ts index 740fd14c3a..5fdee03387 100644 --- a/src/tx/builder/field-types.ts +++ b/src/tx/builder/field-types.ts @@ -97,9 +97,9 @@ export class Deposit extends Field { } export class GasPrice extends Field { - static serialize (value = MIN_GAS_PRICE): Buffer { + static serialize (value: number | string | BigNumber = MIN_GAS_PRICE): Buffer { if (+value < MIN_GAS_PRICE) { - throw new IllegalArgumentError(`Gas price ${value} must be bigger then ${MIN_GAS_PRICE}`) + throw new IllegalArgumentError(`Gas price ${value.toString()} must be bigger then ${MIN_GAS_PRICE}`) } return writeInt(value) } diff --git a/src/tx/builder/index.ts b/src/tx/builder/index.ts index 9323034b56..361b748c00 100644 --- a/src/tx/builder/index.ts +++ b/src/tx/builder/index.ts @@ -8,7 +8,6 @@ import { Field } from './field-types' import { DEFAULT_FEE, FIELD_TYPES, - MIN_GAS_PRICE, RawTxObject, TxField, TxTypeSchemas, @@ -346,41 +345,6 @@ export function validateParams ( ) } -interface TxOptionsRaw { - excludeKeys?: string[] - denomination?: AE_AMOUNT_FORMATS -} -/** - * Build binary transaction - * @param params - Object with tx params - * @param schema - Transaction schema - * @param options - options - * @param options.excludeKeys - Array of keys to exclude for validation and build - * @param options.denomination - Denomination of amounts - * @throws {@link InvalidTxParamsError} - * @returns Array with binary fields of transaction - */ -export function buildRawTx ( - params: TxParamsCommon, - schema: TxField[], - { excludeKeys = [], denomination = AE_AMOUNT_FORMATS.AETTOS }: TxOptionsRaw = {} -): Buffer[] { - params.gasPrice ??= MIN_GAS_PRICE - const filteredSchema = schema.filter(([key]) => !excludeKeys.includes(key)) - - // Transform `amount` type fields to `aettos` - params = transformParams(params, filteredSchema, { denomination }) - // Validation - const valid = validateParams(params, schema, { excludeKeys }) - if (Object.keys(valid).length > 0) { - throw new InvalidTxParamsError('Transaction build error. ' + JSON.stringify(valid)) - } - - return filteredSchema - .map(([key, fieldType]: [keyof TxSchema, FIELD_TYPES, EncodingType]) => serializeField( - params[key], fieldType, params)) -} - /** * Unpack binary transaction * @param binary - Array with binary transaction field's @@ -409,15 +373,13 @@ export interface BuiltTx { txObject: RawTxObject } -export type TxParamsBuild = TxParamsCommon & { - denomination?: AE_AMOUNT_FORMATS -} /** * Build transaction hash - * @param params - Object with tx params + * @param _params - Object with tx params * @param type - Transaction type * @param options - options * @param options.excludeKeys - Array of keys to exclude for validation and build + * @param options.denomination - Denomination of amounts * @param options.prefix - Prefix of transaction * @throws {@link InvalidTxParamsError} * @returns object @@ -425,30 +387,40 @@ export type TxParamsBuild = TxParamsCommon & { * @returns object.rlpEncoded rlp encoded transaction * @returns object.binary binary transaction */ -export function buildTx ( - params: TxParamsBuild, - type: TX_TYPE, +export function buildTx ( + _params: Omit & { VSN?: number }, + type: TxType, { excludeKeys = [], prefix = 'tx', vsn, denomination = AE_AMOUNT_FORMATS.AETTOS }: { excludeKeys?: string[] prefix?: EncodingType vsn?: number denomination?: AE_AMOUNT_FORMATS } = {} -): Prefix extends EncodingType - ? BuiltTx - : BuiltTx { +): BuiltTx { const schemas = TX_SCHEMA[type] vsn ??= Math.max(...Object.keys(schemas).map(a => +a)) if (!isKeyOfObject(vsn, schemas)) throw new SchemaNotFoundError('serialization', TX_TYPE[type], vsn) - const schema = schemas[vsn] + const schema = schemas[vsn] as unknown as TxField[] + + let params = _params as TxParamsCommon & { onNode: Node } + params.VSN = vsn + params.tag = type + const filteredSchema = schema.filter(([key]) => !excludeKeys.includes(key)) + + // Transform `amount` type fields to `aettos` + params = transformParams(params, filteredSchema, { denomination }) + // Validation + const valid = validateParams(params, schema, { excludeKeys }) + if (Object.keys(valid).length > 0) { + throw new InvalidTxParamsError('Transaction build error. ' + JSON.stringify(valid)) + } - const binary = buildRawTx( - { ...params, VSN: vsn, tag: type }, - schema, - { excludeKeys, denomination: params.denomination ?? denomination } - ).filter(e => e !== undefined) + const binary = filteredSchema + .map(([key, fieldType]: [keyof TxSchema, FIELD_TYPES, EncodingType]) => serializeField( + params[key], fieldType, params)) + .filter(e => e !== undefined) const rlpEncoded = rlpEncode(binary) const tx = encode(rlpEncoded, prefix) diff --git a/src/tx/builder/schema.ts b/src/tx/builder/schema.ts index 847bdc68ef..fd9adb91c2 100644 --- a/src/tx/builder/schema.ts +++ b/src/tx/builder/schema.ts @@ -198,7 +198,7 @@ interface BuildFieldTypes[Type] : never +type NullablePartial< + T, + NK extends keyof T = { [K in keyof T]: undefined extends T[K] ? K : never }[keyof T] +> = Partial> & Omit + type BuildTxArgBySchema = UnionToIntersection< SchemaLine extends ReadonlyArray ? Elem extends TxElem - ? { [k in Elem[0]]: BuildTxArgBySchemaType } + ? NullablePartial<{ [k in Elem[0]]: BuildTxArgBySchemaType }> : never : never > diff --git a/src/tx/index.ts b/src/tx/index.ts index 5d25fd2e2f..c565a2e84e 100644 --- a/src/tx/index.ts +++ b/src/tx/index.ts @@ -23,7 +23,7 @@ * These methods provide ability to create native transactions. */ import { - ABI_VERSIONS, CtVersion, PROTOCOL_VM_ABI, TX_TYPE, TX_TTL, TxParamsCommon, TxTypeSchemas + ABI_VERSIONS, CtVersion, PROTOCOL_VM_ABI, TX_TYPE, TX_TTL, TxParamsCommon } from './builder/schema' import { ArgumentError, UnsupportedProtocolError, UnknownTxError, InvalidTxParamsError @@ -35,6 +35,8 @@ import { buildTx as syncBuildTx, calculateFee, unpackTx } from './builder/index' import { isKeyOfObject } from '../utils/other' import AccountBase from '../account/base' +type Int = number | string | BigNumber + // uses a new feature, probably typescript-eslint doesn't support it yet // eslint-disable-next-line @typescript-eslint/no-unused-vars export type BuildTxOptions = @@ -43,7 +45,9 @@ export type BuildTxOptions = // TODO: find a better name or rearrange methods export async function _buildTx ( txType: TxType, - { onAccount, ..._params }: TxTypeSchemas[TxType] & { onNode: Node, onAccount: AccountBase } + { onAccount, ..._params }: + Omit>[0], 'fee' | 'nonce' | 'ttl'> + & { onNode: Node, onAccount: AccountBase, fee?: Int, nonce?: number, ttl?: number } ): Promise> { // TODO: avoid this assertion const params = _params as unknown as TxParamsCommon & { onNode: Node } @@ -103,7 +107,7 @@ export async function _buildTx ( // TODO: do this check on TypeScript level if (senderId == null) throw new InvalidTxParamsError(`Transaction field ${senderKey} is missed`) const extraParams = await prepareTxParams(txType, { ...params, senderId }) - return syncBuildTx({ ...params, ...extraParams }, txType).tx + return syncBuildTx({ ...params, ...extraParams } as any, txType).tx } /** @@ -159,17 +163,13 @@ export async function prepareTxParams ( }: Pick & { senderId: EncodedData<'ak'> vsn?: number - gasLimit?: number | string | BigNumber + gasLimit?: Int absoluteTtl?: boolean strategy?: 'continuity' | 'max' showWarning?: boolean onNode: Node } -): Promise<{ - fee: number | string | BigNumber - ttl: number - nonce: number - }> { +): Promise<{ fee: Int, ttl: number, nonce: number }> { nonce ??= ( await onNode.getAccountNextNonce(senderId, { strategy }).catch(() => ({ nextNonce: 1 })) ).nextNonce diff --git a/test/integration/channel.ts b/test/integration/channel.ts index 25270f3a37..0c323a1b9f 100644 --- a/test/integration/channel.ts +++ b/test/integration/channel.ts @@ -32,6 +32,7 @@ import { ChannelConnectionError } from '../../src/utils/errors' import { EncodedData } from './../../src/utils/encoder' +import { appendSignature } from '../../src/channel/handlers' const wsUrl = process.env.TEST_WS_URL ?? 'ws://localhost:3014/channel' @@ -1146,16 +1147,6 @@ describe('Channel', function () { }) it('can post backchannel update', async () => { - function appendSignature (target: EncodedData<'tx'>, source: EncodedData<'tx'>): EncodedData<'tx'> { - const { txType, tx: { signatures, encodedTx: { rlpEncoded } } } = - unpackTx(target, TX_TYPE.signed) - return buildTx( - { - signatures: signatures.concat(unpackTx(source, TX_TYPE.signed).tx.signatures), - encodedTx: rlpEncoded - }, txType).tx - } - initiatorCh.disconnect() responderCh.disconnect() initiatorCh = await Channel.initialize({ @@ -1183,9 +1174,9 @@ describe('Channel', function () { await aeSdkInitiatior.address(), await aeSdkResponder.address(), 100, - async (tx) => appendSignature( + async (tx) => await appendSignature( await aeSdkResponder.signTransaction(tx), - await aeSdkInitiatior.signTransaction(tx) + async tx => await (aeSdkInitiatior.signTransaction(tx) as Promise>) ) ) result.accepted.should.equal(true) diff --git a/test/integration/index.ts b/test/integration/index.ts index 576148c1fb..0043a47a54 100644 --- a/test/integration/index.ts +++ b/test/integration/index.ts @@ -55,7 +55,6 @@ export const BaseAe = async ( export const spendPromise = (async () => { const ae = await BaseAe({ networkId, withoutGenesisAccount: false }) await ae.awaitHeight(2) - // @ts-expect-error await ae.spend(1e26, account.publicKey) })() diff --git a/test/unit/tx.ts b/test/unit/tx.ts index a3b9db18c5..eda71d99f5 100644 --- a/test/unit/tx.ts +++ b/test/unit/tx.ts @@ -115,7 +115,7 @@ describe('Tx', function () { }) it('Serialize tx: invalid tx VSN', () => { - expect(() => buildTx({}, TX_TYPE.spend, { vsn: 5 })) + expect(() => buildTx({} as any, TX_TYPE.spend, { vsn: 5 })) .to.throw(SchemaNotFoundError, 'Transaction serialization not implemented for spend version 5') }) })