Skip to content

Commit 3d64397

Browse files
Emre Bogazliyanliogluevan-gray
authored andcommitted
Fix transfer function manual outbound transfers
Signed-off-by: Emre Bogazliyanlioglu <emre@wormholelabs.xyz>
1 parent 8715490 commit 3d64397

2 files changed

Lines changed: 105 additions & 13 deletions

File tree

sui/ts/__tests__/ntt.test.ts

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -730,16 +730,6 @@ describe("SuiNtt", () => {
730730
const options = { queue: false };
731731

732732
beforeEach(() => {
733-
// Mock package ID extraction
734-
mockClient.getObject
735-
.mockResolvedValueOnce(
736-
mockSuiObject(
737-
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef::ntt::State",
738-
{}
739-
)
740-
)
741-
.mockResolvedValueOnce(mockCoinMetadata());
742-
743733
// Mock coin metadata query
744734
mockClient.getCoinMetadata.mockResolvedValue(mockCoinMetadata());
745735

@@ -755,6 +745,48 @@ describe("SuiNtt", () => {
755745
nextCursor: null,
756746
hasNextPage: false
757747
});
748+
749+
// Mock getDynamicFields for getWormholePackageId
750+
mockClient.getDynamicFields.mockResolvedValue({
751+
data: [{
752+
name: { type: "CurrentPackage" },
753+
objectId: "0xmockcurrentpackage123"
754+
}],
755+
nextCursor: null,
756+
hasNextPage: false
757+
});
758+
759+
// Mock getObject for multiple different object IDs
760+
mockClient.getObject.mockImplementation((params: any) => {
761+
if (params.id === "0xmockcurrentpackage123") {
762+
// Mock for CurrentPackage object in getWormholePackageId
763+
return Promise.resolve({
764+
data: {
765+
content: {
766+
dataType: "moveObject",
767+
fields: {
768+
value: {
769+
fields: {
770+
package: "0xwormholepackage123"
771+
}
772+
}
773+
}
774+
}
775+
}
776+
});
777+
} else if (params.id === TEST_CONTRACTS.ntt.transceiver.wormhole) {
778+
// Mock for transceiver state object (getPackageIdFromObject)
779+
return Promise.resolve(mockSuiObject(
780+
"0xtransceiver123::wormhole_transceiver::State",
781+
{}
782+
));
783+
}
784+
// Default mock for NTT manager state
785+
return Promise.resolve(mockSuiObject(
786+
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef::ntt::State",
787+
{}
788+
));
789+
});
758790
});
759791

760792
it("should create transfer transaction with correct parameters", async () => {

sui/ts/src/ntt.ts

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -780,8 +780,9 @@ export class SuiNtt<N extends Network, C extends SuiChains>
780780

781781
// get token ID
782782
const token = this.contracts.ntt!["token"];
783-
const isNativeToken = (typeof token === "string" && token === "native") ||
784-
(typeof token === "string" && token === "0x2::sui::SUI");
783+
const isNativeToken =
784+
(typeof token === "string" && token === "native") ||
785+
(typeof token === "string" && token === "0x2::sui::SUI");
785786
const tokenAddress = new SuiAddress(
786787
isNativeToken
787788
? SuiPlatform.nativeTokenId(this.network, this.chain).address
@@ -852,7 +853,20 @@ export class SuiNtt<N extends Network, C extends SuiChains>
852853
],
853854
});
854855

855-
// Now call transfer_tx_sender with just the ticket
856+
// Get next sequence number before calling transfer_tx_sender
857+
const [sequenceBytes32] = txb.moveCall({
858+
target: `${packageId}::state::get_next_sequence`,
859+
typeArguments: [tokenId],
860+
arguments: [txb.object(this.contracts.ntt!["manager"])],
861+
});
862+
863+
// Get Wormhole package ID
864+
const wormholePackageId = await this.getWormholePackageId(
865+
this.provider,
866+
this.coreBridgeStateId
867+
);
868+
869+
// Now call transfer_tx_sender
856870
txb.moveCall({
857871
target: `${packageId}::ntt::transfer_tx_sender`,
858872
typeArguments: [tokenId],
@@ -865,6 +879,52 @@ export class SuiNtt<N extends Network, C extends SuiChains>
865879
],
866880
});
867881

882+
// Get transceiver info
883+
const transceiverStateId = this.contracts.ntt!.transceiver?.["wormhole"];
884+
if (!transceiverStateId) {
885+
throw new Error("Wormhole transceiver not found in contracts");
886+
}
887+
888+
// Get transceiver package ID
889+
const transceiverPackageId = await this.getPackageIdFromObject(
890+
transceiverStateId
891+
);
892+
893+
// Create transceiver message
894+
const [transceiverMessage] = txb.moveCall({
895+
target: `${packageId}::state::create_transceiver_message`,
896+
typeArguments: [
897+
`${transceiverPackageId}::wormhole_transceiver::TransceiverAuth`,
898+
tokenId,
899+
],
900+
arguments: [
901+
txb.object(this.contracts.ntt!["manager"]),
902+
sequenceBytes32 as any,
903+
txb.object(SUI_CLOCK_OBJECT_ID),
904+
],
905+
});
906+
907+
// Release outbound message
908+
const [messageTicket] = txb.moveCall({
909+
target: `${transceiverPackageId}::wormhole_transceiver::release_outbound`,
910+
typeArguments: [`${packageId}::auth::ManagerAuth`],
911+
arguments: [txb.object(transceiverStateId), transceiverMessage as any],
912+
});
913+
914+
// Split fee coin for publishing message
915+
const [feeCoin] = txb.splitCoins(txb.gas, [txb.pure.u64(0n)]);
916+
917+
// Publish message to Wormhole
918+
txb.moveCall({
919+
target: `${wormholePackageId}::publish_message::publish_message`,
920+
arguments: [
921+
txb.object(this.coreBridgeStateId),
922+
feeCoin,
923+
messageTicket as any,
924+
txb.object(SUI_CLOCK_OBJECT_ID),
925+
],
926+
});
927+
868928
// Handle dust by converting back to coin and merging with gas if native or back to sender otherwise
869929
const [dustCoin] = txb.moveCall({
870930
target: `0x2::coin::from_balance`,

0 commit comments

Comments
 (0)