Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,6 @@ import { TransactionType, TransactionBlockInput } from './Transactions';
import { BuilderCallArg, PureCallArg } from './Inputs';
import { create } from './utils';

export const TransactionExpiration = optional(
nullable(
union([
object({ Epoch: integer() }),
object({ None: union([literal(true), literal(null)]) }),
object({ ValidDuring: object({ minEpoch: integer(), maxEpoch: integer(), chain: string(), nonce: integer() }) }),
])
)
);
export type TransactionExpiration = Infer<typeof TransactionExpiration>;

const SuiAddress = string();

const StringEncodedBigint = define<string>('StringEncodedBigint', (val) => {
Expand All @@ -44,6 +33,17 @@ const StringEncodedBigint = define<string>('StringEncodedBigint', (val) => {
}
});

export const TransactionExpiration = optional(
nullable(
union([
object({ Epoch: StringEncodedBigint }),
object({ None: union([literal(true), literal(null)]) }),
object({ ValidDuring: object({ minEpoch: integer(), maxEpoch: integer(), chain: string(), nonce: integer() }) }),
])
)
);
export type TransactionExpiration = Infer<typeof TransactionExpiration>;

const GasConfig = object({
budget: optional(StringEncodedBigint),
price: optional(StringEncodedBigint),
Expand Down
3 changes: 1 addition & 2 deletions modules/sdk-coin-sui/src/lib/mystenlab/types/sui-bcs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export type ValidDuringExpiration = {
*
* Indications the expiration time for a transaction.
*/
export type TransactionExpiration = { None: null } | { Epoch: number } | { ValidDuring: ValidDuringExpiration };
export type TransactionExpiration = { None: null } | { Epoch: number | bigint | string } | { ValidDuring: ValidDuringExpiration };

// Move name of the Vector type.
const VECTOR = 'vector';
Expand Down Expand Up @@ -155,7 +155,6 @@ const BCS_SPEC: TypeSchema = {
CallArg: {
Pure: [VECTOR, BCS.U8],
Object: 'ObjectArg',
ObjVec: [VECTOR, 'ObjectArg'],
BalanceWithdrawal: 'BalanceWithdrawal',
},
TypeTag: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,5 +470,46 @@ describe('Sui Transfer Builder', () => {
const rawTx = tx.toBroadcastFormat();
should.equal(utils.isValidRawTransaction(rawTx), true);
});

it('should round-trip a self-pay transfer with Epoch expiration via fromBytes', async function () {
// Regression test for the BigInt round-trip bug:
// BCS.U64 deserializes u64 as BigInt, but the previous superstruct schema used integer()
// which rejected BigInt, causing fromBytes() to throw a StructError at "expiration".
// StringEncodedBigint now accepts string | number | bigint, fixing the round-trip.
const gasDataNoPayment = {
...testData.gasDataWithoutGasPayment,
payment: [],
};

const txBuilder = factory.getTransferBuilder();
txBuilder.type(SuiTransactionType.Transfer);
txBuilder.sender(testData.sender.address);
txBuilder.send(testData.recipients);
txBuilder.gasData(gasDataNoPayment);
txBuilder.fundsInAddressBalance(FUNDS_IN_ADDRESS_BALANCE);
txBuilder.expiration({ Epoch: 324 }); // number input

const tx = await txBuilder.build();
const rawTx = tx.toBroadcastFormat();
should.equal(utils.isValidRawTransaction(rawTx), true);

// fromBytes must not throw StructError — this was the failing case before the fix
should.doesNotThrow(() => {
const rebuilder = factory.from(rawTx);
should.exist(rebuilder);
});

// Full round-trip: rebuilt tx must serialize identically
const rebuilder = factory.from(rawTx);
rebuilder.addSignature({ pub: testData.sender.publicKey }, Buffer.from(testData.sender.signatureHex));
const rebuiltTx = await rebuilder.build();
rebuiltTx.toBroadcastFormat().should.equal(rawTx);

// Epoch value must survive the round-trip regardless of BigInt/number representation
const rebuiltJson = rebuiltTx.toJson();
const epochVal = (rebuiltJson.expiration as any)?.Epoch;
should.exist(epochVal);
Number(epochVal).should.equal(324);
});
});
});
Loading