@fogo/sessions-sdk 0.0.18 → 0.0.19

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/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.sendTransfer = 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.sendTransfer = 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;
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");
@@ -14,6 +14,12 @@ const compat_1 = require("@solana/compat");
14
14
  const kit_1 = require("@solana/kit");
15
15
  const spl_token_1 = require("@solana/spl-token");
16
16
  const web3_js_1 = require("@solana/web3.js");
17
+ const sdk_1 = require("@wormhole-foundation/sdk");
18
+ const solana_1 = __importDefault(require("@wormhole-foundation/sdk/solana"));
19
+ const sdk_base_1 = require("@wormhole-foundation/sdk-base");
20
+ const sdk_route_ntt_1 = require("@wormhole-foundation/sdk-route-ntt");
21
+ const sdk_solana_core_1 = require("@wormhole-foundation/sdk-solana-core");
22
+ const sdk_solana_ntt_1 = require("@wormhole-foundation/sdk-solana-ntt");
17
23
  const bn_js_1 = __importDefault(require("bn.js"));
18
24
  const bs58_1 = __importDefault(require("bs58"));
19
25
  const zod_1 = require("zod");
@@ -34,6 +40,12 @@ const CURRENT_MAJOR = "0";
34
40
  const CURRENT_MINOR = "3";
35
41
  const CURRENT_INTENT_TRANSFER_MAJOR = "0";
36
42
  const CURRENT_INTENT_TRANSFER_MINOR = "1";
43
+ const CURRENT_BRIDGE_OUT_MAJOR = "0";
44
+ const CURRENT_BRIDGE_OUT_MINOR = "1";
45
+ const SESSION_ESTABLISHMENT_LOOKUP_TABLE_ADDRESS = {
46
+ [connection_js_1.Network.Testnet]: "B8cUjJMqaWWTNNSTXBmeptjWswwCH1gTSCRYv4nu7kJW",
47
+ [connection_js_1.Network.Mainnet]: undefined,
48
+ };
37
49
  const establishSession = async (options) => {
38
50
  const sessionKey = options.createUnsafeExtractableSessionKey
39
51
  ? await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"])
@@ -42,7 +54,7 @@ const establishSession = async (options) => {
42
54
  return sendSessionEstablishTransaction(options, sessionKey, await Promise.all([
43
55
  buildIntentInstruction(options, sessionKey),
44
56
  buildStartSessionInstruction(options, sessionKey),
45
- ]));
57
+ ]), options.sessionEstablishmentLookupTable);
46
58
  }
47
59
  else {
48
60
  const filteredLimits = new Map(options.limits?.entries().filter(([, amount]) => amount > 0n));
@@ -57,12 +69,15 @@ const establishSession = async (options) => {
57
69
  ...buildCreateAssociatedTokenAccountInstructions(options, tokenInfo),
58
70
  intentInstruction,
59
71
  startSessionInstruction,
60
- ]);
72
+ ], options.sessionEstablishmentLookupTable);
61
73
  }
62
74
  };
63
75
  exports.establishSession = establishSession;
64
- const sendSessionEstablishTransaction = async (options, sessionKey, instructions) => {
65
- const result = await options.context.sendTransaction(sessionKey, instructions);
76
+ const sendSessionEstablishTransaction = async (options, sessionKey, instructions, sessionEstablishmentLookupTable) => {
77
+ const result = await options.context.sendTransaction(sessionKey, instructions, {
78
+ addressLookupTable: sessionEstablishmentLookupTable ??
79
+ SESSION_ESTABLISHMENT_LOOKUP_TABLE_ADDRESS[options.context.network],
80
+ });
66
81
  switch (result.type) {
67
82
  case connection_js_1.TransactionResultType.Success: {
68
83
  const session = await createSession(options.context, options.walletPublicKey, sessionKey);
@@ -426,6 +441,13 @@ const getDomainRecordAddress = (domain) => {
426
441
  return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("domain-record"), hash], new web3_js_1.PublicKey(sessions_idls_1.DomainRegistryIdl.address))[0];
427
442
  };
428
443
  exports.getDomainRecordAddress = getDomainRecordAddress;
444
+ const BRIDGING_ADDRESS_LOOKUP_TABLE = {
445
+ [connection_js_1.Network.Testnet]: {
446
+ // USDC
447
+ ELNbJ1RtERV2fjtuZjbTscDekWhVzkQ1LjmiPsxp5uND: "4FCi6LptexBdZtaePsoCMeb1XpCijxnWu96g5LsSb6WP",
448
+ },
449
+ [connection_js_1.Network.Mainnet]: undefined,
450
+ };
429
451
  const buildStartSessionInstruction = async (options, sessionKey, tokens) => {
430
452
  const instruction = new sessions_idls_1.SessionManagerProgram(new anchor_1.AnchorProvider(options.context.connection, {}, {})).methods
431
453
  .startSession()
@@ -508,7 +530,7 @@ const sendTransfer = async (options) => {
508
530
  exports.sendTransfer = sendTransfer;
509
531
  const buildTransferIntentInstruction = async (program, options, symbol) => {
510
532
  const [nonce, { decimals }] = await Promise.all([
511
- getNonce(program, options.walletPublicKey),
533
+ getNonce(program, options.walletPublicKey, NonceType.Transfer),
512
534
  (0, spl_token_1.getMint)(options.context.connection, options.mint),
513
535
  ]);
514
536
  const message = new TextEncoder().encode([
@@ -529,13 +551,208 @@ const buildTransferIntentInstruction = async (program, options, symbol) => {
529
551
  message: await addOffchainMessagePrefixToMessageIfNeeded(options.walletPublicKey, intentSignature, message),
530
552
  });
531
553
  };
532
- const getNonce = async (program, walletPublicKey) => {
554
+ const BRIDGE_OUT_MESSAGE_HEADER = `Fogo Bridge Transfer:
555
+ Signing this intent will bridge out the tokens as described below.
556
+ `;
557
+ const bridgeOut = async (options) => {
558
+ const { wh, route, transferRequest, transferParams, decimals } = await buildWormholeTransfer(options, options.context.connection);
559
+ // @ts-expect-error the wormhole client types are incorrect and do not
560
+ // properly represent the runtime representation.
561
+ const quote = await route.fetchExecutorQuote(transferRequest, transferParams);
562
+ const program = new sessions_idls_1.IntentTransferProgram(new anchor_1.AnchorProvider(options.context.connection, {}, {}));
563
+ const umi = (0, umi_bundle_defaults_1.createUmi)(options.context.connection.rpcEndpoint);
564
+ const metaplexMint = (0, umi_1.publicKey)(options.fromToken.mint.toBase58());
565
+ const metadataAddress = (0, mpl_token_metadata_1.findMetadataPda)(umi, { mint: metaplexMint })[0];
566
+ const outboxItem = web3_js_1.Keypair.generate();
567
+ const [metadata, nttPdas] = await Promise.all([
568
+ (0, mpl_token_metadata_1.safeFetchMetadata)(umi, metadataAddress),
569
+ getNttPdas(options, wh, program, outboxItem.publicKey, new web3_js_1.PublicKey(quote.payeeAddress)),
570
+ ]);
571
+ const bridgeInstruction = await program.methods
572
+ .bridgeNttTokens({
573
+ execAmount: new bn_js_1.default(quote.estimatedCost.toString()),
574
+ relayInstructions: Buffer.from(quote.relayInstructions),
575
+ signedQuoteBytes: Buffer.from(quote.signedQuote),
576
+ })
577
+ .accounts({
578
+ sponsor: options.context.payer,
579
+ mint: options.fromToken.mint,
580
+ metadata:
581
+ // eslint-disable-next-line unicorn/no-null
582
+ metadata?.symbol === undefined ? null : new web3_js_1.PublicKey(metadataAddress),
583
+ source: (0, spl_token_1.getAssociatedTokenAddressSync)(options.fromToken.mint, options.walletPublicKey),
584
+ ntt: nttPdas,
585
+ })
586
+ .instruction();
587
+ return options.context.sendTransaction(options.sessionKey, [
588
+ await buildBridgeOutIntent(program, options, decimals, metadata?.symbol),
589
+ bridgeInstruction,
590
+ ], {
591
+ extraSigners: [outboxItem],
592
+ addressLookupTable: BRIDGING_ADDRESS_LOOKUP_TABLE[options.context.network]?.[options.fromToken.mint.toBase58()],
593
+ });
594
+ };
595
+ exports.bridgeOut = bridgeOut;
596
+ // Here we use the Wormhole SDKs to produce the wormhole pdas that are needed
597
+ // for the bridge out transaction. Currently this is using wormhole SDK apis
598
+ // that are _technically_ public but it seems likely these are not considered to
599
+ // be truly public. It might be better to extract the pdas by using the sdk to
600
+ // generate (but not send) a transaction, and taking the pdas from that. That
601
+ // may be something to revisit in the future if we find that the wormhole sdk
602
+ // upgrades in ways that break these calls.
603
+ const getNttPdas = async (options, wh, program, outboxItemPublicKey, quotePayeeAddress) => {
604
+ const pdas = sdk_solana_ntt_1.NTT.pdas(options.fromToken.manager);
605
+ const solana = wh.getChain("Solana");
606
+ const coreBridgeContract = sdk_base_1.contracts.coreBridge.get(wh.network, "Fogo");
607
+ if (coreBridgeContract === undefined) {
608
+ throw new Error("Core bridge contract address not returned by wormhole!");
609
+ }
610
+ const transceiverPdas = sdk_solana_ntt_1.NTT.transceiverPdas(options.fromToken.manager);
611
+ const [intentTransferSetterPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("intent_transfer")], program.programId);
612
+ const wormholePdas = sdk_solana_core_1.utils.getWormholeDerivedAccounts(options.fromToken.manager, coreBridgeContract);
613
+ return {
614
+ emitter: transceiverPdas.emitterAccount(),
615
+ nttConfig: pdas.configAccount(),
616
+ nttCustody: await sdk_solana_ntt_1.NTT.custodyAccountAddress(pdas, options.fromToken.mint),
617
+ nttInboxRateLimit: pdas.inboxRateLimitAccount(solana.chain),
618
+ nttManager: options.fromToken.manager,
619
+ nttOutboxItem: outboxItemPublicKey,
620
+ nttOutboxRateLimit: pdas.outboxRateLimitAccount(),
621
+ nttPeer: pdas.peerAccount(solana.chain),
622
+ nttSessionAuthority: pdas.sessionAuthority(intentTransferSetterPda, sdk_solana_ntt_1.NTT.transferArgs(options.amount, sdk_1.Wormhole.chainAddress("Solana", options.walletPublicKey.toBase58()), false)),
623
+ nttTokenAuthority: pdas.tokenAuthority(),
624
+ payeeNttWithExecutor: quotePayeeAddress,
625
+ transceiver: options.fromToken.transceiver,
626
+ wormholeBridge: wormholePdas.wormholeBridge,
627
+ wormholeFeeCollector: wormholePdas.wormholeFeeCollector,
628
+ wormholeMessage: transceiverPdas.wormholeMessageAccount(outboxItemPublicKey),
629
+ wormholeSequence: wormholePdas.wormholeSequence,
630
+ };
631
+ };
632
+ const buildBridgeOutIntent = async (program, options, decimals, symbol) => {
633
+ const nonce = await getNonce(program, options.walletPublicKey, NonceType.Bridge);
634
+ const message = new TextEncoder().encode([
635
+ BRIDGE_OUT_MESSAGE_HEADER,
636
+ serializeKV({
637
+ version: `${CURRENT_BRIDGE_OUT_MAJOR}.${CURRENT_BRIDGE_OUT_MINOR}`,
638
+ from_chain_id: options.context.chainId,
639
+ to_chain_id: "solana",
640
+ token: symbol ?? options.fromToken.mint.toBase58(),
641
+ amount: amountToString(options.amount, decimals),
642
+ recipient_address: options.walletPublicKey.toBase58(),
643
+ nonce: nonce === null ? "1" : nonce.nonce.add(new bn_js_1.default(1)).toString(),
644
+ }),
645
+ ].join("\n"));
646
+ const intentSignature = (0, kit_1.signatureBytes)(await options.solanaWallet.signMessage(message));
647
+ return web3_js_1.Ed25519Program.createInstructionWithPublicKey({
648
+ publicKey: options.walletPublicKey.toBytes(),
649
+ signature: intentSignature,
650
+ message: await addOffchainMessagePrefixToMessageIfNeeded(options.walletPublicKey, intentSignature, message),
651
+ });
652
+ };
653
+ const bridgeIn = async (options) => {
654
+ const solanaConnection = await options.context.getSolanaConnection();
655
+ const { route, transferRequest, transferParams } = await buildWormholeTransfer(options, solanaConnection);
656
+ // @ts-expect-error the wormhole client types are incorrect and do not
657
+ // properly represent the runtime representation.
658
+ const quote = await route.quote(transferRequest, transferParams);
659
+ if (quote.success) {
660
+ return await sdk_1.routes.checkAndCompleteTransfer(route, await route.initiate(transferRequest, {
661
+ address: () => options.walletPublicKey.toBase58(),
662
+ chain: () => "Solana",
663
+ signAndSend: (transactions) => Promise.all(transactions.map(({ transaction }) => options.solanaWallet.sendTransaction(
664
+ // Hooray for Wormhole's incomplete typing eh?
665
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
666
+ transaction.transaction, solanaConnection,
667
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
668
+ { signers: transaction.signers, skipPreflight: true }))),
669
+ }, quote, sdk_1.Wormhole.chainAddress("Fogo", options.walletPublicKey.toBase58())));
670
+ }
671
+ else {
672
+ throw quote.error;
673
+ }
674
+ };
675
+ exports.bridgeIn = bridgeIn;
676
+ const buildWormholeTransfer = async (options, connection) => {
677
+ const [wh, { decimals }] = await Promise.all([
678
+ (0, sdk_1.wormhole)(NETWORK_TO_WORMHOLE_NETWORK[options.context.network], [solana_1.default]),
679
+ (0, spl_token_1.getMint)(connection, options.fromToken.mint),
680
+ ]);
681
+ const Route = (0, sdk_route_ntt_1.nttExecutorRoute)({
682
+ ntt: {
683
+ tokens: {
684
+ USDC: [
685
+ {
686
+ chain: options.fromToken.chain,
687
+ manager: options.fromToken.manager.toBase58(),
688
+ token: options.fromToken.mint.toBase58(),
689
+ transceiver: [
690
+ {
691
+ address: options.fromToken.transceiver.toBase58(),
692
+ type: "wormhole",
693
+ },
694
+ ],
695
+ },
696
+ {
697
+ chain: options.toToken.chain,
698
+ manager: options.toToken.manager.toBase58(),
699
+ token: options.toToken.mint.toBase58(),
700
+ transceiver: [
701
+ {
702
+ address: options.toToken.transceiver.toBase58(),
703
+ type: "wormhole",
704
+ },
705
+ ],
706
+ },
707
+ ],
708
+ },
709
+ },
710
+ });
711
+ const route = new Route(wh);
712
+ const transferRequest = await sdk_1.routes.RouteTransferRequest.create(wh, {
713
+ source: sdk_1.Wormhole.tokenId(options.fromToken.chain, options.fromToken.mint.toBase58()),
714
+ destination: sdk_1.Wormhole.tokenId(options.toToken.chain, options.toToken.mint.toBase58()),
715
+ });
716
+ const validated = await route.validate(transferRequest, {
717
+ amount: amountToString(options.amount, decimals),
718
+ options: route.getDefaultOptions(),
719
+ });
720
+ if (validated.valid) {
721
+ return {
722
+ wh,
723
+ route,
724
+ transferRequest,
725
+ transferParams: validated.params,
726
+ decimals,
727
+ };
728
+ }
729
+ else {
730
+ throw validated.error;
731
+ }
732
+ };
733
+ const NETWORK_TO_WORMHOLE_NETWORK = {
734
+ [connection_js_1.Network.Mainnet]: "Mainnet",
735
+ [connection_js_1.Network.Testnet]: "Testnet",
736
+ };
737
+ const getNonce = async (program, walletPublicKey, nonceType) => {
533
738
  const [noncePda] = await (0, kit_1.getProgramDerivedAddress)({
534
739
  programAddress: (0, compat_1.fromLegacyPublicKey)(program.programId),
535
- seeds: [Buffer.from("nonce"), walletPublicKey.toBuffer()],
740
+ seeds: [
741
+ Buffer.from(NONCE_TYPE_TO_SEED[nonceType]),
742
+ walletPublicKey.toBuffer(),
743
+ ],
536
744
  });
537
745
  return program.account.nonce.fetchNullable(noncePda);
538
746
  };
747
+ var NonceType;
748
+ (function (NonceType) {
749
+ NonceType[NonceType["Transfer"] = 0] = "Transfer";
750
+ NonceType[NonceType["Bridge"] = 1] = "Bridge";
751
+ })(NonceType || (NonceType = {}));
752
+ const NONCE_TYPE_TO_SEED = {
753
+ [NonceType.Transfer]: "nonce",
754
+ [NonceType.Bridge]: "bridge_ntt_nonce",
755
+ };
539
756
  const loginTokenPayloadSchema = zod_1.z.object({
540
757
  iat: zod_1.z.number(),
541
758
  sessionPublicKey: zod_1.z.string(),
@@ -1,18 +1,10 @@
1
1
  import type { Transaction, Instruction, TransactionWithLifetime } from "@solana/kit";
2
- import type { AddressLookupTableAccount, TransactionError } from "@solana/web3.js";
3
- import { Connection as Web3Connection, TransactionInstruction, VersionedTransaction, PublicKey } from "@solana/web3.js";
2
+ import type { TransactionError } from "@solana/web3.js";
3
+ import { Keypair, Connection as Web3Connection, TransactionInstruction, VersionedTransaction, PublicKey } from "@solana/web3.js";
4
4
  export declare enum Network {
5
5
  Testnet = 0,
6
6
  Mainnet = 1
7
7
  }
8
- export declare const DEFAULT_RPC: {
9
- 0: string;
10
- 1: string;
11
- };
12
- export declare const DEFAULT_PAYMASTER: {
13
- 0: string;
14
- 1: string;
15
- };
16
8
  export declare enum TransactionResultType {
17
9
  Success = 0,
18
10
  Failed = 1
@@ -32,26 +24,24 @@ export type TransactionResult = ReturnType<(typeof TransactionResult)[keyof type
32
24
  export declare const createSessionConnection: (options: {
33
25
  network: Network;
34
26
  rpc?: string | URL | undefined;
35
- paymaster?: undefined;
36
- sendToPaymaster?: undefined;
37
- sponsor?: undefined;
38
- } | ({
39
- network?: Network | undefined;
40
- rpc: string | URL;
41
27
  } & ({
42
- paymaster: string | URL;
28
+ paymaster?: string | URL | undefined;
43
29
  sendToPaymaster?: undefined;
44
30
  sponsor?: undefined;
45
31
  } | {
46
32
  paymaster?: undefined;
47
33
  sendToPaymaster: (transaction: Transaction) => Promise<TransactionResult>;
48
34
  sponsor: PublicKey;
49
- }))) => {
35
+ })) => {
50
36
  rpc: import("@solana/kit").Rpc<import("@solana/kit").RequestAirdropApi & import("@solana/kit").GetAccountInfoApi & import("@solana/kit").GetBalanceApi & import("@solana/kit").GetBlockApi & import("@solana/kit").GetBlockCommitmentApi & import("@solana/kit").GetBlockHeightApi & import("@solana/kit").GetBlockProductionApi & import("@solana/kit").GetBlocksApi & import("@solana/kit").GetBlocksWithLimitApi & import("@solana/kit").GetBlockTimeApi & import("@solana/kit").GetClusterNodesApi & import("@solana/kit").GetEpochInfoApi & import("@solana/kit").GetEpochScheduleApi & import("@solana/kit").GetFeeForMessageApi & import("@solana/kit").GetFirstAvailableBlockApi & import("@solana/kit").GetGenesisHashApi & import("@solana/kit").GetHealthApi & import("@solana/kit").GetHighestSnapshotSlotApi & import("@solana/kit").GetIdentityApi & import("@solana/kit").GetInflationGovernorApi & import("@solana/kit").GetInflationRateApi & import("@solana/kit").GetInflationRewardApi & import("@solana/kit").GetLargestAccountsApi & import("@solana/kit").GetLatestBlockhashApi & import("@solana/kit").GetLeaderScheduleApi & import("@solana/kit").GetMaxRetransmitSlotApi & import("@solana/kit").GetMaxShredInsertSlotApi & import("@solana/kit").GetMinimumBalanceForRentExemptionApi & import("@solana/kit").GetMultipleAccountsApi & import("@solana/kit").GetProgramAccountsApi & import("@solana/kit").GetRecentPerformanceSamplesApi & import("@solana/kit").GetRecentPrioritizationFeesApi & import("@solana/kit").GetSignaturesForAddressApi & import("@solana/kit").GetSignatureStatusesApi & import("@solana/kit").GetSlotApi & import("@solana/kit").GetSlotLeaderApi & import("@solana/kit").GetSlotLeadersApi & import("@solana/kit").GetStakeMinimumDelegationApi & import("@solana/kit").GetSupplyApi & import("@solana/kit").GetTokenAccountBalanceApi & import("@solana/kit").GetTokenAccountsByDelegateApi & import("@solana/kit").GetTokenAccountsByOwnerApi & import("@solana/kit").GetTokenLargestAccountsApi & import("@solana/kit").GetTokenSupplyApi & import("@solana/kit").GetTransactionApi & import("@solana/kit").GetTransactionCountApi & import("@solana/kit").GetVersionApi & import("@solana/kit").GetVoteAccountsApi & import("@solana/kit").IsBlockhashValidApi & import("@solana/kit").MinimumLedgerSlotApi & import("@solana/kit").SendTransactionApi & import("@solana/kit").SimulateTransactionApi>;
51
37
  connection: Web3Connection;
52
- sendToPaymaster: (domain: string, sponsor: PublicKey, addressLookupTables: AddressLookupTableAccount[] | undefined, sessionKey: CryptoKeyPair | undefined, instructions: (TransactionInstruction | Instruction)[] | VersionedTransaction | (Transaction & TransactionWithLifetime)) => Promise<TransactionResult>;
38
+ network: Network;
39
+ getSolanaConnection: () => Promise<Web3Connection>;
40
+ sendToPaymaster: (domain: string, sponsor: PublicKey, sessionKey: CryptoKeyPair | undefined, instructions: (TransactionInstruction | Instruction)[] | VersionedTransaction | (Transaction & TransactionWithLifetime), extraConfig?: {
41
+ addressLookupTable?: string | undefined;
42
+ extraSigners?: (CryptoKeyPair | Keypair)[] | undefined;
43
+ }) => Promise<TransactionResult>;
53
44
  getSponsor: (domain: string) => Promise<PublicKey>;
54
- getAddressLookupTables: (addressLookupTableAddress?: string) => Promise<AddressLookupTableAccount[] | undefined>;
55
45
  };
56
46
  export type Connection = ReturnType<typeof createSessionConnection>;
57
47
  export {};
package/esm/connection.js CHANGED
@@ -1,24 +1,20 @@
1
- import { fromLegacyPublicKey, fromLegacyTransactionInstruction, fromVersionedTransaction, } from "@solana/compat";
1
+ import { fromLegacyKeypair, fromLegacyPublicKey, fromLegacyTransactionInstruction, fromVersionedTransaction, } from "@solana/compat";
2
2
  import { createSolanaRpc, getBase64EncodedWireTransaction, createTransactionMessage, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, partiallySignTransactionMessageWithSigners, pipe, addSignersToTransactionMessage, compressTransactionMessageUsingAddressLookupTables, createSignerFromKeyPair, partiallySignTransaction, } from "@solana/kit";
3
- import { Connection as Web3Connection, TransactionInstruction, VersionedTransaction, PublicKey, } from "@solana/web3.js";
3
+ import { Keypair, Connection as Web3Connection, TransactionInstruction, VersionedTransaction, PublicKey, } from "@solana/web3.js";
4
4
  import { z } from "zod";
5
5
  export var Network;
6
6
  (function (Network) {
7
7
  Network[Network["Testnet"] = 0] = "Testnet";
8
8
  Network[Network["Mainnet"] = 1] = "Mainnet";
9
9
  })(Network || (Network = {}));
10
- export const DEFAULT_RPC = {
10
+ const DEFAULT_RPC = {
11
11
  [Network.Testnet]: "https://testnet.fogo.io",
12
12
  [Network.Mainnet]: "https://mainnet.fogo.io",
13
13
  };
14
- export const DEFAULT_PAYMASTER = {
14
+ const DEFAULT_PAYMASTER = {
15
15
  [Network.Testnet]: "https://paymaster.fogo.io",
16
16
  [Network.Mainnet]: "https://paymaster.dourolabs.app",
17
17
  };
18
- const DEFAULT_ADDRESS_LOOKUP_TABLE_ADDRESS = {
19
- [Network.Testnet]: "B8cUjJMqaWWTNNSTXBmeptjWswwCH1gTSCRYv4nu7kJW",
20
- [Network.Mainnet]: undefined,
21
- };
22
18
  export var TransactionResultType;
23
19
  (function (TransactionResultType) {
24
20
  TransactionResultType[TransactionResultType["Success"] = 0] = "Success";
@@ -36,26 +32,45 @@ const TransactionResult = {
36
32
  }),
37
33
  };
38
34
  export const createSessionConnection = (options) => {
39
- // For some reason, typescript is unable to narrow this type even though it's
40
- // obvious that `rpc` can only be `undefined` if `network` is defined. I
41
- // don't like the non-null assertion, but here we can guarantee it's safe (and
42
- // typescript really should be able to narrow this...)
43
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
44
35
  const rpcUrl = (options.rpc ?? DEFAULT_RPC[options.network]).toString();
45
36
  const rpc = createSolanaRpc(rpcUrl);
46
37
  const connection = new Web3Connection(rpcUrl, "confirmed");
38
+ const addressLookupTableCache = new Map();
47
39
  return {
48
40
  rpc,
49
41
  connection,
50
- sendToPaymaster: async (domain, sponsor, addressLookupTables, sessionKey, instructions) => {
42
+ network: options.network,
43
+ getSolanaConnection: createSolanaConnectionGetter(options.network),
44
+ sendToPaymaster: async (domain, sponsor, sessionKey, instructions, extraConfig) => {
51
45
  const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
52
- const transaction = await buildTransaction(latestBlockhash, sessionKey, sponsor, instructions, addressLookupTables);
46
+ const transaction = await buildTransaction(connection, latestBlockhash, sessionKey, sponsor, instructions, addressLookupTableCache, extraConfig);
53
47
  return sendToPaymaster(options, domain, transaction);
54
48
  },
55
49
  getSponsor: (domain) => getSponsor(options, domain),
56
- getAddressLookupTables: (addressLookupTableAddress) => getAddressLookupTables(options, connection, addressLookupTableAddress),
57
50
  };
58
51
  };
52
+ const createSolanaConnectionGetter = (network) => {
53
+ let connection;
54
+ return async () => {
55
+ if (connection === undefined) {
56
+ const url = new URL("https://api.fogo.io/api/solana-rpc");
57
+ url.searchParams.set("network", NETWORK_TO_QUERY_PARAM[network]);
58
+ const rpcUrlRes = await fetch(url);
59
+ if (rpcUrlRes.status === 200) {
60
+ const rpcUrl = await rpcUrlRes.text();
61
+ connection = new Web3Connection(rpcUrl);
62
+ }
63
+ else {
64
+ throw new Error("Failed to resolve Solana RPC url");
65
+ }
66
+ }
67
+ return connection;
68
+ };
69
+ };
70
+ const NETWORK_TO_QUERY_PARAM = {
71
+ [Network.Mainnet]: "mainnet",
72
+ [Network.Testnet]: "testnet",
73
+ };
59
74
  const sendToPaymaster = async (options, domain, transaction) => {
60
75
  if (options.sendToPaymaster === undefined) {
61
76
  const url = new URL("/api/sponsor_and_send", options.paymaster ?? DEFAULT_PAYMASTER[options.network]);
@@ -80,29 +95,39 @@ const sendToPaymaster = async (options, domain, transaction) => {
80
95
  return options.sendToPaymaster(transaction);
81
96
  }
82
97
  };
83
- const buildTransaction = async (latestBlockhash, sessionKey, sponsor, instructions, addressLookupTables) => {
84
- const sessionKeySigner = sessionKey
85
- ? await createSignerFromKeyPair(sessionKey)
86
- : undefined;
98
+ const buildTransaction = async (connection, latestBlockhash, sessionKey, sponsor, instructions, addressLookupTableCache, extraConfig) => {
99
+ const [signerKeys, addressLookupTable] = await Promise.all([
100
+ getSignerKeys(sessionKey, extraConfig?.extraSigners),
101
+ extraConfig?.addressLookupTable === undefined
102
+ ? Promise.resolve(undefined)
103
+ : getAddressLookupTable(connection, addressLookupTableCache, extraConfig.addressLookupTable),
104
+ ]);
87
105
  if (Array.isArray(instructions)) {
106
+ const signers = await Promise.all(signerKeys.map((signer) => createSignerFromKeyPair(signer)));
88
107
  return partiallySignTransactionMessageWithSigners(pipe(createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayer(fromLegacyPublicKey(sponsor), tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), (tx) => appendTransactionMessageInstructions(instructions.map((instruction) => instruction instanceof TransactionInstruction
89
108
  ? fromLegacyTransactionInstruction(instruction)
90
- : instruction), tx), (tx) => compressTransactionMessageUsingAddressLookupTables(tx, Object.fromEntries(addressLookupTables?.map((table) => [
91
- fromLegacyPublicKey(table.key),
92
- table.state.addresses.map((address) => fromLegacyPublicKey(address)),
93
- ]) ?? [])), (tx) => sessionKeySigner === undefined
109
+ : instruction), tx), (tx) => addressLookupTable === undefined
94
110
  ? tx
95
- : addSignersToTransactionMessage([sessionKeySigner], tx)));
111
+ : compressTransactionMessageUsingAddressLookupTables(tx, {
112
+ [fromLegacyPublicKey(addressLookupTable.key)]: addressLookupTable.state.addresses.map((address) => fromLegacyPublicKey(address)),
113
+ }), (tx) => addSignersToTransactionMessage(signers, tx)));
96
114
  }
97
115
  else {
98
116
  const tx = instructions instanceof VersionedTransaction
99
117
  ? fromVersionedTransaction(instructions) // VersionedTransaction has a lifetime so it's fine to cast it so we can call partiallySignTransaction
100
118
  : instructions;
101
- return sessionKey === undefined
102
- ? tx
103
- : partiallySignTransaction([sessionKey], tx);
119
+ return partiallySignTransaction(signerKeys, tx);
104
120
  }
105
121
  };
122
+ const getSignerKeys = async (sessionKey, extraSigners) => {
123
+ const extraSignerKeys = extraSigners === undefined
124
+ ? []
125
+ : await Promise.all(extraSigners.map((signer) => signer instanceof Keypair ? fromLegacyKeypair(signer) : signer));
126
+ return [
127
+ ...extraSignerKeys,
128
+ ...(sessionKey === undefined ? [] : [sessionKey]),
129
+ ];
130
+ };
106
131
  const sponsorAndSendResponseSchema = z
107
132
  .discriminatedUnion("type", [
108
133
  z.object({
@@ -138,19 +163,20 @@ const getSponsor = async (options, domain) => {
138
163
  return options.sponsor;
139
164
  }
140
165
  };
141
- const getAddressLookupTables = async (options, connection, addressLookupTableAddress) => {
142
- const altAddress = addressLookupTableAddress ??
143
- (options.network === undefined
144
- ? undefined
145
- : DEFAULT_ADDRESS_LOOKUP_TABLE_ADDRESS[options.network]);
146
- if (altAddress) {
147
- const addressLookupTableResult = await connection.getAddressLookupTable(new PublicKey(altAddress));
148
- return addressLookupTableResult.value
149
- ? [addressLookupTableResult.value]
150
- : undefined;
166
+ const getAddressLookupTable = async (connection, addressLookupTableCache, addressLookupTableAddress) => {
167
+ const value = addressLookupTableCache.get(addressLookupTableAddress);
168
+ if (value === undefined) {
169
+ const result = await connection.getAddressLookupTable(new PublicKey(addressLookupTableAddress));
170
+ if (result.value === null) {
171
+ return;
172
+ }
173
+ else {
174
+ addressLookupTableCache.set(addressLookupTableAddress, result.value);
175
+ return result.value;
176
+ }
151
177
  }
152
178
  else {
153
- return;
179
+ return value;
154
180
  }
155
181
  };
156
182
  class PaymasterResponseError extends Error {
package/esm/context.d.ts CHANGED
@@ -2,14 +2,16 @@ import { Connection as Web3Connection } from "@solana/web3.js";
2
2
  import type { Connection } from "./connection.js";
3
3
  export declare const createSessionContext: (options: {
4
4
  connection: Connection;
5
- addressLookupTableAddress?: string | undefined;
5
+ defaultAddressLookupTableAddress?: string | undefined;
6
6
  domain?: string | undefined;
7
7
  }) => Promise<{
8
8
  chainId: string;
9
9
  domain: string;
10
10
  payer: import("@solana/web3.js").PublicKey;
11
+ getSolanaConnection: () => Promise<Web3Connection>;
11
12
  connection: Web3Connection;
12
13
  rpc: import("@solana/kit").Rpc<import("@solana/kit").RequestAirdropApi & import("@solana/kit").GetAccountInfoApi & import("@solana/kit").GetBalanceApi & import("@solana/kit").GetBlockApi & import("@solana/kit").GetBlockCommitmentApi & import("@solana/kit").GetBlockHeightApi & import("@solana/kit").GetBlockProductionApi & import("@solana/kit").GetBlocksApi & import("@solana/kit").GetBlocksWithLimitApi & import("@solana/kit").GetBlockTimeApi & import("@solana/kit").GetClusterNodesApi & import("@solana/kit").GetEpochInfoApi & import("@solana/kit").GetEpochScheduleApi & import("@solana/kit").GetFeeForMessageApi & import("@solana/kit").GetFirstAvailableBlockApi & import("@solana/kit").GetGenesisHashApi & import("@solana/kit").GetHealthApi & import("@solana/kit").GetHighestSnapshotSlotApi & import("@solana/kit").GetIdentityApi & import("@solana/kit").GetInflationGovernorApi & import("@solana/kit").GetInflationRateApi & import("@solana/kit").GetInflationRewardApi & import("@solana/kit").GetLargestAccountsApi & import("@solana/kit").GetLatestBlockhashApi & import("@solana/kit").GetLeaderScheduleApi & import("@solana/kit").GetMaxRetransmitSlotApi & import("@solana/kit").GetMaxShredInsertSlotApi & import("@solana/kit").GetMinimumBalanceForRentExemptionApi & import("@solana/kit").GetMultipleAccountsApi & import("@solana/kit").GetProgramAccountsApi & import("@solana/kit").GetRecentPerformanceSamplesApi & import("@solana/kit").GetRecentPrioritizationFeesApi & import("@solana/kit").GetSignaturesForAddressApi & import("@solana/kit").GetSignatureStatusesApi & import("@solana/kit").GetSlotApi & import("@solana/kit").GetSlotLeaderApi & import("@solana/kit").GetSlotLeadersApi & import("@solana/kit").GetStakeMinimumDelegationApi & import("@solana/kit").GetSupplyApi & import("@solana/kit").GetTokenAccountBalanceApi & import("@solana/kit").GetTokenAccountsByDelegateApi & import("@solana/kit").GetTokenAccountsByOwnerApi & import("@solana/kit").GetTokenLargestAccountsApi & import("@solana/kit").GetTokenSupplyApi & import("@solana/kit").GetTransactionApi & import("@solana/kit").GetTransactionCountApi & import("@solana/kit").GetVersionApi & import("@solana/kit").GetVoteAccountsApi & import("@solana/kit").IsBlockhashValidApi & import("@solana/kit").MinimumLedgerSlotApi & import("@solana/kit").SendTransactionApi & import("@solana/kit").SimulateTransactionApi>;
13
- sendTransaction: (sessionKey: CryptoKeyPair | undefined, instructions: Parameters<typeof options.connection.sendToPaymaster>[4]) => Promise<import("./connection.js").TransactionResult>;
14
+ network: import("./connection.js").Network;
15
+ sendTransaction: (sessionKey: CryptoKeyPair | undefined, instructions: Parameters<typeof options.connection.sendToPaymaster>[3], extraConfig?: Parameters<typeof options.connection.sendToPaymaster>[4]) => Promise<import("./connection.js").TransactionResult>;
14
16
  }>;
15
17
  export type SessionContext = Awaited<ReturnType<typeof createSessionContext>>;
package/esm/context.js CHANGED
@@ -4,16 +4,21 @@ import { Connection as Web3Connection, Keypair } 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 createSessionContext = async (options) => {
7
- const addressLookupTables = await options.connection.getAddressLookupTables(options.addressLookupTableAddress);
8
7
  const domain = getDomain(options.domain);
9
8
  const sponsor = await options.connection.getSponsor(domain);
10
9
  return {
11
10
  chainId: await fetchChainId(options.connection.connection),
12
11
  domain: getDomain(options.domain),
13
12
  payer: sponsor,
13
+ getSolanaConnection: options.connection.getSolanaConnection,
14
14
  connection: options.connection.connection,
15
15
  rpc: options.connection.rpc,
16
- sendTransaction: (sessionKey, instructions) => options.connection.sendToPaymaster(domain, sponsor, addressLookupTables, sessionKey, instructions),
16
+ network: options.connection.network,
17
+ sendTransaction: (sessionKey, instructions, extraConfig) => options.connection.sendToPaymaster(domain, sponsor, sessionKey, instructions, {
18
+ addressLookupTable: extraConfig?.addressLookupTable ??
19
+ options.defaultAddressLookupTableAddress,
20
+ extraSigners: extraConfig?.extraSigners,
21
+ }),
17
22
  };
18
23
  };
19
24
  const fetchChainId = async (connection) => {