Skip to content

Commit

Permalink
prepareTransaction moved to Transaction Class
Browse files Browse the repository at this point in the history
  • Loading branch information
joticajulian committed Jul 19, 2023
1 parent d3a3843 commit 030a5c0
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 45 deletions.
11 changes: 6 additions & 5 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
dist
lib
docs
local
src/protoModules
dist
lib
docs
local
test.ts
src/protoModules
tests/assets-index/prism*
61 changes: 35 additions & 26 deletions src/Contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
DecodedEventData,
} from "./interface";
import { decodeBase58, encodeBase58, encodeBase64url } from "./utils";
import { Transaction } from "./Transaction";

/**
* The contract class contains the contract ID and contract entries
Expand Down Expand Up @@ -306,20 +307,24 @@ export class Contract {

// write contract (sign and send)
if (!this.signer) throw new Error("signer not found");
let tx = await this.signer.prepareTransaction({
header: {
...(opts.chainId && { chain_id: opts.chainId }),
...(opts.rcLimit && { rc_limit: opts.rcLimit }),
...(opts.nonce && { nonce: opts.nonce }),
...(opts.payer && { payer: opts.payer }),
...(opts.payee && { payee: opts.payee }),
let tx = await Transaction.prepareTransaction(
{
header: {
...(opts.chainId && { chain_id: opts.chainId }),
...(opts.rcLimit && { rc_limit: opts.rcLimit }),
...(opts.nonce && { nonce: opts.nonce }),
...(opts.payer && { payer: opts.payer }),
...(opts.payee && { payee: opts.payee }),
},
operations: [
...(opts.previousOperations ? opts.previousOperations : []),
operation,
...(opts.nextOperations ? opts.nextOperations : []),
],
},
operations: [
...(opts.previousOperations ? opts.previousOperations : []),
operation,
...(opts.nextOperations ? opts.nextOperations : []),
],
});
this.provider,
this.signer?.getAddress()
);

if (opts.sendAbis) {
if (!opts.abis) opts.abis = {};
Expand Down Expand Up @@ -430,20 +435,24 @@ export class Contract {
return { operation };
}

let tx = await this.signer.prepareTransaction({
header: {
...(opts.chainId && { chain_id: opts.chainId }),
...(opts.rcLimit && { rc_limit: opts.rcLimit }),
...(opts.nonce && { nonce: opts.nonce }),
...(opts.payer && { payer: opts.payer }),
...(opts.payee && { payee: opts.payee }),
let tx = await Transaction.prepareTransaction(
{
header: {
...(opts.chainId && { chain_id: opts.chainId }),
...(opts.rcLimit && { rc_limit: opts.rcLimit }),
...(opts.nonce && { nonce: opts.nonce }),
...(opts.payer && { payer: opts.payer }),
...(opts.payee && { payee: opts.payee }),
},
operations: [
...(opts.previousOperations ? opts.previousOperations : []),
operation,
...(opts.nextOperations ? opts.nextOperations : []),
],
},
operations: [
...(opts.previousOperations ? opts.previousOperations : []),
operation,
...(opts.nextOperations ? opts.nextOperations : []),
],
});
this.provider,
this.signer?.getAddress()
);

// return result if the transaction will not be broadcasted
if (!opts.sendTransaction) {
Expand Down
3 changes: 2 additions & 1 deletion src/Signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,9 +639,10 @@ export class Signer implements SignerInterface {

/**
* Function to prepare a transaction
* @deprecated - Use [[Transaction.prepareTransaction]] instead.
* @param tx - Do not set the nonce to get it from the blockchain
* using the provider. The rc_limit is 1e8 by default.
* @returns A prepared transaction. ()
* @returns A prepared transaction.
*/
async prepareTransaction(tx: TransactionJson): Promise<TransactionJson> {
if (!tx.header) {
Expand Down
191 changes: 179 additions & 12 deletions src/Transaction.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-param-reassign, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
import { sha256 } from "@noble/hashes/sha256";
import { Contract } from "./Contract";
import { Provider } from "./Provider";
import { Signer } from "./Signer";
Expand All @@ -8,8 +10,75 @@ import {
TransactionJson,
TransactionOptions,
TransactionReceipt,
TypeField,
WaitFunction,
} from "./interface";
import {
btypeDecode,
calculateMerkleRoot,
encodeBase64url,
toHexString,
} from "./utils";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { koinos } from "./protoModules/protocol-proto.js";

const btypeTransactionHeader: TypeField["subtypes"] = {
chain_id: { type: "bytes" },
rc_limit: { type: "uint64" },
nonce: { type: "bytes" },
operation_merkle_root: { type: "bytes" },
payer: { type: "bytes", btype: "ADDRESS" },
payee: { type: "bytes", btype: "ADDRESS" },
};

const btypesOperation: TypeField["subtypes"] = {
upload_contract: {
type: "object",
subtypes: {
contract_id: { type: "bytes", btype: "CONTRACT_ID" },
bytecode: { type: "bytes" },
abi: { type: "string" },
authorizes_call_contract: { type: "bool" },
authorizes_transaction_application: { type: "bool" },
authorizes_upload_contract: { type: "bool" },
},
},
call_contract: {
type: "object",
subtypes: {
contract_id: { type: "bytes", btype: "CONTRACT_ID" },
entry_point: { type: "uint32" },
args: { type: "bytes" },
},
},
set_system_call: {
type: "object",
subtypes: {
call_id: { type: "uint32" },
target: {
type: "object",
subtypes: {
thunk_id: { type: "uint32" },
system_call_bundle: {
type: "object",
subtypes: {
contract_id: { type: "bytes", btype: "CONTRACT_ID" },
entry_point: { type: "uint32" },
},
},
},
},
},
},
set_system_contract: {
type: "object",
subtypes: {
contract_id: { type: "bytes", btype: "CONTRACT_ID" },
system_contract: { type: "bool" },
},
},
};

export class Transaction {
/**
Expand Down Expand Up @@ -43,7 +112,7 @@ export class Transaction {
options?: TransactionOptions;
}) {
this.signer = c?.signer;
this.provider = c?.provider;
this.provider = c?.provider || c?.signer?.provider;
this.options = {
broadcast: true,
sendAbis: true,
Expand Down Expand Up @@ -147,6 +216,110 @@ export class Transaction {
this.transaction.operations.push(operation);
}

/**
* Function to prepare a transaction
* @param tx - Do not set the nonce to get it from the blockchain
* using the provider. The rc_limit is 1e8 by default.
* @param provider - Provider
* @param payer - payer to be used in case it is not defined in the transaction
* @returns A prepared transaction.
*/
static async prepareTransaction(
tx: TransactionJson,
provider?: Provider,
payer?: string
): Promise<TransactionJson> {
if (!tx.header) {
tx.header = {};
}

const { payer: payerHeader, payee } = tx.header;
if (!payerHeader) tx.header.payer = payer;
payer = tx.header.payer;
if (!payer) throw new Error("payer is undefined");

let nonce: string;
if (tx.header.nonce === undefined) {
if (!provider)
throw new Error(
"Cannot get the nonce because provider is undefined. To skip this call set a nonce in the transaction header"
);
nonce = await provider.getNextNonce(payee || payer);
} else {
nonce = tx.header.nonce;
}

let rcLimit: string;
if (tx.header.rc_limit === undefined) {
if (!provider)
throw new Error(
"Cannot get the rc_limit because provider is undefined. To skip this call set a rc_limit in the transaction header"
);
rcLimit = await provider.getAccountRc(payer);
} else {
rcLimit = tx.header.rc_limit;
}

if (!tx.header.chain_id) {
if (!provider)
throw new Error(
"Cannot get the chain_id because provider is undefined. To skip this call set a chain_id"
);
tx.header.chain_id = await provider.getChainId();
}

const operationsHashes: Uint8Array[] = [];

if (tx.operations) {
for (let index = 0; index < tx.operations?.length; index += 1) {
const operationDecoded = btypeDecode(
tx.operations[index],
btypesOperation!,
false
);
const message = koinos.protocol.operation.create(operationDecoded);
const operationEncoded = koinos.protocol.operation
.encode(message)
.finish() as Uint8Array;
operationsHashes.push(sha256(operationEncoded));
}
}
const operationMerkleRoot = encodeBase64url(
new Uint8Array([
// multihash sha256: 18, 32
18,
32,
...calculateMerkleRoot(operationsHashes),
])
);

tx.header = {
chain_id: tx.header.chain_id,
rc_limit: rcLimit,
nonce,
operation_merkle_root: operationMerkleRoot,
payer,
...(payee && { payee }),
// TODO: Option to resolve names (payer, payee)
};

const headerDecoded = btypeDecode(
tx.header,
btypeTransactionHeader!,
false
);
const message = koinos.protocol.transaction_header.create(headerDecoded);
const headerBytes = koinos.protocol.transaction_header
.encode(message)
.finish() as Uint8Array;

const hash = sha256(headerBytes);

// multihash 0x1220. 12: code sha2-256. 20: length (32 bytes)
tx.id = `0x1220${toHexString(hash)}`;
return tx;
}

/**
* Functon to prepare the transaction (set headers, merkle
* root, etc)
Expand All @@ -165,17 +338,11 @@ export class Transaction {
...header,
};
}

if (this.signer) {
this.transaction = await this.signer.prepareTransaction(this.transaction);
} else {
if (!this.transaction.header || !this.transaction.header.payer) {
throw new Error("no payer defined");
}
const signer = Signer.fromSeed("0");
signer.provider = this.provider;
this.transaction = await signer.prepareTransaction(this.transaction);
}
this.transaction = await Transaction.prepareTransaction(
this.transaction,
this.provider,
this.signer?.getAddress()
);
return this.transaction;
}

Expand Down
6 changes: 5 additions & 1 deletion tests/wallet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,11 @@ describe("Wallet and Contract", () => {
],
};

const tx = await signer.prepareTransaction(transaction);
const tx = await Transaction.prepareTransaction(
transaction,
signer.provider,
signer.getAddress()
);

expect(tx).toStrictEqual({
header: {
Expand Down

0 comments on commit 030a5c0

Please sign in to comment.