diff --git a/src/contract/factory.ts b/src/contract/factory.ts index 2476b3c8..2872a2df 100644 --- a/src/contract/factory.ts +++ b/src/contract/factory.ts @@ -10,7 +10,8 @@ import type { BytesLike } from '../utils/index.js'; import { getZoneForAddress, isQiAddress } from '../utils/index.js'; import type { ContractInterface, ContractMethodArgs, ContractDeployTransaction, ContractRunner } from './types.js'; import type { ContractTransactionResponse } from './wrappers.js'; -import { Wallet, randomBytes } from '../quais.js'; +import { Wallet } from '../wallet/index.js'; +import { randomBytes } from '../crypto/index.js'; import { getContractAddress } from '../address/address.js'; import { getStatic } from '../utils/properties.js'; import { QuaiTransactionRequest } from '../providers/provider.js'; diff --git a/src/providers/abstract-provider.ts b/src/providers/abstract-provider.ts index 63d2acca..f95fb142 100644 --- a/src/providers/abstract-provider.ts +++ b/src/providers/abstract-provider.ts @@ -12,7 +12,7 @@ // migrate the listener to the static event. We also need to maintain a map // of Signer to address so we can sync respond to listenerCount. -import { getAddress, resolveAddress } from '../address/index.js'; +import { computeAddress, resolveAddress } from '../address/index.js'; import { Shard, toShard, toZone, Zone } from '../constants/index.js'; import { TxInput, TxOutput } from '../transaction/index.js'; import { Outpoint } from '../transaction/utxo.js'; @@ -58,6 +58,7 @@ import type { Networkish } from './network.js'; import type { BlockParams, LogParams, + OutpointResponseParams, QiTransactionResponseParams, TransactionReceiptParams, TransactionResponseParams, @@ -76,7 +77,6 @@ import type { import { WorkObjectLike } from '../transaction/work-object.js'; import { QiTransaction, QuaiTransaction } from '../transaction/index.js'; import { QuaiTransactionResponseParams } from './formatting.js'; -import { keccak256, SigningKey } from '../crypto/index.js'; type Timer = ReturnType; @@ -1098,12 +1098,8 @@ export class AbstractProvider implements Provider { const addr = Array.isArray((request)[key]) ? 'address' in request[key][0] - ? ((request)[key]).map((it) => resolveAddress(hexlify(it.address))) - : ((request)[key]).map((it) => - getAddress( - keccak256('0x' + SigningKey.computePublicKey(it.pub_key).substring(4)).substring(26), - ), - ) + ? ((request)[key]).map((it) => it.address) + : ((request)[key]).map((it) => computeAddress(it.pubkey)) : resolveAddress((request)[key]); if (isPromise(addr)) { if (Array.isArray(addr)) { @@ -1320,7 +1316,16 @@ export class AbstractProvider implements Provider { } async getOutpointsByAddress(address: AddressLike): Promise { - return await this.#getAccountValue({ method: 'getOutpointsByAddress' }, address, 'latest'); + const outpoints: OutpointResponseParams[] = await this.#getAccountValue( + { method: 'getOutpointsByAddress' }, + address, + 'latest', + ); + return outpoints.map((outpoint: OutpointResponseParams) => ({ + txhash: outpoint.Txhash, + index: outpoint.Index, + denomination: outpoint.Denomination, + })); } async getTransactionCount(address: AddressLike, blockTag?: BlockTag): Promise { diff --git a/src/providers/format.ts b/src/providers/format.ts index 92a9f72e..1192a107 100644 --- a/src/providers/format.ts +++ b/src/providers/format.ts @@ -4,6 +4,7 @@ import { getAddress } from '../address/index.js'; import { Signature } from '../crypto/index.js'; import { accessListify } from '../transaction/index.js'; +import { hexlify } from '../utils/data'; import { getBigInt, getNumber, @@ -380,8 +381,8 @@ export function formatTransactionResponse(value: any): TransactionResponseParams index: allowNull((value: any) => (value ? BigInt(value) : null), null), chainId: allowNull((value: any) => (value ? BigInt(value) : null), null), signature: (value: any) => value, - txInputs: allowNull((value: any) => value, null), - txOutputs: allowNull((value: any) => value, null), + txInputs: allowNull((value: any) => value.map(_formatTxInput), null), + txOutputs: allowNull((value: any) => value.map(_formatTxOutput), null), }, { index: ['transactionIndex'], @@ -396,3 +397,21 @@ export function formatTransactionResponse(value: any): TransactionResponseParams return result; } + +const _formatTxInput = object( + { + txhash: formatHash, + index: getNumber, + pubkey: hexlify, + }, + { + txhash: ['previous_out_point', 'hash', 'value'], + index: ['previous_out_point', 'index'], + pubkey: ['pub_key'], + }, +); + +const _formatTxOutput = object({ + address: (addr: string) => hexlify(getAddress(addr)), + denomination: getNumber, +}); diff --git a/src/providers/formatting.ts b/src/providers/formatting.ts index 58afebcf..9f1250e3 100644 --- a/src/providers/formatting.ts +++ b/src/providers/formatting.ts @@ -435,3 +435,9 @@ export interface QiTransactionResponseParams { txInputs?: TxInput[]; } + +export interface OutpointResponseParams { + Txhash: string; + Index: number; + Denomination: number; +} diff --git a/src/providers/provider-jsonrpc.ts b/src/providers/provider-jsonrpc.ts index e4a437d1..66146248 100644 --- a/src/providers/provider-jsonrpc.ts +++ b/src/providers/provider-jsonrpc.ts @@ -13,7 +13,7 @@ // https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/eth1.0-apis/assembled-spec/openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=true&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false import { AbiCoder } from '../abi/index.js'; -import { getAddress } from '../address/index.js'; +import { getAddress, resolveAddress } from '../address/index.js'; import { accessListify, QuaiTransactionLike } from '../transaction/index.js'; import { getBigInt, @@ -26,7 +26,6 @@ import { isError, FetchRequest, defineProperties, - getBytes, resolveProperties, } from '../utils/index.js'; @@ -34,22 +33,15 @@ import { AbstractProvider, UnmanagedSubscriber } from './abstract-provider.js'; import { Network } from './network.js'; import { FilterIdEventSubscriber, FilterIdPendingSubscriber } from './subscriber-filterid.js'; -import type { TransactionLike } from '../transaction/index.js'; +import type { TransactionLike, TxInput, TxOutput } from '../transaction/index.js'; import type { PerformActionRequest, Subscriber, Subscription } from './abstract-provider.js'; import type { Networkish } from './network.js'; import type { Provider, QuaiTransactionRequest, TransactionRequest, TransactionResponse } from './provider.js'; -import { UTXOEntry, UTXOTransactionOutput } from '../transaction/utxo.js'; import { Shard, toShard } from '../constants/index.js'; -import { - AbstractSigner, - resolveAddress, - Signer, - toUtf8Bytes, - TypedDataDomain, - TypedDataEncoder, - TypedDataField, -} from '../quais'; +import { TypedDataDomain, TypedDataEncoder, TypedDataField } from '../hash/index.js'; +import { AbstractSigner, Signer } from '../signers/index.js'; +import { toUtf8Bytes } from '../encoding/index.js'; import { addressFromTransactionRequest } from './provider.js'; type Timer = ReturnType; @@ -234,9 +226,9 @@ export interface AbstractJsonRpcTransactionRequest { export type JsonRpcTransactionRequest = QiJsonRpcTransactionRequest | QuaiJsonRpcTransactionRequest; export interface QiJsonRpcTransactionRequest extends AbstractJsonRpcTransactionRequest { - txInputs?: Array; + txInputs?: Array; - txOutputs?: Array; + txOutputs?: Array; } /** @@ -374,20 +366,6 @@ export class JsonRpcSigner extends AbstractSigner { })(), ); } - } else { - // Make sure the from matches the sender - if (tx.outputs) { - for (let i = 0; i < tx.outputs.length; i++) { - if (tx.outputs[i].address) { - promises.push( - (async () => { - const address = await resolveAddress(hexlify(tx.outputs![i].address)); - tx.outputs![i].address = getBytes(address); - })(), - ); - } - } - } } // Wait until all of our properties are filled in diff --git a/src/providers/provider.ts b/src/providers/provider.ts index 2bbe35cc..1eb55ee3 100644 --- a/src/providers/provider.ts +++ b/src/providers/provider.ts @@ -9,9 +9,8 @@ import { isError, makeError, } from '../utils/index.js'; -import { getAddress } from '../address/index.js'; +import { computeAddress } from '../address/index.js'; import { accessListify } from '../transaction/index.js'; -import { keccak256, SigningKey } from '../crypto/index.js'; import type { AddressLike } from '../address/index.js'; import type { BigNumberish, EventEmitterable } from '../utils/index.js'; @@ -135,9 +134,7 @@ export function addressFromTransactionRequest(tx: TransactionRequest): AddressLi return tx.from; } if (tx.inputs) { - return getAddress( - keccak256('0x' + SigningKey.computePublicKey(tx.inputs[0].pub_key).substring(4)).substring(26), - ); + return computeAddress(tx.inputs[0].pubkey); } if ('to' in tx && tx.to !== null) { return tx.to as AddressLike; @@ -2441,7 +2438,7 @@ export interface Provider extends ContractRunner, EventEmitterable} A promise resolving to the UTXO entries. + * @returns {Promise} A promise resolving to the UTXO entries. * @note On nodes without archive access enabled, the `blockTag` may be * **silently ignored** by the node, which may cause issues if relied on. */ diff --git a/src/transaction/abstract-transaction.ts b/src/transaction/abstract-transaction.ts index cf6abc65..9b749b45 100644 --- a/src/transaction/abstract-transaction.ts +++ b/src/transaction/abstract-transaction.ts @@ -4,7 +4,6 @@ import { getBigInt, assert, assertArgument } from '../utils/index.js'; import type { BigNumberish } from '../utils/index.js'; import type { SignatureLike } from '../crypto/index.js'; import { encodeProtoTransaction } from '../encoding/proto-encode.js'; -import type { TxInput, TxOutput } from './utxo.js'; import { Zone } from '../constants/index.js'; /** @@ -147,12 +146,12 @@ export interface ProtoTransaction { /** * @todo Write documentation for this property. */ - tx_ins?: { tx_ins: Array }; + tx_ins?: { tx_ins: Array }; /** * @todo Write documentation for this property. */ - tx_outs?: { tx_outs: Array }; + tx_outs?: { tx_outs: Array }; /** * @todo Write documentation for this property. @@ -160,6 +159,33 @@ export interface ProtoTransaction { signature?: Uint8Array; } +/** + * @category Transaction + * @todo Write documentation for this type. + * + * @todo If not used, replace with `ignore` + */ +export type ProtoTxOutput = { + address: Uint8Array; + denomination: number; +}; + +/** + * @category Transaction + * @todo Write documentation for this type. + * + * @todo If not used, replace with `ignore` + */ +export type ProtoTxInput = { + previous_out_point: { + hash: { + value: Uint8Array; + }; + index: number; + }; + pub_key: Uint8Array; +}; + /** * @category Transaction * @todo Write documentation for this interface. diff --git a/src/transaction/qi-transaction.ts b/src/transaction/qi-transaction.ts index 6f251095..84606a15 100644 --- a/src/transaction/qi-transaction.ts +++ b/src/transaction/qi-transaction.ts @@ -65,8 +65,7 @@ export class QiTransaction extends AbstractTransaction implements QiTran throw new Error('Transaction must have at least one input and one output'); } - const pubKey = hexlify(this.txInputs[0].pub_key); - const senderAddr = computeAddress(pubKey || ''); + const senderAddr = computeAddress(this.txInputs[0].pubkey || ''); if (!this.destZone || !this.originZone) { throw new Error( @@ -98,8 +97,7 @@ export class QiTransaction extends AbstractTransaction implements QiTran * The zone of the sender address */ get originZone(): Zone | undefined { - const pubKey = hexlify(this.txInputs[0].pub_key); - const senderAddr = computeAddress(pubKey || ''); + const senderAddr = computeAddress(this.txInputs[0].pubkey || ''); const zone = getZoneForAddress(senderAddr); return zone ?? undefined; @@ -109,7 +107,7 @@ export class QiTransaction extends AbstractTransaction implements QiTran * The zone of the recipient address */ get destZone(): Zone | undefined { - const zone = getZoneForAddress(hexlify(this.txOutputs[0].address) || ''); + const zone = getZoneForAddress(this.txOutputs[0].address); return zone ?? undefined; } @@ -183,8 +181,21 @@ export class QiTransaction extends AbstractTransaction implements QiTran const protoTx: ProtoTransaction = { type: this.type || 2, chain_id: formatNumber(this.chainId || 0, 'chainId'), - tx_ins: { tx_ins: this.txInputs }, - tx_outs: { tx_outs: this.txOutputs }, + tx_ins: { + tx_ins: this.txInputs.map((input) => ({ + previous_out_point: { + hash: { value: getBytes(input.txhash) }, + index: input.index, + }, + pub_key: getBytes(input.pubkey), + })), + }, + tx_outs: { + tx_outs: this.txOutputs.map((output) => ({ + address: getBytes(output.address), + denomination: output.denomination, + })), + }, }; if (this.signature && includeSignature) { @@ -246,8 +257,17 @@ export class QiTransaction extends AbstractTransaction implements QiTran tx.chainId = toBigInt(protoTx.chain_id); if (protoTx.type == 2) { - tx.txInputs = protoTx.tx_ins?.tx_ins ?? []; - tx.txOutputs = protoTx.tx_outs?.tx_outs ?? []; + tx.txInputs = + protoTx.tx_ins?.tx_ins.map((input) => ({ + txhash: hexlify(input.previous_out_point.hash.value), + index: input.previous_out_point.index, + pubkey: hexlify(input.pub_key), + })) ?? []; + tx.txOutputs = + protoTx.tx_outs?.tx_outs.map((output) => ({ + address: hexlify(output.address), + denomination: output.denomination, + })) ?? []; } if (protoTx.signature) { diff --git a/src/transaction/utxo.ts b/src/transaction/utxo.ts index 8a362632..288feafc 100644 --- a/src/transaction/utxo.ts +++ b/src/transaction/utxo.ts @@ -9,20 +9,9 @@ import type { BigNumberish } from '../utils/index.js'; * @todo If not used, replace with `ignore` */ export type Outpoint = { - Txhash: string; - Index: number; - Denomination: number; -}; - -/** - * @category Transaction - * @todo Write documentation for this type. - * - * @todo If not used, replace with `ignore` - */ -export type UTXOTransactionInput = { - previousOutPoint: Outpoint; - pubKey: Uint8Array; + txhash: string; + index: number; + denomination: number; }; /** @@ -31,7 +20,7 @@ export type UTXOTransactionInput = { * * @todo If not used, replace with `ignore` */ -export interface UTXOEntry extends UTXOEntryLike { +export interface UTXOEntry { denomination: null | bigint; address: string; } @@ -42,38 +31,11 @@ export interface UTXOEntry extends UTXOEntryLike { * * @todo If not used, replace with `ignore` */ -export interface UTXOEntryLike { - denomination: null | BigNumberish; - address: null | string; +export interface UTXOLike extends UTXOEntry { + txhash?: null | string; + index?: null | number; } -/** - * @category Transaction - * @todo Write documentation for this type. - * - * @todo If not used, replace with `ignore` - */ -export type UTXOTransactionOutputLike = UTXOEntryLike; - -/** - * @category Transaction - * @todo Write documentation for this type. - * - * @todo If not used, replace with `ignore` - */ -export type UTXOTransactionOutput = UTXOEntry; - -/** - * @category Transaction - * @todo Write documentation for this type. - * - * @todo If not used, replace with `ignore` - */ -export type TxOutput = { - address: Uint8Array; - denomination: number; -}; - /** * @category Transaction * @todo Write documentation for this type. @@ -81,13 +43,9 @@ export type TxOutput = { * @todo If not used, replace with `ignore` */ export type TxInput = { - previous_out_point: { - hash: { - value: Uint8Array; - }; - index: number; - }; - pub_key: Uint8Array; + txhash: string; + index: number; + pubkey: string; }; /** @@ -96,21 +54,11 @@ export type TxInput = { * * @todo If not used, replace with `ignore` */ -export interface UTXOEntry { - denomination: null | bigint; +export type TxOutput = { address: string; -} + denomination: number; +}; -/** - * @category Transaction - * @todo Write documentation for this type. - * - * @todo If not used, replace with `ignore` - */ -export interface UTXOLike extends UTXOEntry { - txhash?: null | string; - index?: null | number; -} /** * @category Transaction diff --git a/src/wallet/qi-hdwallet.ts b/src/wallet/qi-hdwallet.ts index f33f66c4..f9c17dcf 100644 --- a/src/wallet/qi-hdwallet.ts +++ b/src/wallet/qi-hdwallet.ts @@ -123,14 +123,14 @@ export class QiHDWallet extends AbstractHDWallet { throw new Error('Transaction has no inputs'); } const input = tx.inputs[0]; - const address = computeAddress(hexlify(input.pub_key)); + const address = computeAddress(input.pubkey); const shard = getZoneForAddress(address); if (!shard) { throw new Error(`Address ${address} not found in any shard`); } // verify all inputs are from the same shard - if (tx.inputs.some((input) => getZoneForAddress(computeAddress(hexlify(input.pub_key))) !== shard)) { + if (tx.inputs.some((input) => getZoneForAddress(computeAddress(input.pubkey)) !== shard)) { throw new Error('All inputs must be from the same shard'); } @@ -188,9 +188,8 @@ export class QiHDWallet extends AbstractHDWallet { // Helper method that returns the private key for the public key private derivePrivateKeyForInput(input: TxInput): string { - if (!input.pub_key) throw new Error('Missing public key for input'); - const pubKey = hexlify(input.pub_key); - const address = computeAddress(pubKey); + if (!input.pubkey) throw new Error('Missing public key for input'); + const address = computeAddress(input.pubkey); // get address info const addressInfo = this.getAddressInfo(address); if (!addressInfo) throw new Error(`Address not found: ${address}`); @@ -427,7 +426,7 @@ export class QiHDWallet extends AbstractHDWallet { throw new Error(`Account ${info.account} not found in wallet`); } // validate Outpoint - if (info.outpoint.Txhash == null || info.outpoint.Index == null || info.outpoint.Denomination == null) { + if (info.outpoint.txhash == null || info.outpoint.index == null || info.outpoint.denomination == null) { throw new Error(`Invalid Outpoint: ${JSON.stringify(info)} `); } });