Skip to content

Commit

Permalink
Update Qi UTXO related types (#204)
Browse files Browse the repository at this point in the history
* Update Qi UTXO related types

* Update tx input and outpoint types

* Fix pubkey being hexlified

* add methods 'signMessage' and 'signMessageBytes'

* refactor '_getHDNodeForAddress' to support change addresses

* Fallback to wallet address when populating tx in `signTransaction`

* Remove Qi logic

* Better from address validation

* add JsonRpcSigner code back

* add getSigner back to browser provider

* Update version

* move 'deserialize' to child classes and eliminate cyclic dependencies

* add method to validate imported outpoints

* add new method 'signTypedData'

* add JSDOC comments to new methods

* Update Qi UTXO related types

* Update tx input and outpoint types

* Fix compile errors

* Fix importing from quais export file

---------

Co-authored-by: Alejo Acosta <[email protected]>
Co-authored-by: robertlincecum <[email protected]>
  • Loading branch information
3 people authored Jun 18, 2024
1 parent 3ad8e6a commit d4e211b
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 130 deletions.
3 changes: 2 additions & 1 deletion src/contract/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
23 changes: 14 additions & 9 deletions src/providers/abstract-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -58,6 +58,7 @@ import type { Networkish } from './network.js';
import type {
BlockParams,
LogParams,
OutpointResponseParams,
QiTransactionResponseParams,
TransactionReceiptParams,
TransactionResponseParams,
Expand All @@ -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<typeof setTimeout>;

Expand Down Expand Up @@ -1098,12 +1098,8 @@ export class AbstractProvider<C = FetchRequest> implements Provider {

const addr = Array.isArray((<any>request)[key])
? 'address' in <any>request[key][0]
? (<TxOutput[]>(<any>request)[key]).map((it) => resolveAddress(hexlify(it.address)))
: (<TxInput[]>(<any>request)[key]).map((it) =>
getAddress(
keccak256('0x' + SigningKey.computePublicKey(it.pub_key).substring(4)).substring(26),
),
)
? (<TxOutput[]>(<any>request)[key]).map((it) => it.address)
: (<TxInput[]>(<any>request)[key]).map((it) => computeAddress(it.pubkey))
: resolveAddress((<any>request)[key]);
if (isPromise(addr)) {
if (Array.isArray(addr)) {
Expand Down Expand Up @@ -1320,7 +1316,16 @@ export class AbstractProvider<C = FetchRequest> implements Provider {
}

async getOutpointsByAddress(address: AddressLike): Promise<Outpoint[]> {
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<number> {
Expand Down
23 changes: 21 additions & 2 deletions src/providers/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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'],
Expand All @@ -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,
});
6 changes: 6 additions & 0 deletions src/providers/formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,9 @@ export interface QiTransactionResponseParams {

txInputs?: TxInput[];
}

export interface OutpointResponseParams {
Txhash: string;
Index: number;
Denomination: number;
}
36 changes: 7 additions & 29 deletions src/providers/provider-jsonrpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -26,30 +26,22 @@ import {
isError,
FetchRequest,
defineProperties,
getBytes,
resolveProperties,
} from '../utils/index.js';

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<typeof setTimeout>;
Expand Down Expand Up @@ -234,9 +226,9 @@ export interface AbstractJsonRpcTransactionRequest {
export type JsonRpcTransactionRequest = QiJsonRpcTransactionRequest | QuaiJsonRpcTransactionRequest;

export interface QiJsonRpcTransactionRequest extends AbstractJsonRpcTransactionRequest {
txInputs?: Array<UTXOEntry>;
txInputs?: Array<TxInput>;

txOutputs?: Array<UTXOTransactionOutput>;
txOutputs?: Array<TxOutput>;
}

/**
Expand Down Expand Up @@ -374,20 +366,6 @@ export class JsonRpcSigner extends AbstractSigner<JsonRpcApiProvider> {
})(),
);
}
} 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
Expand Down
9 changes: 3 additions & 6 deletions src/providers/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -2441,7 +2438,7 @@ export interface Provider extends ContractRunner, EventEmitterable<ProviderEvent
*
* @param {AddressLike} address - The address to fetch the UTXO entries for.
*
* @returns {Promise<UTXOEntry[]>} A promise resolving to the UTXO entries.
* @returns {Promise<Outpoint[]>} 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.
*/
Expand Down
32 changes: 29 additions & 3 deletions src/transaction/abstract-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -147,19 +146,46 @@ export interface ProtoTransaction {
/**
* @todo Write documentation for this property.
*/
tx_ins?: { tx_ins: Array<TxInput> };
tx_ins?: { tx_ins: Array<ProtoTxInput> };

/**
* @todo Write documentation for this property.
*/
tx_outs?: { tx_outs: Array<TxOutput> };
tx_outs?: { tx_outs: Array<ProtoTxOutput> };

/**
* @todo Write documentation for this property.
*/
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.
Expand Down
38 changes: 29 additions & 9 deletions src/transaction/qi-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ export class QiTransaction extends AbstractTransaction<string> 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(
Expand Down Expand Up @@ -98,8 +97,7 @@ export class QiTransaction extends AbstractTransaction<string> 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;
Expand All @@ -109,7 +107,7 @@ export class QiTransaction extends AbstractTransaction<string> 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;
}

Expand Down Expand Up @@ -183,8 +181,21 @@ export class QiTransaction extends AbstractTransaction<string> 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) {
Expand Down Expand Up @@ -246,8 +257,17 @@ export class QiTransaction extends AbstractTransaction<string> 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) {
Expand Down
Loading

0 comments on commit d4e211b

Please sign in to comment.