diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6eb56197e..b0a05871e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T
## Fixed
+- Fix `serializeAsBytes is not a function` error when wallet extensions (e.g. Petra) bundle an older SDK and serialize v6 transaction objects. Added `serializeEntryFunctionBytesCompat()` helper with runtime fallback to the pre-v6 `bcsToBytes()` + `serializeBytes()` pattern (DVR-143)
- Fix simple function arguments for `Vector>` types: BCS-encoded values (e.g. `AccountAddress.ONE`) passed as elements of `vector >` are now automatically wrapped in `MoveOption` instead of throwing a type mismatch error
- Resolve moderate security advisories in `confidential-assets` dev tooling by pinning transitive `file-type` and `yauzl` (via `@swc/cli` → `@xhmikosr/downloader`) to patched releases
- Remove hardcoded `maxGasAmount: 2000` from e2e tests (Account Derivation APIs, WebAuthn submission) that caused `MAX_GAS_UNITS_BELOW_MIN_TRANSACTION_GAS_UNITS` failures after the on-chain minimum gas increase
diff --git a/src/bcs/serializable/movePrimitives.ts b/src/bcs/serializable/movePrimitives.ts
index 9aec5c10b..71ba96a33 100644
--- a/src/bcs/serializable/movePrimitives.ts
+++ b/src/bcs/serializable/movePrimitives.ts
@@ -21,7 +21,13 @@ import {
MAX_I256_BIG_INT,
} from "../consts";
import { Deserializer } from "../deserializer";
-import { Serializable, Serializer, ensureBoolean, validateNumberInRange } from "../serializer";
+import {
+ Serializable,
+ Serializer,
+ ensureBoolean,
+ serializeEntryFunctionBytesCompat,
+ validateNumberInRange,
+} from "../serializer";
import { TransactionArgument } from "../../transactions/instances/transactionArgument";
import { AnyNumber, Uint16, Uint32, Uint8, Int8, Int16, Int32, ScriptTransactionArgumentVariants } from "../../types";
@@ -76,14 +82,14 @@ export class Bool extends Serializable implements TransactionArgument {
/**
* Serializes the current instance for use in an entry function by converting it to a byte sequence.
* This allows the instance to be properly formatted for serialization in transactions.
- * Uses the optimized serializeAsBytes method to reduce allocations.
+ * Uses serializeAsBytes when available, with a fallback for older Serializer versions.
*
* @param serializer - The serializer instance used to serialize the byte sequence.
* @group Implementation
* @category BCS
*/
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
/**
@@ -138,7 +144,7 @@ export class U8 extends Serializable implements TransactionArgument {
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
serializeForScriptFunction(serializer: Serializer): void {
@@ -174,7 +180,7 @@ export class U16 extends Serializable implements TransactionArgument {
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
serializeForScriptFunction(serializer: Serializer): void {
@@ -209,7 +215,7 @@ export class U32 extends Serializable implements TransactionArgument {
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
serializeForScriptFunction(serializer: Serializer): void {
@@ -247,7 +253,7 @@ export class U64 extends Serializable implements TransactionArgument {
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
serializeForScriptFunction(serializer: Serializer): void {
@@ -283,7 +289,7 @@ export class U128 extends Serializable implements TransactionArgument {
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
serializeForScriptFunction(serializer: Serializer): void {
@@ -319,7 +325,7 @@ export class U256 extends Serializable implements TransactionArgument {
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
serializeForScriptFunction(serializer: Serializer): void {
@@ -354,7 +360,7 @@ export class I8 extends Serializable implements TransactionArgument {
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
serializeForScriptFunction(serializer: Serializer): void {
@@ -390,7 +396,7 @@ export class I16 extends Serializable implements TransactionArgument {
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
serializeForScriptFunction(serializer: Serializer): void {
@@ -425,7 +431,7 @@ export class I32 extends Serializable implements TransactionArgument {
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
serializeForScriptFunction(serializer: Serializer): void {
@@ -463,7 +469,7 @@ export class I64 extends Serializable implements TransactionArgument {
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
serializeForScriptFunction(serializer: Serializer): void {
@@ -499,7 +505,7 @@ export class I128 extends Serializable implements TransactionArgument {
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
serializeForScriptFunction(serializer: Serializer): void {
@@ -535,7 +541,7 @@ export class I256 extends Serializable implements TransactionArgument {
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
serializeForScriptFunction(serializer: Serializer): void {
diff --git a/src/bcs/serializable/moveStructs.ts b/src/bcs/serializable/moveStructs.ts
index 8431c8ed0..a5591e802 100644
--- a/src/bcs/serializable/moveStructs.ts
+++ b/src/bcs/serializable/moveStructs.ts
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { Bool, U128, U16, U256, U32, U64, U8, I8, I16, I32, I64, I128, I256 } from "./movePrimitives";
-import { Serializable, Serializer } from "../serializer";
+import { Serializable, Serializer, serializeEntryFunctionBytesCompat } from "../serializer";
import { Deserializable, Deserializer } from "../deserializer";
import { AnyNumber, HexInput, ScriptTransactionArgumentVariants } from "../../types";
import { Hex } from "../../core/hex";
@@ -69,14 +69,14 @@ export class MoveVector
/**
* Serializes the current instance into a byte sequence suitable for entry functions.
* This allows the data to be properly formatted for transmission or storage.
- * Uses the optimized serializeAsBytes method to reduce allocations.
+ * Uses serializeAsBytes when available, with a fallback for older Serializer versions.
*
* @param serializer - The serializer instance used to serialize the byte sequence.
* @group Implementation
* @category BCS
*/
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
/**
@@ -493,7 +493,7 @@ export class MoveString extends Serializable implements TransactionArgument {
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
serializeForScriptFunction(serializer: Serializer): void {
@@ -530,7 +530,7 @@ export class MoveOption
}
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
/**
diff --git a/src/bcs/serializer.ts b/src/bcs/serializer.ts
index c065e9efb..cb9cf6e72 100644
--- a/src/bcs/serializer.ts
+++ b/src/bcs/serializer.ts
@@ -77,6 +77,25 @@ export abstract class Serializable {
}
}
+/**
+ * Serialize a Serializable value as length-prefixed bytes into a Serializer,
+ * with backwards compatibility for older Serializer implementations that lack
+ * the `serializeAsBytes` method. This is critical for cross-version compatibility
+ * when SDK objects built with a newer SDK are serialized by an older SDK's Serializer
+ * (e.g., wallet extensions bundling an older SDK version).
+ *
+ * @param serializer - The serializer to write into (may be from any SDK version).
+ * @param value - The Serializable value to serialize as bytes.
+ */
+export function serializeEntryFunctionBytesCompat(serializer: Serializer, value: Serializable): void {
+ if (typeof (serializer as { serializeAsBytes?: unknown }).serializeAsBytes === "function") {
+ serializer.serializeAsBytes(value);
+ } else {
+ const bcsBytes = value.bcsToBytes();
+ serializer.serializeBytes(bcsBytes);
+ }
+}
+
/**
* Minimum buffer growth increment to avoid too many small reallocations.
*/
diff --git a/src/core/accountAddress.ts b/src/core/accountAddress.ts
index 34cf0735b..cbdc151ac 100644
--- a/src/core/accountAddress.ts
+++ b/src/core/accountAddress.ts
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
-import { Serializable, Serializer } from "../bcs/serializer";
+import { Serializable, Serializer, serializeEntryFunctionBytesCompat } from "../bcs/serializer";
import { Deserializer } from "../bcs/deserializer";
import { ParsingError, ParsingResult } from "./common";
import { TransactionArgument } from "../transactions/instances/transactionArgument";
@@ -240,14 +240,14 @@ export class AccountAddress extends Serializable implements TransactionArgument
/**
* Serializes the current instance into a byte sequence suitable for entry functions.
* This allows for the proper encoding of data when interacting with entry functions in the blockchain.
- * Uses the optimized serializeAsBytes method to reduce allocations.
+ * Uses serializeAsBytes when available, with a fallback for older Serializer versions.
*
* @param serializer - The serializer instance used to convert the data into bytes.
* @group Implementation
* @category Serialization
*/
serializeForEntryFunction(serializer: Serializer): void {
- serializer.serializeAsBytes(this);
+ serializeEntryFunctionBytesCompat(serializer, this);
}
/**
diff --git a/tests/unit/walletSerializerCompat.test.ts b/tests/unit/walletSerializerCompat.test.ts
new file mode 100644
index 000000000..c72f8c2b9
--- /dev/null
+++ b/tests/unit/walletSerializerCompat.test.ts
@@ -0,0 +1,388 @@
+// Copyright © Aptos Foundation
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * Tests that verify cross-version serializer compatibility for the wallet adapter flow.
+ *
+ * When a wallet (e.g. Petra) bundles an older SDK version and receives a transaction
+ * object built with a newer SDK, the wallet's older Serializer may lack newer methods
+ * like `serializeAsBytes`. This test simulates that scenario by creating a "legacy"
+ * serializer that mirrors the older SDK's Serializer API, then verifying that v6
+ * transaction objects can still be serialized through it.
+ *
+ * See: DVR-143 (Petra fee payer error after upgrading to TS SDK 6.2.0)
+ */
+
+import {
+ AccountAddress,
+ Bool,
+ Deserializer,
+ EntryFunction,
+ MoveOption,
+ MoveString,
+ MoveVector,
+ RawTransaction,
+ Serializer,
+ SimpleTransaction,
+ TransactionPayloadEntryFunction,
+ TypeTagAddress,
+ U128,
+ U16,
+ U256,
+ U32,
+ U64,
+ U8,
+ I8,
+ I16,
+ I32,
+ I64,
+ I128,
+ I256,
+ ChainId,
+ FeePayerRawTransaction,
+} from "../../src";
+
+/**
+ * Creates a Serializer instance that mimics an older SDK version's Serializer
+ * (one that does NOT have the `serializeAsBytes` method). This simulates what
+ * happens when a wallet bundles an older SDK and tries to serialize a transaction
+ * built with a newer SDK.
+ */
+function createLegacySerializer(): Serializer {
+ const serializer = new Serializer();
+ // Remove the serializeAsBytes method to simulate an older Serializer
+ (serializer as Record).serializeAsBytes = undefined;
+ return serializer;
+}
+
+describe("Cross-version serializer compatibility (wallet adapter flow)", () => {
+ describe("Move primitives serialize correctly with a legacy serializer", () => {
+ const testCases: Array<{
+ name: string;
+ value: { serializeForEntryFunction(s: Serializer): void; bcsToBytes(): Uint8Array };
+ }> = [
+ { name: "Bool(true)", value: new Bool(true) },
+ { name: "Bool(false)", value: new Bool(false) },
+ { name: "U8(42)", value: new U8(42) },
+ { name: "U16(1000)", value: new U16(1000) },
+ { name: "U32(100000)", value: new U32(100000) },
+ { name: "U64(1000000n)", value: new U64(1000000n) },
+ { name: "U128(999999999999n)", value: new U128(999999999999n) },
+ { name: "U256(12345678901234567890n)", value: new U256(12345678901234567890n) },
+ { name: "I8(-42)", value: new I8(-42) },
+ { name: "I16(-1000)", value: new I16(-1000) },
+ { name: "I32(-100000)", value: new I32(-100000) },
+ { name: "I64(-1000000n)", value: new I64(-1000000n) },
+ { name: "I128(-999999999999n)", value: new I128(-999999999999n) },
+ { name: "I256(-12345678901234567890n)", value: new I256(-12345678901234567890n) },
+ ];
+
+ for (const { name, value } of testCases) {
+ it(`${name} produces identical bytes with legacy vs modern serializer`, () => {
+ const modernSerializer = new Serializer();
+ value.serializeForEntryFunction(modernSerializer);
+ const modernBytes = modernSerializer.toUint8Array();
+
+ const legacySerializer = createLegacySerializer();
+ value.serializeForEntryFunction(legacySerializer);
+ const legacyBytes = legacySerializer.toUint8Array();
+
+ expect(legacyBytes).toEqual(modernBytes);
+ });
+ }
+ });
+
+ describe("Complex types serialize correctly with a legacy serializer", () => {
+ it("AccountAddress produces identical bytes", () => {
+ const addr = AccountAddress.from("0x1");
+
+ const modernSerializer = new Serializer();
+ addr.serializeForEntryFunction(modernSerializer);
+ const modernBytes = modernSerializer.toUint8Array();
+
+ const legacySerializer = createLegacySerializer();
+ addr.serializeForEntryFunction(legacySerializer);
+ const legacyBytes = legacySerializer.toUint8Array();
+
+ expect(legacyBytes).toEqual(modernBytes);
+ });
+
+ it("MoveVector produces identical bytes", () => {
+ const vec = MoveVector.U8([1, 2, 3, 4, 5]);
+
+ const modernSerializer = new Serializer();
+ vec.serializeForEntryFunction(modernSerializer);
+ const modernBytes = modernSerializer.toUint8Array();
+
+ const legacySerializer = createLegacySerializer();
+ vec.serializeForEntryFunction(legacySerializer);
+ const legacyBytes = legacySerializer.toUint8Array();
+
+ expect(legacyBytes).toEqual(modernBytes);
+ });
+
+ it("MoveVector produces identical bytes", () => {
+ const vec = MoveVector.U64([100n, 200n, 300n]);
+
+ const modernSerializer = new Serializer();
+ vec.serializeForEntryFunction(modernSerializer);
+ const modernBytes = modernSerializer.toUint8Array();
+
+ const legacySerializer = createLegacySerializer();
+ vec.serializeForEntryFunction(legacySerializer);
+ const legacyBytes = legacySerializer.toUint8Array();
+
+ expect(legacyBytes).toEqual(modernBytes);
+ });
+
+ it("MoveString produces identical bytes", () => {
+ const str = new MoveString("hello world");
+
+ const modernSerializer = new Serializer();
+ str.serializeForEntryFunction(modernSerializer);
+ const modernBytes = modernSerializer.toUint8Array();
+
+ const legacySerializer = createLegacySerializer();
+ str.serializeForEntryFunction(legacySerializer);
+ const legacyBytes = legacySerializer.toUint8Array();
+
+ expect(legacyBytes).toEqual(modernBytes);
+ });
+
+ it("MoveOption produces identical bytes", () => {
+ const opt = MoveOption.U64(12345n);
+
+ const modernSerializer = new Serializer();
+ opt.serializeForEntryFunction(modernSerializer);
+ const modernBytes = modernSerializer.toUint8Array();
+
+ const legacySerializer = createLegacySerializer();
+ opt.serializeForEntryFunction(legacySerializer);
+ const legacyBytes = legacySerializer.toUint8Array();
+
+ expect(legacyBytes).toEqual(modernBytes);
+ });
+ });
+
+ describe("Full transaction serialization with legacy serializer", () => {
+ it("RawTransaction with entry function serializes correctly through a legacy serializer", () => {
+ const sender = AccountAddress.from("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
+ const recipient = AccountAddress.from("0x1");
+ const amount = new U64(100);
+
+ const entryFunction = EntryFunction.build(
+ "0x1::aptos_account",
+ "transfer_coins",
+ [new TypeTagAddress()],
+ [recipient, amount],
+ );
+ const payload = new TransactionPayloadEntryFunction(entryFunction);
+ const rawTxn = new RawTransaction(
+ sender,
+ 0n,
+ payload,
+ 200000n,
+ 100n,
+ BigInt(Math.floor(Date.now() / 1000) + 600),
+ new ChainId(2),
+ );
+
+ // Serialize with modern serializer
+ const modernSerializer = new Serializer();
+ rawTxn.serialize(modernSerializer);
+ const modernBytes = modernSerializer.toUint8Array();
+
+ // Simulate what a wallet with an older SDK would do:
+ // create its own serializer (missing serializeAsBytes) and serialize the v6 RawTransaction
+ const legacySerializer = createLegacySerializer();
+ rawTxn.serialize(legacySerializer);
+ const legacyBytes = legacySerializer.toUint8Array();
+
+ expect(legacyBytes).toEqual(modernBytes);
+ });
+
+ it("SimpleTransaction (no fee payer) serializes correctly through a legacy serializer", () => {
+ const sender = AccountAddress.from("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
+ const recipient = AccountAddress.from("0x1");
+ const amount = new U64(100);
+
+ const entryFunction = EntryFunction.build(
+ "0x1::aptos_account",
+ "transfer_coins",
+ [new TypeTagAddress()],
+ [recipient, amount],
+ );
+ const payload = new TransactionPayloadEntryFunction(entryFunction);
+ const rawTxn = new RawTransaction(
+ sender,
+ 0n,
+ payload,
+ 200000n,
+ 100n,
+ BigInt(Math.floor(Date.now() / 1000) + 600),
+ new ChainId(2),
+ );
+ const simpleTxn = new SimpleTransaction(rawTxn);
+
+ const modernSerializer = new Serializer();
+ simpleTxn.serialize(modernSerializer);
+ const modernBytes = modernSerializer.toUint8Array();
+
+ const legacySerializer = createLegacySerializer();
+ simpleTxn.serialize(legacySerializer);
+ const legacyBytes = legacySerializer.toUint8Array();
+
+ expect(legacyBytes).toEqual(modernBytes);
+ });
+
+ it("SimpleTransaction with fee payer serializes correctly through a legacy serializer", () => {
+ const sender = AccountAddress.from("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
+ const recipient = AccountAddress.from("0x1");
+ const amount = new U64(100);
+ const feePayer = AccountAddress.from("0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321");
+
+ const entryFunction = EntryFunction.build(
+ "0x1::aptos_account",
+ "transfer_coins",
+ [new TypeTagAddress()],
+ [recipient, amount],
+ );
+ const payload = new TransactionPayloadEntryFunction(entryFunction);
+ const rawTxn = new RawTransaction(
+ sender,
+ 0n,
+ payload,
+ 200000n,
+ 100n,
+ BigInt(Math.floor(Date.now() / 1000) + 600),
+ new ChainId(2),
+ );
+
+ const simpleTxn = new SimpleTransaction(rawTxn, feePayer);
+
+ const modernSerializer = new Serializer();
+ simpleTxn.serialize(modernSerializer);
+ const modernBytes = modernSerializer.toUint8Array();
+
+ const legacySerializer = createLegacySerializer();
+ simpleTxn.serialize(legacySerializer);
+ const legacyBytes = legacySerializer.toUint8Array();
+
+ expect(legacyBytes).toEqual(modernBytes);
+ });
+
+ it("FeePayerRawTransaction serializes correctly through a legacy serializer", () => {
+ const sender = AccountAddress.from("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
+ const recipient = AccountAddress.from("0x1");
+ const amount = new U64(100);
+ const feePayer = AccountAddress.from("0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321");
+
+ const entryFunction = EntryFunction.build(
+ "0x1::aptos_account",
+ "transfer_coins",
+ [new TypeTagAddress()],
+ [recipient, amount],
+ );
+ const payload = new TransactionPayloadEntryFunction(entryFunction);
+ const rawTxn = new RawTransaction(
+ sender,
+ 0n,
+ payload,
+ 200000n,
+ 100n,
+ BigInt(Math.floor(Date.now() / 1000) + 600),
+ new ChainId(2),
+ );
+
+ const feePayerTxn = new FeePayerRawTransaction(rawTxn, [], feePayer);
+
+ const modernSerializer = new Serializer();
+ feePayerTxn.serialize(modernSerializer);
+ const modernBytes = modernSerializer.toUint8Array();
+
+ const legacySerializer = createLegacySerializer();
+ feePayerTxn.serialize(legacySerializer);
+ const legacyBytes = legacySerializer.toUint8Array();
+
+ expect(legacyBytes).toEqual(modernBytes);
+ });
+
+ it("bcsToBytes still works correctly on transaction objects (uses internal v6 Serializer)", () => {
+ const sender = AccountAddress.from("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
+ const recipient = AccountAddress.from("0x1");
+ const amount = new U64(100);
+ const feePayer = AccountAddress.from("0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321");
+
+ const entryFunction = EntryFunction.build(
+ "0x1::aptos_account",
+ "transfer_coins",
+ [new TypeTagAddress()],
+ [recipient, amount],
+ );
+ const payload = new TransactionPayloadEntryFunction(entryFunction);
+ const rawTxn = new RawTransaction(
+ sender,
+ 0n,
+ payload,
+ 200000n,
+ 100n,
+ BigInt(Math.floor(Date.now() / 1000) + 600),
+ new ChainId(2),
+ );
+ const simpleTxn = new SimpleTransaction(rawTxn, feePayer);
+
+ // bcsToBytes uses the internal Serializer (which always has serializeAsBytes)
+ const bytes = simpleTxn.bcsToBytes();
+ expect(bytes).toBeInstanceOf(Uint8Array);
+ expect(bytes.length).toBeGreaterThan(0);
+
+ // Verify round-trip
+ const deserializedTxn = SimpleTransaction.deserialize(new Deserializer(bytes));
+ expect(deserializedTxn.rawTransaction.sender.toString()).toBe(sender.toString());
+ expect(deserializedTxn.feePayerAddress?.toString()).toBe(feePayer.toString());
+ });
+ });
+
+ describe("Wallet adapter simulation: wallet with older serializer signs a v6 fee payer transaction", () => {
+ it("generates identical signing messages for fee payer transaction when serialized with legacy serializer", () => {
+ const sender = AccountAddress.from("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
+ const recipient = AccountAddress.from("0x1");
+ const amount = new U64(100);
+ const feePayer = AccountAddress.from("0x0000000000000000000000000000000000000000000000000000000000000000");
+
+ const entryFunction = EntryFunction.build(
+ "0x1::aptos_account",
+ "transfer_coins",
+ [new TypeTagAddress()],
+ [recipient, amount],
+ );
+ const payload = new TransactionPayloadEntryFunction(entryFunction);
+ const rawTxn = new RawTransaction(
+ sender,
+ 1n,
+ payload,
+ 200000n,
+ 100n,
+ BigInt(Math.floor(Date.now() / 1000) + 600),
+ new ChainId(2),
+ );
+
+ // This is what the wallet would do: create a FeePayerRawTransaction from
+ // the SimpleTransaction's fields and serialize it for signing
+ const feePayerTxn = new FeePayerRawTransaction(rawTxn, [], feePayer);
+
+ // Modern serializer (has serializeAsBytes)
+ const modernSerializer = new Serializer();
+ feePayerTxn.serialize(modernSerializer);
+ const modernBytes = modernSerializer.toUint8Array();
+
+ // Legacy serializer (lacks serializeAsBytes - simulates older wallet SDK)
+ const legacySerializer = createLegacySerializer();
+ feePayerTxn.serialize(legacySerializer);
+ const legacyBytes = legacySerializer.toUint8Array();
+
+ // The signing message must be identical regardless of serializer version
+ expect(legacyBytes).toEqual(modernBytes);
+ });
+ });
+});