@m0-foundation/ntt-sdk-route 0.0.12 → 0.0.14

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,12 +1,12 @@
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';
6
6
  import { SolanaChains, SolanaUnsignedTransaction } from '@wormhole-foundation/sdk-solana';
7
7
  import { NttRoute } from '@wormhole-foundation/sdk-route-ntt';
8
8
  import { TransactionRequest } from 'ethers';
9
- import { PublicKey, TransactionInstruction, Connection, AddressLookupTableAccount } from '@solana/web3.js';
9
+ import { Keypair, PublicKey, TransactionInstruction, Connection, AddressLookupTableAccount } from '@solana/web3.js';
10
10
 
11
11
  type Op = NttRoute.Options;
12
12
  type Tp = routes.TransferParams<Op>;
@@ -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;
@@ -44,8 +45,10 @@ declare class M0AutomaticRoute<N extends Network> extends routes.AutomaticRoute<
44
45
  */
45
46
  transferMLike<N extends Network, C extends EvmChains>(ntt: EvmNtt<N, C>, sender: AccountAddress<C>, amount: bigint, destination: ChainAddress, sourceToken: string, destinationToken: string, options: Ntt.TransferOptions): AsyncGenerator<EvmUnsignedTransaction<N, C>>;
46
47
  createUnsignedTx<N extends Network, C extends EvmChains>(ntt: EvmNtt<N, C>, txReq: TransactionRequest, description: string, parallelizable?: boolean): EvmUnsignedTransaction<N, C>;
47
- 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): AsyncGenerator<SolanaUnsignedTransaction<N, C>>;
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, outboxItem: PublicKey): 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,12 +1,12 @@
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';
6
6
  import { SolanaChains, SolanaUnsignedTransaction } from '@wormhole-foundation/sdk-solana';
7
7
  import { NttRoute } from '@wormhole-foundation/sdk-route-ntt';
8
8
  import { TransactionRequest } from 'ethers';
9
- import { PublicKey, TransactionInstruction, Connection, AddressLookupTableAccount } from '@solana/web3.js';
9
+ import { Keypair, PublicKey, TransactionInstruction, Connection, AddressLookupTableAccount } from '@solana/web3.js';
10
10
 
11
11
  type Op = NttRoute.Options;
12
12
  type Tp = routes.TransferParams<Op>;
@@ -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;
@@ -44,8 +45,10 @@ declare class M0AutomaticRoute<N extends Network> extends routes.AutomaticRoute<
44
45
  */
45
46
  transferMLike<N extends Network, C extends EvmChains>(ntt: EvmNtt<N, C>, sender: AccountAddress<C>, amount: bigint, destination: ChainAddress, sourceToken: string, destinationToken: string, options: Ntt.TransferOptions): AsyncGenerator<EvmUnsignedTransaction<N, C>>;
46
47
  createUnsignedTx<N extends Network, C extends EvmChains>(ntt: EvmNtt<N, C>, txReq: TransactionRequest, description: string, parallelizable?: boolean): EvmUnsignedTransaction<N, C>;
47
- 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): AsyncGenerator<SolanaUnsignedTransaction<N, C>>;
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, outboxItem: PublicKey): 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: {
@@ -76,12 +91,11 @@ var SolanaRoutes = class _SolanaRoutes {
76
91
  }
77
92
  }[network];
78
93
  }
79
- static getSolanaContracts(chainContext) {
80
- const programs = _SolanaRoutes.getPrograms(
81
- chainContext.network
82
- );
94
+ static getSolanaContracts(network, chain) {
95
+ const programs = _SolanaRoutes.getPrograms(network);
83
96
  const extPrograms = _SolanaRoutes.getExtPrograms(
84
- chainContext.network
97
+ network,
98
+ chain
85
99
  );
86
100
  return {
87
101
  token: programs.mMint.toBase58(),
@@ -91,10 +105,19 @@ var SolanaRoutes = class _SolanaRoutes {
91
105
  quoter: programs.quoter.toBase58()
92
106
  };
93
107
  }
108
+ getSolanaContracts() {
109
+ return _SolanaRoutes.getSolanaContracts(this.network, this.ntt.chain);
110
+ }
94
111
  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);
112
+ const recipientAddress = sdkConnect.toUniversal(
113
+ recipient.chain,
114
+ recipient.address.toString()
115
+ ).toUint8Array();
116
+ if (recipientAddress.length !== 32) {
117
+ throw new Error(
118
+ `recipient address must be 32 bytes, got ${recipientAddress.length} bytes`
119
+ );
120
+ }
98
121
  if (destinationToken.length !== 32) {
99
122
  throw new Error(
100
123
  `destinationToken must be 32 bytes, got ${destinationToken.length} bytes`
@@ -107,6 +130,16 @@ var SolanaRoutes = class _SolanaRoutes {
107
130
  );
108
131
  }
109
132
  const { program: extProgram, tokenProgram: extTokenProgram } = extension;
133
+ const [tokenAuth] = web3_js.PublicKey.findProgramAddressSync(
134
+ [Buffer.from("token_authority")],
135
+ this.programs.portal
136
+ );
137
+ const sessionAuth = this.ntt.pdas.sessionAuthority(tokenAuth, {
138
+ amount: new BN__default.default(amount2),
139
+ recipientChain: { id: sdkConnect.chainToChainId(recipient.chain) },
140
+ recipientAddress: [...recipientAddress],
141
+ shouldQueue
142
+ });
110
143
  return new web3_js.TransactionInstruction({
111
144
  programId: this.ntt.program.programId,
112
145
  keys: [
@@ -131,10 +164,7 @@ var SolanaRoutes = class _SolanaRoutes {
131
164
  // from (token auth m token account)
132
165
  pubkey: splToken.getAssociatedTokenAddressSync(
133
166
  this.programs.mMint,
134
- web3_js.PublicKey.findProgramAddressSync(
135
- [Buffer.from("token_authority")],
136
- this.ntt.program.programId
137
- )[0],
167
+ tokenAuth,
138
168
  true,
139
169
  splToken.TOKEN_2022_PROGRAM_ID
140
170
  ),
@@ -185,24 +215,13 @@ var SolanaRoutes = class _SolanaRoutes {
185
215
  },
186
216
  {
187
217
  // 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
- }),
218
+ pubkey: sessionAuth,
197
219
  isSigner: false,
198
220
  isWritable: false
199
221
  },
200
222
  {
201
223
  // token auth
202
- pubkey: web3_js.PublicKey.findProgramAddressSync(
203
- [Buffer.from("token_authority")],
204
- this.ntt.program.programId
205
- )[0],
224
+ pubkey: tokenAuth,
206
225
  isSigner: false,
207
226
  isWritable: false
208
227
  },
@@ -309,10 +328,78 @@ var SolanaRoutes = class _SolanaRoutes {
309
328
  // chain_id
310
329
  recipientAddress,
311
330
  // recipient_address
312
- destinationToken,
313
- // destination_token
314
- Buffer.from([Number(shouldQueue)])
331
+ Buffer.from([Number(shouldQueue)]),
315
332
  // should_queue
333
+ destinationToken
334
+ // destination_token
335
+ ])
336
+ });
337
+ }
338
+ getExecutorRelayIx(sender, quote, destinationChain, outboxItem) {
339
+ const nttPeer = web3_js.PublicKey.findProgramAddressSync(
340
+ [
341
+ Buffer.from("peer"),
342
+ new BN__default.default(sdkConnect.chainToChainId(destinationChain)).toArrayLike(Buffer, "le", 2)
343
+ ],
344
+ this.programs.portal
345
+ )[0];
346
+ const signedQuoteBytes = Buffer.from(quote.signedQuote);
347
+ const relayInstructions = Buffer.from(quote.relayInstructions);
348
+ return new web3_js.TransactionInstruction({
349
+ keys: [
350
+ {
351
+ pubkey: sender,
352
+ isSigner: true,
353
+ isWritable: true
354
+ },
355
+ {
356
+ // payee
357
+ pubkey: new web3_js.PublicKey(quote.payeeAddress),
358
+ isSigner: false,
359
+ isWritable: true
360
+ },
361
+ {
362
+ // ntt_program_id
363
+ pubkey: this.programs.portal,
364
+ isSigner: false,
365
+ isWritable: false
366
+ },
367
+ {
368
+ pubkey: nttPeer,
369
+ isSigner: false,
370
+ isWritable: false
371
+ },
372
+ {
373
+ // ntt_message
374
+ pubkey: outboxItem,
375
+ isSigner: false,
376
+ isWritable: false
377
+ },
378
+ {
379
+ // executor_program
380
+ pubkey: new web3_js.PublicKey("execXUrAsMnqMmTHj5m7N1YQgsDz3cwGLYCYyuDRciV"),
381
+ isSigner: false,
382
+ isWritable: false
383
+ },
384
+ {
385
+ pubkey: web3_js.SystemProgram.programId,
386
+ isSigner: false,
387
+ isWritable: false
388
+ }
389
+ ],
390
+ programId: new web3_js.PublicKey("nex1gkSWtRBheEJuQZMqHhbMG5A45qPU76KqnCZNVHR"),
391
+ data: Buffer.concat([
392
+ Buffer.from(sha2.sha256("global:relay_ntt_mesage").subarray(0, 8)),
393
+ new BN__default.default(sdkConnect.chainToChainId(destinationChain)).toArrayLike(Buffer, "le", 2),
394
+ // recipient_chain
395
+ new BN__default.default(signedQuoteBytes.length).toArrayLike(Buffer, "le", 4),
396
+ // vec length
397
+ Buffer.from(signedQuoteBytes),
398
+ // signed_quote_bytes
399
+ new BN__default.default(relayInstructions.length).toArrayLike(Buffer, "le", 4),
400
+ // vec length
401
+ Buffer.from(relayInstructions)
402
+ // relay_instructions
316
403
  ])
317
404
  });
318
405
  }
@@ -577,6 +664,62 @@ function pk(address) {
577
664
  return new web3_js.PublicKey(address);
578
665
  }
579
666
 
667
+ // src/executor.ts
668
+ function getExecutorConfig(network = "Mainnet") {
669
+ const svmContracts = SolanaRoutes.getSolanaContracts(network, "Solana");
670
+ const svmChains = ["Solana", "Fogo"];
671
+ const evmChains = network === "Mainnet" ? ["Ethereum", "Optimism", "Arbitrum"] : ["Sepolia", "ArbitrumSepolia", "OptimismSepolia"];
672
+ return {
673
+ ntt: {
674
+ tokens: {
675
+ M0: [
676
+ ...svmChains.map((chain) => ({
677
+ chain,
678
+ token: svmContracts.token,
679
+ manager: svmContracts.manager,
680
+ transceiver: [
681
+ {
682
+ type: "wormhole",
683
+ address: svmContracts.transceiver.wormhole
684
+ }
685
+ ],
686
+ quoter: svmContracts.quoter
687
+ })),
688
+ ...evmChains.map((chain) => ({
689
+ chain,
690
+ token: M0AutomaticRoute.EVM_CONTRACTS.token,
691
+ manager: M0AutomaticRoute.EVM_CONTRACTS.manager,
692
+ transceiver: [
693
+ {
694
+ type: "wormhole",
695
+ address: M0AutomaticRoute.EVM_CONTRACTS.transceiver.wormhole
696
+ }
697
+ ],
698
+ quoter: M0AutomaticRoute.EVM_CONTRACTS.quoter
699
+ }))
700
+ ]
701
+ }
702
+ },
703
+ referrerFee: {
704
+ feeDbps: 0n,
705
+ perTokenOverrides: {
706
+ // SVM chains require extra compute when receiving messages
707
+ // so we need to override the gas cost
708
+ Solana: {
709
+ [svmContracts.token]: {
710
+ msgValue: 15000000n
711
+ }
712
+ },
713
+ Fogo: {
714
+ [svmContracts.token]: {
715
+ msgValue: 15000000n
716
+ }
717
+ }
718
+ }
719
+ }
720
+ };
721
+ }
722
+
580
723
  // src/m0AutomaticRoute.ts
581
724
  var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.AutomaticRoute {
582
725
  static supportedNetworks() {
@@ -604,21 +747,18 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
604
747
  static getContracts(chainContext) {
605
748
  switch (chainContext.chain) {
606
749
  case "Ethereum":
607
- return this.EVM_CONTRACTS;
608
750
  case "Optimism":
609
- return this.EVM_CONTRACTS;
610
751
  case "Arbitrum":
611
- return this.EVM_CONTRACTS;
612
752
  case "Sepolia":
613
- return this.EVM_CONTRACTS;
614
753
  case "OptimismSepolia":
615
- return this.EVM_CONTRACTS;
616
754
  case "ArbitrumSepolia":
617
755
  return this.EVM_CONTRACTS;
618
756
  case "Solana":
619
- return SolanaRoutes.getSolanaContracts(chainContext);
620
757
  case "Fogo":
621
- return SolanaRoutes.getSolanaContracts(chainContext);
758
+ return SolanaRoutes.getSolanaContracts(
759
+ chainContext.network,
760
+ chainContext.chain
761
+ );
622
762
  default:
623
763
  throw new Error(`Unsupported chain: ${chainContext.chain}`);
624
764
  }
@@ -636,9 +776,11 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
636
776
  return [];
637
777
  }
638
778
  const { token: mToken, mLikeTokens } = this.getContracts(toChain);
639
- return [mToken, ...mLikeTokens].map(
640
- (x) => sdkConnect.Wormhole.tokenId(toChain.chain, x)
641
- );
779
+ const tokens = mLikeTokens.map((x) => sdkConnect.Wormhole.tokenId(toChain.chain, x));
780
+ if (toChain.chain === "Solana" || toChain.chain === "Fogo") {
781
+ return tokens;
782
+ }
783
+ return [...tokens, sdkConnect.Wormhole.tokenId(toChain.chain, mToken)];
642
784
  }
643
785
  static isProtocolSupported(chain) {
644
786
  return chain.supportsProtocol("Ntt");
@@ -654,10 +796,6 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
654
796
  }
655
797
  async validate(request, params) {
656
798
  const options = params.options ?? this.getDefaultOptions();
657
- const gasDropoff = sdkConnect.amount.parse(
658
- options.gasDropoff ?? "0.0",
659
- request.toChain.config.nativeTokenDecimals
660
- );
661
799
  const parsedAmount = sdkConnect.amount.parse(params.amount, request.source.decimals);
662
800
  const trimmedAmount = sdkRouteNtt.NttRoute.trimAmount(
663
801
  parsedAmount,
@@ -673,8 +811,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
673
811
  destinationContracts: toContracts,
674
812
  options: {
675
813
  queue: false,
676
- automatic: true,
677
- gasDropoff: sdkConnect.amount.units(gasDropoff)
814
+ automatic: true
678
815
  }
679
816
  },
680
817
  options
@@ -719,7 +856,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
719
856
  )
720
857
  },
721
858
  destinationNativeGas: sdkConnect.amount.fromBaseUnits(
722
- params.normalizedParams.options.gasDropoff ?? 0n,
859
+ 0n,
723
860
  toChain.config.nativeTokenDecimals
724
861
  ),
725
862
  eta: sdkConnect.finality.estimateFinalityTime(request.fromChain.chain)
@@ -780,18 +917,18 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
780
917
  */
781
918
  async *transferMLike(ntt, sender, amount2, destination, sourceToken, destinationToken, options) {
782
919
  const senderAddress = new sdkEvm.EvmAddress(sender).toString();
783
- const totalPrice = await ntt.quoteDeliveryPrice(destination.chain, options);
784
920
  const tokenContract = sdkEvm.EvmPlatform.getTokenImplementation(
785
921
  ntt.provider,
786
922
  sourceToken
787
923
  );
924
+ const spenderAddress = this.requiresExecutor(destination.chain) ? _M0AutomaticRoute.EXECUTOR_ENTRYPOINT : ntt.managerAddress;
788
925
  const allowance = await tokenContract.allowance(
789
926
  senderAddress,
790
- ntt.managerAddress
927
+ spenderAddress
791
928
  );
792
929
  if (allowance < amount2) {
793
930
  const txReq2 = await tokenContract.approve.populateTransaction(
794
- ntt.managerAddress,
931
+ spenderAddress,
795
932
  amount2
796
933
  );
797
934
  yield this.createUnsignedTx(
@@ -801,6 +938,36 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
801
938
  );
802
939
  }
803
940
  const receiver = sdkConnect.universalAddress(destination);
941
+ if (this.requiresExecutor(destination.chain)) {
942
+ const quote = await this.getExecutorQuote(
943
+ ntt.network,
944
+ ntt.chain,
945
+ destination.chain,
946
+ amount2
947
+ );
948
+ const contract2 = new ethers.Contract(_M0AutomaticRoute.EXECUTOR_ENTRYPOINT, [
949
+ "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)"
950
+ ]);
951
+ const executorArgs = {
952
+ value: quote.estimatedCost,
953
+ refundAddress: senderAddress,
954
+ signedQuote: quote.signedQuote,
955
+ instructions: quote.relayInstructions
956
+ };
957
+ const txReq2 = await contract2.getFunction("transferMLikeToken").populateTransaction(
958
+ amount2,
959
+ sourceToken,
960
+ sdkConnect.toChainId(destination.chain),
961
+ sdkConnect.toUniversal(destination.chain, destinationToken).toString(),
962
+ receiver,
963
+ receiver,
964
+ executorArgs,
965
+ Uint8Array.from(Buffer.from("01000101", "hex")),
966
+ { value: quote.estimatedCost }
967
+ );
968
+ yield ntt.createUnsignedTx(sdkEvm.addFrom(txReq2, senderAddress), "Ntt.transfer");
969
+ return;
970
+ }
804
971
  const contract = new ethers.Contract(ntt.managerAddress, [
805
972
  "function transferMLikeToken(uint256 amount, address sourceToken, uint16 destinationChainId, bytes32 destinationToken, bytes32 recipient, bytes32 refundAddress) external payable returns (uint64 sequence)"
806
973
  ]);
@@ -811,7 +978,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
811
978
  sdkConnect.toUniversal(destination.chain, destinationToken).toString(),
812
979
  receiver,
813
980
  receiver,
814
- { value: totalPrice }
981
+ { value: await ntt.quoteDeliveryPrice(destination.chain, options) }
815
982
  );
816
983
  yield ntt.createUnsignedTx(sdkEvm.addFrom(txReq, senderAddress), "Ntt.transfer");
817
984
  }
@@ -824,14 +991,14 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
824
991
  parallelizable
825
992
  );
826
993
  }
827
- async *transferSolanaExtension(ntt, sender, amount2, recipient, sourceToken, destinationToken, options) {
994
+ async *transferSolanaExtension(ntt, sender, amount2, recipient, sourceToken, destinationToken, options, outboxItem) {
828
995
  const router = new SolanaRoutes(ntt);
829
- if ((await ntt.getConfig()).mint.toBase58() === sourceToken) {
996
+ if (router.getSolanaContracts().token === sourceToken) {
830
997
  return ntt.transfer(sender, amount2, recipient, options);
831
998
  }
832
999
  const config = await ntt.getConfig();
833
1000
  if (config.paused) throw new Error("Contract is paused");
834
- const outboxItem = web3_js.Keypair.generate();
1001
+ outboxItem = outboxItem ?? web3_js.Keypair.generate();
835
1002
  const payerAddress = new sdkSolana.SolanaAddress(sender).unwrap();
836
1003
  const ixs = [
837
1004
  router.getTransferExtensionBurnIx(
@@ -861,18 +1028,34 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.Automa
861
1028
  const tx = new web3_js.Transaction();
862
1029
  tx.feePayer = payerAddress;
863
1030
  tx.add(...ixs);
864
- if (options.automatic) {
865
- if (!ntt.quoter)
1031
+ if (this.requiresExecutor(recipient.chain)) {
1032
+ const quote = await this.getExecutorQuote(
1033
+ ntt.network,
1034
+ ntt.chain,
1035
+ recipient.chain,
1036
+ amount2
1037
+ );
1038
+ tx.add(
1039
+ router.getExecutorRelayIx(
1040
+ payerAddress,
1041
+ quote,
1042
+ recipient.chain,
1043
+ outboxItem.publicKey
1044
+ )
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: {
@@ -70,12 +83,11 @@ var SolanaRoutes = class _SolanaRoutes {
70
83
  }
71
84
  }[network];
72
85
  }
73
- static getSolanaContracts(chainContext) {
74
- const programs = _SolanaRoutes.getPrograms(
75
- chainContext.network
76
- );
86
+ static getSolanaContracts(network, chain) {
87
+ const programs = _SolanaRoutes.getPrograms(network);
77
88
  const extPrograms = _SolanaRoutes.getExtPrograms(
78
- chainContext.network
89
+ network,
90
+ chain
79
91
  );
80
92
  return {
81
93
  token: programs.mMint.toBase58(),
@@ -85,10 +97,19 @@ var SolanaRoutes = class _SolanaRoutes {
85
97
  quoter: programs.quoter.toBase58()
86
98
  };
87
99
  }
100
+ getSolanaContracts() {
101
+ return _SolanaRoutes.getSolanaContracts(this.network, this.ntt.chain);
102
+ }
88
103
  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);
104
+ const recipientAddress = toUniversal(
105
+ recipient.chain,
106
+ recipient.address.toString()
107
+ ).toUint8Array();
108
+ if (recipientAddress.length !== 32) {
109
+ throw new Error(
110
+ `recipient address must be 32 bytes, got ${recipientAddress.length} bytes`
111
+ );
112
+ }
92
113
  if (destinationToken.length !== 32) {
93
114
  throw new Error(
94
115
  `destinationToken must be 32 bytes, got ${destinationToken.length} bytes`
@@ -101,6 +122,16 @@ var SolanaRoutes = class _SolanaRoutes {
101
122
  );
102
123
  }
103
124
  const { program: extProgram, tokenProgram: extTokenProgram } = extension;
125
+ const [tokenAuth] = PublicKey.findProgramAddressSync(
126
+ [Buffer.from("token_authority")],
127
+ this.programs.portal
128
+ );
129
+ const sessionAuth = this.ntt.pdas.sessionAuthority(tokenAuth, {
130
+ amount: new BN(amount2),
131
+ recipientChain: { id: chainToChainId(recipient.chain) },
132
+ recipientAddress: [...recipientAddress],
133
+ shouldQueue
134
+ });
104
135
  return new TransactionInstruction({
105
136
  programId: this.ntt.program.programId,
106
137
  keys: [
@@ -125,10 +156,7 @@ var SolanaRoutes = class _SolanaRoutes {
125
156
  // from (token auth m token account)
126
157
  pubkey: getAssociatedTokenAddressSync(
127
158
  this.programs.mMint,
128
- PublicKey.findProgramAddressSync(
129
- [Buffer.from("token_authority")],
130
- this.ntt.program.programId
131
- )[0],
159
+ tokenAuth,
132
160
  true,
133
161
  TOKEN_2022_PROGRAM_ID
134
162
  ),
@@ -179,24 +207,13 @@ var SolanaRoutes = class _SolanaRoutes {
179
207
  },
180
208
  {
181
209
  // 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
- }),
210
+ pubkey: sessionAuth,
191
211
  isSigner: false,
192
212
  isWritable: false
193
213
  },
194
214
  {
195
215
  // token auth
196
- pubkey: PublicKey.findProgramAddressSync(
197
- [Buffer.from("token_authority")],
198
- this.ntt.program.programId
199
- )[0],
216
+ pubkey: tokenAuth,
200
217
  isSigner: false,
201
218
  isWritable: false
202
219
  },
@@ -303,10 +320,78 @@ var SolanaRoutes = class _SolanaRoutes {
303
320
  // chain_id
304
321
  recipientAddress,
305
322
  // recipient_address
306
- destinationToken,
307
- // destination_token
308
- Buffer.from([Number(shouldQueue)])
323
+ Buffer.from([Number(shouldQueue)]),
309
324
  // should_queue
325
+ destinationToken
326
+ // destination_token
327
+ ])
328
+ });
329
+ }
330
+ getExecutorRelayIx(sender, quote, destinationChain, outboxItem) {
331
+ const nttPeer = PublicKey.findProgramAddressSync(
332
+ [
333
+ Buffer.from("peer"),
334
+ new BN(chainToChainId(destinationChain)).toArrayLike(Buffer, "le", 2)
335
+ ],
336
+ this.programs.portal
337
+ )[0];
338
+ const signedQuoteBytes = Buffer.from(quote.signedQuote);
339
+ const relayInstructions = Buffer.from(quote.relayInstructions);
340
+ return new TransactionInstruction({
341
+ keys: [
342
+ {
343
+ pubkey: sender,
344
+ isSigner: true,
345
+ isWritable: true
346
+ },
347
+ {
348
+ // payee
349
+ pubkey: new PublicKey(quote.payeeAddress),
350
+ isSigner: false,
351
+ isWritable: true
352
+ },
353
+ {
354
+ // ntt_program_id
355
+ pubkey: this.programs.portal,
356
+ isSigner: false,
357
+ isWritable: false
358
+ },
359
+ {
360
+ pubkey: nttPeer,
361
+ isSigner: false,
362
+ isWritable: false
363
+ },
364
+ {
365
+ // ntt_message
366
+ pubkey: outboxItem,
367
+ isSigner: false,
368
+ isWritable: false
369
+ },
370
+ {
371
+ // executor_program
372
+ pubkey: new PublicKey("execXUrAsMnqMmTHj5m7N1YQgsDz3cwGLYCYyuDRciV"),
373
+ isSigner: false,
374
+ isWritable: false
375
+ },
376
+ {
377
+ pubkey: SystemProgram.programId,
378
+ isSigner: false,
379
+ isWritable: false
380
+ }
381
+ ],
382
+ programId: new PublicKey("nex1gkSWtRBheEJuQZMqHhbMG5A45qPU76KqnCZNVHR"),
383
+ data: Buffer.concat([
384
+ Buffer.from(sha256("global:relay_ntt_mesage").subarray(0, 8)),
385
+ new BN(chainToChainId(destinationChain)).toArrayLike(Buffer, "le", 2),
386
+ // recipient_chain
387
+ new BN(signedQuoteBytes.length).toArrayLike(Buffer, "le", 4),
388
+ // vec length
389
+ Buffer.from(signedQuoteBytes),
390
+ // signed_quote_bytes
391
+ new BN(relayInstructions.length).toArrayLike(Buffer, "le", 4),
392
+ // vec length
393
+ Buffer.from(relayInstructions)
394
+ // relay_instructions
310
395
  ])
311
396
  });
312
397
  }
@@ -571,6 +656,62 @@ function pk(address) {
571
656
  return new PublicKey(address);
572
657
  }
573
658
 
659
+ // src/executor.ts
660
+ function getExecutorConfig(network = "Mainnet") {
661
+ const svmContracts = SolanaRoutes.getSolanaContracts(network, "Solana");
662
+ const svmChains = ["Solana", "Fogo"];
663
+ const evmChains = network === "Mainnet" ? ["Ethereum", "Optimism", "Arbitrum"] : ["Sepolia", "ArbitrumSepolia", "OptimismSepolia"];
664
+ return {
665
+ ntt: {
666
+ tokens: {
667
+ M0: [
668
+ ...svmChains.map((chain) => ({
669
+ chain,
670
+ token: svmContracts.token,
671
+ manager: svmContracts.manager,
672
+ transceiver: [
673
+ {
674
+ type: "wormhole",
675
+ address: svmContracts.transceiver.wormhole
676
+ }
677
+ ],
678
+ quoter: svmContracts.quoter
679
+ })),
680
+ ...evmChains.map((chain) => ({
681
+ chain,
682
+ token: M0AutomaticRoute.EVM_CONTRACTS.token,
683
+ manager: M0AutomaticRoute.EVM_CONTRACTS.manager,
684
+ transceiver: [
685
+ {
686
+ type: "wormhole",
687
+ address: M0AutomaticRoute.EVM_CONTRACTS.transceiver.wormhole
688
+ }
689
+ ],
690
+ quoter: M0AutomaticRoute.EVM_CONTRACTS.quoter
691
+ }))
692
+ ]
693
+ }
694
+ },
695
+ referrerFee: {
696
+ feeDbps: 0n,
697
+ perTokenOverrides: {
698
+ // SVM chains require extra compute when receiving messages
699
+ // so we need to override the gas cost
700
+ Solana: {
701
+ [svmContracts.token]: {
702
+ msgValue: 15000000n
703
+ }
704
+ },
705
+ Fogo: {
706
+ [svmContracts.token]: {
707
+ msgValue: 15000000n
708
+ }
709
+ }
710
+ }
711
+ }
712
+ };
713
+ }
714
+
574
715
  // src/m0AutomaticRoute.ts
575
716
  var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
576
717
  static supportedNetworks() {
@@ -598,21 +739,18 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
598
739
  static getContracts(chainContext) {
599
740
  switch (chainContext.chain) {
600
741
  case "Ethereum":
601
- return this.EVM_CONTRACTS;
602
742
  case "Optimism":
603
- return this.EVM_CONTRACTS;
604
743
  case "Arbitrum":
605
- return this.EVM_CONTRACTS;
606
744
  case "Sepolia":
607
- return this.EVM_CONTRACTS;
608
745
  case "OptimismSepolia":
609
- return this.EVM_CONTRACTS;
610
746
  case "ArbitrumSepolia":
611
747
  return this.EVM_CONTRACTS;
612
748
  case "Solana":
613
- return SolanaRoutes.getSolanaContracts(chainContext);
614
749
  case "Fogo":
615
- return SolanaRoutes.getSolanaContracts(chainContext);
750
+ return SolanaRoutes.getSolanaContracts(
751
+ chainContext.network,
752
+ chainContext.chain
753
+ );
616
754
  default:
617
755
  throw new Error(`Unsupported chain: ${chainContext.chain}`);
618
756
  }
@@ -630,9 +768,11 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
630
768
  return [];
631
769
  }
632
770
  const { token: mToken, mLikeTokens } = this.getContracts(toChain);
633
- return [mToken, ...mLikeTokens].map(
634
- (x) => Wormhole.tokenId(toChain.chain, x)
635
- );
771
+ const tokens = mLikeTokens.map((x) => Wormhole.tokenId(toChain.chain, x));
772
+ if (toChain.chain === "Solana" || toChain.chain === "Fogo") {
773
+ return tokens;
774
+ }
775
+ return [...tokens, Wormhole.tokenId(toChain.chain, mToken)];
636
776
  }
637
777
  static isProtocolSupported(chain) {
638
778
  return chain.supportsProtocol("Ntt");
@@ -648,10 +788,6 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
648
788
  }
649
789
  async validate(request, params) {
650
790
  const options = params.options ?? this.getDefaultOptions();
651
- const gasDropoff = amount.parse(
652
- options.gasDropoff ?? "0.0",
653
- request.toChain.config.nativeTokenDecimals
654
- );
655
791
  const parsedAmount = amount.parse(params.amount, request.source.decimals);
656
792
  const trimmedAmount = NttRoute.trimAmount(
657
793
  parsedAmount,
@@ -667,8 +803,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
667
803
  destinationContracts: toContracts,
668
804
  options: {
669
805
  queue: false,
670
- automatic: true,
671
- gasDropoff: amount.units(gasDropoff)
806
+ automatic: true
672
807
  }
673
808
  },
674
809
  options
@@ -713,7 +848,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
713
848
  )
714
849
  },
715
850
  destinationNativeGas: amount.fromBaseUnits(
716
- params.normalizedParams.options.gasDropoff ?? 0n,
851
+ 0n,
717
852
  toChain.config.nativeTokenDecimals
718
853
  ),
719
854
  eta: finality.estimateFinalityTime(request.fromChain.chain)
@@ -774,18 +909,18 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
774
909
  */
775
910
  async *transferMLike(ntt, sender, amount2, destination, sourceToken, destinationToken, options) {
776
911
  const senderAddress = new EvmAddress(sender).toString();
777
- const totalPrice = await ntt.quoteDeliveryPrice(destination.chain, options);
778
912
  const tokenContract = EvmPlatform.getTokenImplementation(
779
913
  ntt.provider,
780
914
  sourceToken
781
915
  );
916
+ const spenderAddress = this.requiresExecutor(destination.chain) ? _M0AutomaticRoute.EXECUTOR_ENTRYPOINT : ntt.managerAddress;
782
917
  const allowance = await tokenContract.allowance(
783
918
  senderAddress,
784
- ntt.managerAddress
919
+ spenderAddress
785
920
  );
786
921
  if (allowance < amount2) {
787
922
  const txReq2 = await tokenContract.approve.populateTransaction(
788
- ntt.managerAddress,
923
+ spenderAddress,
789
924
  amount2
790
925
  );
791
926
  yield this.createUnsignedTx(
@@ -795,6 +930,36 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
795
930
  );
796
931
  }
797
932
  const receiver = universalAddress(destination);
933
+ if (this.requiresExecutor(destination.chain)) {
934
+ const quote = await this.getExecutorQuote(
935
+ ntt.network,
936
+ ntt.chain,
937
+ destination.chain,
938
+ amount2
939
+ );
940
+ const contract2 = new Contract(_M0AutomaticRoute.EXECUTOR_ENTRYPOINT, [
941
+ "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)"
942
+ ]);
943
+ const executorArgs = {
944
+ value: quote.estimatedCost,
945
+ refundAddress: senderAddress,
946
+ signedQuote: quote.signedQuote,
947
+ instructions: quote.relayInstructions
948
+ };
949
+ const txReq2 = await contract2.getFunction("transferMLikeToken").populateTransaction(
950
+ amount2,
951
+ sourceToken,
952
+ toChainId(destination.chain),
953
+ toUniversal(destination.chain, destinationToken).toString(),
954
+ receiver,
955
+ receiver,
956
+ executorArgs,
957
+ Uint8Array.from(Buffer.from("01000101", "hex")),
958
+ { value: quote.estimatedCost }
959
+ );
960
+ yield ntt.createUnsignedTx(addFrom(txReq2, senderAddress), "Ntt.transfer");
961
+ return;
962
+ }
798
963
  const contract = new Contract(ntt.managerAddress, [
799
964
  "function transferMLikeToken(uint256 amount, address sourceToken, uint16 destinationChainId, bytes32 destinationToken, bytes32 recipient, bytes32 refundAddress) external payable returns (uint64 sequence)"
800
965
  ]);
@@ -805,7 +970,7 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
805
970
  toUniversal(destination.chain, destinationToken).toString(),
806
971
  receiver,
807
972
  receiver,
808
- { value: totalPrice }
973
+ { value: await ntt.quoteDeliveryPrice(destination.chain, options) }
809
974
  );
810
975
  yield ntt.createUnsignedTx(addFrom(txReq, senderAddress), "Ntt.transfer");
811
976
  }
@@ -818,14 +983,14 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
818
983
  parallelizable
819
984
  );
820
985
  }
821
- async *transferSolanaExtension(ntt, sender, amount2, recipient, sourceToken, destinationToken, options) {
986
+ async *transferSolanaExtension(ntt, sender, amount2, recipient, sourceToken, destinationToken, options, outboxItem) {
822
987
  const router = new SolanaRoutes(ntt);
823
- if ((await ntt.getConfig()).mint.toBase58() === sourceToken) {
988
+ if (router.getSolanaContracts().token === sourceToken) {
824
989
  return ntt.transfer(sender, amount2, recipient, options);
825
990
  }
826
991
  const config = await ntt.getConfig();
827
992
  if (config.paused) throw new Error("Contract is paused");
828
- const outboxItem = Keypair.generate();
993
+ outboxItem = outboxItem ?? Keypair.generate();
829
994
  const payerAddress = new SolanaAddress(sender).unwrap();
830
995
  const ixs = [
831
996
  router.getTransferExtensionBurnIx(
@@ -855,18 +1020,34 @@ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
855
1020
  const tx = new Transaction();
856
1021
  tx.feePayer = payerAddress;
857
1022
  tx.add(...ixs);
858
- if (options.automatic) {
859
- if (!ntt.quoter)
1023
+ if (this.requiresExecutor(recipient.chain)) {
1024
+ const quote = await this.getExecutorQuote(
1025
+ ntt.network,
1026
+ ntt.chain,
1027
+ recipient.chain,
1028
+ amount2
1029
+ );
1030
+ tx.add(
1031
+ router.getExecutorRelayIx(
1032
+ payerAddress,
1033
+ quote,
1034
+ recipient.chain,
1035
+ outboxItem.publicKey
1036
+ )
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.12",
3
+ "version": "0.0.14",
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
+ }