Skip to content

Commit

Permalink
Merge pull request #538 from multiversx/TOOL-366-add-entrypoints
Browse files Browse the repository at this point in the history
Add entrypoints
  • Loading branch information
danielailie authored Nov 19, 2024
2 parents 0bdc506 + dd67b79 commit f377ac6
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 5 deletions.
68 changes: 68 additions & 0 deletions src/entrypoints/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
export interface EntrypointConfig {
networkProviderUrl: string;
networkProviderKind: string;
chainId: string;
}

export class TestnetEntrypointConfig {
networkProviderUrl: string;
networkProviderKind: string;
chainId: string;

constructor({
networkProviderUrl = "https://testnet-api.multiversx.com",
networkProviderKind = "api",
chainId = "T",
}: Partial<EntrypointConfig> = {}) {
this.networkProviderUrl = networkProviderUrl;
this.networkProviderKind = networkProviderKind;
this.chainId = chainId;
}
}

export class DevnetEntrypointConfig {
networkProviderUrl: string;
networkProviderKind: string;
chainId: string;
constructor({
networkProviderUrl = "https://devnet-api.multiversx.com",
networkProviderKind = "api",
chainId = "D",
}: Partial<EntrypointConfig> = {}) {
this.networkProviderUrl = networkProviderUrl;
this.networkProviderKind = networkProviderKind;
this.chainId = chainId;
}
}

export class MainnetEntrypointConfig {
networkProviderUrl: string;
networkProviderKind: string;
chainId: string;

constructor({
networkProviderUrl = "https://api.multiversx.com",
networkProviderKind = "api",
chainId = "1",
}: Partial<EntrypointConfig> = {}) {
this.networkProviderUrl = networkProviderUrl;
this.networkProviderKind = networkProviderKind;
this.chainId = chainId;
}
}

export class LocalnetEntrypointConfig {
networkProviderUrl: string;
networkProviderKind: string;
chainId: string;

constructor({
networkProviderUrl = "http://localhost:7950",
networkProviderKind = "proxy",
chainId = "localnet",
}: Partial<EntrypointConfig> = {}) {
this.networkProviderUrl = networkProviderUrl;
this.networkProviderKind = networkProviderKind;
this.chainId = chainId;
}
}
125 changes: 125 additions & 0 deletions src/entrypoints/entrypoints.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { assert } from "chai";
import { readFileSync } from "fs";
import { Account } from "../accounts/account";
import { Address } from "../address";
import { loadAbiRegistry, loadTestWallet, TestWallet } from "../testutils";
import { TransactionComputer } from "../transactionComputer";
import { DevnetEntrypoint } from "./entrypoints";

describe("TestEntrypoint", () => {
const entrypoint = new DevnetEntrypoint();
let alicePem: TestWallet;
let bobPem: TestWallet;
let txComputer: TransactionComputer;

before(async function () {
alicePem = await loadTestWallet("alice");
bobPem = await loadTestWallet("bob");
txComputer = new TransactionComputer();
});

it("native transfer", async () => {
const controller = entrypoint.createTransfersController();
const sender = Account.newFromPem(alicePem.pemFileText);
sender.nonce = 77777;

const transaction = await controller.createTransactionForTransfer(
sender,
BigInt(sender.getNonceThenIncrement().valueOf()),
{
receiver: sender.address,
nativeAmount: BigInt(0),
data: Buffer.from("hello"),
},
);
assert.equal(
Buffer.from(transaction.signature).toString("hex"),
"69bc7d1777edd0a901e6cf94830475716205c5efdf2fd44d4be31badead59fc8418b34f0aa3b2c80ba14aed5edd30031757d826af58a1abb690a0bee89ba9309",
);
});

it("contract flow", async function () {
this.timeout(30000);
const abi = await loadAbiRegistry("src/testdata/adder.abi.json");
const sender = Account.newFromPem(alicePem.pemFileText);
const accountAddress = new Address(sender.address);
sender.nonce = await entrypoint.recallAccountNonce(accountAddress);

const controller = entrypoint.createSmartContractController(abi);
const bytecode = readFileSync("src/testdata/adder.wasm");

const transaction = await controller.createTransactionForDeploy(
sender,
BigInt(sender.getNonceThenIncrement().valueOf()),
{
bytecode,
gasLimit: BigInt(10_000_000),
arguments: [0],
},
);

const txHash = await entrypoint.sendTransaction(transaction);
const outcome = await controller.awaitCompletedDeploy(txHash);

assert.equal(outcome.contracts.length, 1);

const contractAddress = Address.fromBech32(outcome.contracts[0].address);

const executeTransaction = await controller.createTransactionForExecute(
sender,
BigInt(sender.getNonceThenIncrement().valueOf()),
{
contract: contractAddress,
gasLimit: BigInt(10_000_000),
function: "add",
arguments: [7],
},
);

const txHashExecute = await entrypoint.sendTransaction(executeTransaction);
await entrypoint.awaitCompletedTransaction(txHashExecute);

const queryResult = await controller.queryContract(contractAddress, "getSum", []);
assert.equal(queryResult.length, 1);
assert.equal(queryResult[0], 7);
});

it("create relayed transaction", async function () {
const transferController = entrypoint.createTransfersController();
const sender = Account.newFromPem(alicePem.pemFileText);
sender.nonce = 77777;

const relayer = Account.newFromPem(bobPem.pemFileText);
relayer.nonce = 7;

const transaction = await transferController.createTransactionForTransfer(
sender,
BigInt(sender.getNonceThenIncrement().valueOf()),
{
receiver: sender.address,
data: Buffer.from("hello"),
},
);
const innerTransactionGasLimit = transaction.gasLimit;
transaction.gasLimit = BigInt(0);
transaction.signature = await sender.sign(txComputer.computeBytesForSigning(transaction));

const relayedController = entrypoint.createRelayedController();
const relayedTransaction = await relayedController.createRelayedV2Transaction(
relayer,
BigInt(relayer.getNonceThenIncrement().valueOf()),
{
innerTransaction: transaction,
innerTransactionGasLimit,
},
);
assert.equal(relayedTransaction.chainID, "D");
assert.deepEqual(
relayedTransaction.data,
Buffer.from(
"relayedTxV2@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@012fd1@68656c6c6f@c1eed3ac766d6b94aa53a1348d38eac8db60be0a1b2d0873247b61b8b25bbcb45bf9c1518227bcadd5044d4c027bdb935e0164243b2b2df9a5b250a10aca260e",
),
);
assert.equal(relayedTransaction.gasLimit, 442000n);
});
});
142 changes: 142 additions & 0 deletions src/entrypoints/entrypoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { AbiRegistry } from "../abi";
import { AccountController } from "../accountManagement";
import { IAccount } from "../accounts/interfaces";
import { Address } from "../address";
import { DelegationController } from "../delegation";
import { ErrInvalidNetworkProviderKind } from "../errors";
import { Message, MessageComputer } from "../message";
import { ApiNetworkProvider, ProxyNetworkProvider, TransactionOnNetwork } from "../networkProviders";
import { RelayedController } from "../relayed/relayedController";
import { SmartContractController } from "../smartContracts/smartContractController";
import { TokenManagementController } from "../tokenManagement";
import { Transaction } from "../transaction";
import { TransactionComputer } from "../transactionComputer";
import { TransactionWatcher } from "../transactionWatcher";
import { TransfersController } from "../transfers/transfersControllers";
import { UserVerifier } from "../wallet";
import { DevnetEntrypointConfig, MainnetEntrypointConfig, TestnetEntrypointConfig } from "./config";

class NetworkEntrypoint {
private networkProvider: ApiNetworkProvider | ProxyNetworkProvider;
private chainId: string;

constructor(options: { networkProviderUrl: string; networkProviderKind: string; chainId: string }) {
if (options.networkProviderKind === "proxy") {
this.networkProvider = new ProxyNetworkProvider(options.networkProviderUrl);
} else if (options.networkProviderKind === "api") {
this.networkProvider = new ApiNetworkProvider(options.networkProviderUrl);
} else {
throw new ErrInvalidNetworkProviderKind();
}

this.chainId = options.chainId;
}

async signTransaction(transaction: Transaction, account: IAccount): Promise<void> {
const txComputer = new TransactionComputer();
transaction.signature = await account.sign(txComputer.computeBytesForSigning(transaction));
}

verifyTransactionSignature(transaction: Transaction): boolean {
const verifier = UserVerifier.fromAddress(Address.fromBech32(transaction.sender));
const txComputer = new TransactionComputer();
return verifier.verify(txComputer.computeBytesForVerifying(transaction), transaction.signature);
}

async signMessage(message: Message, account: IAccount): Promise<void> {
const messageComputer = new MessageComputer();
message.signature = await account.sign(messageComputer.computeBytesForSigning(message));
}

verifyMessageSignature(message: Message): boolean {
if (!message.address) {
throw new Error("`address` property of Message is not set");
}

if (!message.signature) {
throw new Error("`signature` property of Message is not set");
}

const verifier = UserVerifier.fromAddress(message.address);
const messageComputer = new MessageComputer();
return verifier.verify(messageComputer.computeBytesForVerifying(message), message.signature);
}

async recallAccountNonce(address: Address): Promise<number> {
return (await this.networkProvider.getAccount(address)).nonce;
}

sendTransactions(transactions: Transaction[]): Promise<string[]> {
return this.networkProvider.sendTransactions(transactions);
}

sendTransaction(transaction: Transaction): Promise<string> {
return this.networkProvider.sendTransaction(transaction);
}

async awaitCompletedTransaction(txHash: string): Promise<TransactionOnNetwork> {
const transactionAwaiter = new TransactionWatcher(this.networkProvider);
return transactionAwaiter.awaitCompleted(txHash);
}

createNetworkProvider(): ApiNetworkProvider | ProxyNetworkProvider {
return this.networkProvider;
}

createDelegationController(): DelegationController {
return new DelegationController({ chainID: this.chainId, networkProvider: this.networkProvider });
}

createAccountController(): AccountController {
return new AccountController({ chainID: this.chainId });
}

createRelayedController(): RelayedController {
return new RelayedController({ chainID: this.chainId });
}

createSmartContractController(abi?: AbiRegistry): SmartContractController {
return new SmartContractController({ chainID: this.chainId, networkProvider: this.networkProvider, abi });
}

createTokenManagementController(): TokenManagementController {
return new TokenManagementController({ chainID: this.chainId, networkProvider: this.networkProvider });
}

createTransfersController(): TransfersController {
return new TransfersController({ chainID: this.chainId });
}
}

export class TestnetEntrypoint extends NetworkEntrypoint {
constructor(url?: string, kind?: string) {
const entrypointConfig = new TestnetEntrypointConfig();
super({
networkProviderUrl: url || entrypointConfig.networkProviderUrl,
networkProviderKind: kind || entrypointConfig.networkProviderKind,
chainId: entrypointConfig.chainId,
});
}
}

export class DevnetEntrypoint extends NetworkEntrypoint {
constructor(url?: string, kind?: string) {
const entrypointConfig = new DevnetEntrypointConfig();
super({
networkProviderUrl: url || entrypointConfig.networkProviderUrl,
networkProviderKind: kind || entrypointConfig.networkProviderKind,
chainId: entrypointConfig.chainId,
});
}
}

export class MainnetEntrypoint extends NetworkEntrypoint {
constructor(url?: string, kind?: string) {
const entrypointConfig = new MainnetEntrypointConfig();
super({
networkProviderUrl: url || entrypointConfig.networkProviderUrl,
networkProviderKind: kind || entrypointConfig.networkProviderKind,
chainId: entrypointConfig.chainId,
});
}
}
2 changes: 2 additions & 0 deletions src/entrypoints/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./config";
export * from "./entrypoints";
9 changes: 9 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,3 +448,12 @@ export class ErrContractQuery extends Err {
super(originalError.message.replace("executeQuery:", ""));
}
}

/**
* Signals that the network provider provided is not valid
*/
export class ErrInvalidNetworkProviderKind extends Err {
public constructor() {
super("Invalid network provider kind. Choose between `api` and `proxy`.");
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export * from "./asyncTimer";
export * from "./config";
export * from "./converters";
export * from "./delegation";
export * from "./entrypoints";
export * from "./errors";
export * from "./gasEstimator";
export * from "./interface";
Expand Down
7 changes: 3 additions & 4 deletions src/message.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { IAddress } from "./interface";
import { DEFAULT_MESSAGE_VERSION, MESSAGE_PREFIX, SDK_JS_SIGNER, UNKNOWN_SIGNER } from "./constants";
import { Address } from "./address";
import { DEFAULT_MESSAGE_VERSION, MESSAGE_PREFIX, SDK_JS_SIGNER, UNKNOWN_SIGNER } from "./constants";

const createKeccakHash = require("keccak");

Expand All @@ -16,7 +15,7 @@ export class Message {
/**
* Address of the wallet that performed the signing operation.
*/
public address?: IAddress;
public address?: Address;
/**
* Number representing the message version.
*/
Expand All @@ -29,7 +28,7 @@ export class Message {
constructor(options: {
data: Uint8Array;
signature?: Uint8Array;
address?: IAddress;
address?: Address;
version?: number;
signer?: string;
}) {
Expand Down
1 change: 0 additions & 1 deletion src/relayed/relayedController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export class RelayedController {
const transaction = this.factory.createRelayedV2Transaction(sender.address, options);

transaction.nonce = nonce;
transaction.gasLimit = BigInt(0);
transaction.signature = await sender.sign(this.txComputer.computeBytesForSigning(transaction));

return transaction;
Expand Down

0 comments on commit f377ac6

Please sign in to comment.