Skip to content

Commit 9c76f79

Browse files
Merge pull request #494 from multiversx/AddInnerTransactionForRelayV3
Add inner transaction for relay v3
2 parents 52f0efb + f113266 commit 9c76f79

File tree

3 files changed

+169
-14
lines changed

3 files changed

+169
-14
lines changed

src/networkProviders/interface.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ export interface INetworkProvider {
4646
/**
4747
* Fetches data about the non-fungible tokens held by account.
4848
*/
49-
getNonFungibleTokensOfAccount(address: IAddress, pagination?: IPagination): Promise<NonFungibleTokenOfAccountOnNetwork[]>;
49+
getNonFungibleTokensOfAccount(
50+
address: IAddress,
51+
pagination?: IPagination,
52+
): Promise<NonFungibleTokenOfAccountOnNetwork[]>;
5053

5154
/**
5255
* Fetches data about a specific fungible token held by an account.
@@ -56,7 +59,11 @@ export interface INetworkProvider {
5659
/**
5760
* Fetches data about a specific non-fungible token (instance) held by an account.
5861
*/
59-
getNonFungibleTokenOfAccount(address: IAddress, collection: string, nonce: number): Promise<NonFungibleTokenOfAccountOnNetwork>;
62+
getNonFungibleTokenOfAccount(
63+
address: IAddress,
64+
collection: string,
65+
nonce: number,
66+
): Promise<NonFungibleTokenOfAccountOnNetwork>;
6067

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

8188
/**
8289
* Simulates the processing of an already-signed transaction.
83-
*
90+
*
8491
*/
8592
simulateTransaction(tx: ITransaction): Promise<any>;
8693

@@ -118,8 +125,8 @@ export interface INetworkProvider {
118125
export interface IContractQuery {
119126
address: IAddress;
120127
caller?: IAddress;
121-
func: { toString(): string; };
122-
value?: { toString(): string; };
128+
func: { toString(): string };
129+
value?: { toString(): string };
123130
getEncodedArguments(): string[];
124131
}
125132

@@ -132,7 +139,9 @@ export interface ITransaction {
132139
toSendable(): any;
133140
}
134141

135-
export interface IAddress { bech32(): string; }
142+
export interface IAddress {
143+
bech32(): string;
144+
}
136145

137146
export interface ITransactionNext {
138147
sender: string;
@@ -150,4 +159,6 @@ export interface ITransactionNext {
150159
guardian: string;
151160
signature: Uint8Array;
152161
guardianSignature: Uint8Array;
153-
}
162+
relayer?: string;
163+
innerTransactions?: ITransactionNext[];
164+
}

src/networkProviders/providers.dev.net.spec.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,49 @@ describe("test network providers on devnet: Proxy and API", function () {
271271
}
272272
});
273273

274+
it("should have same response for getTransaction() (relayed V3)", async function () {
275+
this.timeout(20000);
276+
277+
// Transaction was sent using mxpy, as follows:
278+
// 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
279+
// 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
280+
281+
const txHash = "8cdfb790be8cd4b331da486ba014ed56d0dbac70c1cfbadf11db2edd48d0e437";
282+
const apiResponse = await apiProvider.getTransaction(txHash);
283+
const proxyResponse = await proxyProvider.getTransaction(txHash, true);
284+
285+
assert.deepEqual(proxyResponse.innerTransactions, [
286+
{
287+
nonce: BigInt(9340),
288+
value: BigInt("1000000000000000000"),
289+
receiver: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
290+
sender: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",
291+
data: Buffer.from([]),
292+
gasPrice: BigInt(1000000000),
293+
gasLimit: BigInt(50000),
294+
chainID: "D",
295+
version: 2,
296+
signature: Buffer.from(
297+
"0993c2f3a47c01cf8330e54571ea9340aae481d0d5212af31b62eb7194e199231f105134aae28a75bb48b53a3dff09d6c6208843c8e0376617cf62d3bfb60204",
298+
"hex",
299+
),
300+
senderUsername: "",
301+
receiverUsername: "",
302+
guardian: "",
303+
guardianSignature: Buffer.from([]),
304+
options: 0,
305+
relayer: "erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede",
306+
},
307+
]);
308+
309+
ignoreKnownTransactionDifferencesBetweenProviders(apiResponse, proxyResponse);
310+
assert.deepEqual(apiResponse, proxyResponse);
311+
312+
// Also assert completion
313+
assert.isTrue(apiResponse.isCompleted);
314+
assert.isTrue(proxyResponse.isCompleted);
315+
});
316+
274317
// TODO: Strive to have as little differences as possible between Proxy and API.
275318
function ignoreKnownTransactionDifferencesBetweenProviders(
276319
apiResponse: TransactionOnNetwork,
@@ -284,8 +327,68 @@ describe("test network providers on devnet: Proxy and API", function () {
284327
proxyResponse.blockNonce = 0;
285328
proxyResponse.hyperblockNonce = 0;
286329
proxyResponse.hyperblockHash = "";
330+
331+
// API does not provide "innerTransactions" (Spica), for the moment.
332+
proxyResponse.innerTransactions = [];
287333
}
288334

335+
it("should send `TransactionNext` (as relayed V3)", async function () {
336+
this.timeout(50000);
337+
338+
// Transaction was created using mxpy, as follows:
339+
// 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
340+
// mxpy tx new --pem=~/multiversx-sdk/testwallets/latest/users/grace.pem --receiver=erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede --gas-limit=100000 --nonce=42 --chain=D --inner-transactions=inner.json
341+
342+
const transactionNext: ITransactionNext = {
343+
nonce: BigInt(42),
344+
value: BigInt(0),
345+
receiver: "erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede",
346+
sender: "erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede",
347+
data: new Uint8Array(),
348+
gasPrice: BigInt(1000000000),
349+
gasLimit: BigInt(100000),
350+
chainID: "D",
351+
version: 2,
352+
signature: Buffer.from(
353+
"c623854967c954d13681035d5b24be68a5a58d25e7efdc75c7d59b5c389e1ad6c9d21a6f41149ec6e8bd051d74f3636a60ce047062f05c748600c36348238e0b",
354+
"hex",
355+
),
356+
senderUsername: "",
357+
receiverUsername: "",
358+
guardian: "",
359+
guardianSignature: new Uint8Array(),
360+
options: 0,
361+
innerTransactions: [
362+
{
363+
nonce: BigInt(7),
364+
value: BigInt("1000000000000000000"),
365+
receiver: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
366+
sender: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",
367+
data: new Uint8Array(),
368+
gasPrice: BigInt(1000000000),
369+
gasLimit: BigInt(50000),
370+
chainID: "D",
371+
version: 2,
372+
signature: Buffer.from(
373+
"40808231154b9924c0d5f885d320f4ab666308f7443ea128ac26029b1de07abfcee6412e1249a9c0fcf79638d9691be3c9fe75dd7c85462082f9b86c4008b30e",
374+
"hex",
375+
),
376+
senderUsername: "",
377+
receiverUsername: "",
378+
guardian: "",
379+
guardianSignature: new Uint8Array(),
380+
options: 0,
381+
relayer: "erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede",
382+
},
383+
],
384+
};
385+
386+
const apiTxNextHash = await apiProvider.sendTransaction(transactionNext);
387+
const proxyTxNextHash = await proxyProvider.sendTransaction(transactionNext);
388+
389+
assert.equal(apiTxNextHash, proxyTxNextHash);
390+
});
391+
289392
it("should have the same response for transactions with events", async function () {
290393
const hash = "1b04eb849cf87f2d3086c77b4b825d126437b88014327bbf01437476751cb040";
291394

src/networkProviders/transactions.ts

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { TransactionLogs } from "./transactionLogs";
66
import { TransactionReceipt } from "./transactionReceipt";
77

88
export function prepareTransactionForBroadcasting(transaction: ITransaction | ITransactionNext): any {
9-
if ("toSendable" in transaction){
9+
if ("toSendable" in transaction) {
1010
return transaction.toSendable();
1111
}
1212

@@ -15,8 +15,12 @@ export function prepareTransactionForBroadcasting(transaction: ITransaction | IT
1515
value: transaction.value.toString(),
1616
receiver: transaction.receiver,
1717
sender: transaction.sender,
18-
senderUsername: transaction.senderUsername ? Buffer.from(transaction.senderUsername).toString("base64") : undefined,
19-
receiverUsername: transaction.receiverUsername ? Buffer.from(transaction.receiverUsername).toString("base64") : undefined,
18+
senderUsername: transaction.senderUsername
19+
? Buffer.from(transaction.senderUsername).toString("base64")
20+
: undefined,
21+
receiverUsername: transaction.receiverUsername
22+
? Buffer.from(transaction.receiverUsername).toString("base64")
23+
: undefined,
2024
gasPrice: Number(transaction.gasPrice),
2125
gasLimit: Number(transaction.gasLimit),
2226
data: transaction.data.length === 0 ? undefined : Buffer.from(transaction.data).toString("base64"),
@@ -25,8 +29,15 @@ export function prepareTransactionForBroadcasting(transaction: ITransaction | IT
2529
options: transaction.options,
2630
guardian: transaction.guardian || undefined,
2731
signature: Buffer.from(transaction.signature).toString("hex"),
28-
guardianSignature: transaction.guardianSignature.length === 0 ? undefined : Buffer.from(transaction.guardianSignature).toString("hex"),
29-
}
32+
guardianSignature:
33+
transaction.guardianSignature.length === 0
34+
? undefined
35+
: Buffer.from(transaction.guardianSignature).toString("hex"),
36+
relayer: transaction.relayer ? transaction.relayer : undefined,
37+
innerTransactions: transaction.innerTransactions
38+
? transaction.innerTransactions.map((tx) => prepareTransactionForBroadcasting(tx))
39+
: undefined,
40+
};
3041
}
3142

3243
export class TransactionOnNetwork {
@@ -54,18 +65,23 @@ export class TransactionOnNetwork {
5465
receipt: TransactionReceipt = new TransactionReceipt();
5566
contractResults: ContractResults = new ContractResults([]);
5667
logs: TransactionLogs = new TransactionLogs();
68+
innerTransactions: ITransactionNext[] = [];
5769

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

62-
static fromProxyHttpResponse(txHash: string, response: any, processStatus?: TransactionStatus | undefined): TransactionOnNetwork {
74+
static fromProxyHttpResponse(
75+
txHash: string,
76+
response: any,
77+
processStatus?: TransactionStatus | undefined,
78+
): TransactionOnNetwork {
6379
let result = TransactionOnNetwork.fromHttpResponse(txHash, response);
6480
result.contractResults = ContractResults.fromProxyHttpResponse(response.smartContractResults || []);
6581

6682
if (processStatus) {
6783
result.status = processStatus;
68-
result.isCompleted = result.status.isSuccessful() || result.status.isFailed()
84+
result.isCompleted = result.status.isSuccessful() || result.status.isFailed();
6985
}
7086

7187
return result;
@@ -102,10 +118,35 @@ export class TransactionOnNetwork {
102118

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

106123
return result;
107124
}
108125

126+
private static innerTransactionFromHttpResource(resource: any): ITransactionNext {
127+
return {
128+
nonce: BigInt(resource.nonce || 0),
129+
value: BigInt(resource.value || 0),
130+
receiver: resource.receiver,
131+
sender: resource.sender,
132+
// We discard "senderUsername" and "receiverUsername" (to avoid future discrepancies between Proxy and API):
133+
senderUsername: "",
134+
receiverUsername: "",
135+
gasPrice: BigInt(resource.gasPrice),
136+
gasLimit: BigInt(resource.gasLimit),
137+
data: Buffer.from(resource.data || "", "base64"),
138+
chainID: resource.chainID,
139+
version: resource.version,
140+
options: resource.options || 0,
141+
guardian: resource.guardian || "",
142+
signature: Buffer.from(resource.signature, "hex"),
143+
guardianSignature: resource.guardianSignature
144+
? Buffer.from(resource.guardianSignature, "hex")
145+
: Buffer.from([]),
146+
relayer: resource.relayer,
147+
};
148+
}
149+
109150
getDateTime(): Date {
110151
return new Date(this.timestamp * 1000);
111152
}

0 commit comments

Comments
 (0)