diff --git a/package-lock.json b/package-lock.json index 8d9c32d7..75b73116 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "13.15.0", + "version": "13.16.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "13.15.0", + "version": "13.16.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index abe0b1d3..8d91ddeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "13.15.0", + "version": "13.16.0", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", diff --git a/src/interface.ts b/src/interface.ts index d1258c1e..545160b5 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -16,6 +16,7 @@ export interface IPlainTransactionObject { receiverUsername?: string; senderUsername?: string; guardian?: string; + relayer?: string; gasPrice: number; gasLimit: number; data?: string; @@ -24,6 +25,7 @@ export interface IPlainTransactionObject { options?: number; signature?: string; guardianSignature?: string; + relayerSignature?: string; } export interface ISignature { diff --git a/src/proto/compiled.js b/src/proto/compiled.js index feed796f..5677d707 100644 --- a/src/proto/compiled.js +++ b/src/proto/compiled.js @@ -46,6 +46,8 @@ * @property {number|null} [Options] Transaction Options * @property {Uint8Array|null} [GuardianAddr] Transaction GuardianAddr * @property {Uint8Array|null} [GuardianSignature] Transaction GuardianSignature + * @property {Uint8Array|null} [Relayer] Transaction Relayer + * @property {Uint8Array|null} [RelayerSignature] Transaction RelayerSignature */ /** @@ -183,6 +185,22 @@ */ Transaction.prototype.GuardianSignature = $util.newBuffer([]); + /** + * Transaction Relayer. + * @member {Uint8Array} Relayer + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.Relayer = $util.newBuffer([]); + + /** + * Transaction RelayerSignature. + * @member {Uint8Array} RelayerSignature + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.RelayerSignature = $util.newBuffer([]); + /** * Creates a new Transaction instance using the specified properties. * @function create @@ -237,6 +255,10 @@ writer.uint32(/* id 14, wireType 2 =*/114).bytes(message.GuardianAddr); if (message.GuardianSignature != null && Object.hasOwnProperty.call(message, "GuardianSignature")) writer.uint32(/* id 15, wireType 2 =*/122).bytes(message.GuardianSignature); + if (message.Relayer != null && Object.hasOwnProperty.call(message, "Relayer")) + writer.uint32(/* id 16, wireType 2 =*/130).bytes(message.Relayer); + if (message.RelayerSignature != null && Object.hasOwnProperty.call(message, "RelayerSignature")) + writer.uint32(/* id 17, wireType 2 =*/138).bytes(message.RelayerSignature); return writer; }; @@ -331,6 +353,14 @@ message.GuardianSignature = reader.bytes(); break; } + case 16: { + message.Relayer = reader.bytes(); + break; + } + case 17: { + message.RelayerSignature = reader.bytes(); + break; + } default: reader.skipType(tag & 7); break; @@ -411,6 +441,12 @@ if (message.GuardianSignature != null && message.hasOwnProperty("GuardianSignature")) if (!(message.GuardianSignature && typeof message.GuardianSignature.length === "number" || $util.isString(message.GuardianSignature))) return "GuardianSignature: buffer expected"; + if (message.Relayer != null && message.hasOwnProperty("Relayer")) + if (!(message.Relayer && typeof message.Relayer.length === "number" || $util.isString(message.Relayer))) + return "Relayer: buffer expected"; + if (message.RelayerSignature != null && message.hasOwnProperty("RelayerSignature")) + if (!(message.RelayerSignature && typeof message.RelayerSignature.length === "number" || $util.isString(message.RelayerSignature))) + return "RelayerSignature: buffer expected"; return null; }; @@ -507,6 +543,16 @@ $util.base64.decode(object.GuardianSignature, message.GuardianSignature = $util.newBuffer($util.base64.length(object.GuardianSignature)), 0); else if (object.GuardianSignature.length >= 0) message.GuardianSignature = object.GuardianSignature; + if (object.Relayer != null) + if (typeof object.Relayer === "string") + $util.base64.decode(object.Relayer, message.Relayer = $util.newBuffer($util.base64.length(object.Relayer)), 0); + else if (object.Relayer.length >= 0) + message.Relayer = object.Relayer; + if (object.RelayerSignature != null) + if (typeof object.RelayerSignature === "string") + $util.base64.decode(object.RelayerSignature, message.RelayerSignature = $util.newBuffer($util.base64.length(object.RelayerSignature)), 0); + else if (object.RelayerSignature.length >= 0) + message.RelayerSignature = object.RelayerSignature; return message; }; @@ -611,6 +657,20 @@ if (options.bytes !== Array) object.GuardianSignature = $util.newBuffer(object.GuardianSignature); } + if (options.bytes === String) + object.Relayer = ""; + else { + object.Relayer = []; + if (options.bytes !== Array) + object.Relayer = $util.newBuffer(object.Relayer); + } + if (options.bytes === String) + object.RelayerSignature = ""; + else { + object.RelayerSignature = []; + if (options.bytes !== Array) + object.RelayerSignature = $util.newBuffer(object.RelayerSignature); + } } if (message.Nonce != null && message.hasOwnProperty("Nonce")) if (typeof message.Nonce === "number") @@ -651,6 +711,10 @@ object.GuardianAddr = options.bytes === String ? $util.base64.encode(message.GuardianAddr, 0, message.GuardianAddr.length) : options.bytes === Array ? Array.prototype.slice.call(message.GuardianAddr) : message.GuardianAddr; if (message.GuardianSignature != null && message.hasOwnProperty("GuardianSignature")) object.GuardianSignature = options.bytes === String ? $util.base64.encode(message.GuardianSignature, 0, message.GuardianSignature.length) : options.bytes === Array ? Array.prototype.slice.call(message.GuardianSignature) : message.GuardianSignature; + if (message.Relayer != null && message.hasOwnProperty("Relayer")) + object.Relayer = options.bytes === String ? $util.base64.encode(message.Relayer, 0, message.Relayer.length) : options.bytes === Array ? Array.prototype.slice.call(message.Relayer) : message.Relayer; + if (message.RelayerSignature != null && message.hasOwnProperty("RelayerSignature")) + object.RelayerSignature = options.bytes === String ? $util.base64.encode(message.RelayerSignature, 0, message.RelayerSignature.length) : options.bytes === Array ? Array.prototype.slice.call(message.RelayerSignature) : message.RelayerSignature; return object; }; diff --git a/src/proto/serializer.ts b/src/proto/serializer.ts index f3842617..19090548 100644 --- a/src/proto/serializer.ts +++ b/src/proto/serializer.ts @@ -59,9 +59,18 @@ export class ProtoSerializer { protoTransaction.GuardianSignature = transaction.guardianSignature; } + if (this.isRelayedTransaction(transaction)) { + protoTransaction.Relayer = transaction.relayer?.getPublicKey(); + protoTransaction.RelayerSignature = transaction.relayerSignature; + } + return protoTransaction; } + private isRelayedTransaction(transaction: ITransaction) { + return !transaction.relayer.isEmpty(); + } + /** * Custom serialization, compatible with mx-chain-go. */ diff --git a/src/proto/transaction.proto b/src/proto/transaction.proto index b3b7dfd1..efc8ad04 100644 --- a/src/proto/transaction.proto +++ b/src/proto/transaction.proto @@ -25,4 +25,6 @@ message Transaction { uint32 Options = 13; bytes GuardianAddr = 14; bytes GuardianSignature = 15; + bytes Relayer = 16; + bytes RelayerSignature = 17; } diff --git a/src/transaction.spec.ts b/src/transaction.spec.ts index 4d1042f1..f128b4b1 100644 --- a/src/transaction.spec.ts +++ b/src/transaction.spec.ts @@ -784,8 +784,49 @@ describe("test transaction", async () => { version: 2, options: undefined, guardian: undefined, + relayer: undefined, signature: undefined, guardianSignature: undefined, + relayerSignature: undefined, }); }); + it("should serialize transaction with relayer", async () => { + const transaction = new Transaction({ + chainID: networkConfig.ChainID, + sender: wallets.alice.address, + receiver: wallets.alice.address, + relayer: wallets.bob.address, + gasLimit: 50000n, + value: 0n, + version: 2, + nonce: 89n, + }); + + const serializedTransactionBytes = transactionComputer.computeBytesForSigning(transaction); + const serializedTransaction = Buffer.from(serializedTransactionBytes).toString(); + + assert.equal( + serializedTransaction, + `{"nonce":89,"value":"0","receiver":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th","sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th","gasPrice":1000000000,"gasLimit":50000,"chainID":"D","version":2,"relayer":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"}`, + ); + }); + + it("should test relayed v3", async () => { + const transaction = new Transaction({ + chainID: networkConfig.ChainID, + sender: wallets.alice.address, + receiver: wallets.alice.address, + senderUsername: "alice", + receiverUsername: "bob", + gasLimit: 80000n, + value: 0n, + version: 2, + nonce: 89n, + data: Buffer.from("hello"), + }); + + assert.isFalse(transactionComputer.isRelayedV3Transaction(transaction)); + transaction.relayer = wallets.carol.address; + assert.isTrue(transactionComputer.isRelayedV3Transaction(transaction)); + }); }); diff --git a/src/transaction.ts b/src/transaction.ts index 0328afea..3f61a40e 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -76,6 +76,12 @@ export class Transaction { */ public guardian: Address; + /** + * The relayer address. + * Note: in the next major version, `sender`, `receiver` and `guardian` will also have the type `Address`, instead of `string`. + */ + public relayer: Address; + /** * The signature. */ @@ -86,6 +92,11 @@ export class Transaction { */ public guardianSignature: Uint8Array; + /** + * The signature of the relayer. + */ + public relayerSignature: Uint8Array; + /** * Creates a new Transaction object. */ @@ -103,8 +114,10 @@ export class Transaction { version?: number; options?: number; guardian?: Address; + relayer?: Address; signature?: Uint8Array; guardianSignature?: Uint8Array; + relayerSignature?: Uint8Array; }) { this.nonce = BigInt(options.nonce?.valueOf() || 0n); // We still rely on "bigNumber" for value, because client code might be passing a BigNumber object as a legacy "ITransactionValue", @@ -121,9 +134,11 @@ export class Transaction { this.version = Number(options.version?.valueOf() || TRANSACTION_VERSION_DEFAULT); this.options = Number(options.options?.valueOf() || TRANSACTION_OPTIONS_DEFAULT); this.guardian = options.guardian ?? Address.empty(); + this.relayer = options.relayer ? options.relayer : Address.empty(); this.signature = options.signature || Buffer.from([]); this.guardianSignature = options.guardianSignature || Buffer.from([]); + this.relayerSignature = options.relayerSignature || Buffer.from([]); } /** @@ -358,8 +373,10 @@ export class Transaction { version: this.version, options: this.options == 0 ? undefined : this.options, guardian: this.guardian.isEmpty() ? undefined : this.guardian.toBech32(), + relayer: this.relayer.isEmpty() ? undefined : this.relayer.toBech32(), signature: this.toHexOrUndefined(this.signature), guardianSignature: this.toHexOrUndefined(this.guardianSignature), + relayerSignature: this.toHexOrUndefined(this.relayerSignature), }; return plainObject; @@ -389,6 +406,7 @@ export class Transaction { sender: Address.newFromBech32(object.sender), senderUsername: Buffer.from(object.senderUsername || "", "base64").toString(), guardian: object.guardian ? Address.newFromBech32(object.guardian) : Address.empty(), + relayer: object.relayer ? Address.newFromBech32(object.relayer) : Address.empty(), gasPrice: BigInt(object.gasPrice), gasLimit: BigInt(object.gasLimit), data: Buffer.from(object.data || "", "base64"), @@ -397,6 +415,7 @@ export class Transaction { options: Number(object.options), signature: Buffer.from(object.signature || "", "hex"), guardianSignature: Buffer.from(object.guardianSignature || "", "hex"), + relayerSignature: Buffer.from(object.relayerSignature || "", "hex"), }); return transaction; diff --git a/src/transactionComputer.ts b/src/transactionComputer.ts index 52bd568e..dfc82754 100644 --- a/src/transactionComputer.ts +++ b/src/transactionComputer.ts @@ -94,6 +94,10 @@ export class TransactionComputer { transaction.guardian = guardian; } + isRelayedV3Transaction(transaction: Transaction) { + return !transaction.relayer.isEmpty(); + } + applyOptionsForHashSigning(transaction: Transaction) { if (transaction.version < MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS) { transaction.version = MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS; @@ -122,6 +126,7 @@ export class TransactionComputer { obj.version = transaction.version; obj.options = transaction.options ? transaction.options : undefined; obj.guardian = transaction.guardian.isEmpty() ? undefined : transaction.guardian.toBech32(); + obj.relayer = transaction.relayer?.isEmpty() ? undefined : transaction.relayer?.toBech32(); return obj; }