Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qi gas fee #309

Merged
merged 20 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions examples/wallets/qi-send.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const quais = require('../../lib/commonjs/quais');
require('dotenv').config();

// Descrepancy between our serialized data and go quais in that ours in inlcude extra data at the end -> 201406c186bf3b66571cfdd8c7d9336df2298e4d4a9a2af7fcca36fbdfb0b43459a41c45b6c8885dc1f828d44fd005572cbac4cd72dc598790429255d19ec32f7750e

async function main() {
// Create provider
console.log('RPC URL: ', process.env.RPC_URL);
const provider = new quais.JsonRpcProvider(process.env.RPC_URL);

// Create wallet and connect to provider
const mnemonic = quais.Mnemonic.fromPhrase(process.env.MNEMONIC);
const aliceQiWallet = quais.QiHDWallet.fromMnemonic(mnemonic);
aliceQiWallet.connect(provider);

// Initialize Qi wallet
console.log('Initializing Alice wallet...');
await aliceQiWallet.scan(quais.Zone.Cyprus1);
console.log('Alice wallet scan complete');
console.log('Serializing Alice wallet...');
const serializedWallet = aliceQiWallet.serialize();

const summary = {
'Total Addresses': serializedWallet.addresses.length,
'Change Addresses': serializedWallet.changeAddresses.length,
'Gap Addresses': serializedWallet.gapAddresses.length,
'Gap Change Addresses': serializedWallet.gapChangeAddresses.length,
Outpoints: serializedWallet.outpoints.length,
'Coin Type': serializedWallet.coinType,
Version: serializedWallet.version,
};

console.log('Alice Wallet Summary:');
console.table(summary);

const addressTable = serializedWallet.addresses.map((addr) => ({
PubKey: addr.pubKey,
Address: addr.address,
Index: addr.index,
Change: addr.change ? 'Yes' : 'No',
Zone: addr.zone,
}));

console.log('\nAlice Wallet Addresses (first 10):');
console.table(addressTable.slice(0, 10));

const outpointsInfoTable = serializedWallet.outpoints.map((outpoint) => ({
Address: outpoint.address,
Denomination: outpoint.outpoint.denomination,
Index: outpoint.outpoint.index,
TxHash: outpoint.outpoint.txhash,
Zone: outpoint.zone,
Account: outpoint.account,
}));

console.log('\nAlice Outpoints Info (first 10):');
console.table(outpointsInfoTable.slice(0, 10));

console.log(`Generating Bob's wallet and payment code...`);
const bobMnemonic = quais.Mnemonic.fromPhrase(
'innocent perfect bus miss prevent night oval position aspect nut angle usage expose grace juice',
);
const bobQiWallet = quais.QiHDWallet.fromMnemonic(bobMnemonic);
const bobPaymentCode = await bobQiWallet.getPaymentCode(0);
console.log('Bob Payment code: ', bobPaymentCode);

// Alice opens a channel to send Qi to Bob
aliceQiWallet.openChannel(bobPaymentCode, 'sender');

// Alice sends 1000 Qi to Bob
const tx = await aliceQiWallet.sendTransaction(bobPaymentCode, 750000, quais.Zone.Cyprus1, quais.Zone.Cyprus1);
console.log('Transaction sent: ', tx);
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
267 changes: 242 additions & 25 deletions src/_tests/unit/coinselection.unit.test.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/encoding/protoc/proto_block.proto
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,5 @@ message ProtoOutPoint {
message ProtoTxOut {
optional uint32 denomination = 1;
optional bytes address = 2;
optional bytes lock = 3;
}
40 changes: 38 additions & 2 deletions src/encoding/protoc/proto_block.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Generated by the protoc-gen-ts. DO NOT EDIT!
* compiler version: 4.25.3
* compiler version: 4.24.3
* source: proto_block.proto
* git: https://github.com/thesayyn/protoc-gen-ts */
import * as dependency_1 from "./proto_common.js";
Expand Down Expand Up @@ -4394,11 +4394,13 @@ export namespace block {
}
}
export class ProtoTxOut extends pb_1.Message {
#one_of_decls: number[][] = [[1], [2]];
#one_of_decls: number[][] = [[1], [2], [3]];
constructor(data?: any[] | ({} & (({
denomination?: number;
}) | ({
address?: Uint8Array;
}) | ({
lock?: Uint8Array;
})))) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
Expand All @@ -4409,6 +4411,9 @@ export namespace block {
if ("address" in data && data.address != undefined) {
this.address = data.address;
}
if ("lock" in data && data.lock != undefined) {
this.lock = data.lock;
}
}
}
get denomination() {
Expand All @@ -4429,6 +4434,15 @@ export namespace block {
get has_address() {
return pb_1.Message.getField(this, 2) != null;
}
get lock() {
return pb_1.Message.getFieldWithDefault(this, 3, new Uint8Array(0)) as Uint8Array;
}
set lock(value: Uint8Array) {
pb_1.Message.setOneofField(this, 3, this.#one_of_decls[2], value);
}
get has_lock() {
return pb_1.Message.getField(this, 3) != null;
}
get _denomination() {
const cases: {
[index: number]: "none" | "denomination";
Expand All @@ -4447,9 +4461,19 @@ export namespace block {
};
return cases[pb_1.Message.computeOneofCase(this, [2])];
}
get _lock() {
const cases: {
[index: number]: "none" | "lock";
} = {
0: "none",
3: "lock"
};
return cases[pb_1.Message.computeOneofCase(this, [3])];
}
static fromObject(data: {
denomination?: number;
address?: Uint8Array;
lock?: Uint8Array;
}): ProtoTxOut {
const message = new ProtoTxOut({});
if (data.denomination != null) {
Expand All @@ -4458,19 +4482,26 @@ export namespace block {
if (data.address != null) {
message.address = data.address;
}
if (data.lock != null) {
message.lock = data.lock;
}
return message;
}
toObject() {
const data: {
denomination?: number;
address?: Uint8Array;
lock?: Uint8Array;
} = {};
if (this.denomination != null) {
data.denomination = this.denomination;
}
if (this.address != null) {
data.address = this.address;
}
if (this.lock != null) {
data.lock = this.lock;
}
return data;
}
serialize(): Uint8Array;
Expand All @@ -4481,6 +4512,8 @@ export namespace block {
writer.writeUint32(1, this.denomination);
if (this.has_address)
writer.writeBytes(2, this.address);
if (this.has_lock)
writer.writeBytes(3, this.lock);
if (!w)
return writer.getResultBuffer();
}
Expand All @@ -4496,6 +4529,9 @@ export namespace block {
case 2:
message.address = reader.readBytes();
break;
case 3:
message.lock = reader.readBytes();
break;
default: reader.skipField();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/encoding/protoc/proto_common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Generated by the protoc-gen-ts. DO NOT EDIT!
* compiler version: 4.25.3
* compiler version: 4.24.3
* source: proto_common.proto
* git: https://github.com/thesayyn/protoc-gen-ts */
import * as pb_1 from "google-protobuf";
Expand Down
63 changes: 37 additions & 26 deletions src/providers/abstract-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,7 @@ import type { BigNumberish } from '../utils/index.js';
import type { Listener } from '../utils/index.js';

import type { Networkish } from './network.js';
import type {
BlockParams,
LogParams,
OutpointResponseParams,
QiTransactionResponseParams,
TransactionReceiptParams,
TransactionResponseParams,
} from './formatting.js';
import type { BlockParams, LogParams, OutpointResponseParams, TransactionReceiptParams } from './formatting.js';

import type {
BlockTag,
Expand Down Expand Up @@ -1020,11 +1013,21 @@ export class AbstractProvider<C = FetchRequest> implements Provider {
*/
// TODO: `newtork` is not used, remove or re-write
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_wrapTransactionResponse(tx: TransactionResponseParams, network: Network): TransactionResponse {
if ('from' in tx) {
return new QuaiTransactionResponse(formatTransactionResponse(tx) as QuaiTransactionResponseParams, this);
} else {
return new QiTransactionResponse(formatTransactionResponse(tx) as QiTransactionResponseParams, this);
_wrapTransactionResponse(tx: any, network: Network): TransactionResponse {
try {
if (tx.type === 0 || tx.type === 1) {
// For QuaiTransaction, format and wrap as before
const formattedTx = formatTransactionResponse(tx) as QuaiTransactionResponseParams;
return new QuaiTransactionResponse(formattedTx, this);
} else if (tx.type === 2) {
// For QiTransaction, use fromProto() directly
return new QiTransactionResponse(tx, this);
} else {
throw new Error('Unknown transaction type');
}
} catch (error) {
console.error('Error in _wrapTransactionResponse:', error);
throw error;
}
}

Expand Down Expand Up @@ -1529,25 +1532,33 @@ export class AbstractProvider<C = FetchRequest> implements Provider {
// Write
async broadcastTransaction(zone: Zone, signedTx: string): Promise<TransactionResponse> {
const type = decodeProtoTransaction(getBytes(signedTx)).type;
const { blockNumber, hash, network } = await resolveProperties({
blockNumber: this.getBlockNumber(toShard(zone)),
hash: this._perform({
method: 'broadcastTransaction',
signedTransaction: signedTx,
zone: zone,
}),
network: this.getNetwork(),
});
try {
const { blockNumber, hash, network } = await resolveProperties({
blockNumber: this.getBlockNumber(toShard(zone)),
hash: this._perform({
method: 'broadcastTransaction',
signedTransaction: signedTx,
zone: zone,
}),
network: this.getNetwork(),
});

const tx = type == 2 ? QiTransaction.from(signedTx) : QuaiTransaction.from(signedTx);
const tx = type == 2 ? QiTransaction.from(signedTx) : QuaiTransaction.from(signedTx);
const txObj = tx.toJSON();

this.#validateTransactionHash(tx.hash || '', hash);
return this._wrapTransactionResponse(<any>tx, network).replaceableTransaction(blockNumber);
this.#validateTransactionHash(tx.hash || '', hash);

const wrappedTx = this._wrapTransactionResponse(<any>txObj, network);
return wrappedTx.replaceableTransaction(blockNumber);
} catch (error) {
console.error('Error in broadcastTransaction:', error);
throw error;
}
}

#validateTransactionHash(computedHash: string, nodehash: string) {
if (computedHash !== nodehash) {
throw new Error('Transaction hash mismatch');
throw new Error(`Transaction hash mismatch: ${computedHash} !== ${nodehash}`);
}
}

Expand Down
19 changes: 16 additions & 3 deletions src/providers/provider-jsonrpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,7 @@ export abstract class JsonRpcApiProvider<C = FetchRequest> extends AbstractProvi
if (tx && tx.type != null && getBigInt(tx.type)) {
// If there are no EIP-1559 properties, it might be non-EIP-a559
if (tx.maxFeePerGas == null && tx.maxPriorityFeePerGas == null) {
const feeData = await this.getFeeData(req.zone);
const feeData = await this.getFeeData(req.zone, tx.type === 1); // tx type 1 is Quai and 2 is Qi
if (feeData.maxFeePerGas == null && feeData.maxPriorityFeePerGas == null) {
// Network doesn't know about EIP-1559 (and hence type)
req = Object.assign({}, req, {
Expand Down Expand Up @@ -1119,7 +1119,6 @@ export abstract class JsonRpcApiProvider<C = FetchRequest> extends AbstractProvi
(<any>result)[dstKey] = toQuantity(getBigInt((<any>tx)[key], `tx.${key}`));
});

// Make sure addresses and data are lowercase
['from', 'to', 'data'].forEach((key) => {
if ((<any>tx)[key] == null) {
return;
Expand All @@ -1132,8 +1131,22 @@ export abstract class JsonRpcApiProvider<C = FetchRequest> extends AbstractProvi
(result as QuaiJsonRpcTransactionRequest)['accessList'] = accessListify(tx.accessList);
}
} else {
throw new Error('No Qi getRPCTransaction implementation yet');
if ((<any>tx).txInputs != null) {
(result as QiJsonRpcTransactionRequest)['txInputs'] = (<any>tx).txInputs.map((input: TxInput) => ({
txhash: hexlify(input.txhash),
index: toQuantity(getBigInt(input.index, `tx.txInputs.${input.index}`)),
pubkey: hexlify(input.pubkey),
}));
}

if ((<any>tx).txOutputs != null) {
(result as QiJsonRpcTransactionRequest)['txOutputs'] = (<any>tx).txOutputs.map((output: TxOutput) => ({
address: hexlify(output.address),
denomination: toQuantity(getBigInt(output.denomination, `tx.txOutputs.${output.denomination}`)),
}));
}
}

return result;
}

Expand Down
3 changes: 2 additions & 1 deletion src/providers/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2743,9 +2743,10 @@ export interface Provider extends ContractRunner, EventEmitterable<ProviderEvent
* Get the best guess at the recommended {@link FeeData | **FeeData**}.
*
* @param {Zone} zone - The shard to fetch the fee data from.
* @param {boolean} txType - The transaction type to fetch the fee data for (true for Quai, false for Qi)
* @returns {Promise<FeeData>} A promise resolving to the fee data.
*/
getFeeData(zone: Zone): Promise<FeeData>;
getFeeData(zone: Zone, txType: boolean): Promise<FeeData>;

/**
* Get a work object to package a transaction in.
Expand Down
2 changes: 2 additions & 0 deletions src/quais.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ export {
decryptKeystoreJson,
encryptKeystoreJson,
encryptKeystoreJsonSync,
SerializedHDWallet,
SerializedQiHDWallet,
} from './wallet/index.js';

// WORDLIST
Expand Down
6 changes: 1 addition & 5 deletions src/signers/abstract-signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,6 @@ export abstract class AbstractSigner<P extends null | Provider = null | Provider
pop.nonce = await this.getNonce('pending');
}

if (pop.type == null) {
pop.type = getTxType(pop.from ?? null, pop.to ?? null);
}

if (pop.gasLimit == null) {
if (pop.type == 0) pop.gasLimit = await this.estimateGas(pop);
else {
Expand All @@ -138,7 +134,7 @@ export abstract class AbstractSigner<P extends null | Provider = null | Provider
pop.chainId = network.chainId;
}
if (pop.maxFeePerGas == null || pop.maxPriorityFeePerGas == null) {
const feeData = await provider.getFeeData(zone);
const feeData = await provider.getFeeData(zone, true);

if (pop.maxFeePerGas == null) {
pop.maxFeePerGas = feeData.maxFeePerGas;
Expand Down
Loading
Loading