@fogo/sessions-sdk 0.0.17 → 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/connection.d.ts +47 -0
- package/cjs/connection.js +191 -0
- package/cjs/context.d.ts +14 -37
- package/cjs/context.js +13 -126
- package/cjs/index.d.ts +43 -4
- package/cjs/index.js +234 -14
- package/esm/connection.d.ts +47 -0
- package/esm/connection.js +187 -0
- package/esm/context.d.ts +14 -37
- package/esm/context.js +13 -126
- package/esm/index.d.ts +43 -4
- package/esm/index.js +226 -10
- package/package.json +7 -2
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.TransactionResultType = 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,14 +14,23 @@ 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");
|
|
20
|
-
const
|
|
26
|
+
const connection_js_1 = require("./connection.js");
|
|
21
27
|
const crypto_js_1 = require("./crypto.js");
|
|
22
|
-
var
|
|
23
|
-
Object.defineProperty(exports, "createSessionContext", { enumerable: true, get: function () { return
|
|
24
|
-
|
|
28
|
+
var context_js_1 = require("./context.js");
|
|
29
|
+
Object.defineProperty(exports, "createSessionContext", { enumerable: true, get: function () { return context_js_1.createSessionContext; } });
|
|
30
|
+
var connection_js_2 = require("./connection.js");
|
|
31
|
+
Object.defineProperty(exports, "Network", { enumerable: true, get: function () { return connection_js_2.Network; } });
|
|
32
|
+
Object.defineProperty(exports, "TransactionResultType", { enumerable: true, get: function () { return connection_js_2.TransactionResultType; } });
|
|
33
|
+
Object.defineProperty(exports, "createSessionConnection", { enumerable: true, get: function () { return connection_js_2.createSessionConnection; } });
|
|
25
34
|
const MESSAGE_HEADER = `Fogo Sessions:
|
|
26
35
|
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
36
|
`;
|
|
@@ -31,6 +40,12 @@ const CURRENT_MAJOR = "0";
|
|
|
31
40
|
const CURRENT_MINOR = "3";
|
|
32
41
|
const CURRENT_INTENT_TRANSFER_MAJOR = "0";
|
|
33
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
|
+
};
|
|
34
49
|
const establishSession = async (options) => {
|
|
35
50
|
const sessionKey = options.createUnsafeExtractableSessionKey
|
|
36
51
|
? await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"])
|
|
@@ -39,7 +54,7 @@ const establishSession = async (options) => {
|
|
|
39
54
|
return sendSessionEstablishTransaction(options, sessionKey, await Promise.all([
|
|
40
55
|
buildIntentInstruction(options, sessionKey),
|
|
41
56
|
buildStartSessionInstruction(options, sessionKey),
|
|
42
|
-
]));
|
|
57
|
+
]), options.sessionEstablishmentLookupTable);
|
|
43
58
|
}
|
|
44
59
|
else {
|
|
45
60
|
const filteredLimits = new Map(options.limits?.entries().filter(([, amount]) => amount > 0n));
|
|
@@ -54,20 +69,23 @@ const establishSession = async (options) => {
|
|
|
54
69
|
...buildCreateAssociatedTokenAccountInstructions(options, tokenInfo),
|
|
55
70
|
intentInstruction,
|
|
56
71
|
startSessionInstruction,
|
|
57
|
-
]);
|
|
72
|
+
], options.sessionEstablishmentLookupTable);
|
|
58
73
|
}
|
|
59
74
|
};
|
|
60
75
|
exports.establishSession = establishSession;
|
|
61
|
-
const sendSessionEstablishTransaction = async (options, sessionKey, instructions) => {
|
|
62
|
-
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
|
+
});
|
|
63
81
|
switch (result.type) {
|
|
64
|
-
case
|
|
82
|
+
case connection_js_1.TransactionResultType.Success: {
|
|
65
83
|
const session = await createSession(options.context, options.walletPublicKey, sessionKey);
|
|
66
84
|
return session
|
|
67
85
|
? EstablishSessionResult.Success(result.signature, session)
|
|
68
86
|
: EstablishSessionResult.Failed(result.signature, new Error("Transaction succeeded, but the session was not created"));
|
|
69
87
|
}
|
|
70
|
-
case
|
|
88
|
+
case connection_js_1.TransactionResultType.Failed: {
|
|
71
89
|
return EstablishSessionResult.Failed(result.signature, result.error);
|
|
72
90
|
}
|
|
73
91
|
}
|
|
@@ -423,6 +441,13 @@ const getDomainRecordAddress = (domain) => {
|
|
|
423
441
|
return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("domain-record"), hash], new web3_js_1.PublicKey(sessions_idls_1.DomainRegistryIdl.address))[0];
|
|
424
442
|
};
|
|
425
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
|
+
};
|
|
426
451
|
const buildStartSessionInstruction = async (options, sessionKey, tokens) => {
|
|
427
452
|
const instruction = new sessions_idls_1.SessionManagerProgram(new anchor_1.AnchorProvider(options.context.connection, {}, {})).methods
|
|
428
453
|
.startSession()
|
|
@@ -505,7 +530,7 @@ const sendTransfer = async (options) => {
|
|
|
505
530
|
exports.sendTransfer = sendTransfer;
|
|
506
531
|
const buildTransferIntentInstruction = async (program, options, symbol) => {
|
|
507
532
|
const [nonce, { decimals }] = await Promise.all([
|
|
508
|
-
getNonce(program, options.walletPublicKey),
|
|
533
|
+
getNonce(program, options.walletPublicKey, NonceType.Transfer),
|
|
509
534
|
(0, spl_token_1.getMint)(options.context.connection, options.mint),
|
|
510
535
|
]);
|
|
511
536
|
const message = new TextEncoder().encode([
|
|
@@ -526,13 +551,208 @@ const buildTransferIntentInstruction = async (program, options, symbol) => {
|
|
|
526
551
|
message: await addOffchainMessagePrefixToMessageIfNeeded(options.walletPublicKey, intentSignature, message),
|
|
527
552
|
});
|
|
528
553
|
};
|
|
529
|
-
const
|
|
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) => {
|
|
530
738
|
const [noncePda] = await (0, kit_1.getProgramDerivedAddress)({
|
|
531
739
|
programAddress: (0, compat_1.fromLegacyPublicKey)(program.programId),
|
|
532
|
-
seeds: [
|
|
740
|
+
seeds: [
|
|
741
|
+
Buffer.from(NONCE_TYPE_TO_SEED[nonceType]),
|
|
742
|
+
walletPublicKey.toBuffer(),
|
|
743
|
+
],
|
|
533
744
|
});
|
|
534
745
|
return program.account.nonce.fetchNullable(noncePda);
|
|
535
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
|
+
};
|
|
536
756
|
const loginTokenPayloadSchema = zod_1.z.object({
|
|
537
757
|
iat: zod_1.z.number(),
|
|
538
758
|
sessionPublicKey: zod_1.z.string(),
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Transaction, Instruction, TransactionWithLifetime } from "@solana/kit";
|
|
2
|
+
import type { TransactionError } from "@solana/web3.js";
|
|
3
|
+
import { Keypair, Connection as Web3Connection, TransactionInstruction, VersionedTransaction, PublicKey } from "@solana/web3.js";
|
|
4
|
+
export declare enum Network {
|
|
5
|
+
Testnet = 0,
|
|
6
|
+
Mainnet = 1
|
|
7
|
+
}
|
|
8
|
+
export declare enum TransactionResultType {
|
|
9
|
+
Success = 0,
|
|
10
|
+
Failed = 1
|
|
11
|
+
}
|
|
12
|
+
declare const TransactionResult: {
|
|
13
|
+
Success: (signature: string) => {
|
|
14
|
+
type: TransactionResultType.Success;
|
|
15
|
+
signature: string;
|
|
16
|
+
};
|
|
17
|
+
Failed: (signature: string, error: TransactionError) => {
|
|
18
|
+
type: TransactionResultType.Failed;
|
|
19
|
+
signature: string;
|
|
20
|
+
error: TransactionError;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export type TransactionResult = ReturnType<(typeof TransactionResult)[keyof typeof TransactionResult]>;
|
|
24
|
+
export declare const createSessionConnection: (options: {
|
|
25
|
+
network: Network;
|
|
26
|
+
rpc?: string | URL | undefined;
|
|
27
|
+
} & ({
|
|
28
|
+
paymaster?: string | URL | undefined;
|
|
29
|
+
sendToPaymaster?: undefined;
|
|
30
|
+
sponsor?: undefined;
|
|
31
|
+
} | {
|
|
32
|
+
paymaster?: undefined;
|
|
33
|
+
sendToPaymaster: (transaction: Transaction) => Promise<TransactionResult>;
|
|
34
|
+
sponsor: PublicKey;
|
|
35
|
+
})) => {
|
|
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>;
|
|
37
|
+
connection: Web3Connection;
|
|
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>;
|
|
44
|
+
getSponsor: (domain: string) => Promise<PublicKey>;
|
|
45
|
+
};
|
|
46
|
+
export type Connection = ReturnType<typeof createSessionConnection>;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,187 @@
|
|
|
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";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
export var Network;
|
|
6
|
+
(function (Network) {
|
|
7
|
+
Network[Network["Testnet"] = 0] = "Testnet";
|
|
8
|
+
Network[Network["Mainnet"] = 1] = "Mainnet";
|
|
9
|
+
})(Network || (Network = {}));
|
|
10
|
+
const DEFAULT_RPC = {
|
|
11
|
+
[Network.Testnet]: "https://testnet.fogo.io",
|
|
12
|
+
[Network.Mainnet]: "https://mainnet.fogo.io",
|
|
13
|
+
};
|
|
14
|
+
const DEFAULT_PAYMASTER = {
|
|
15
|
+
[Network.Testnet]: "https://paymaster.fogo.io",
|
|
16
|
+
[Network.Mainnet]: "https://paymaster.dourolabs.app",
|
|
17
|
+
};
|
|
18
|
+
export var TransactionResultType;
|
|
19
|
+
(function (TransactionResultType) {
|
|
20
|
+
TransactionResultType[TransactionResultType["Success"] = 0] = "Success";
|
|
21
|
+
TransactionResultType[TransactionResultType["Failed"] = 1] = "Failed";
|
|
22
|
+
})(TransactionResultType || (TransactionResultType = {}));
|
|
23
|
+
const TransactionResult = {
|
|
24
|
+
Success: (signature) => ({
|
|
25
|
+
type: TransactionResultType.Success,
|
|
26
|
+
signature,
|
|
27
|
+
}),
|
|
28
|
+
Failed: (signature, error) => ({
|
|
29
|
+
type: TransactionResultType.Failed,
|
|
30
|
+
signature,
|
|
31
|
+
error,
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
export const createSessionConnection = (options) => {
|
|
35
|
+
const rpcUrl = (options.rpc ?? DEFAULT_RPC[options.network]).toString();
|
|
36
|
+
const rpc = createSolanaRpc(rpcUrl);
|
|
37
|
+
const connection = new Web3Connection(rpcUrl, "confirmed");
|
|
38
|
+
const addressLookupTableCache = new Map();
|
|
39
|
+
return {
|
|
40
|
+
rpc,
|
|
41
|
+
connection,
|
|
42
|
+
network: options.network,
|
|
43
|
+
getSolanaConnection: createSolanaConnectionGetter(options.network),
|
|
44
|
+
sendToPaymaster: async (domain, sponsor, sessionKey, instructions, extraConfig) => {
|
|
45
|
+
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
|
|
46
|
+
const transaction = await buildTransaction(connection, latestBlockhash, sessionKey, sponsor, instructions, addressLookupTableCache, extraConfig);
|
|
47
|
+
return sendToPaymaster(options, domain, transaction);
|
|
48
|
+
},
|
|
49
|
+
getSponsor: (domain) => getSponsor(options, domain),
|
|
50
|
+
};
|
|
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
|
+
};
|
|
74
|
+
const sendToPaymaster = async (options, domain, transaction) => {
|
|
75
|
+
if (options.sendToPaymaster === undefined) {
|
|
76
|
+
const url = new URL("/api/sponsor_and_send", options.paymaster ?? DEFAULT_PAYMASTER[options.network]);
|
|
77
|
+
url.searchParams.set("domain", domain);
|
|
78
|
+
const response = await fetch(url, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: {
|
|
81
|
+
"Content-Type": "application/json",
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
transaction: getBase64EncodedWireTransaction(transaction),
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
if (response.status === 200) {
|
|
88
|
+
return sponsorAndSendResponseSchema.parse(await response.json());
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
throw new PaymasterResponseError(response.status, await response.text());
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
return options.sendToPaymaster(transaction);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
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
|
+
]);
|
|
105
|
+
if (Array.isArray(instructions)) {
|
|
106
|
+
const signers = await Promise.all(signerKeys.map((signer) => createSignerFromKeyPair(signer)));
|
|
107
|
+
return partiallySignTransactionMessageWithSigners(pipe(createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayer(fromLegacyPublicKey(sponsor), tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), (tx) => appendTransactionMessageInstructions(instructions.map((instruction) => instruction instanceof TransactionInstruction
|
|
108
|
+
? fromLegacyTransactionInstruction(instruction)
|
|
109
|
+
: instruction), tx), (tx) => addressLookupTable === undefined
|
|
110
|
+
? tx
|
|
111
|
+
: compressTransactionMessageUsingAddressLookupTables(tx, {
|
|
112
|
+
[fromLegacyPublicKey(addressLookupTable.key)]: addressLookupTable.state.addresses.map((address) => fromLegacyPublicKey(address)),
|
|
113
|
+
}), (tx) => addSignersToTransactionMessage(signers, tx)));
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
const tx = instructions instanceof VersionedTransaction
|
|
117
|
+
? fromVersionedTransaction(instructions) // VersionedTransaction has a lifetime so it's fine to cast it so we can call partiallySignTransaction
|
|
118
|
+
: instructions;
|
|
119
|
+
return partiallySignTransaction(signerKeys, tx);
|
|
120
|
+
}
|
|
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
|
+
};
|
|
131
|
+
const sponsorAndSendResponseSchema = z
|
|
132
|
+
.discriminatedUnion("type", [
|
|
133
|
+
z.object({
|
|
134
|
+
type: z.literal("success"),
|
|
135
|
+
signature: z.string(),
|
|
136
|
+
}),
|
|
137
|
+
z.object({
|
|
138
|
+
type: z.literal("failed"),
|
|
139
|
+
signature: z.string(),
|
|
140
|
+
error: z.object({
|
|
141
|
+
InstructionError: z.tuple([z.number(), z.unknown()]),
|
|
142
|
+
}),
|
|
143
|
+
}),
|
|
144
|
+
])
|
|
145
|
+
.transform((data) => {
|
|
146
|
+
return data.type === "success"
|
|
147
|
+
? TransactionResult.Success(data.signature)
|
|
148
|
+
: TransactionResult.Failed(data.signature, data.error);
|
|
149
|
+
});
|
|
150
|
+
const getSponsor = async (options, domain) => {
|
|
151
|
+
if (options.sponsor === undefined) {
|
|
152
|
+
const url = new URL("/api/sponsor_pubkey", options.paymaster ?? DEFAULT_PAYMASTER[options.network]);
|
|
153
|
+
url.searchParams.set("domain", domain);
|
|
154
|
+
const response = await fetch(url);
|
|
155
|
+
if (response.status === 200) {
|
|
156
|
+
return new PublicKey(z.string().parse(await response.text()));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
throw new PaymasterResponseError(response.status, await response.text());
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
return options.sponsor;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
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
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
class PaymasterResponseError extends Error {
|
|
183
|
+
constructor(statusCode, message) {
|
|
184
|
+
super(`Paymaster sent a ${statusCode.toString()} response: ${message}`);
|
|
185
|
+
this.name = "PaymasterResponseError";
|
|
186
|
+
}
|
|
187
|
+
}
|
package/esm/context.d.ts
CHANGED
|
@@ -1,40 +1,17 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { Connection
|
|
3
|
-
import { PublicKey, TransactionInstruction, VersionedTransaction } from "@solana/web3.js";
|
|
4
|
-
export type SessionContext = {
|
|
5
|
-
chainId: string;
|
|
6
|
-
connection: Connection;
|
|
7
|
-
payer: PublicKey;
|
|
8
|
-
domain: string;
|
|
9
|
-
sendTransaction: (sessionKey: CryptoKeyPair | undefined, instructions: (TransactionInstruction | Instruction)[] | VersionedTransaction | (Transaction & TransactionWithLifetime)) => Promise<TransactionResult>;
|
|
10
|
-
};
|
|
11
|
-
export declare enum TransactionResultType {
|
|
12
|
-
Success = 0,
|
|
13
|
-
Failed = 1
|
|
14
|
-
}
|
|
15
|
-
declare const TransactionResult: {
|
|
16
|
-
Success: (signature: string) => {
|
|
17
|
-
type: TransactionResultType.Success;
|
|
18
|
-
signature: string;
|
|
19
|
-
};
|
|
20
|
-
Failed: (signature: string, error: TransactionError) => {
|
|
21
|
-
type: TransactionResultType.Failed;
|
|
22
|
-
signature: string;
|
|
23
|
-
error: TransactionError;
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
export type TransactionResult = ReturnType<(typeof TransactionResult)[keyof typeof TransactionResult]>;
|
|
1
|
+
import { Connection as Web3Connection } from "@solana/web3.js";
|
|
2
|
+
import type { Connection } from "./connection.js";
|
|
27
3
|
export declare const createSessionContext: (options: {
|
|
28
4
|
connection: Connection;
|
|
29
|
-
|
|
5
|
+
defaultAddressLookupTableAddress?: string | undefined;
|
|
30
6
|
domain?: string | undefined;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
7
|
+
}) => Promise<{
|
|
8
|
+
chainId: string;
|
|
9
|
+
domain: string;
|
|
10
|
+
payer: import("@solana/web3.js").PublicKey;
|
|
11
|
+
getSolanaConnection: () => Promise<Web3Connection>;
|
|
12
|
+
connection: Web3Connection;
|
|
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>;
|
|
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>;
|
|
16
|
+
}>;
|
|
17
|
+
export type SessionContext = Awaited<ReturnType<typeof createSessionContext>>;
|