@m0-foundation/ntt-sdk-route 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Network, routes, Chain, ChainContext, TokenId, Signer, ChainAddress, AccountAddress } from '@wormhole-foundation/sdk-connect';
2
- import { Ntt } from '@wormhole-foundation/sdk-definitions-ntt';
2
+ import { Ntt, NttWithExecutor } from '@wormhole-foundation/sdk-definitions-ntt';
3
3
  import { EvmNtt } from '@wormhole-foundation/sdk-evm-ntt';
4
4
  import { SolanaNtt } from '@wormhole-foundation/sdk-solana-ntt';
5
5
  import { EvmChains, EvmUnsignedTransaction } from '@wormhole-foundation/sdk-evm';
@@ -21,6 +21,7 @@ type Contracts = Ntt.Contracts & {
21
21
  declare class M0AutomaticRoute<N extends Network> extends routes.AutomaticRoute<N, Op, Vp, R> implements routes.StaticRouteMethods<typeof M0AutomaticRoute> {
22
22
  static NATIVE_GAS_DROPOFF_SUPPORTED: boolean;
23
23
  static EVM_WRAPPED_M_TOKEN: string;
24
+ static EXECUTOR_ENTRYPOINT: string;
24
25
  static EVM_CONTRACTS: Contracts;
25
26
  static meta: {
26
27
  name: string;
@@ -46,6 +47,8 @@ declare class M0AutomaticRoute<N extends Network> extends routes.AutomaticRoute<
46
47
  createUnsignedTx<N extends Network, C extends EvmChains>(ntt: EvmNtt<N, C>, txReq: TransactionRequest, description: string, parallelizable?: boolean): EvmUnsignedTransaction<N, C>;
47
48
  transferSolanaExtension<N extends Network, C extends SolanaChains>(ntt: SolanaNtt<N, C>, sender: AccountAddress<C>, amount: bigint, recipient: ChainAddress, sourceToken: string, destinationToken: string, options: Ntt.TransferOptions, outboxItem?: Keypair): AsyncGenerator<SolanaUnsignedTransaction<N, C>>;
48
49
  track(receipt: R, timeout?: number): AsyncGenerator<R, void, unknown>;
50
+ private getExecutorQuote;
51
+ private requiresExecutor;
49
52
  }
50
53
 
51
54
  type SvmNetwork = Exclude<Network, "Devnet">;
@@ -61,10 +64,14 @@ declare class SolanaRoutes<N extends Network, C extends SolanaChains> {
61
64
  constructor(ntt: SolanaNtt<N, C>);
62
65
  private static getPrograms;
63
66
  private static getExtPrograms;
64
- static getSolanaContracts(chainContext: ChainContext<Network>): Ntt.Contracts & {
67
+ static getSolanaContracts(network: Network, chain: SolanaChains): Ntt.Contracts & {
68
+ mLikeTokens: string[];
69
+ };
70
+ getSolanaContracts(): Ntt.Contracts & {
65
71
  mLikeTokens: string[];
66
72
  };
67
73
  getTransferExtensionBurnIx(amount: bigint, recipient: ChainAddress, payer: PublicKey, outboxItem: PublicKey, extMint: PublicKey, destinationToken: Uint8Array, shouldQueue?: boolean): TransactionInstruction;
74
+ getExecutorRelayIx(sender: PublicKey, quote: NttWithExecutor.Quote, destinationChain: Chain): Promise<TransactionInstruction>;
68
75
  getReleaseInboundMintExtensionIx(nttMessage: Ntt.Message, emitterChain: Chain, payer: PublicKey, extMint: PublicKey, extAta: PublicKey): TransactionInstruction;
69
76
  getAddressLookupTableAccounts(connection: Connection): Promise<AddressLookupTableAccount>;
70
77
  static createReleaseInboundMintInstruction<N extends Network, C extends SolanaChains>(ntt: SolanaNtt<N, C>, args: {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Network, routes, Chain, ChainContext, TokenId, Signer, ChainAddress, AccountAddress } from '@wormhole-foundation/sdk-connect';
2
- import { Ntt } from '@wormhole-foundation/sdk-definitions-ntt';
2
+ import { Ntt, NttWithExecutor } from '@wormhole-foundation/sdk-definitions-ntt';
3
3
  import { EvmNtt } from '@wormhole-foundation/sdk-evm-ntt';
4
4
  import { SolanaNtt } from '@wormhole-foundation/sdk-solana-ntt';
5
5
  import { EvmChains, EvmUnsignedTransaction } from '@wormhole-foundation/sdk-evm';
@@ -21,6 +21,7 @@ type Contracts = Ntt.Contracts & {
21
21
  declare class M0AutomaticRoute<N extends Network> extends routes.AutomaticRoute<N, Op, Vp, R> implements routes.StaticRouteMethods<typeof M0AutomaticRoute> {
22
22
  static NATIVE_GAS_DROPOFF_SUPPORTED: boolean;
23
23
  static EVM_WRAPPED_M_TOKEN: string;
24
+ static EXECUTOR_ENTRYPOINT: string;
24
25
  static EVM_CONTRACTS: Contracts;
25
26
  static meta: {
26
27
  name: string;
@@ -46,6 +47,8 @@ declare class M0AutomaticRoute<N extends Network> extends routes.AutomaticRoute<
46
47
  createUnsignedTx<N extends Network, C extends EvmChains>(ntt: EvmNtt<N, C>, txReq: TransactionRequest, description: string, parallelizable?: boolean): EvmUnsignedTransaction<N, C>;
47
48
  transferSolanaExtension<N extends Network, C extends SolanaChains>(ntt: SolanaNtt<N, C>, sender: AccountAddress<C>, amount: bigint, recipient: ChainAddress, sourceToken: string, destinationToken: string, options: Ntt.TransferOptions, outboxItem?: Keypair): AsyncGenerator<SolanaUnsignedTransaction<N, C>>;
48
49
  track(receipt: R, timeout?: number): AsyncGenerator<R, void, unknown>;
50
+ private getExecutorQuote;
51
+ private requiresExecutor;
49
52
  }
50
53
 
51
54
  type SvmNetwork = Exclude<Network, "Devnet">;
@@ -61,10 +64,14 @@ declare class SolanaRoutes<N extends Network, C extends SolanaChains> {
61
64
  constructor(ntt: SolanaNtt<N, C>);
62
65
  private static getPrograms;
63
66
  private static getExtPrograms;
64
- static getSolanaContracts(chainContext: ChainContext<Network>): Ntt.Contracts & {
67
+ static getSolanaContracts(network: Network, chain: SolanaChains): Ntt.Contracts & {
68
+ mLikeTokens: string[];
69
+ };
70
+ getSolanaContracts(): Ntt.Contracts & {
65
71
  mLikeTokens: string[];
66
72
  };
67
73
  getTransferExtensionBurnIx(amount: bigint, recipient: ChainAddress, payer: PublicKey, outboxItem: PublicKey, extMint: PublicKey, destinationToken: Uint8Array, shouldQueue?: boolean): TransactionInstruction;
74
+ getExecutorRelayIx(sender: PublicKey, quote: NttWithExecutor.Quote, destinationChain: Chain): Promise<TransactionInstruction>;
68
75
  getReleaseInboundMintExtensionIx(nttMessage: Ntt.Message, emitterChain: Chain, payer: PublicKey, extMint: PublicKey, extAta: PublicKey): TransactionInstruction;
69
76
  getAddressLookupTableAccounts(connection: Connection): Promise<AddressLookupTableAccount>;
70
77
  static createReleaseInboundMintInstruction<N extends Network, C extends SolanaChains>(ntt: SolanaNtt<N, C>, args: {
package/dist/index.js CHANGED
@@ -2,19 +2,23 @@
2
2
 
3
3
  var sdkConnect = require('@wormhole-foundation/sdk-connect');
4
4
  var sdkDefinitionsNtt = require('@wormhole-foundation/sdk-definitions-ntt');
5
- var sdkSolanaNtt = require('@wormhole-foundation/sdk-solana-ntt');
6
5
  var sdkEvm = require('@wormhole-foundation/sdk-evm');
7
6
  var sdkSolana = require('@wormhole-foundation/sdk-solana');
8
7
  var sdkRouteNtt = require('@wormhole-foundation/sdk-route-ntt');
9
8
  var ethers = require('ethers');
10
9
  var web3_js = require('@solana/web3.js');
11
10
  var splToken = require('@solana/spl-token');
11
+ var sdkSolanaNtt = require('@wormhole-foundation/sdk-solana-ntt');
12
12
  var BN = require('bn.js');
13
13
  var sha2 = require('@noble/hashes/sha2');
14
+ var evm = require('@wormhole-foundation/sdk/platforms/evm');
15
+ var solana = require('@wormhole-foundation/sdk/platforms/solana');
14
16
 
15
17
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
18
 
17
19
  var BN__default = /*#__PURE__*/_interopDefault(BN);
20
+ var evm__default = /*#__PURE__*/_interopDefault(evm);
21
+ var solana__default = /*#__PURE__*/_interopDefault(solana);
18
22
 
19
23
  // src/m0AutomaticRoute.ts
20
24
  var SolanaRoutes = class _SolanaRoutes {
@@ -22,7 +26,10 @@ var SolanaRoutes = class _SolanaRoutes {
22
26
  this.ntt = ntt;
23
27
  this.network = ntt.network;
24
28
  this.programs = _SolanaRoutes.getPrograms(this.network);
25
- this.extPrograms = _SolanaRoutes.getExtPrograms(this.network);
29
+ this.extPrograms = _SolanaRoutes.getExtPrograms(
30
+ this.network,
31
+ this.ntt.chain
32
+ );
26
33
  }
27
34
  static getPrograms(network) {
28
35
  return {
@@ -30,7 +37,7 @@ var SolanaRoutes = class _SolanaRoutes {
30
37
  swap: pk("MSwapi3WhNKMUGm9YrxGhypgUEt7wYQH3ZgG32XoWzH"),
31
38
  earn: pk("mz2vDzjbQDUDXBH6FPF5s4odCJ4y8YLE5QWaZ8XdZ9Z"),
32
39
  lut: pk("9JLRqBqkznKiSoNfotA4ywSRdnWb2fE76SiFrAfkaRCD"),
33
- mMint: pk("mzerokyEX9TNDoK4o2YZQBDmMzjokAeN6M2g2S3pLJo"),
40
+ mMint: pk("mzerojk9tg56ebsrEAhfkyc9VgKjTW2zDqp6C5mhjzH"),
34
41
  portal: pk("mzp1q2j5Hr1QuLC3KFBCAUz5aUckT6qyuZKZ3WJnMmY"),
35
42
  quoter: pk("Nqd6XqA8LbsCuG8MLWWuP865NV6jR1MbXeKxD4HLKDJ")
36
43
  },
@@ -38,13 +45,21 @@ var SolanaRoutes = class _SolanaRoutes {
38
45
  swap: pk("MSwapi3WhNKMUGm9YrxGhypgUEt7wYQH3ZgG32XoWzH"),
39
46
  earn: pk("mz2vDzjbQDUDXBH6FPF5s4odCJ4y8YLE5QWaZ8XdZ9Z"),
40
47
  lut: pk("6GhuWPuAmiJeeSVsr58KjqHcAejJRndCx9BVtHkaYHUR"),
41
- mMint: pk("mzeroZRGCah3j5xEWp2Nih3GDejSBbH1rbHoxDg8By6"),
48
+ mMint: pk("mzerojk9tg56ebsrEAhfkyc9VgKjTW2zDqp6C5mhjzH"),
42
49
  portal: pk("mzp1q2j5Hr1QuLC3KFBCAUz5aUckT6qyuZKZ3WJnMmY"),
43
50
  quoter: pk("Nqd6XqA8LbsCuG8MLWWuP865NV6jR1MbXeKxD4HLKDJ")
44
51
  }
45
52
  }[network];
46
53
  }
47
- static getExtPrograms(network) {
54
+ static getExtPrograms(network, chain) {
55
+ if (chain === "Fogo") {
56
+ return {
57
+ fUSDqquEMUU8UmU2YWYGZy2Lda1oMzBc88Mkzc1PRDw: {
58
+ program: pk("extUkDFf3HLekkxbcZ3XRUizMjbxMJgKBay3p9xGVmg"),
59
+ tokenProgram: splToken.TOKEN_PROGRAM_ID
60
+ }
61
+ };
62
+ }
48
63
  return {
49
64
  Mainnet: {
50
65
  mzeroXDoBpRVhnEXBra27qzAMdxgpWVY3DzQW7xMVJp: {
@@ -72,16 +87,19 @@ var SolanaRoutes = class _SolanaRoutes {
72
87
  usdkyPPxgV7sfNyKb8eDz66ogPrkRXG3wS2FVb6LLUf: {
73
88
  program: pk("3PskKTHgboCbUSQPMcCAZdZNFHbNvSoZ8zEFYANCdob7"),
74
89
  tokenProgram: splToken.TOKEN_2022_PROGRAM_ID
90
+ },
91
+ fUSDqquEMUU8UmU2YWYGZy2Lda1oMzBc88Mkzc1PRDw: {
92
+ program: pk("extUkDFf3HLekkxbcZ3XRUizMjbxMJgKBay3p9xGVmg"),
93
+ tokenProgram: splToken.TOKEN_PROGRAM_ID
75
94
  }
76
95
  }
77
96
  }[network];
78
97
  }
79
- static getSolanaContracts(chainContext) {
80
- const programs = _SolanaRoutes.getPrograms(
81
- chainContext.network
82
- );
98
+ static getSolanaContracts(network, chain) {
99
+ const programs = _SolanaRoutes.getPrograms(network);
83
100
  const extPrograms = _SolanaRoutes.getExtPrograms(
84
- chainContext.network
101
+ network,
102
+ chain
85
103
  );
86
104
  return {
87
105
  token: programs.mMint.toBase58(),
@@ -91,10 +109,19 @@ var SolanaRoutes = class _SolanaRoutes {
91
109
  quoter: programs.quoter.toBase58()
92
110
  };
93
111
  }
112
+ getSolanaContracts() {
113
+ return _SolanaRoutes.getSolanaContracts(this.network, this.ntt.chain);
114
+ }
94
115
  getTransferExtensionBurnIx(amount2, recipient, payer, outboxItem, extMint, destinationToken, shouldQueue = true) {
95
- const recipientAddress = Buffer.alloc(32);
96
- const dest = Buffer.from(recipient.address.toUint8Array());
97
- dest.copy(recipientAddress);
116
+ const recipientAddress = sdkConnect.toUniversal(
117
+ recipient.chain,
118
+ recipient.address.toString()
119
+ ).toUint8Array();
120
+ if (recipientAddress.length !== 32) {
121
+ throw new Error(
122
+ `recipient address must be 32 bytes, got ${recipientAddress.length} bytes`
123
+ );
124
+ }
98
125
  if (destinationToken.length !== 32) {
99
126
  throw new Error(
100
127
  `destinationToken must be 32 bytes, got ${destinationToken.length} bytes`
@@ -107,6 +134,16 @@ var SolanaRoutes = class _SolanaRoutes {
107
134
  );
108
135
  }
109
136
  const { program: extProgram, tokenProgram: extTokenProgram } = extension;
137
+ const [tokenAuth] = web3_js.PublicKey.findProgramAddressSync(
138
+ [Buffer.from("token_authority")],
139
+ this.programs.portal
140
+ );
141
+ const sessionAuth = this.ntt.pdas.sessionAuthority(tokenAuth, {
142
+ amount: new BN__default.default(amount2),
143
+ recipientChain: { id: sdkConnect.chainToChainId(recipient.chain) },
144
+ recipientAddress: [...recipientAddress],
145
+ shouldQueue
146
+ });
110
147
  return new web3_js.TransactionInstruction({
111
148
  programId: this.ntt.program.programId,
112
149
  keys: [
@@ -131,10 +168,7 @@ var SolanaRoutes = class _SolanaRoutes {
131
168
  // from (token auth m token account)
132
169
  pubkey: splToken.getAssociatedTokenAddressSync(
133
170
  this.programs.mMint,
134
- web3_js.PublicKey.findProgramAddressSync(
135
- [Buffer.from("token_authority")],
136
- this.ntt.program.programId
137
- )[0],
171
+ tokenAuth,
138
172
  true,
139
173
  splToken.TOKEN_2022_PROGRAM_ID
140
174
  ),
@@ -185,24 +219,13 @@ var SolanaRoutes = class _SolanaRoutes {
185
219
  },
186
220
  {
187
221
  // session auth
188
- pubkey: this.ntt.pdas.sessionAuthority(payer, {
189
- amount: new BN__default.default(amount2),
190
- recipientChain: {
191
- id: 2
192
- // Ethereum
193
- },
194
- recipientAddress: [...Array(32)],
195
- shouldQueue: false
196
- }),
222
+ pubkey: sessionAuth,
197
223
  isSigner: false,
198
224
  isWritable: false
199
225
  },
200
226
  {
201
227
  // token auth
202
- pubkey: web3_js.PublicKey.findProgramAddressSync(
203
- [Buffer.from("token_authority")],
204
- this.ntt.program.programId
205
- )[0],
228
+ pubkey: tokenAuth,
206
229
  isSigner: false,
207
230
  isWritable: false
208
231
  },
@@ -309,10 +332,79 @@ var SolanaRoutes = class _SolanaRoutes {
309
332
  // chain_id
310
333
  recipientAddress,
311
334
  // recipient_address
312
- destinationToken,
313
- // destination_token
314
- Buffer.from([Number(shouldQueue)])
335
+ Buffer.from([Number(shouldQueue)]),
315
336
  // should_queue
337
+ destinationToken
338
+ // destination_token
339
+ ])
340
+ });
341
+ }
342
+ async getExecutorRelayIx(sender, quote, destinationChain) {
343
+ const emitter = web3_js.PublicKey.findProgramAddressSync(
344
+ [Buffer.from("emitter")],
345
+ this.ntt.program.programId
346
+ )[0];
347
+ const bridgeSequence = web3_js.PublicKey.findProgramAddressSync(
348
+ [Buffer.from("Sequence"), emitter.toBytes()],
349
+ new web3_js.PublicKey(this.ntt.contracts.coreBridge)
350
+ )[0];
351
+ const info = await this.ntt.connection.getAccountInfo(bridgeSequence);
352
+ const sequence = new BN__default.default(info.data, "le");
353
+ const vaaReqBytes = Buffer.concat([
354
+ Buffer.from("ERV1"),
355
+ // type
356
+ new BN__default.default(sdkConnect.chainToChainId(this.ntt.chain)).toArrayLike(Buffer, "be", 2),
357
+ // emitter chain
358
+ emitter.toBuffer(),
359
+ // emitter address
360
+ sequence.toArrayLike(Buffer, "be", 8)
361
+ // sequence
362
+ ]);
363
+ const signedQuoteBytes = Buffer.from(quote.signedQuote);
364
+ const relayInstructions = Buffer.from(quote.relayInstructions);
365
+ return new web3_js.TransactionInstruction({
366
+ keys: [
367
+ {
368
+ pubkey: sender,
369
+ isSigner: true,
370
+ isWritable: true
371
+ },
372
+ {
373
+ // payee
374
+ pubkey: new web3_js.PublicKey(quote.payeeAddress),
375
+ isSigner: false,
376
+ isWritable: true
377
+ },
378
+ {
379
+ pubkey: web3_js.SystemProgram.programId,
380
+ isSigner: false,
381
+ isWritable: false
382
+ }
383
+ ],
384
+ programId: new web3_js.PublicKey("execXUrAsMnqMmTHj5m7N1YQgsDz3cwGLYCYyuDRciV"),
385
+ data: Buffer.concat([
386
+ Buffer.from(sha2.sha256("global:request_for_execution").subarray(0, 8)),
387
+ // [109, 107, 87, 37, 151, 192, 119, 115]
388
+ new BN__default.default(quote.estimatedCost.toString()).toArrayLike(Buffer, "le", 8),
389
+ // amount
390
+ new BN__default.default(sdkConnect.chainToChainId(destinationChain)).toArrayLike(Buffer, "le", 2),
391
+ // dst_chain
392
+ this.ntt.program.programId.toBuffer(),
393
+ // peer portal address
394
+ sender.toBuffer(),
395
+ // refund_addr
396
+ new BN__default.default(signedQuoteBytes.length).toArrayLike(Buffer, "le", 4),
397
+ // vec length
398
+ signedQuoteBytes,
399
+ // signed_quote_bytes
400
+ new BN__default.default(vaaReqBytes.length).toArrayLike(Buffer, "le", 4),
401
+ // vec length
402
+ vaaReqBytes,
403
+ // request_bytes
404
+ new BN__default.default(relayInstructions.length).toArrayLike(Buffer, "le", 4),
405
+ // vec length
406
+ relayInstructions
407
+ // relay_instructions
316
408
  ])
317
409
  });
318
410
  }
@@ -577,6 +669,62 @@ function pk(address) {
577
669
  return new web3_js.PublicKey(address);
578
670
  }
579
671
 
672
+ // src/executor.ts
673
+ function getExecutorConfig(network = "Mainnet") {
674
+ const svmContracts = SolanaRoutes.getSolanaContracts(network, "Solana");
675
+ const svmChains = ["Solana", "Fogo"];
676
+ const evmChains = network === "Mainnet" ? ["Ethereum", "Optimism", "Arbitrum"] : ["Sepolia", "ArbitrumSepolia", "OptimismSepolia"];
677
+ return {
678
+ ntt: {
679
+ tokens: {
680
+ M0: [
681
+ ...svmChains.map((chain) => ({
682
+ chain,
683
+ token: svmContracts.token,
684
+ manager: svmContracts.manager,
685
+ transceiver: [
686
+ {
687
+ type: "wormhole",
688
+ address: svmContracts.transceiver.wormhole
689
+ }
690
+ ],
691
+ quoter: svmContracts.quoter
692
+ })),
693
+ ...evmChains.map((chain) => ({
694
+ chain,
695
+ token: M0AutomaticRoute.EVM_CONTRACTS.token,
696
+ manager: M0AutomaticRoute.EVM_CONTRACTS.manager,
697
+ transceiver: [
698
+ {
699
+ type: "wormhole",
700
+ address: M0AutomaticRoute.EVM_CONTRACTS.transceiver.wormhole
701
+ }
702
+ ],
703
+ quoter: M0AutomaticRoute.EVM_CONTRACTS.quoter
704
+ }))
705
+ ]
706
+ }
707
+ },
708
+ referrerFee: {
709
+ feeDbps: 0n,
710
+ perTokenOverrides: {
711
+ // SVM chains require extra compute when receiving messages
712
+ // so we need to override the gas cost
713
+ Solana: {
714
+ [svmContracts.token]: {
715
+ msgValue: 15000000n
716
+ }
717
+ },
718
+ Fogo: {
719
+ [svmContracts.token]: {
720
+ msgValue: 15000000n
721
+ }
722
+ }
723
+ }
724
+ }
725
+ };
726
+ }
727
+
580
728
  // src/m0AutomaticRoute.ts
581
729
  var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.AutomaticRoute {
582
730
  static supportedNetworks() {
@@ -604,21 +752,18 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
604
752
  static getContracts(chainContext) {
605
753
  switch (chainContext.chain) {
606
754
  case "Ethereum":
607
- return this.EVM_CONTRACTS;
608
755
  case "Optimism":
609
- return this.EVM_CONTRACTS;
610
756
  case "Arbitrum":
611
- return this.EVM_CONTRACTS;
612
757
  case "Sepolia":
613
- return this.EVM_CONTRACTS;
614
758
  case "OptimismSepolia":
615
- return this.EVM_CONTRACTS;
616
759
  case "ArbitrumSepolia":
617
760
  return this.EVM_CONTRACTS;
618
761
  case "Solana":
619
- return SolanaRoutes.getSolanaContracts(chainContext);
620
762
  case "Fogo":
621
- return SolanaRoutes.getSolanaContracts(chainContext);
763
+ return SolanaRoutes.getSolanaContracts(
764
+ chainContext.network,
765
+ chainContext.chain
766
+ );
622
767
  default:
623
768
  throw new Error(`Unsupported chain: ${chainContext.chain}`);
624
769
  }
@@ -636,9 +781,11 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
636
781
  return [];
637
782
  }
638
783
  const { token: mToken, mLikeTokens } = this.getContracts(toChain);
639
- return [mToken, ...mLikeTokens].map(
640
- (x) => sdkConnect.Wormhole.tokenId(toChain.chain, x)
641
- );
784
+ const tokens = mLikeTokens.map((x) => sdkConnect.Wormhole.tokenId(toChain.chain, x));
785
+ if (toChain.chain === "Solana" || toChain.chain === "Fogo") {
786
+ return tokens;
787
+ }
788
+ return [...tokens, sdkConnect.Wormhole.tokenId(toChain.chain, mToken)];
642
789
  }
643
790
  static isProtocolSupported(chain) {
644
791
  return chain.supportsProtocol("Ntt");
@@ -654,10 +801,6 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
654
801
  }
655
802
  async validate(request, params) {
656
803
  const options = params.options ?? this.getDefaultOptions();
657
- const gasDropoff = sdkConnect.amount.parse(
658
- options.gasDropoff ?? "0.0",
659
- request.toChain.config.nativeTokenDecimals
660
- );
661
804
  const parsedAmount = sdkConnect.amount.parse(params.amount, request.source.decimals);
662
805
  const trimmedAmount = sdkRouteNtt.NttRoute.trimAmount(
663
806
  parsedAmount,
@@ -673,8 +816,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
673
816
  destinationContracts: toContracts,
674
817
  options: {
675
818
  queue: false,
676
- automatic: true,
677
- gasDropoff: sdkConnect.amount.units(gasDropoff)
819
+ automatic: true
678
820
  }
679
821
  },
680
822
  options
@@ -719,7 +861,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
719
861
  )
720
862
  },
721
863
  destinationNativeGas: sdkConnect.amount.fromBaseUnits(
722
- params.normalizedParams.options.gasDropoff ?? 0n,
864
+ 0n,
723
865
  toChain.config.nativeTokenDecimals
724
866
  ),
725
867
  eta: sdkConnect.finality.estimateFinalityTime(request.fromChain.chain)
@@ -780,18 +922,18 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
780
922
  */
781
923
  async *transferMLike(ntt, sender, amount2, destination, sourceToken, destinationToken, options) {
782
924
  const senderAddress = new sdkEvm.EvmAddress(sender).toString();
783
- const totalPrice = await ntt.quoteDeliveryPrice(destination.chain, options);
784
925
  const tokenContract = sdkEvm.EvmPlatform.getTokenImplementation(
785
926
  ntt.provider,
786
927
  sourceToken
787
928
  );
929
+ const spenderAddress = this.requiresExecutor(destination.chain) ? _M0AutomaticRoute.EXECUTOR_ENTRYPOINT : ntt.managerAddress;
788
930
  const allowance = await tokenContract.allowance(
789
931
  senderAddress,
790
- ntt.managerAddress
932
+ spenderAddress
791
933
  );
792
934
  if (allowance < amount2) {
793
935
  const txReq2 = await tokenContract.approve.populateTransaction(
794
- ntt.managerAddress,
936
+ spenderAddress,
795
937
  amount2
796
938
  );
797
939
  yield this.createUnsignedTx(
@@ -801,6 +943,36 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
801
943
  );
802
944
  }
803
945
  const receiver = sdkConnect.universalAddress(destination);
946
+ if (this.requiresExecutor(destination.chain)) {
947
+ const quote = await this.getExecutorQuote(
948
+ ntt.network,
949
+ ntt.chain,
950
+ destination.chain,
951
+ amount2
952
+ );
953
+ const contract2 = new ethers.Contract(_M0AutomaticRoute.EXECUTOR_ENTRYPOINT, [
954
+ "function transferMLikeToken(uint256 amount, address sourceToken, uint16 destinationChainId, bytes32 destinationToken, bytes32 recipient, bytes32 refundAddress, (uint256 value, address refundAddress, bytes signedQuote, bytes instructions) executorArgs, bytes memory transceiverInstructions) external payable returns (bytes32 messageId)"
955
+ ]);
956
+ const executorArgs = {
957
+ value: quote.estimatedCost,
958
+ refundAddress: senderAddress,
959
+ signedQuote: quote.signedQuote,
960
+ instructions: quote.relayInstructions
961
+ };
962
+ const txReq2 = await contract2.getFunction("transferMLikeToken").populateTransaction(
963
+ amount2,
964
+ sourceToken,
965
+ sdkConnect.toChainId(destination.chain),
966
+ sdkConnect.toUniversal(destination.chain, destinationToken).toString(),
967
+ receiver,
968
+ receiver,
969
+ executorArgs,
970
+ Uint8Array.from(Buffer.from("01000101", "hex")),
971
+ { value: quote.estimatedCost }
972
+ );
973
+ yield ntt.createUnsignedTx(sdkEvm.addFrom(txReq2, senderAddress), "Ntt.transfer");
974
+ return;
975
+ }
804
976
  const contract = new ethers.Contract(ntt.managerAddress, [
805
977
  "function transferMLikeToken(uint256 amount, address sourceToken, uint16 destinationChainId, bytes32 destinationToken, bytes32 recipient, bytes32 refundAddress) external payable returns (uint64 sequence)"
806
978
  ]);
@@ -811,7 +983,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
811
983
  sdkConnect.toUniversal(destination.chain, destinationToken).toString(),
812
984
  receiver,
813
985
  receiver,
814
- { value: totalPrice }
986
+ { value: await ntt.quoteDeliveryPrice(destination.chain, options) }
815
987
  );
816
988
  yield ntt.createUnsignedTx(sdkEvm.addFrom(txReq, senderAddress), "Ntt.transfer");
817
989
  }
@@ -826,7 +998,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
826
998
  }
827
999
  async *transferSolanaExtension(ntt, sender, amount2, recipient, sourceToken, destinationToken, options, outboxItem) {
828
1000
  const router = new SolanaRoutes(ntt);
829
- if ((await ntt.getConfig()).mint.toBase58() === sourceToken) {
1001
+ if (router.getSolanaContracts().token === sourceToken) {
830
1002
  return ntt.transfer(sender, amount2, recipient, options);
831
1003
  }
832
1004
  const config = await ntt.getConfig();
@@ -861,18 +1033,29 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
861
1033
  const tx = new web3_js.Transaction();
862
1034
  tx.feePayer = payerAddress;
863
1035
  tx.add(...ixs);
864
- if (options.automatic) {
865
- if (!ntt.quoter)
1036
+ if (this.requiresExecutor(recipient.chain)) {
1037
+ const quote = await this.getExecutorQuote(
1038
+ ntt.network,
1039
+ ntt.chain,
1040
+ recipient.chain,
1041
+ amount2
1042
+ );
1043
+ tx.add(
1044
+ await router.getExecutorRelayIx(payerAddress, quote, recipient.chain)
1045
+ );
1046
+ } else if (options.automatic) {
1047
+ if (!ntt.quoter) {
866
1048
  throw new Error(
867
1049
  "No quoter available, cannot initiate an automatic transfer."
868
1050
  );
1051
+ }
869
1052
  const fee = await ntt.quoteDeliveryPrice(recipient.chain, options);
870
1053
  const relayIx = await ntt.quoter.createRequestRelayInstruction(
871
1054
  payerAddress,
872
1055
  outboxItem.publicKey,
873
1056
  recipient.chain,
874
1057
  Number(fee) / web3_js.LAMPORTS_PER_SOL,
875
- Number(options.gasDropoff ?? 0n) / sdkSolanaNtt.WEI_PER_GWEI
1058
+ 0
876
1059
  );
877
1060
  tx.add(relayIx);
878
1061
  }
@@ -952,11 +1135,44 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
952
1135
  }
953
1136
  yield receipt;
954
1137
  }
1138
+ async getExecutorQuote(network, sourceChain, destinationChain, amount2) {
1139
+ const wh = new sdkConnect.Wormhole(network, [solana__default.default.Platform, evm__default.default.Platform]);
1140
+ const executorRoute = sdkRouteNtt.nttExecutorRoute(getExecutorConfig(network));
1141
+ const routeInstance = new executorRoute(wh);
1142
+ const resolveM = (chain) => {
1143
+ if (sdkConnect.chainToPlatform(chain) === "Solana") {
1144
+ const c = chain;
1145
+ return SolanaRoutes.getSolanaContracts(network, c).token;
1146
+ }
1147
+ return _M0AutomaticRoute.EVM_CONTRACTS.token;
1148
+ };
1149
+ const transferRequest = await sdkConnect.routes.RouteTransferRequest.create(wh, {
1150
+ source: sdkConnect.Wormhole.tokenId(sourceChain, resolveM(sourceChain)),
1151
+ destination: sdkConnect.Wormhole.tokenId(
1152
+ destinationChain,
1153
+ resolveM(destinationChain)
1154
+ )
1155
+ });
1156
+ const validated = await routeInstance.validate(transferRequest, {
1157
+ amount: amount2.toString()
1158
+ });
1159
+ if (!validated.valid) {
1160
+ throw new Error(`Validation failed: ${validated.error.message}`);
1161
+ }
1162
+ return await routeInstance.fetchExecutorQuote(
1163
+ transferRequest,
1164
+ validated.params
1165
+ );
1166
+ }
1167
+ requiresExecutor(destination) {
1168
+ return sdkConnect.chainToPlatform(destination) === "Solana";
1169
+ }
955
1170
  };
956
1171
  // ntt does not support gas drop-off currently
957
1172
  _M0AutomaticRoute.NATIVE_GAS_DROPOFF_SUPPORTED = false;
958
1173
  // Wrapped M token address is the same on EVM chains
959
1174
  _M0AutomaticRoute.EVM_WRAPPED_M_TOKEN = "0x437cc33344a0B27A429f795ff6B469C72698B291";
1175
+ _M0AutomaticRoute.EXECUTOR_ENTRYPOINT = "0x8518040a9cf9dfb55a4f099bb0eaabeefeb03643";
960
1176
  // Contract addresses are the same on all EVM chains
961
1177
  _M0AutomaticRoute.EVM_CONTRACTS = {
962
1178
  // M token address is the same on EVM chains
package/dist/index.mjs CHANGED
@@ -1,14 +1,16 @@
1
1
  import { routes, Wormhole, isSameToken, amount, finality, chainToPlatform, canonicalAddress, signSendWait, TransferState, universalAddress, toChainId, toUniversal, isSourceInitiated, isSourceFinalized, isAttested, isRedeemed, chainToChainId } from '@wormhole-foundation/sdk-connect';
2
2
  import { Ntt } from '@wormhole-foundation/sdk-definitions-ntt';
3
- import { WEI_PER_GWEI, NTT } from '@wormhole-foundation/sdk-solana-ntt';
4
3
  import { EvmAddress, EvmPlatform, addFrom, EvmUnsignedTransaction, addChainId } from '@wormhole-foundation/sdk-evm';
5
4
  import { SolanaAddress } from '@wormhole-foundation/sdk-solana';
6
- import { NttRoute } from '@wormhole-foundation/sdk-route-ntt';
5
+ import { NttRoute, nttExecutorRoute } from '@wormhole-foundation/sdk-route-ntt';
7
6
  import { Contract } from 'ethers';
8
7
  import { Keypair, PublicKey, Transaction, LAMPORTS_PER_SOL, TransactionMessage, VersionedTransaction, TransactionInstruction, SystemProgram, AddressLookupTableAccount } from '@solana/web3.js';
9
- import { TOKEN_2022_PROGRAM_ID, getAssociatedTokenAddressSync, createAssociatedTokenAccountInstruction } from '@solana/spl-token';
8
+ import { TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, getAssociatedTokenAddressSync, createAssociatedTokenAccountInstruction } from '@solana/spl-token';
9
+ import { NTT } from '@wormhole-foundation/sdk-solana-ntt';
10
10
  import BN from 'bn.js';
11
11
  import { sha256 } from '@noble/hashes/sha2';
12
+ import evm from '@wormhole-foundation/sdk/platforms/evm';
13
+ import solana from '@wormhole-foundation/sdk/platforms/solana';
12
14
 
13
15
  // src/m0AutomaticRoute.ts
14
16
  var SolanaRoutes = class _SolanaRoutes {
@@ -16,7 +18,10 @@ var SolanaRoutes = class _SolanaRoutes {
16
18
  this.ntt = ntt;
17
19
  this.network = ntt.network;
18
20
  this.programs = _SolanaRoutes.getPrograms(this.network);
19
- this.extPrograms = _SolanaRoutes.getExtPrograms(this.network);
21
+ this.extPrograms = _SolanaRoutes.getExtPrograms(
22
+ this.network,
23
+ this.ntt.chain
24
+ );
20
25
  }
21
26
  static getPrograms(network) {
22
27
  return {
@@ -24,7 +29,7 @@ var SolanaRoutes = class _SolanaRoutes {
24
29
  swap: pk("MSwapi3WhNKMUGm9YrxGhypgUEt7wYQH3ZgG32XoWzH"),
25
30
  earn: pk("mz2vDzjbQDUDXBH6FPF5s4odCJ4y8YLE5QWaZ8XdZ9Z"),
26
31
  lut: pk("9JLRqBqkznKiSoNfotA4ywSRdnWb2fE76SiFrAfkaRCD"),
27
- mMint: pk("mzerokyEX9TNDoK4o2YZQBDmMzjokAeN6M2g2S3pLJo"),
32
+ mMint: pk("mzerojk9tg56ebsrEAhfkyc9VgKjTW2zDqp6C5mhjzH"),
28
33
  portal: pk("mzp1q2j5Hr1QuLC3KFBCAUz5aUckT6qyuZKZ3WJnMmY"),
29
34
  quoter: pk("Nqd6XqA8LbsCuG8MLWWuP865NV6jR1MbXeKxD4HLKDJ")
30
35
  },
@@ -32,13 +37,21 @@ var SolanaRoutes = class _SolanaRoutes {
32
37
  swap: pk("MSwapi3WhNKMUGm9YrxGhypgUEt7wYQH3ZgG32XoWzH"),
33
38
  earn: pk("mz2vDzjbQDUDXBH6FPF5s4odCJ4y8YLE5QWaZ8XdZ9Z"),
34
39
  lut: pk("6GhuWPuAmiJeeSVsr58KjqHcAejJRndCx9BVtHkaYHUR"),
35
- mMint: pk("mzeroZRGCah3j5xEWp2Nih3GDejSBbH1rbHoxDg8By6"),
40
+ mMint: pk("mzerojk9tg56ebsrEAhfkyc9VgKjTW2zDqp6C5mhjzH"),
36
41
  portal: pk("mzp1q2j5Hr1QuLC3KFBCAUz5aUckT6qyuZKZ3WJnMmY"),
37
42
  quoter: pk("Nqd6XqA8LbsCuG8MLWWuP865NV6jR1MbXeKxD4HLKDJ")
38
43
  }
39
44
  }[network];
40
45
  }
41
- static getExtPrograms(network) {
46
+ static getExtPrograms(network, chain) {
47
+ if (chain === "Fogo") {
48
+ return {
49
+ fUSDqquEMUU8UmU2YWYGZy2Lda1oMzBc88Mkzc1PRDw: {
50
+ program: pk("extUkDFf3HLekkxbcZ3XRUizMjbxMJgKBay3p9xGVmg"),
51
+ tokenProgram: TOKEN_PROGRAM_ID
52
+ }
53
+ };
54
+ }
42
55
  return {
43
56
  Mainnet: {
44
57
  mzeroXDoBpRVhnEXBra27qzAMdxgpWVY3DzQW7xMVJp: {
@@ -66,16 +79,19 @@ var SolanaRoutes = class _SolanaRoutes {
66
79
  usdkyPPxgV7sfNyKb8eDz66ogPrkRXG3wS2FVb6LLUf: {
67
80
  program: pk("3PskKTHgboCbUSQPMcCAZdZNFHbNvSoZ8zEFYANCdob7"),
68
81
  tokenProgram: TOKEN_2022_PROGRAM_ID
82
+ },
83
+ fUSDqquEMUU8UmU2YWYGZy2Lda1oMzBc88Mkzc1PRDw: {
84
+ program: pk("extUkDFf3HLekkxbcZ3XRUizMjbxMJgKBay3p9xGVmg"),
85
+ tokenProgram: TOKEN_PROGRAM_ID
69
86
  }
70
87
  }
71
88
  }[network];
72
89
  }
73
- static getSolanaContracts(chainContext) {
74
- const programs = _SolanaRoutes.getPrograms(
75
- chainContext.network
76
- );
90
+ static getSolanaContracts(network, chain) {
91
+ const programs = _SolanaRoutes.getPrograms(network);
77
92
  const extPrograms = _SolanaRoutes.getExtPrograms(
78
- chainContext.network
93
+ network,
94
+ chain
79
95
  );
80
96
  return {
81
97
  token: programs.mMint.toBase58(),
@@ -85,10 +101,19 @@ var SolanaRoutes = class _SolanaRoutes {
85
101
  quoter: programs.quoter.toBase58()
86
102
  };
87
103
  }
104
+ getSolanaContracts() {
105
+ return _SolanaRoutes.getSolanaContracts(this.network, this.ntt.chain);
106
+ }
88
107
  getTransferExtensionBurnIx(amount2, recipient, payer, outboxItem, extMint, destinationToken, shouldQueue = true) {
89
- const recipientAddress = Buffer.alloc(32);
90
- const dest = Buffer.from(recipient.address.toUint8Array());
91
- dest.copy(recipientAddress);
108
+ const recipientAddress = toUniversal(
109
+ recipient.chain,
110
+ recipient.address.toString()
111
+ ).toUint8Array();
112
+ if (recipientAddress.length !== 32) {
113
+ throw new Error(
114
+ `recipient address must be 32 bytes, got ${recipientAddress.length} bytes`
115
+ );
116
+ }
92
117
  if (destinationToken.length !== 32) {
93
118
  throw new Error(
94
119
  `destinationToken must be 32 bytes, got ${destinationToken.length} bytes`
@@ -101,6 +126,16 @@ var SolanaRoutes = class _SolanaRoutes {
101
126
  );
102
127
  }
103
128
  const { program: extProgram, tokenProgram: extTokenProgram } = extension;
129
+ const [tokenAuth] = PublicKey.findProgramAddressSync(
130
+ [Buffer.from("token_authority")],
131
+ this.programs.portal
132
+ );
133
+ const sessionAuth = this.ntt.pdas.sessionAuthority(tokenAuth, {
134
+ amount: new BN(amount2),
135
+ recipientChain: { id: chainToChainId(recipient.chain) },
136
+ recipientAddress: [...recipientAddress],
137
+ shouldQueue
138
+ });
104
139
  return new TransactionInstruction({
105
140
  programId: this.ntt.program.programId,
106
141
  keys: [
@@ -125,10 +160,7 @@ var SolanaRoutes = class _SolanaRoutes {
125
160
  // from (token auth m token account)
126
161
  pubkey: getAssociatedTokenAddressSync(
127
162
  this.programs.mMint,
128
- PublicKey.findProgramAddressSync(
129
- [Buffer.from("token_authority")],
130
- this.ntt.program.programId
131
- )[0],
163
+ tokenAuth,
132
164
  true,
133
165
  TOKEN_2022_PROGRAM_ID
134
166
  ),
@@ -179,24 +211,13 @@ var SolanaRoutes = class _SolanaRoutes {
179
211
  },
180
212
  {
181
213
  // session auth
182
- pubkey: this.ntt.pdas.sessionAuthority(payer, {
183
- amount: new BN(amount2),
184
- recipientChain: {
185
- id: 2
186
- // Ethereum
187
- },
188
- recipientAddress: [...Array(32)],
189
- shouldQueue: false
190
- }),
214
+ pubkey: sessionAuth,
191
215
  isSigner: false,
192
216
  isWritable: false
193
217
  },
194
218
  {
195
219
  // token auth
196
- pubkey: PublicKey.findProgramAddressSync(
197
- [Buffer.from("token_authority")],
198
- this.ntt.program.programId
199
- )[0],
220
+ pubkey: tokenAuth,
200
221
  isSigner: false,
201
222
  isWritable: false
202
223
  },
@@ -303,10 +324,79 @@ var SolanaRoutes = class _SolanaRoutes {
303
324
  // chain_id
304
325
  recipientAddress,
305
326
  // recipient_address
306
- destinationToken,
307
- // destination_token
308
- Buffer.from([Number(shouldQueue)])
327
+ Buffer.from([Number(shouldQueue)]),
309
328
  // should_queue
329
+ destinationToken
330
+ // destination_token
331
+ ])
332
+ });
333
+ }
334
+ async getExecutorRelayIx(sender, quote, destinationChain) {
335
+ const emitter = PublicKey.findProgramAddressSync(
336
+ [Buffer.from("emitter")],
337
+ this.ntt.program.programId
338
+ )[0];
339
+ const bridgeSequence = PublicKey.findProgramAddressSync(
340
+ [Buffer.from("Sequence"), emitter.toBytes()],
341
+ new PublicKey(this.ntt.contracts.coreBridge)
342
+ )[0];
343
+ const info = await this.ntt.connection.getAccountInfo(bridgeSequence);
344
+ const sequence = new BN(info.data, "le");
345
+ const vaaReqBytes = Buffer.concat([
346
+ Buffer.from("ERV1"),
347
+ // type
348
+ new BN(chainToChainId(this.ntt.chain)).toArrayLike(Buffer, "be", 2),
349
+ // emitter chain
350
+ emitter.toBuffer(),
351
+ // emitter address
352
+ sequence.toArrayLike(Buffer, "be", 8)
353
+ // sequence
354
+ ]);
355
+ const signedQuoteBytes = Buffer.from(quote.signedQuote);
356
+ const relayInstructions = Buffer.from(quote.relayInstructions);
357
+ return new TransactionInstruction({
358
+ keys: [
359
+ {
360
+ pubkey: sender,
361
+ isSigner: true,
362
+ isWritable: true
363
+ },
364
+ {
365
+ // payee
366
+ pubkey: new PublicKey(quote.payeeAddress),
367
+ isSigner: false,
368
+ isWritable: true
369
+ },
370
+ {
371
+ pubkey: SystemProgram.programId,
372
+ isSigner: false,
373
+ isWritable: false
374
+ }
375
+ ],
376
+ programId: new PublicKey("execXUrAsMnqMmTHj5m7N1YQgsDz3cwGLYCYyuDRciV"),
377
+ data: Buffer.concat([
378
+ Buffer.from(sha256("global:request_for_execution").subarray(0, 8)),
379
+ // [109, 107, 87, 37, 151, 192, 119, 115]
380
+ new BN(quote.estimatedCost.toString()).toArrayLike(Buffer, "le", 8),
381
+ // amount
382
+ new BN(chainToChainId(destinationChain)).toArrayLike(Buffer, "le", 2),
383
+ // dst_chain
384
+ this.ntt.program.programId.toBuffer(),
385
+ // peer portal address
386
+ sender.toBuffer(),
387
+ // refund_addr
388
+ new BN(signedQuoteBytes.length).toArrayLike(Buffer, "le", 4),
389
+ // vec length
390
+ signedQuoteBytes,
391
+ // signed_quote_bytes
392
+ new BN(vaaReqBytes.length).toArrayLike(Buffer, "le", 4),
393
+ // vec length
394
+ vaaReqBytes,
395
+ // request_bytes
396
+ new BN(relayInstructions.length).toArrayLike(Buffer, "le", 4),
397
+ // vec length
398
+ relayInstructions
399
+ // relay_instructions
310
400
  ])
311
401
  });
312
402
  }
@@ -571,6 +661,62 @@ function pk(address) {
571
661
  return new PublicKey(address);
572
662
  }
573
663
 
664
+ // src/executor.ts
665
+ function getExecutorConfig(network = "Mainnet") {
666
+ const svmContracts = SolanaRoutes.getSolanaContracts(network, "Solana");
667
+ const svmChains = ["Solana", "Fogo"];
668
+ const evmChains = network === "Mainnet" ? ["Ethereum", "Optimism", "Arbitrum"] : ["Sepolia", "ArbitrumSepolia", "OptimismSepolia"];
669
+ return {
670
+ ntt: {
671
+ tokens: {
672
+ M0: [
673
+ ...svmChains.map((chain) => ({
674
+ chain,
675
+ token: svmContracts.token,
676
+ manager: svmContracts.manager,
677
+ transceiver: [
678
+ {
679
+ type: "wormhole",
680
+ address: svmContracts.transceiver.wormhole
681
+ }
682
+ ],
683
+ quoter: svmContracts.quoter
684
+ })),
685
+ ...evmChains.map((chain) => ({
686
+ chain,
687
+ token: M0AutomaticRoute.EVM_CONTRACTS.token,
688
+ manager: M0AutomaticRoute.EVM_CONTRACTS.manager,
689
+ transceiver: [
690
+ {
691
+ type: "wormhole",
692
+ address: M0AutomaticRoute.EVM_CONTRACTS.transceiver.wormhole
693
+ }
694
+ ],
695
+ quoter: M0AutomaticRoute.EVM_CONTRACTS.quoter
696
+ }))
697
+ ]
698
+ }
699
+ },
700
+ referrerFee: {
701
+ feeDbps: 0n,
702
+ perTokenOverrides: {
703
+ // SVM chains require extra compute when receiving messages
704
+ // so we need to override the gas cost
705
+ Solana: {
706
+ [svmContracts.token]: {
707
+ msgValue: 15000000n
708
+ }
709
+ },
710
+ Fogo: {
711
+ [svmContracts.token]: {
712
+ msgValue: 15000000n
713
+ }
714
+ }
715
+ }
716
+ }
717
+ };
718
+ }
719
+
574
720
  // src/m0AutomaticRoute.ts
575
721
  var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
576
722
  static supportedNetworks() {
@@ -598,21 +744,18 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
598
744
  static getContracts(chainContext) {
599
745
  switch (chainContext.chain) {
600
746
  case "Ethereum":
601
- return this.EVM_CONTRACTS;
602
747
  case "Optimism":
603
- return this.EVM_CONTRACTS;
604
748
  case "Arbitrum":
605
- return this.EVM_CONTRACTS;
606
749
  case "Sepolia":
607
- return this.EVM_CONTRACTS;
608
750
  case "OptimismSepolia":
609
- return this.EVM_CONTRACTS;
610
751
  case "ArbitrumSepolia":
611
752
  return this.EVM_CONTRACTS;
612
753
  case "Solana":
613
- return SolanaRoutes.getSolanaContracts(chainContext);
614
754
  case "Fogo":
615
- return SolanaRoutes.getSolanaContracts(chainContext);
755
+ return SolanaRoutes.getSolanaContracts(
756
+ chainContext.network,
757
+ chainContext.chain
758
+ );
616
759
  default:
617
760
  throw new Error(`Unsupported chain: ${chainContext.chain}`);
618
761
  }
@@ -630,9 +773,11 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
630
773
  return [];
631
774
  }
632
775
  const { token: mToken, mLikeTokens } = this.getContracts(toChain);
633
- return [mToken, ...mLikeTokens].map(
634
- (x) => Wormhole.tokenId(toChain.chain, x)
635
- );
776
+ const tokens = mLikeTokens.map((x) => Wormhole.tokenId(toChain.chain, x));
777
+ if (toChain.chain === "Solana" || toChain.chain === "Fogo") {
778
+ return tokens;
779
+ }
780
+ return [...tokens, Wormhole.tokenId(toChain.chain, mToken)];
636
781
  }
637
782
  static isProtocolSupported(chain) {
638
783
  return chain.supportsProtocol("Ntt");
@@ -648,10 +793,6 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
648
793
  }
649
794
  async validate(request, params) {
650
795
  const options = params.options ?? this.getDefaultOptions();
651
- const gasDropoff = amount.parse(
652
- options.gasDropoff ?? "0.0",
653
- request.toChain.config.nativeTokenDecimals
654
- );
655
796
  const parsedAmount = amount.parse(params.amount, request.source.decimals);
656
797
  const trimmedAmount = NttRoute.trimAmount(
657
798
  parsedAmount,
@@ -667,8 +808,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
667
808
  destinationContracts: toContracts,
668
809
  options: {
669
810
  queue: false,
670
- automatic: true,
671
- gasDropoff: amount.units(gasDropoff)
811
+ automatic: true
672
812
  }
673
813
  },
674
814
  options
@@ -713,7 +853,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
713
853
  )
714
854
  },
715
855
  destinationNativeGas: amount.fromBaseUnits(
716
- params.normalizedParams.options.gasDropoff ?? 0n,
856
+ 0n,
717
857
  toChain.config.nativeTokenDecimals
718
858
  ),
719
859
  eta: finality.estimateFinalityTime(request.fromChain.chain)
@@ -774,18 +914,18 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
774
914
  */
775
915
  async *transferMLike(ntt, sender, amount2, destination, sourceToken, destinationToken, options) {
776
916
  const senderAddress = new EvmAddress(sender).toString();
777
- const totalPrice = await ntt.quoteDeliveryPrice(destination.chain, options);
778
917
  const tokenContract = EvmPlatform.getTokenImplementation(
779
918
  ntt.provider,
780
919
  sourceToken
781
920
  );
921
+ const spenderAddress = this.requiresExecutor(destination.chain) ? _M0AutomaticRoute.EXECUTOR_ENTRYPOINT : ntt.managerAddress;
782
922
  const allowance = await tokenContract.allowance(
783
923
  senderAddress,
784
- ntt.managerAddress
924
+ spenderAddress
785
925
  );
786
926
  if (allowance < amount2) {
787
927
  const txReq2 = await tokenContract.approve.populateTransaction(
788
- ntt.managerAddress,
928
+ spenderAddress,
789
929
  amount2
790
930
  );
791
931
  yield this.createUnsignedTx(
@@ -795,6 +935,36 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
795
935
  );
796
936
  }
797
937
  const receiver = universalAddress(destination);
938
+ if (this.requiresExecutor(destination.chain)) {
939
+ const quote = await this.getExecutorQuote(
940
+ ntt.network,
941
+ ntt.chain,
942
+ destination.chain,
943
+ amount2
944
+ );
945
+ const contract2 = new Contract(_M0AutomaticRoute.EXECUTOR_ENTRYPOINT, [
946
+ "function transferMLikeToken(uint256 amount, address sourceToken, uint16 destinationChainId, bytes32 destinationToken, bytes32 recipient, bytes32 refundAddress, (uint256 value, address refundAddress, bytes signedQuote, bytes instructions) executorArgs, bytes memory transceiverInstructions) external payable returns (bytes32 messageId)"
947
+ ]);
948
+ const executorArgs = {
949
+ value: quote.estimatedCost,
950
+ refundAddress: senderAddress,
951
+ signedQuote: quote.signedQuote,
952
+ instructions: quote.relayInstructions
953
+ };
954
+ const txReq2 = await contract2.getFunction("transferMLikeToken").populateTransaction(
955
+ amount2,
956
+ sourceToken,
957
+ toChainId(destination.chain),
958
+ toUniversal(destination.chain, destinationToken).toString(),
959
+ receiver,
960
+ receiver,
961
+ executorArgs,
962
+ Uint8Array.from(Buffer.from("01000101", "hex")),
963
+ { value: quote.estimatedCost }
964
+ );
965
+ yield ntt.createUnsignedTx(addFrom(txReq2, senderAddress), "Ntt.transfer");
966
+ return;
967
+ }
798
968
  const contract = new Contract(ntt.managerAddress, [
799
969
  "function transferMLikeToken(uint256 amount, address sourceToken, uint16 destinationChainId, bytes32 destinationToken, bytes32 recipient, bytes32 refundAddress) external payable returns (uint64 sequence)"
800
970
  ]);
@@ -805,7 +975,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
805
975
  toUniversal(destination.chain, destinationToken).toString(),
806
976
  receiver,
807
977
  receiver,
808
- { value: totalPrice }
978
+ { value: await ntt.quoteDeliveryPrice(destination.chain, options) }
809
979
  );
810
980
  yield ntt.createUnsignedTx(addFrom(txReq, senderAddress), "Ntt.transfer");
811
981
  }
@@ -820,7 +990,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
820
990
  }
821
991
  async *transferSolanaExtension(ntt, sender, amount2, recipient, sourceToken, destinationToken, options, outboxItem) {
822
992
  const router = new SolanaRoutes(ntt);
823
- if ((await ntt.getConfig()).mint.toBase58() === sourceToken) {
993
+ if (router.getSolanaContracts().token === sourceToken) {
824
994
  return ntt.transfer(sender, amount2, recipient, options);
825
995
  }
826
996
  const config = await ntt.getConfig();
@@ -855,18 +1025,29 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
855
1025
  const tx = new Transaction();
856
1026
  tx.feePayer = payerAddress;
857
1027
  tx.add(...ixs);
858
- if (options.automatic) {
859
- if (!ntt.quoter)
1028
+ if (this.requiresExecutor(recipient.chain)) {
1029
+ const quote = await this.getExecutorQuote(
1030
+ ntt.network,
1031
+ ntt.chain,
1032
+ recipient.chain,
1033
+ amount2
1034
+ );
1035
+ tx.add(
1036
+ await router.getExecutorRelayIx(payerAddress, quote, recipient.chain)
1037
+ );
1038
+ } else if (options.automatic) {
1039
+ if (!ntt.quoter) {
860
1040
  throw new Error(
861
1041
  "No quoter available, cannot initiate an automatic transfer."
862
1042
  );
1043
+ }
863
1044
  const fee = await ntt.quoteDeliveryPrice(recipient.chain, options);
864
1045
  const relayIx = await ntt.quoter.createRequestRelayInstruction(
865
1046
  payerAddress,
866
1047
  outboxItem.publicKey,
867
1048
  recipient.chain,
868
1049
  Number(fee) / LAMPORTS_PER_SOL,
869
- Number(options.gasDropoff ?? 0n) / WEI_PER_GWEI
1050
+ 0
870
1051
  );
871
1052
  tx.add(relayIx);
872
1053
  }
@@ -946,11 +1127,44 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
946
1127
  }
947
1128
  yield receipt;
948
1129
  }
1130
+ async getExecutorQuote(network, sourceChain, destinationChain, amount2) {
1131
+ const wh = new Wormhole(network, [solana.Platform, evm.Platform]);
1132
+ const executorRoute = nttExecutorRoute(getExecutorConfig(network));
1133
+ const routeInstance = new executorRoute(wh);
1134
+ const resolveM = (chain) => {
1135
+ if (chainToPlatform(chain) === "Solana") {
1136
+ const c = chain;
1137
+ return SolanaRoutes.getSolanaContracts(network, c).token;
1138
+ }
1139
+ return _M0AutomaticRoute.EVM_CONTRACTS.token;
1140
+ };
1141
+ const transferRequest = await routes.RouteTransferRequest.create(wh, {
1142
+ source: Wormhole.tokenId(sourceChain, resolveM(sourceChain)),
1143
+ destination: Wormhole.tokenId(
1144
+ destinationChain,
1145
+ resolveM(destinationChain)
1146
+ )
1147
+ });
1148
+ const validated = await routeInstance.validate(transferRequest, {
1149
+ amount: amount2.toString()
1150
+ });
1151
+ if (!validated.valid) {
1152
+ throw new Error(`Validation failed: ${validated.error.message}`);
1153
+ }
1154
+ return await routeInstance.fetchExecutorQuote(
1155
+ transferRequest,
1156
+ validated.params
1157
+ );
1158
+ }
1159
+ requiresExecutor(destination) {
1160
+ return chainToPlatform(destination) === "Solana";
1161
+ }
949
1162
  };
950
1163
  // ntt does not support gas drop-off currently
951
1164
  _M0AutomaticRoute.NATIVE_GAS_DROPOFF_SUPPORTED = false;
952
1165
  // Wrapped M token address is the same on EVM chains
953
1166
  _M0AutomaticRoute.EVM_WRAPPED_M_TOKEN = "0x437cc33344a0B27A429f795ff6B469C72698B291";
1167
+ _M0AutomaticRoute.EXECUTOR_ENTRYPOINT = "0x8518040a9cf9dfb55a4f099bb0eaabeefeb03643";
954
1168
  // Contract addresses are the same on all EVM chains
955
1169
  _M0AutomaticRoute.EVM_CONTRACTS = {
956
1170
  // M token address is the same on EVM chains
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m0-foundation/ntt-sdk-route",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "exports": {
5
5
  ".": {
6
6
  "types": "./dist/index.d.ts",
@@ -20,34 +20,35 @@
20
20
  "files": [
21
21
  "dist"
22
22
  ],
23
- "scripts": {
24
- "build": "$npm_execpath tsup --clean",
25
- "clean": "rm -rf dist"
26
- },
27
23
  "devDependencies": {
28
24
  "@changesets/cli": "^2.28.1",
29
25
  "@types/bn.js": "^5.2.0",
30
26
  "@types/node": "~20.17.17",
27
+ "ts-node": "^10.9.2",
31
28
  "tsup": "~8.3.6",
32
29
  "typescript": "~5.6.3"
33
30
  },
34
31
  "publishConfig": {
35
32
  "access": "public"
36
33
  },
37
- "packageManager": "pnpm@10.2.0",
38
34
  "dependencies": {
39
35
  "@noble/hashes": "^1.8.0",
40
36
  "@solana/spl-token": "^0.4.13",
41
37
  "@solana/web3.js": "^1.98.2",
42
- "@wormhole-foundation/sdk": "^1.7.0",
43
- "@wormhole-foundation/sdk-connect": "^1.7.0",
44
- "@wormhole-foundation/sdk-definitions-ntt": "^0.7.1",
45
- "@wormhole-foundation/sdk-evm": "^1.7.0",
46
- "@wormhole-foundation/sdk-evm-ntt": "^0.7.1",
47
- "@wormhole-foundation/sdk-route-ntt": "^0.7.1",
48
- "@wormhole-foundation/sdk-solana": "^1.7.0",
49
- "@wormhole-foundation/sdk-solana-ntt": "^0.7.1",
38
+ "@wormhole-foundation/sdk": "3.4.7",
39
+ "@wormhole-foundation/sdk-connect": "3.4.7",
40
+ "@wormhole-foundation/sdk-definitions-ntt": "2.0.5",
41
+ "@wormhole-foundation/sdk-evm": "3.4.7",
42
+ "@wormhole-foundation/sdk-evm-ntt": "2.0.5",
43
+ "@wormhole-foundation/sdk-route-ntt": "2.0.5",
44
+ "@wormhole-foundation/sdk-solana": "3.4.7",
45
+ "@wormhole-foundation/sdk-solana-ntt": "2.0.5",
50
46
  "bn.js": "^5.2.2",
51
47
  "ethers": "^6.5.1"
48
+ },
49
+ "scripts": {
50
+ "build": "$npm_execpath tsup --clean",
51
+ "clean": "rm -rf dist",
52
+ "test-bridge": "node --env-file=.env -r ts-node/register scripts/bridge.ts"
52
53
  }
53
- }
54
+ }