Skip to content

Commit

Permalink
Merge pull request #494 from multiversx/AddInnerTransactionForRelayV3
Browse files Browse the repository at this point in the history
Add inner transaction for relay v3
  • Loading branch information
andreibancioiu authored Oct 3, 2024
2 parents 52f0efb + f113266 commit 9c76f79
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 14 deletions.
25 changes: 18 additions & 7 deletions src/networkProviders/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ export interface INetworkProvider {
/**
* Fetches data about the non-fungible tokens held by account.
*/
getNonFungibleTokensOfAccount(address: IAddress, pagination?: IPagination): Promise<NonFungibleTokenOfAccountOnNetwork[]>;
getNonFungibleTokensOfAccount(
address: IAddress,
pagination?: IPagination,
): Promise<NonFungibleTokenOfAccountOnNetwork[]>;

/**
* Fetches data about a specific fungible token held by an account.
Expand All @@ -56,7 +59,11 @@ export interface INetworkProvider {
/**
* Fetches data about a specific non-fungible token (instance) held by an account.
*/
getNonFungibleTokenOfAccount(address: IAddress, collection: string, nonce: number): Promise<NonFungibleTokenOfAccountOnNetwork>;
getNonFungibleTokenOfAccount(
address: IAddress,
collection: string,
nonce: number,
): Promise<NonFungibleTokenOfAccountOnNetwork>;

/**
* Fetches the state of a transaction.
Expand All @@ -80,7 +87,7 @@ export interface INetworkProvider {

/**
* Simulates the processing of an already-signed transaction.
*
*
*/
simulateTransaction(tx: ITransaction): Promise<any>;

Expand Down Expand Up @@ -118,8 +125,8 @@ export interface INetworkProvider {
export interface IContractQuery {
address: IAddress;
caller?: IAddress;
func: { toString(): string; };
value?: { toString(): string; };
func: { toString(): string };
value?: { toString(): string };
getEncodedArguments(): string[];
}

Expand All @@ -132,7 +139,9 @@ export interface ITransaction {
toSendable(): any;
}

export interface IAddress { bech32(): string; }
export interface IAddress {
bech32(): string;
}

export interface ITransactionNext {
sender: string;
Expand All @@ -150,4 +159,6 @@ export interface ITransactionNext {
guardian: string;
signature: Uint8Array;
guardianSignature: Uint8Array;
}
relayer?: string;
innerTransactions?: ITransactionNext[];
}
103 changes: 103 additions & 0 deletions src/networkProviders/providers.dev.net.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,49 @@ describe("test network providers on devnet: Proxy and API", function () {
}
});

it("should have same response for getTransaction() (relayed V3)", async function () {
this.timeout(20000);

// Transaction was sent using mxpy, as follows:
// mxpy tx new --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem --receiver=erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx --value=1000000000000000000 --gas-limit=50000 --recall-nonce --relayer=erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede --inner-transactions-outfile=inner.json --proxy=https://devnet-gateway.multiversx.com
// mxpy tx new --pem=~/multiversx-sdk/testwallets/latest/users/grace.pem --receiver=erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede --gas-limit=100000 --recall-nonce --chain=D --inner-transactions=inner.json --proxy=https://devnet-gateway.multiversx.com --send

const txHash = "8cdfb790be8cd4b331da486ba014ed56d0dbac70c1cfbadf11db2edd48d0e437";
const apiResponse = await apiProvider.getTransaction(txHash);
const proxyResponse = await proxyProvider.getTransaction(txHash, true);

assert.deepEqual(proxyResponse.innerTransactions, [
{
nonce: BigInt(9340),
value: BigInt("1000000000000000000"),
receiver: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
sender: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",
data: Buffer.from([]),
gasPrice: BigInt(1000000000),
gasLimit: BigInt(50000),
chainID: "D",
version: 2,
signature: Buffer.from(
"0993c2f3a47c01cf8330e54571ea9340aae481d0d5212af31b62eb7194e199231f105134aae28a75bb48b53a3dff09d6c6208843c8e0376617cf62d3bfb60204",
"hex",
),
senderUsername: "",
receiverUsername: "",
guardian: "",
guardianSignature: Buffer.from([]),
options: 0,
relayer: "erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede",
},
]);

ignoreKnownTransactionDifferencesBetweenProviders(apiResponse, proxyResponse);
assert.deepEqual(apiResponse, proxyResponse);

// Also assert completion
assert.isTrue(apiResponse.isCompleted);
assert.isTrue(proxyResponse.isCompleted);
});

// TODO: Strive to have as little differences as possible between Proxy and API.
function ignoreKnownTransactionDifferencesBetweenProviders(
apiResponse: TransactionOnNetwork,
Expand All @@ -284,8 +327,68 @@ describe("test network providers on devnet: Proxy and API", function () {
proxyResponse.blockNonce = 0;
proxyResponse.hyperblockNonce = 0;
proxyResponse.hyperblockHash = "";

// API does not provide "innerTransactions" (Spica), for the moment.
proxyResponse.innerTransactions = [];
}

it("should send `TransactionNext` (as relayed V3)", async function () {
this.timeout(50000);

// Transaction was created using mxpy, as follows:
// mxpy tx new --pem=~/multiversx-sdk/testwallets/latest/users/alice.pem --receiver=erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx --value=1000000000000000000 --gas-limit=50000 --nonce=7 --chain=D --relayer=erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede --inner-transactions-outfile=inner.json
// mxpy tx new --pem=~/multiversx-sdk/testwallets/latest/users/grace.pem --receiver=erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede --gas-limit=100000 --nonce=42 --chain=D --inner-transactions=inner.json

const transactionNext: ITransactionNext = {
nonce: BigInt(42),
value: BigInt(0),
receiver: "erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede",
sender: "erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede",
data: new Uint8Array(),
gasPrice: BigInt(1000000000),
gasLimit: BigInt(100000),
chainID: "D",
version: 2,
signature: Buffer.from(
"c623854967c954d13681035d5b24be68a5a58d25e7efdc75c7d59b5c389e1ad6c9d21a6f41149ec6e8bd051d74f3636a60ce047062f05c748600c36348238e0b",
"hex",
),
senderUsername: "",
receiverUsername: "",
guardian: "",
guardianSignature: new Uint8Array(),
options: 0,
innerTransactions: [
{
nonce: BigInt(7),
value: BigInt("1000000000000000000"),
receiver: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
sender: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",
data: new Uint8Array(),
gasPrice: BigInt(1000000000),
gasLimit: BigInt(50000),
chainID: "D",
version: 2,
signature: Buffer.from(
"40808231154b9924c0d5f885d320f4ab666308f7443ea128ac26029b1de07abfcee6412e1249a9c0fcf79638d9691be3c9fe75dd7c85462082f9b86c4008b30e",
"hex",
),
senderUsername: "",
receiverUsername: "",
guardian: "",
guardianSignature: new Uint8Array(),
options: 0,
relayer: "erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede",
},
],
};

const apiTxNextHash = await apiProvider.sendTransaction(transactionNext);
const proxyTxNextHash = await proxyProvider.sendTransaction(transactionNext);

assert.equal(apiTxNextHash, proxyTxNextHash);
});

it("should have the same response for transactions with events", async function () {
const hash = "1b04eb849cf87f2d3086c77b4b825d126437b88014327bbf01437476751cb040";

Expand Down
55 changes: 48 additions & 7 deletions src/networkProviders/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { TransactionLogs } from "./transactionLogs";
import { TransactionReceipt } from "./transactionReceipt";

export function prepareTransactionForBroadcasting(transaction: ITransaction | ITransactionNext): any {
if ("toSendable" in transaction){
if ("toSendable" in transaction) {
return transaction.toSendable();
}

Expand All @@ -15,8 +15,12 @@ export function prepareTransactionForBroadcasting(transaction: ITransaction | IT
value: transaction.value.toString(),
receiver: transaction.receiver,
sender: transaction.sender,
senderUsername: transaction.senderUsername ? Buffer.from(transaction.senderUsername).toString("base64") : undefined,
receiverUsername: transaction.receiverUsername ? Buffer.from(transaction.receiverUsername).toString("base64") : undefined,
senderUsername: transaction.senderUsername
? Buffer.from(transaction.senderUsername).toString("base64")
: undefined,
receiverUsername: transaction.receiverUsername
? Buffer.from(transaction.receiverUsername).toString("base64")
: undefined,
gasPrice: Number(transaction.gasPrice),
gasLimit: Number(transaction.gasLimit),
data: transaction.data.length === 0 ? undefined : Buffer.from(transaction.data).toString("base64"),
Expand All @@ -25,8 +29,15 @@ export function prepareTransactionForBroadcasting(transaction: ITransaction | IT
options: transaction.options,
guardian: transaction.guardian || undefined,
signature: Buffer.from(transaction.signature).toString("hex"),
guardianSignature: transaction.guardianSignature.length === 0 ? undefined : Buffer.from(transaction.guardianSignature).toString("hex"),
}
guardianSignature:
transaction.guardianSignature.length === 0
? undefined
: Buffer.from(transaction.guardianSignature).toString("hex"),
relayer: transaction.relayer ? transaction.relayer : undefined,
innerTransactions: transaction.innerTransactions
? transaction.innerTransactions.map((tx) => prepareTransactionForBroadcasting(tx))
: undefined,
};
}

export class TransactionOnNetwork {
Expand Down Expand Up @@ -54,18 +65,23 @@ export class TransactionOnNetwork {
receipt: TransactionReceipt = new TransactionReceipt();
contractResults: ContractResults = new ContractResults([]);
logs: TransactionLogs = new TransactionLogs();
innerTransactions: ITransactionNext[] = [];

constructor(init?: Partial<TransactionOnNetwork>) {
Object.assign(this, init);
}

static fromProxyHttpResponse(txHash: string, response: any, processStatus?: TransactionStatus | undefined): TransactionOnNetwork {
static fromProxyHttpResponse(
txHash: string,
response: any,
processStatus?: TransactionStatus | undefined,
): TransactionOnNetwork {
let result = TransactionOnNetwork.fromHttpResponse(txHash, response);
result.contractResults = ContractResults.fromProxyHttpResponse(response.smartContractResults || []);

if (processStatus) {
result.status = processStatus;
result.isCompleted = result.status.isSuccessful() || result.status.isFailed()
result.isCompleted = result.status.isSuccessful() || result.status.isFailed();
}

return result;
Expand Down Expand Up @@ -102,10 +118,35 @@ export class TransactionOnNetwork {

result.receipt = TransactionReceipt.fromHttpResponse(response.receipt || {});
result.logs = TransactionLogs.fromHttpResponse(response.logs || {});
result.innerTransactions = (response.innerTransactions || []).map(this.innerTransactionFromHttpResource);

return result;
}

private static innerTransactionFromHttpResource(resource: any): ITransactionNext {
return {
nonce: BigInt(resource.nonce || 0),
value: BigInt(resource.value || 0),
receiver: resource.receiver,
sender: resource.sender,
// We discard "senderUsername" and "receiverUsername" (to avoid future discrepancies between Proxy and API):
senderUsername: "",
receiverUsername: "",
gasPrice: BigInt(resource.gasPrice),
gasLimit: BigInt(resource.gasLimit),
data: Buffer.from(resource.data || "", "base64"),
chainID: resource.chainID,
version: resource.version,
options: resource.options || 0,
guardian: resource.guardian || "",
signature: Buffer.from(resource.signature, "hex"),
guardianSignature: resource.guardianSignature
? Buffer.from(resource.guardianSignature, "hex")
: Buffer.from([]),
relayer: resource.relayer,
};
}

getDateTime(): Date {
return new Date(this.timestamp * 1000);
}
Expand Down

0 comments on commit 9c76f79

Please sign in to comment.