@fogo/sessions-sdk 0.0.28 → 0.0.30

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.
@@ -1,6 +1,6 @@
1
- import type { Transaction, Instruction, TransactionWithLifetime, Rpc, GetLatestBlockhashApi } from "@solana/kit";
1
+ import type { GetLatestBlockhashApi, Instruction, Rpc, Transaction, TransactionWithLifetime } from "@solana/kit";
2
2
  import type { TransactionError } from "@solana/web3.js";
3
- import { Keypair, Connection as Web3Connection, TransactionInstruction, VersionedTransaction, PublicKey } from "@solana/web3.js";
3
+ import { Keypair, PublicKey, TransactionInstruction, VersionedTransaction, Connection as Web3Connection } from "@solana/web3.js";
4
4
  export declare enum Network {
5
5
  Testnet = 0,
6
6
  Mainnet = 1
package/cjs/index.d.ts CHANGED
@@ -1,17 +1,19 @@
1
- import type { BaseSignerWalletAdapter, MessageSignerWalletAdapterProps } from "@solana/wallet-adapter-base";
2
- import type { TransactionError } from "@solana/web3.js";
1
+ import type { TransactionError, TransactionInstruction, VersionedTransaction } from "@solana/web3.js";
3
2
  import { Connection, PublicKey } from "@solana/web3.js";
4
3
  import type { Chain } from "@wormhole-foundation/sdk";
5
4
  import BN from "bn.js";
6
5
  import { z } from "zod";
7
6
  import type { TransactionOrInstructions, TransactionResult } from "./connection.js";
8
7
  import type { SendTransactionOptions, SessionContext } from "./context.js";
9
- export { type SessionContext, type SendTransactionOptions, createSessionContext, } from "./context.js";
10
- export { type TransactionResult, type Connection, type TransactionOrInstructions, Network, TransactionResultType, createSessionConnection, } from "./connection.js";
8
+ export { type Connection, createSessionConnection, Network, type TransactionOrInstructions, type TransactionResult, TransactionResultType, } from "./connection.js";
9
+ export { createSessionContext, type SendTransactionOptions, type SessionContext, } from "./context.js";
11
10
  type EstablishSessionOptions = {
12
11
  context: SessionContext;
13
12
  walletPublicKey: PublicKey;
14
- signMessage: (message: Uint8Array) => Promise<Uint8Array>;
13
+ signMessage: (message: Uint8Array) => Promise<{
14
+ signedMessage: Uint8Array;
15
+ signature: Uint8Array;
16
+ }>;
15
17
  expires: Date;
16
18
  extra?: Record<string, string> | undefined;
17
19
  createUnsafeExtractableSessionKey?: boolean | undefined;
@@ -26,7 +28,10 @@ export declare const establishSession: (options: EstablishSessionOptions) => Pro
26
28
  export declare const replaceSession: (options: {
27
29
  context: SessionContext;
28
30
  session: Session;
29
- signMessage: (message: Uint8Array) => Promise<Uint8Array>;
31
+ signMessage: (message: Uint8Array) => Promise<{
32
+ signedMessage: Uint8Array;
33
+ signature: Uint8Array;
34
+ }>;
30
35
  expires: Date;
31
36
  extra?: Record<string, string> | undefined;
32
37
  } & ({
@@ -2223,6 +2228,9 @@ export type Session = {
2223
2228
  sessionKey: CryptoKeyPair;
2224
2229
  walletPublicKey: PublicKey;
2225
2230
  payer: PublicKey;
2231
+ getSystemProgramSessionWrapInstruction: (amount: bigint) => TransactionInstruction;
2232
+ getSessionWrapInstructions: (amount: bigint) => TransactionInstruction[];
2233
+ getSessionUnwrapInstructions: () => TransactionInstruction[];
2226
2234
  sendTransaction: (instructions: TransactionOrInstructions, extraConfig?: SendTransactionOptions) => Promise<TransactionResult>;
2227
2235
  sessionInfo: NonNullable<z.infer<typeof sessionInfoSchema>>;
2228
2236
  };
@@ -2243,19 +2251,37 @@ export declare const getBridgeOutFee: (context: SessionContext) => Promise<{
2243
2251
  type SendTransferOptions = {
2244
2252
  context: SessionContext;
2245
2253
  walletPublicKey: PublicKey;
2246
- signMessage: (message: Uint8Array) => Promise<Uint8Array>;
2254
+ signMessage: (message: Uint8Array) => Promise<{
2255
+ signedMessage: Uint8Array;
2256
+ signature: Uint8Array;
2257
+ }>;
2247
2258
  mint: PublicKey;
2248
2259
  amount: bigint;
2249
2260
  recipient: PublicKey;
2250
2261
  feeConfig: Awaited<ReturnType<typeof getTransferFee>>;
2251
2262
  };
2252
2263
  export declare const sendTransfer: (options: SendTransferOptions) => Promise<TransactionResult>;
2264
+ type SendNativeTransferOptions = {
2265
+ context: SessionContext;
2266
+ walletPublicKey: PublicKey;
2267
+ signMessage: (message: Uint8Array) => Promise<{
2268
+ signedMessage: Uint8Array;
2269
+ signature: Uint8Array;
2270
+ }>;
2271
+ amount: bigint;
2272
+ recipient: PublicKey;
2273
+ feeConfig: Awaited<ReturnType<typeof getTransferFee>>;
2274
+ };
2275
+ export declare const sendNativeTransfer: (options: SendNativeTransferOptions) => Promise<TransactionResult>;
2253
2276
  type SendBridgeOutOptions = {
2254
2277
  context: SessionContext;
2255
2278
  sessionKey: CryptoKeyPair;
2256
2279
  sessionPublicKey: PublicKey;
2257
2280
  walletPublicKey: PublicKey;
2258
- solanaWallet: MessageSignerWalletAdapterProps;
2281
+ signMessage: (message: Uint8Array) => Promise<{
2282
+ signedMessage: Uint8Array;
2283
+ signature: Uint8Array;
2284
+ }>;
2259
2285
  amount: bigint;
2260
2286
  fromToken: WormholeToken & {
2261
2287
  chain: "Fogo";
@@ -2275,7 +2301,7 @@ export declare const bridgeOut: (options: SendBridgeOutOptions) => Promise<Trans
2275
2301
  type SendBridgeInOptions = {
2276
2302
  context: SessionContext;
2277
2303
  walletPublicKey: PublicKey;
2278
- solanaWallet: BaseSignerWalletAdapter;
2304
+ signTransaction: (transaction: VersionedTransaction) => Promise<VersionedTransaction>;
2279
2305
  amount: bigint;
2280
2306
  fromToken: WormholeToken & {
2281
2307
  chain: "Solana";
package/cjs/index.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.verifyLogInToken = exports.createLogInToken = exports.bridgeIn = exports.bridgeOut = exports.sendTransfer = exports.getBridgeOutFee = exports.getTransferFee = exports.SessionResultType = exports.getDomainRecordAddress = exports.AuthorizedTokens = exports.AuthorizedProgramsType = exports.getSessionAccount = exports.reestablishSession = exports.revokeSession = exports.replaceSession = exports.establishSession = exports.createSessionConnection = exports.TransactionResultType = exports.Network = exports.createSessionContext = void 0;
6
+ exports.verifyLogInToken = exports.createLogInToken = exports.bridgeIn = exports.bridgeOut = exports.sendNativeTransfer = exports.sendTransfer = exports.getBridgeOutFee = exports.getTransferFee = exports.SessionResultType = exports.getDomainRecordAddress = exports.AuthorizedTokens = exports.AuthorizedProgramsType = exports.getSessionAccount = exports.reestablishSession = exports.revokeSession = exports.replaceSession = exports.establishSession = exports.createSessionContext = exports.TransactionResultType = exports.Network = exports.createSessionConnection = void 0;
7
7
  const anchor_1 = require("@coral-xyz/anchor");
8
8
  const sessions_idls_1 = require("@fogo/sessions-idls");
9
9
  const mpl_token_metadata_1 = require("@metaplex-foundation/mpl-token-metadata");
@@ -26,12 +26,13 @@ const zod_1 = require("zod");
26
26
  const connection_js_1 = require("./connection.js");
27
27
  const context_js_1 = require("./context.js");
28
28
  const crypto_js_1 = require("./crypto.js");
29
- var context_js_2 = require("./context.js");
30
- Object.defineProperty(exports, "createSessionContext", { enumerable: true, get: function () { return context_js_2.createSessionContext; } });
29
+ const instructions_js_1 = require("./instructions.js");
31
30
  var connection_js_2 = require("./connection.js");
31
+ Object.defineProperty(exports, "createSessionConnection", { enumerable: true, get: function () { return connection_js_2.createSessionConnection; } });
32
32
  Object.defineProperty(exports, "Network", { enumerable: true, get: function () { return connection_js_2.Network; } });
33
33
  Object.defineProperty(exports, "TransactionResultType", { enumerable: true, get: function () { return connection_js_2.TransactionResultType; } });
34
- Object.defineProperty(exports, "createSessionConnection", { enumerable: true, get: function () { return connection_js_2.createSessionConnection; } });
34
+ var context_js_2 = require("./context.js");
35
+ Object.defineProperty(exports, "createSessionContext", { enumerable: true, get: function () { return context_js_2.createSessionContext; } });
35
36
  const MESSAGE_HEADER = `Fogo Sessions:
36
37
  Signing this intent will allow this app to interact with your on-chain balances. Please make sure you trust this app and the domain in the message matches the domain of the current web application.
37
38
  `;
@@ -53,7 +54,7 @@ const establishSession = async (options) => {
53
54
  : await (0, kit_1.generateKeyPair)();
54
55
  if (options.unlimited) {
55
56
  return sendSessionEstablishTransaction(options, sessionKey, await Promise.all([
56
- buildIntentInstruction(options, sessionKey),
57
+ buildStartSessionIntentInstruction(options, sessionKey),
57
58
  buildStartSessionInstruction(options, sessionKey),
58
59
  ]), options.sessionEstablishmentLookupTable);
59
60
  }
@@ -63,7 +64,7 @@ const establishSession = async (options) => {
63
64
  ? await getTokenInfo(options.context, filteredLimits)
64
65
  : [];
65
66
  const [intentInstruction, startSessionInstruction] = await Promise.all([
66
- buildIntentInstruction(options, sessionKey, tokenInfo),
67
+ buildStartSessionIntentInstruction(options, sessionKey, tokenInfo),
67
68
  buildStartSessionInstruction(options, sessionKey, tokenInfo),
68
69
  ]);
69
70
  return sendSessionEstablishTransaction(options, sessionKey, [intentInstruction, startSessionInstruction], options.sessionEstablishmentLookupTable);
@@ -130,6 +131,11 @@ const createSession = async (context, walletPublicKey, sessionKey) => {
130
131
  walletPublicKey,
131
132
  sessionKey,
132
133
  payer: context.payer,
134
+ getSystemProgramSessionWrapInstruction: (amount) => (0, instructions_js_1.createSystemProgramSessionWrapInstruction)(sessionPublicKey, walletPublicKey, amount),
135
+ getSessionWrapInstructions: (amount) => (0, instructions_js_1.createSessionWrapInstructions)(sessionPublicKey, walletPublicKey, amount),
136
+ getSessionUnwrapInstructions: () => [
137
+ (0, instructions_js_1.createSessionUnwrapInstruction)(sessionPublicKey, walletPublicKey),
138
+ ],
133
139
  sendTransaction: (instructions, extraConfig) => context.sendTransaction(sessionKey, instructions, extraConfig),
134
140
  sessionInfo,
135
141
  };
@@ -354,7 +360,7 @@ const SymbolOrMint = {
354
360
  mint,
355
361
  }),
356
362
  };
357
- const getTokenInfo = async (context, limits) => {
363
+ const getTokenInfo = (context, limits) => {
358
364
  const umi = (0, umi_bundle_defaults_1.createUmi)(context.connection.rpcEndpoint);
359
365
  return Promise.all(limits.entries().map(async ([mint, amount]) => {
360
366
  const metaplexMint = (0, umi_1.publicKey)(mint.toBase58());
@@ -374,64 +380,23 @@ const getTokenInfo = async (context, limits) => {
374
380
  };
375
381
  }));
376
382
  };
377
- const serializeU16LE = (value) => {
378
- const result = new ArrayBuffer(2);
379
- new DataView(result).setUint16(0, value, true); // littleEndian = true
380
- return new Uint8Array(result);
381
- };
382
- // Some wallets add a prefix to the messag before signing, for example Ledger through Phantom
383
- const addOffchainMessagePrefixToMessageIfNeeded = async (walletPublicKey, signature, message) => {
384
- const publicKey = await crypto.subtle.importKey("raw", walletPublicKey.toBytes(), { name: "Ed25519" }, true, ["verify"]);
385
- if (await (0, kit_1.verifySignature)(publicKey, signature, message)) {
386
- return message;
387
- }
388
- else {
389
- // Source: https://github.com/anza-xyz/solana-sdk/blob/master/offchain-message/src/lib.rs#L162
390
- const messageWithOffchainMessagePrefix = Uint8Array.from([
391
- // eslint-disable-next-line unicorn/number-literal-case
392
- 0xff,
393
- ...new TextEncoder().encode("solana offchain"),
394
- 0,
395
- 1,
396
- ...serializeU16LE(message.length),
397
- ...message,
398
- ]);
399
- if (await (0, kit_1.verifySignature)(publicKey, signature, messageWithOffchainMessagePrefix)) {
400
- return messageWithOffchainMessagePrefix;
401
- }
402
- else {
403
- throw new Error("The signature provided by the browser wallet is not valid");
404
- }
405
- }
406
- };
407
- const buildIntentInstruction = async (options, sessionKey, tokens) => {
408
- const message = await buildMessage({
409
- chainId: options.context.chainId,
410
- domain: options.context.domain,
411
- sessionKey,
412
- expires: options.expires,
413
- tokens,
414
- extra: options.extra,
415
- });
416
- const intentSignature = (0, kit_1.signatureBytes)(await options.signMessage(message));
383
+ const buildStartSessionIntentInstruction = async (options, sessionKey, tokens) => buildIntentInstruction(options, MESSAGE_HEADER, {
384
+ version: `${CURRENT_MAJOR}.${CURRENT_MINOR}`,
385
+ chain_id: options.context.chainId,
386
+ domain: options.context.domain,
387
+ expires: options.expires.toISOString(),
388
+ session_key: await (0, kit_1.getAddressFromPublicKey)(sessionKey.publicKey),
389
+ tokens: serializeTokenList(tokens),
390
+ });
391
+ const buildIntentInstruction = async (options, header, body, extra) => {
392
+ const message = new TextEncoder().encode([header, serializeKV(body), extra && serializeExtra(extra)].join("\n"));
393
+ const { signature, signedMessage } = await options.signMessage(message);
417
394
  return web3_js_1.Ed25519Program.createInstructionWithPublicKey({
418
395
  publicKey: options.walletPublicKey.toBytes(),
419
- signature: intentSignature,
420
- message: await addOffchainMessagePrefixToMessageIfNeeded(options.walletPublicKey, intentSignature, message),
396
+ signature,
397
+ message: signedMessage,
421
398
  });
422
399
  };
423
- const buildMessage = async (body) => new TextEncoder().encode([
424
- MESSAGE_HEADER,
425
- serializeKV({
426
- version: `${CURRENT_MAJOR}.${CURRENT_MINOR}`,
427
- chain_id: body.chainId,
428
- domain: body.domain,
429
- expires: body.expires.toISOString(),
430
- session_key: await (0, kit_1.getAddressFromPublicKey)(body.sessionKey.publicKey),
431
- tokens: serializeTokenList(body.tokens),
432
- }),
433
- body.extra && serializeExtra(body.extra),
434
- ].join("\n"));
435
400
  const serializeExtra = (extra) => {
436
401
  for (const [key, value] of Object.entries(extra)) {
437
402
  if (!/^[a-z]+(_[a-z0-9]+)*$/.test(key)) {
@@ -618,24 +583,50 @@ const buildTransferIntentInstruction = async (program, options, symbol, feeToken
618
583
  getNonce(program, options.walletPublicKey, NonceType.Transfer),
619
584
  (0, spl_token_1.getMint)(options.context.connection, options.mint),
620
585
  ]);
621
- const message = new TextEncoder().encode([
622
- TRANSFER_MESSAGE_HEADER,
623
- serializeKV({
624
- version: `${CURRENT_INTENT_TRANSFER_MAJOR}.${CURRENT_INTENT_TRANSFER_MINOR}`,
625
- chain_id: options.context.chainId,
626
- token: symbol ?? options.mint.toBase58(),
627
- amount: amountToString(options.amount, decimals),
628
- recipient: options.recipient.toBase58(),
629
- fee_token: feeToken,
630
- fee_amount: feeAmount,
631
- nonce: nonce === null ? "1" : nonce.nonce.add(new bn_js_1.default(1)).toString(),
632
- }),
633
- ].join("\n"));
634
- const intentSignature = (0, kit_1.signatureBytes)(await options.signMessage(message));
635
- return web3_js_1.Ed25519Program.createInstructionWithPublicKey({
636
- publicKey: options.walletPublicKey.toBytes(),
637
- signature: intentSignature,
638
- message: await addOffchainMessagePrefixToMessageIfNeeded(options.walletPublicKey, intentSignature, message),
586
+ return buildIntentInstruction(options, TRANSFER_MESSAGE_HEADER, {
587
+ version: `${CURRENT_INTENT_TRANSFER_MAJOR}.${CURRENT_INTENT_TRANSFER_MINOR}`,
588
+ chain_id: options.context.chainId,
589
+ token: symbol ?? options.mint.toBase58(),
590
+ amount: amountToString(options.amount, decimals),
591
+ recipient: options.recipient.toBase58(),
592
+ fee_token: feeToken,
593
+ fee_amount: feeAmount,
594
+ nonce: nonce === null ? "1" : nonce.nonce.add(new bn_js_1.default(1)).toString(),
595
+ });
596
+ };
597
+ const sendNativeTransfer = async (options) => {
598
+ const program = new sessions_idls_1.IntentTransferProgram(new anchor_1.AnchorProvider(options.context.connection, {}, {}));
599
+ return options.context.sendTransaction(undefined, [
600
+ await buildNativeTransferIntentInstruction(program, options, options.feeConfig.symbolOrMint, amountToString(options.feeConfig.fee, options.feeConfig.decimals)),
601
+ await program.methods
602
+ .sendNative()
603
+ .accounts({
604
+ feeMetadata: options.feeConfig.metadata,
605
+ feeMint: options.feeConfig.mint,
606
+ feeSource: (0, spl_token_1.getAssociatedTokenAddressSync)(options.feeConfig.mint, options.walletPublicKey),
607
+ source: options.walletPublicKey,
608
+ destination: options.recipient,
609
+ sponsor: options.context.internalPayer,
610
+ })
611
+ .instruction(),
612
+ ], {
613
+ variation: "Intent Transfer",
614
+ paymasterDomain: context_js_1.SESSIONS_INTERNAL_PAYMASTER_DOMAIN,
615
+ });
616
+ };
617
+ exports.sendNativeTransfer = sendNativeTransfer;
618
+ const FOGO_DECIMALS = 9;
619
+ const buildNativeTransferIntentInstruction = async (program, options, feeToken, feeAmount) => {
620
+ const nonce = await getNonce(program, options.walletPublicKey, NonceType.Transfer);
621
+ return buildIntentInstruction(options, TRANSFER_MESSAGE_HEADER, {
622
+ version: `${CURRENT_INTENT_TRANSFER_MAJOR}.${CURRENT_INTENT_TRANSFER_MINOR}`,
623
+ chain_id: options.context.chainId,
624
+ token: "FOGO",
625
+ amount: amountToString(options.amount, FOGO_DECIMALS),
626
+ recipient: options.recipient.toBase58(),
627
+ fee_token: feeToken,
628
+ fee_amount: feeAmount,
629
+ nonce: nonce === null ? "1" : nonce.nonce.add(new bn_js_1.default(1)).toString(),
639
630
  });
640
631
  };
641
632
  const BRIDGE_OUT_MESSAGE_HEADER = `Fogo Bridge Transfer:
@@ -738,25 +729,16 @@ const getNttPdas = async (options, wh, program, outboxItemPublicKey, quotePayeeA
738
729
  };
739
730
  const buildBridgeOutIntent = async (program, options, decimals, symbol, feeToken, feeAmount) => {
740
731
  const nonce = await getNonce(program, options.walletPublicKey, NonceType.Bridge);
741
- const message = new TextEncoder().encode([
742
- BRIDGE_OUT_MESSAGE_HEADER,
743
- serializeKV({
744
- version: `${CURRENT_BRIDGE_OUT_MAJOR}.${CURRENT_BRIDGE_OUT_MINOR}`,
745
- from_chain_id: options.context.chainId,
746
- to_chain_id: "solana",
747
- token: symbol ?? options.fromToken.mint.toBase58(),
748
- amount: amountToString(options.amount, decimals),
749
- recipient_address: options.walletPublicKey.toBase58(),
750
- fee_token: feeToken,
751
- fee_amount: feeAmount,
752
- nonce: nonce === null ? "1" : nonce.nonce.add(new bn_js_1.default(1)).toString(),
753
- }),
754
- ].join("\n"));
755
- const intentSignature = (0, kit_1.signatureBytes)(await options.solanaWallet.signMessage(message));
756
- return web3_js_1.Ed25519Program.createInstructionWithPublicKey({
757
- publicKey: options.walletPublicKey.toBytes(),
758
- signature: intentSignature,
759
- message: await addOffchainMessagePrefixToMessageIfNeeded(options.walletPublicKey, intentSignature, message),
732
+ return buildIntentInstruction(options, BRIDGE_OUT_MESSAGE_HEADER, {
733
+ version: `${CURRENT_BRIDGE_OUT_MAJOR}.${CURRENT_BRIDGE_OUT_MINOR}`,
734
+ from_chain_id: options.context.chainId,
735
+ to_chain_id: "solana",
736
+ token: symbol ?? options.fromToken.mint.toBase58(),
737
+ amount: amountToString(options.amount, decimals),
738
+ recipient_address: options.walletPublicKey.toBase58(),
739
+ fee_token: feeToken,
740
+ fee_amount: feeAmount,
741
+ nonce: nonce === null ? "1" : nonce.nonce.add(new bn_js_1.default(1)).toString(),
760
742
  });
761
743
  };
762
744
  const bridgeIn = async (options) => {
@@ -770,10 +752,9 @@ const bridgeIn = async (options) => {
770
752
  address: () => options.walletPublicKey.toBase58(),
771
753
  chain: () => "Solana",
772
754
  sign: (transactions) => Promise.all(transactions.map(async ({ transaction }) => {
773
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
774
- const signedTx = await options.solanaWallet.signTransaction(
755
+ const signedTx = await options.signTransaction(
775
756
  // Hooray for Wormhole's incomplete typing eh?
776
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
757
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
777
758
  transaction.transaction);
778
759
  // Hooray for Wormhole's incomplete typing eh?
779
760
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
@@ -0,0 +1,18 @@
1
+ import { type PublicKey, TransactionInstruction } from "@solana/web3.js";
2
+ /**
3
+ * Creates the system program instruction `SessionWrap`, only available on Fogo, which allows a session key to transfer native token from its user's wallet to its user's wrapped token associated token account.
4
+ * This instruction may be combined with the `CreateAssociatedTokenAccountIdempotent` and `SyncNative` instructions for a session to wrap tokens on behalf of its user.
5
+ */
6
+ export declare function createSystemProgramSessionWrapInstruction(sessionKey: PublicKey, walletPublicKey: PublicKey, amount: bigint): TransactionInstruction;
7
+ /**
8
+ * Creates the sequence of instructions required to wrap native tokens within a session.
9
+ *
10
+ * Note: This function sets the session key as the payer for the `CreateAssociatedTokenAccountIdempotent` instruction, which is unconventional since the session key can't spend funds.
11
+ * It works because at the time `CreateAssociatedTokenAccountIdempotent` is called, the `userTokenAccount` has already been funded by the `SessionWrap` instruction.
12
+ * The paymaster will reject the transaction if the payer of the `CreateAssociatedTokenAccountIdempotent` is set to the paymaster payer to avoid the paymaster's funds getting drained.
13
+ */
14
+ export declare function createSessionWrapInstructions(sessionKey: PublicKey, walletPublicKey: PublicKey, amount: bigint): TransactionInstruction[];
15
+ /**
16
+ * Creates the instruction required to unwrap native tokens within a session.
17
+ */
18
+ export declare function createSessionUnwrapInstruction(sessionKey: PublicKey, walletPublicKey: PublicKey): TransactionInstruction;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSystemProgramSessionWrapInstruction = createSystemProgramSessionWrapInstruction;
4
+ exports.createSessionWrapInstructions = createSessionWrapInstructions;
5
+ exports.createSessionUnwrapInstruction = createSessionUnwrapInstruction;
6
+ const spl_token_1 = require("@solana/spl-token");
7
+ const web3_js_1 = require("@solana/web3.js");
8
+ const SESSION_WRAP_DISCRIMINATOR = 4_000_000;
9
+ function getNativeMintAssociatedTokenAddressSync(walletPublicKey) {
10
+ return (0, spl_token_1.getAssociatedTokenAddressSync)(spl_token_1.NATIVE_MINT, walletPublicKey);
11
+ }
12
+ /**
13
+ * Creates the system program instruction `SessionWrap`, only available on Fogo, which allows a session key to transfer native token from its user's wallet to its user's wrapped token associated token account.
14
+ * This instruction may be combined with the `CreateAssociatedTokenAccountIdempotent` and `SyncNative` instructions for a session to wrap tokens on behalf of its user.
15
+ */
16
+ function createSystemProgramSessionWrapInstruction(sessionKey, walletPublicKey, amount) {
17
+ const data = new Uint8Array(12);
18
+ const view = new DataView(data.buffer);
19
+ view.setUint32(0, SESSION_WRAP_DISCRIMINATOR, true);
20
+ view.setBigUint64(4, amount, true);
21
+ return new web3_js_1.TransactionInstruction({
22
+ programId: web3_js_1.SystemProgram.programId,
23
+ keys: [
24
+ { pubkey: walletPublicKey, isSigner: false, isWritable: true },
25
+ {
26
+ pubkey: getNativeMintAssociatedTokenAddressSync(walletPublicKey),
27
+ isSigner: false,
28
+ isWritable: true,
29
+ },
30
+ { pubkey: sessionKey, isSigner: true, isWritable: false },
31
+ ],
32
+ data: Buffer.from(data),
33
+ });
34
+ }
35
+ /**
36
+ * Creates the sequence of instructions required to wrap native tokens within a session.
37
+ *
38
+ * Note: This function sets the session key as the payer for the `CreateAssociatedTokenAccountIdempotent` instruction, which is unconventional since the session key can't spend funds.
39
+ * It works because at the time `CreateAssociatedTokenAccountIdempotent` is called, the `userTokenAccount` has already been funded by the `SessionWrap` instruction.
40
+ * The paymaster will reject the transaction if the payer of the `CreateAssociatedTokenAccountIdempotent` is set to the paymaster payer to avoid the paymaster's funds getting drained.
41
+ */
42
+ function createSessionWrapInstructions(sessionKey, walletPublicKey, amount) {
43
+ const userTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(spl_token_1.NATIVE_MINT, walletPublicKey);
44
+ return [
45
+ createSystemProgramSessionWrapInstruction(sessionKey, walletPublicKey, amount),
46
+ (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(sessionKey, // This is unconventional! Read the note in the function's docs.
47
+ userTokenAccount, walletPublicKey, spl_token_1.NATIVE_MINT),
48
+ (0, spl_token_1.createSyncNativeInstruction)(userTokenAccount),
49
+ ];
50
+ }
51
+ /**
52
+ * Creates the instruction required to unwrap native tokens within a session.
53
+ */
54
+ function createSessionUnwrapInstruction(sessionKey, walletPublicKey) {
55
+ return (0, spl_token_1.createCloseAccountInstruction)(getNativeMintAssociatedTokenAddressSync(walletPublicKey), walletPublicKey, sessionKey);
56
+ }
@@ -1,6 +1,6 @@
1
- import type { Transaction, Instruction, TransactionWithLifetime, Rpc, GetLatestBlockhashApi } from "@solana/kit";
1
+ import type { GetLatestBlockhashApi, Instruction, Rpc, Transaction, TransactionWithLifetime } from "@solana/kit";
2
2
  import type { TransactionError } from "@solana/web3.js";
3
- import { Keypair, Connection as Web3Connection, TransactionInstruction, VersionedTransaction, PublicKey } from "@solana/web3.js";
3
+ import { Keypair, PublicKey, TransactionInstruction, VersionedTransaction, Connection as Web3Connection } from "@solana/web3.js";
4
4
  export declare enum Network {
5
5
  Testnet = 0,
6
6
  Mainnet = 1
package/esm/connection.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { fromLegacyKeypair, fromLegacyPublicKey, fromLegacyTransactionInstruction, fromVersionedTransaction, } from "@solana/compat";
2
- import { createSolanaRpc, getBase64EncodedWireTransaction, createTransactionMessage, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, partiallySignTransactionMessageWithSigners, pipe, addSignersToTransactionMessage, compressTransactionMessageUsingAddressLookupTables, createSignerFromKeyPair, partiallySignTransaction, } from "@solana/kit";
3
- import { Keypair, Connection as Web3Connection, TransactionInstruction, VersionedTransaction, PublicKey, } from "@solana/web3.js";
2
+ import { addSignersToTransactionMessage, appendTransactionMessageInstructions, compressTransactionMessageUsingAddressLookupTables, createSignerFromKeyPair, createSolanaRpc, createTransactionMessage, getBase64EncodedWireTransaction, partiallySignTransaction, partiallySignTransactionMessageWithSigners, pipe, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, } from "@solana/kit";
3
+ import { Keypair, PublicKey, TransactionInstruction, VersionedTransaction, Connection as Web3Connection, } from "@solana/web3.js";
4
4
  import { z } from "zod";
5
5
  export var Network;
6
6
  (function (Network) {
package/esm/context.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AnchorProvider } from "@coral-xyz/anchor";
2
2
  import { ChainIdProgram } from "@fogo/sessions-idls";
3
- import { Connection as Web3Connection, Keypair } from "@solana/web3.js";
3
+ import { Keypair, Connection as Web3Connection } from "@solana/web3.js";
4
4
  // eslint-disable-next-line unicorn/no-typeof-undefined
5
5
  const IS_BROWSER = typeof globalThis.window !== "undefined";
6
6
  export const SESSIONS_INTERNAL_PAYMASTER_DOMAIN = "sessions";
package/esm/index.d.ts CHANGED
@@ -1,17 +1,19 @@
1
- import type { BaseSignerWalletAdapter, MessageSignerWalletAdapterProps } from "@solana/wallet-adapter-base";
2
- import type { TransactionError } from "@solana/web3.js";
1
+ import type { TransactionError, TransactionInstruction, VersionedTransaction } from "@solana/web3.js";
3
2
  import { Connection, PublicKey } from "@solana/web3.js";
4
3
  import type { Chain } from "@wormhole-foundation/sdk";
5
4
  import BN from "bn.js";
6
5
  import { z } from "zod";
7
6
  import type { TransactionOrInstructions, TransactionResult } from "./connection.js";
8
7
  import type { SendTransactionOptions, SessionContext } from "./context.js";
9
- export { type SessionContext, type SendTransactionOptions, createSessionContext, } from "./context.js";
10
- export { type TransactionResult, type Connection, type TransactionOrInstructions, Network, TransactionResultType, createSessionConnection, } from "./connection.js";
8
+ export { type Connection, createSessionConnection, Network, type TransactionOrInstructions, type TransactionResult, TransactionResultType, } from "./connection.js";
9
+ export { createSessionContext, type SendTransactionOptions, type SessionContext, } from "./context.js";
11
10
  type EstablishSessionOptions = {
12
11
  context: SessionContext;
13
12
  walletPublicKey: PublicKey;
14
- signMessage: (message: Uint8Array) => Promise<Uint8Array>;
13
+ signMessage: (message: Uint8Array) => Promise<{
14
+ signedMessage: Uint8Array;
15
+ signature: Uint8Array;
16
+ }>;
15
17
  expires: Date;
16
18
  extra?: Record<string, string> | undefined;
17
19
  createUnsafeExtractableSessionKey?: boolean | undefined;
@@ -26,7 +28,10 @@ export declare const establishSession: (options: EstablishSessionOptions) => Pro
26
28
  export declare const replaceSession: (options: {
27
29
  context: SessionContext;
28
30
  session: Session;
29
- signMessage: (message: Uint8Array) => Promise<Uint8Array>;
31
+ signMessage: (message: Uint8Array) => Promise<{
32
+ signedMessage: Uint8Array;
33
+ signature: Uint8Array;
34
+ }>;
30
35
  expires: Date;
31
36
  extra?: Record<string, string> | undefined;
32
37
  } & ({
@@ -2223,6 +2228,9 @@ export type Session = {
2223
2228
  sessionKey: CryptoKeyPair;
2224
2229
  walletPublicKey: PublicKey;
2225
2230
  payer: PublicKey;
2231
+ getSystemProgramSessionWrapInstruction: (amount: bigint) => TransactionInstruction;
2232
+ getSessionWrapInstructions: (amount: bigint) => TransactionInstruction[];
2233
+ getSessionUnwrapInstructions: () => TransactionInstruction[];
2226
2234
  sendTransaction: (instructions: TransactionOrInstructions, extraConfig?: SendTransactionOptions) => Promise<TransactionResult>;
2227
2235
  sessionInfo: NonNullable<z.infer<typeof sessionInfoSchema>>;
2228
2236
  };
@@ -2243,19 +2251,37 @@ export declare const getBridgeOutFee: (context: SessionContext) => Promise<{
2243
2251
  type SendTransferOptions = {
2244
2252
  context: SessionContext;
2245
2253
  walletPublicKey: PublicKey;
2246
- signMessage: (message: Uint8Array) => Promise<Uint8Array>;
2254
+ signMessage: (message: Uint8Array) => Promise<{
2255
+ signedMessage: Uint8Array;
2256
+ signature: Uint8Array;
2257
+ }>;
2247
2258
  mint: PublicKey;
2248
2259
  amount: bigint;
2249
2260
  recipient: PublicKey;
2250
2261
  feeConfig: Awaited<ReturnType<typeof getTransferFee>>;
2251
2262
  };
2252
2263
  export declare const sendTransfer: (options: SendTransferOptions) => Promise<TransactionResult>;
2264
+ type SendNativeTransferOptions = {
2265
+ context: SessionContext;
2266
+ walletPublicKey: PublicKey;
2267
+ signMessage: (message: Uint8Array) => Promise<{
2268
+ signedMessage: Uint8Array;
2269
+ signature: Uint8Array;
2270
+ }>;
2271
+ amount: bigint;
2272
+ recipient: PublicKey;
2273
+ feeConfig: Awaited<ReturnType<typeof getTransferFee>>;
2274
+ };
2275
+ export declare const sendNativeTransfer: (options: SendNativeTransferOptions) => Promise<TransactionResult>;
2253
2276
  type SendBridgeOutOptions = {
2254
2277
  context: SessionContext;
2255
2278
  sessionKey: CryptoKeyPair;
2256
2279
  sessionPublicKey: PublicKey;
2257
2280
  walletPublicKey: PublicKey;
2258
- solanaWallet: MessageSignerWalletAdapterProps;
2281
+ signMessage: (message: Uint8Array) => Promise<{
2282
+ signedMessage: Uint8Array;
2283
+ signature: Uint8Array;
2284
+ }>;
2259
2285
  amount: bigint;
2260
2286
  fromToken: WormholeToken & {
2261
2287
  chain: "Fogo";
@@ -2275,7 +2301,7 @@ export declare const bridgeOut: (options: SendBridgeOutOptions) => Promise<Trans
2275
2301
  type SendBridgeInOptions = {
2276
2302
  context: SessionContext;
2277
2303
  walletPublicKey: PublicKey;
2278
- solanaWallet: BaseSignerWalletAdapter;
2304
+ signTransaction: (transaction: VersionedTransaction) => Promise<VersionedTransaction>;
2279
2305
  amount: bigint;
2280
2306
  fromToken: WormholeToken & {
2281
2307
  chain: "Solana";
package/esm/index.js CHANGED
@@ -5,10 +5,10 @@ import { publicKey as metaplexPublicKey } from "@metaplex-foundation/umi";
5
5
  import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
6
6
  import { sha256 } from "@noble/hashes/sha2";
7
7
  import { fromLegacyPublicKey } from "@solana/compat";
8
- import { generateKeyPair, getAddressFromPublicKey, getProgramDerivedAddress, signatureBytes, verifySignature, } from "@solana/kit";
8
+ import { generateKeyPair, getAddressFromPublicKey, getProgramDerivedAddress, } from "@solana/kit";
9
9
  import { getAssociatedTokenAddressSync, getMint } from "@solana/spl-token";
10
10
  import { ComputeBudgetProgram, Connection, Ed25519Program, Keypair, PublicKey, } from "@solana/web3.js";
11
- import { Wormhole, wormhole, routes } from "@wormhole-foundation/sdk";
11
+ import { routes, Wormhole, wormhole } from "@wormhole-foundation/sdk";
12
12
  import solanaSdk from "@wormhole-foundation/sdk/solana";
13
13
  import { contracts } from "@wormhole-foundation/sdk-base";
14
14
  import { nttExecutorRoute } from "@wormhole-foundation/sdk-route-ntt";
@@ -20,8 +20,9 @@ import { z } from "zod";
20
20
  import { Network, TransactionResultType } from "./connection.js";
21
21
  import { SESSIONS_INTERNAL_PAYMASTER_DOMAIN } from "./context.js";
22
22
  import { importKey, signMessageWithKey, verifyMessageWithKey, } from "./crypto.js";
23
+ import { createSessionUnwrapInstruction, createSessionWrapInstructions, createSystemProgramSessionWrapInstruction, } from "./instructions.js";
24
+ export { createSessionConnection, Network, TransactionResultType, } from "./connection.js";
23
25
  export { createSessionContext, } from "./context.js";
24
- export { Network, TransactionResultType, createSessionConnection, } from "./connection.js";
25
26
  const MESSAGE_HEADER = `Fogo Sessions:
26
27
  Signing this intent will allow this app to interact with your on-chain balances. Please make sure you trust this app and the domain in the message matches the domain of the current web application.
27
28
  `;
@@ -43,7 +44,7 @@ export const establishSession = async (options) => {
43
44
  : await generateKeyPair();
44
45
  if (options.unlimited) {
45
46
  return sendSessionEstablishTransaction(options, sessionKey, await Promise.all([
46
- buildIntentInstruction(options, sessionKey),
47
+ buildStartSessionIntentInstruction(options, sessionKey),
47
48
  buildStartSessionInstruction(options, sessionKey),
48
49
  ]), options.sessionEstablishmentLookupTable);
49
50
  }
@@ -53,7 +54,7 @@ export const establishSession = async (options) => {
53
54
  ? await getTokenInfo(options.context, filteredLimits)
54
55
  : [];
55
56
  const [intentInstruction, startSessionInstruction] = await Promise.all([
56
- buildIntentInstruction(options, sessionKey, tokenInfo),
57
+ buildStartSessionIntentInstruction(options, sessionKey, tokenInfo),
57
58
  buildStartSessionInstruction(options, sessionKey, tokenInfo),
58
59
  ]);
59
60
  return sendSessionEstablishTransaction(options, sessionKey, [intentInstruction, startSessionInstruction], options.sessionEstablishmentLookupTable);
@@ -115,6 +116,11 @@ const createSession = async (context, walletPublicKey, sessionKey) => {
115
116
  walletPublicKey,
116
117
  sessionKey,
117
118
  payer: context.payer,
119
+ getSystemProgramSessionWrapInstruction: (amount) => createSystemProgramSessionWrapInstruction(sessionPublicKey, walletPublicKey, amount),
120
+ getSessionWrapInstructions: (amount) => createSessionWrapInstructions(sessionPublicKey, walletPublicKey, amount),
121
+ getSessionUnwrapInstructions: () => [
122
+ createSessionUnwrapInstruction(sessionPublicKey, walletPublicKey),
123
+ ],
118
124
  sendTransaction: (instructions, extraConfig) => context.sendTransaction(sessionKey, instructions, extraConfig),
119
125
  sessionInfo,
120
126
  };
@@ -339,7 +345,7 @@ const SymbolOrMint = {
339
345
  mint,
340
346
  }),
341
347
  };
342
- const getTokenInfo = async (context, limits) => {
348
+ const getTokenInfo = (context, limits) => {
343
349
  const umi = createUmi(context.connection.rpcEndpoint);
344
350
  return Promise.all(limits.entries().map(async ([mint, amount]) => {
345
351
  const metaplexMint = metaplexPublicKey(mint.toBase58());
@@ -359,64 +365,23 @@ const getTokenInfo = async (context, limits) => {
359
365
  };
360
366
  }));
361
367
  };
362
- const serializeU16LE = (value) => {
363
- const result = new ArrayBuffer(2);
364
- new DataView(result).setUint16(0, value, true); // littleEndian = true
365
- return new Uint8Array(result);
366
- };
367
- // Some wallets add a prefix to the messag before signing, for example Ledger through Phantom
368
- const addOffchainMessagePrefixToMessageIfNeeded = async (walletPublicKey, signature, message) => {
369
- const publicKey = await crypto.subtle.importKey("raw", walletPublicKey.toBytes(), { name: "Ed25519" }, true, ["verify"]);
370
- if (await verifySignature(publicKey, signature, message)) {
371
- return message;
372
- }
373
- else {
374
- // Source: https://github.com/anza-xyz/solana-sdk/blob/master/offchain-message/src/lib.rs#L162
375
- const messageWithOffchainMessagePrefix = Uint8Array.from([
376
- // eslint-disable-next-line unicorn/number-literal-case
377
- 0xff,
378
- ...new TextEncoder().encode("solana offchain"),
379
- 0,
380
- 1,
381
- ...serializeU16LE(message.length),
382
- ...message,
383
- ]);
384
- if (await verifySignature(publicKey, signature, messageWithOffchainMessagePrefix)) {
385
- return messageWithOffchainMessagePrefix;
386
- }
387
- else {
388
- throw new Error("The signature provided by the browser wallet is not valid");
389
- }
390
- }
391
- };
392
- const buildIntentInstruction = async (options, sessionKey, tokens) => {
393
- const message = await buildMessage({
394
- chainId: options.context.chainId,
395
- domain: options.context.domain,
396
- sessionKey,
397
- expires: options.expires,
398
- tokens,
399
- extra: options.extra,
400
- });
401
- const intentSignature = signatureBytes(await options.signMessage(message));
368
+ const buildStartSessionIntentInstruction = async (options, sessionKey, tokens) => buildIntentInstruction(options, MESSAGE_HEADER, {
369
+ version: `${CURRENT_MAJOR}.${CURRENT_MINOR}`,
370
+ chain_id: options.context.chainId,
371
+ domain: options.context.domain,
372
+ expires: options.expires.toISOString(),
373
+ session_key: await getAddressFromPublicKey(sessionKey.publicKey),
374
+ tokens: serializeTokenList(tokens),
375
+ });
376
+ const buildIntentInstruction = async (options, header, body, extra) => {
377
+ const message = new TextEncoder().encode([header, serializeKV(body), extra && serializeExtra(extra)].join("\n"));
378
+ const { signature, signedMessage } = await options.signMessage(message);
402
379
  return Ed25519Program.createInstructionWithPublicKey({
403
380
  publicKey: options.walletPublicKey.toBytes(),
404
- signature: intentSignature,
405
- message: await addOffchainMessagePrefixToMessageIfNeeded(options.walletPublicKey, intentSignature, message),
381
+ signature,
382
+ message: signedMessage,
406
383
  });
407
384
  };
408
- const buildMessage = async (body) => new TextEncoder().encode([
409
- MESSAGE_HEADER,
410
- serializeKV({
411
- version: `${CURRENT_MAJOR}.${CURRENT_MINOR}`,
412
- chain_id: body.chainId,
413
- domain: body.domain,
414
- expires: body.expires.toISOString(),
415
- session_key: await getAddressFromPublicKey(body.sessionKey.publicKey),
416
- tokens: serializeTokenList(body.tokens),
417
- }),
418
- body.extra && serializeExtra(body.extra),
419
- ].join("\n"));
420
385
  const serializeExtra = (extra) => {
421
386
  for (const [key, value] of Object.entries(extra)) {
422
387
  if (!/^[a-z]+(_[a-z0-9]+)*$/.test(key)) {
@@ -599,24 +564,49 @@ const buildTransferIntentInstruction = async (program, options, symbol, feeToken
599
564
  getNonce(program, options.walletPublicKey, NonceType.Transfer),
600
565
  getMint(options.context.connection, options.mint),
601
566
  ]);
602
- const message = new TextEncoder().encode([
603
- TRANSFER_MESSAGE_HEADER,
604
- serializeKV({
605
- version: `${CURRENT_INTENT_TRANSFER_MAJOR}.${CURRENT_INTENT_TRANSFER_MINOR}`,
606
- chain_id: options.context.chainId,
607
- token: symbol ?? options.mint.toBase58(),
608
- amount: amountToString(options.amount, decimals),
609
- recipient: options.recipient.toBase58(),
610
- fee_token: feeToken,
611
- fee_amount: feeAmount,
612
- nonce: nonce === null ? "1" : nonce.nonce.add(new BN(1)).toString(),
613
- }),
614
- ].join("\n"));
615
- const intentSignature = signatureBytes(await options.signMessage(message));
616
- return Ed25519Program.createInstructionWithPublicKey({
617
- publicKey: options.walletPublicKey.toBytes(),
618
- signature: intentSignature,
619
- message: await addOffchainMessagePrefixToMessageIfNeeded(options.walletPublicKey, intentSignature, message),
567
+ return buildIntentInstruction(options, TRANSFER_MESSAGE_HEADER, {
568
+ version: `${CURRENT_INTENT_TRANSFER_MAJOR}.${CURRENT_INTENT_TRANSFER_MINOR}`,
569
+ chain_id: options.context.chainId,
570
+ token: symbol ?? options.mint.toBase58(),
571
+ amount: amountToString(options.amount, decimals),
572
+ recipient: options.recipient.toBase58(),
573
+ fee_token: feeToken,
574
+ fee_amount: feeAmount,
575
+ nonce: nonce === null ? "1" : nonce.nonce.add(new BN(1)).toString(),
576
+ });
577
+ };
578
+ export const sendNativeTransfer = async (options) => {
579
+ const program = new IntentTransferProgram(new AnchorProvider(options.context.connection, {}, {}));
580
+ return options.context.sendTransaction(undefined, [
581
+ await buildNativeTransferIntentInstruction(program, options, options.feeConfig.symbolOrMint, amountToString(options.feeConfig.fee, options.feeConfig.decimals)),
582
+ await program.methods
583
+ .sendNative()
584
+ .accounts({
585
+ feeMetadata: options.feeConfig.metadata,
586
+ feeMint: options.feeConfig.mint,
587
+ feeSource: getAssociatedTokenAddressSync(options.feeConfig.mint, options.walletPublicKey),
588
+ source: options.walletPublicKey,
589
+ destination: options.recipient,
590
+ sponsor: options.context.internalPayer,
591
+ })
592
+ .instruction(),
593
+ ], {
594
+ variation: "Intent Transfer",
595
+ paymasterDomain: SESSIONS_INTERNAL_PAYMASTER_DOMAIN,
596
+ });
597
+ };
598
+ const FOGO_DECIMALS = 9;
599
+ const buildNativeTransferIntentInstruction = async (program, options, feeToken, feeAmount) => {
600
+ const nonce = await getNonce(program, options.walletPublicKey, NonceType.Transfer);
601
+ return buildIntentInstruction(options, TRANSFER_MESSAGE_HEADER, {
602
+ version: `${CURRENT_INTENT_TRANSFER_MAJOR}.${CURRENT_INTENT_TRANSFER_MINOR}`,
603
+ chain_id: options.context.chainId,
604
+ token: "FOGO",
605
+ amount: amountToString(options.amount, FOGO_DECIMALS),
606
+ recipient: options.recipient.toBase58(),
607
+ fee_token: feeToken,
608
+ fee_amount: feeAmount,
609
+ nonce: nonce === null ? "1" : nonce.nonce.add(new BN(1)).toString(),
620
610
  });
621
611
  };
622
612
  const BRIDGE_OUT_MESSAGE_HEADER = `Fogo Bridge Transfer:
@@ -718,25 +708,16 @@ const getNttPdas = async (options, wh, program, outboxItemPublicKey, quotePayeeA
718
708
  };
719
709
  const buildBridgeOutIntent = async (program, options, decimals, symbol, feeToken, feeAmount) => {
720
710
  const nonce = await getNonce(program, options.walletPublicKey, NonceType.Bridge);
721
- const message = new TextEncoder().encode([
722
- BRIDGE_OUT_MESSAGE_HEADER,
723
- serializeKV({
724
- version: `${CURRENT_BRIDGE_OUT_MAJOR}.${CURRENT_BRIDGE_OUT_MINOR}`,
725
- from_chain_id: options.context.chainId,
726
- to_chain_id: "solana",
727
- token: symbol ?? options.fromToken.mint.toBase58(),
728
- amount: amountToString(options.amount, decimals),
729
- recipient_address: options.walletPublicKey.toBase58(),
730
- fee_token: feeToken,
731
- fee_amount: feeAmount,
732
- nonce: nonce === null ? "1" : nonce.nonce.add(new BN(1)).toString(),
733
- }),
734
- ].join("\n"));
735
- const intentSignature = signatureBytes(await options.solanaWallet.signMessage(message));
736
- return Ed25519Program.createInstructionWithPublicKey({
737
- publicKey: options.walletPublicKey.toBytes(),
738
- signature: intentSignature,
739
- message: await addOffchainMessagePrefixToMessageIfNeeded(options.walletPublicKey, intentSignature, message),
711
+ return buildIntentInstruction(options, BRIDGE_OUT_MESSAGE_HEADER, {
712
+ version: `${CURRENT_BRIDGE_OUT_MAJOR}.${CURRENT_BRIDGE_OUT_MINOR}`,
713
+ from_chain_id: options.context.chainId,
714
+ to_chain_id: "solana",
715
+ token: symbol ?? options.fromToken.mint.toBase58(),
716
+ amount: amountToString(options.amount, decimals),
717
+ recipient_address: options.walletPublicKey.toBase58(),
718
+ fee_token: feeToken,
719
+ fee_amount: feeAmount,
720
+ nonce: nonce === null ? "1" : nonce.nonce.add(new BN(1)).toString(),
740
721
  });
741
722
  };
742
723
  export const bridgeIn = async (options) => {
@@ -750,10 +731,9 @@ export const bridgeIn = async (options) => {
750
731
  address: () => options.walletPublicKey.toBase58(),
751
732
  chain: () => "Solana",
752
733
  sign: (transactions) => Promise.all(transactions.map(async ({ transaction }) => {
753
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
754
- const signedTx = await options.solanaWallet.signTransaction(
734
+ const signedTx = await options.signTransaction(
755
735
  // Hooray for Wormhole's incomplete typing eh?
756
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
736
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
757
737
  transaction.transaction);
758
738
  // Hooray for Wormhole's incomplete typing eh?
759
739
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
@@ -0,0 +1,18 @@
1
+ import { type PublicKey, TransactionInstruction } from "@solana/web3.js";
2
+ /**
3
+ * Creates the system program instruction `SessionWrap`, only available on Fogo, which allows a session key to transfer native token from its user's wallet to its user's wrapped token associated token account.
4
+ * This instruction may be combined with the `CreateAssociatedTokenAccountIdempotent` and `SyncNative` instructions for a session to wrap tokens on behalf of its user.
5
+ */
6
+ export declare function createSystemProgramSessionWrapInstruction(sessionKey: PublicKey, walletPublicKey: PublicKey, amount: bigint): TransactionInstruction;
7
+ /**
8
+ * Creates the sequence of instructions required to wrap native tokens within a session.
9
+ *
10
+ * Note: This function sets the session key as the payer for the `CreateAssociatedTokenAccountIdempotent` instruction, which is unconventional since the session key can't spend funds.
11
+ * It works because at the time `CreateAssociatedTokenAccountIdempotent` is called, the `userTokenAccount` has already been funded by the `SessionWrap` instruction.
12
+ * The paymaster will reject the transaction if the payer of the `CreateAssociatedTokenAccountIdempotent` is set to the paymaster payer to avoid the paymaster's funds getting drained.
13
+ */
14
+ export declare function createSessionWrapInstructions(sessionKey: PublicKey, walletPublicKey: PublicKey, amount: bigint): TransactionInstruction[];
15
+ /**
16
+ * Creates the instruction required to unwrap native tokens within a session.
17
+ */
18
+ export declare function createSessionUnwrapInstruction(sessionKey: PublicKey, walletPublicKey: PublicKey): TransactionInstruction;
@@ -0,0 +1,51 @@
1
+ import { createAssociatedTokenAccountIdempotentInstruction, createCloseAccountInstruction, createSyncNativeInstruction, getAssociatedTokenAddressSync, NATIVE_MINT, } from "@solana/spl-token";
2
+ import { SystemProgram, TransactionInstruction, } from "@solana/web3.js";
3
+ const SESSION_WRAP_DISCRIMINATOR = 4_000_000;
4
+ function getNativeMintAssociatedTokenAddressSync(walletPublicKey) {
5
+ return getAssociatedTokenAddressSync(NATIVE_MINT, walletPublicKey);
6
+ }
7
+ /**
8
+ * Creates the system program instruction `SessionWrap`, only available on Fogo, which allows a session key to transfer native token from its user's wallet to its user's wrapped token associated token account.
9
+ * This instruction may be combined with the `CreateAssociatedTokenAccountIdempotent` and `SyncNative` instructions for a session to wrap tokens on behalf of its user.
10
+ */
11
+ export function createSystemProgramSessionWrapInstruction(sessionKey, walletPublicKey, amount) {
12
+ const data = new Uint8Array(12);
13
+ const view = new DataView(data.buffer);
14
+ view.setUint32(0, SESSION_WRAP_DISCRIMINATOR, true);
15
+ view.setBigUint64(4, amount, true);
16
+ return new TransactionInstruction({
17
+ programId: SystemProgram.programId,
18
+ keys: [
19
+ { pubkey: walletPublicKey, isSigner: false, isWritable: true },
20
+ {
21
+ pubkey: getNativeMintAssociatedTokenAddressSync(walletPublicKey),
22
+ isSigner: false,
23
+ isWritable: true,
24
+ },
25
+ { pubkey: sessionKey, isSigner: true, isWritable: false },
26
+ ],
27
+ data: Buffer.from(data),
28
+ });
29
+ }
30
+ /**
31
+ * Creates the sequence of instructions required to wrap native tokens within a session.
32
+ *
33
+ * Note: This function sets the session key as the payer for the `CreateAssociatedTokenAccountIdempotent` instruction, which is unconventional since the session key can't spend funds.
34
+ * It works because at the time `CreateAssociatedTokenAccountIdempotent` is called, the `userTokenAccount` has already been funded by the `SessionWrap` instruction.
35
+ * The paymaster will reject the transaction if the payer of the `CreateAssociatedTokenAccountIdempotent` is set to the paymaster payer to avoid the paymaster's funds getting drained.
36
+ */
37
+ export function createSessionWrapInstructions(sessionKey, walletPublicKey, amount) {
38
+ const userTokenAccount = getAssociatedTokenAddressSync(NATIVE_MINT, walletPublicKey);
39
+ return [
40
+ createSystemProgramSessionWrapInstruction(sessionKey, walletPublicKey, amount),
41
+ createAssociatedTokenAccountIdempotentInstruction(sessionKey, // This is unconventional! Read the note in the function's docs.
42
+ userTokenAccount, walletPublicKey, NATIVE_MINT),
43
+ createSyncNativeInstruction(userTokenAccount),
44
+ ];
45
+ }
46
+ /**
47
+ * Creates the instruction required to unwrap native tokens within a session.
48
+ */
49
+ export function createSessionUnwrapInstruction(sessionKey, walletPublicKey) {
50
+ return createCloseAccountInstruction(getNativeMintAssociatedTokenAddressSync(walletPublicKey), walletPublicKey, sessionKey);
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fogo/sessions-sdk",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "description": "A set of utilities for integrating with Fogo sessions",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,7 +17,7 @@
17
17
  "main": "./cjs/index.js",
18
18
  "types": "./cjs/index.d.ts",
19
19
  "engines": {
20
- "node": ">=18"
20
+ "node": ">=22"
21
21
  },
22
22
  "exports": {
23
23
  ".": {
@@ -48,7 +48,7 @@
48
48
  "@wormhole-foundation/sdk-solana-ntt": "^4.0.1",
49
49
  "bn.js": "^5.1.2",
50
50
  "bs58": "^6.0.0",
51
- "zod": "^3.25.62",
52
- "@fogo/sessions-idls": "^0.0.12"
51
+ "zod": "3.25.67",
52
+ "@fogo/sessions-idls": "^0.0.13"
53
53
  }
54
54
  }