@loyal-labs/private-transactions 0.2.8 → 0.2.9
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/README.md +69 -35
- package/dist/index.d.ts +66 -2
- package/dist/index.js +2349 -350
- package/dist/src/LoyalPrivateTransactionsClient.d.ts +15 -3
- package/dist/src/actions/shieldTokens.d.ts +69 -0
- package/dist/src/actions/undelegateDeposit.d.ts +25 -0
- package/dist/src/actions/unshieldTokens.d.ts +62 -0
- package/dist/src/checks/enshureChecks.d.ts +5 -0
- package/dist/src/constants.d.ts +1 -1
- package/dist/src/enumerate-deposits.d.ts +23 -0
- package/dist/src/fee-estimate.d.ts +25 -0
- package/dist/src/idl/telegram_private_transfer.d.ts +119 -3
- package/dist/src/instructions/closeDeposit.d.ts +4 -0
- package/dist/src/instructions/closePermission.d.ts +2 -0
- package/dist/src/instructions/closeUsernameDeposit.d.ts +4 -0
- package/dist/src/instructions/createPermission.d.ts +4 -0
- package/dist/src/instructions/delegateDeposit.d.ts +4 -0
- package/dist/src/instructions/initializeDeposit.d.ts +4 -0
- package/dist/src/instructions/initializeUsernameDeposit.d.ts +4 -0
- package/dist/src/instructions/modifyBalance.d.ts +4 -0
- package/dist/src/instructions/undelegateDeposit.d.ts +4 -0
- package/dist/src/instructions/undelegatePermission.d.ts +2 -0
- package/dist/src/kamino.d.ts +1 -1
- package/dist/src/rent-estimate.d.ts +46 -0
- package/dist/src/transaction-debug.d.ts +40 -0
- package/dist/src/types.d.ts +182 -2
- package/dist/src/utils.d.ts +7 -0
- package/dist/src/wsol.d.ts +14 -0
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
// src/LoyalPrivateTransactionsClient.ts
|
|
2
2
|
import {
|
|
3
|
-
Connection,
|
|
4
|
-
PublicKey as
|
|
5
|
-
SystemProgram
|
|
3
|
+
Connection as Connection3,
|
|
4
|
+
PublicKey as PublicKey7,
|
|
5
|
+
SystemProgram as SystemProgram5,
|
|
6
|
+
Transaction as Transaction7
|
|
6
7
|
} from "@solana/web3.js";
|
|
7
|
-
import { AnchorProvider, BN, Program } from "@coral-xyz/anchor";
|
|
8
|
-
import {
|
|
9
|
-
getAssociatedTokenAddressSync,
|
|
10
|
-
TOKEN_PROGRAM_ID,
|
|
11
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
12
|
-
} from "@solana/spl-token";
|
|
8
|
+
import { AnchorProvider as AnchorProvider2, BN as BN2, Program as Program2 } from "@coral-xyz/anchor";
|
|
9
|
+
import { TOKEN_PROGRAM_ID as TOKEN_PROGRAM_ID4 } from "@solana/spl-token";
|
|
13
10
|
import {
|
|
14
|
-
|
|
11
|
+
verifyTeeIntegrity,
|
|
15
12
|
getAuthToken
|
|
16
13
|
} from "@magicblock-labs/ephemeral-rollups-sdk";
|
|
17
14
|
// src/idl/telegram_private_transfer.json
|
|
@@ -105,6 +102,107 @@ var telegram_private_transfer_default = {
|
|
|
105
102
|
}
|
|
106
103
|
]
|
|
107
104
|
},
|
|
105
|
+
{
|
|
106
|
+
name: "close_deposit",
|
|
107
|
+
docs: [
|
|
108
|
+
"Closes an empty user deposit account and returns its rent to the deposit owner."
|
|
109
|
+
],
|
|
110
|
+
discriminator: [
|
|
111
|
+
200,
|
|
112
|
+
19,
|
|
113
|
+
254,
|
|
114
|
+
192,
|
|
115
|
+
15,
|
|
116
|
+
110,
|
|
117
|
+
209,
|
|
118
|
+
179
|
|
119
|
+
],
|
|
120
|
+
accounts: [
|
|
121
|
+
{
|
|
122
|
+
name: "user",
|
|
123
|
+
writable: true,
|
|
124
|
+
signer: true,
|
|
125
|
+
relations: [
|
|
126
|
+
"deposit"
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "deposit",
|
|
131
|
+
writable: true,
|
|
132
|
+
pda: {
|
|
133
|
+
seeds: [
|
|
134
|
+
{
|
|
135
|
+
kind: "const",
|
|
136
|
+
value: [
|
|
137
|
+
100,
|
|
138
|
+
101,
|
|
139
|
+
112,
|
|
140
|
+
111,
|
|
141
|
+
115,
|
|
142
|
+
105,
|
|
143
|
+
116,
|
|
144
|
+
95,
|
|
145
|
+
118,
|
|
146
|
+
50
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
kind: "account",
|
|
151
|
+
path: "user"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
kind: "account",
|
|
155
|
+
path: "token_mint"
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: "token_mint",
|
|
162
|
+
relations: [
|
|
163
|
+
"deposit"
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
args: []
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "close_username_deposit",
|
|
171
|
+
docs: [
|
|
172
|
+
"Closes an empty username deposit account after verified username ownership."
|
|
173
|
+
],
|
|
174
|
+
discriminator: [
|
|
175
|
+
238,
|
|
176
|
+
181,
|
|
177
|
+
185,
|
|
178
|
+
209,
|
|
179
|
+
149,
|
|
180
|
+
161,
|
|
181
|
+
124,
|
|
182
|
+
79
|
|
183
|
+
],
|
|
184
|
+
accounts: [
|
|
185
|
+
{
|
|
186
|
+
name: "authority",
|
|
187
|
+
writable: true,
|
|
188
|
+
signer: true
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "deposit",
|
|
192
|
+
writable: true
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "token_mint",
|
|
196
|
+
relations: [
|
|
197
|
+
"deposit"
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: "session"
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
args: []
|
|
205
|
+
},
|
|
108
206
|
{
|
|
109
207
|
name: "create_permission",
|
|
110
208
|
docs: [
|
|
@@ -817,10 +915,20 @@ var telegram_private_transfer_default = {
|
|
|
817
915
|
{
|
|
818
916
|
name: "modify_balance",
|
|
819
917
|
docs: [
|
|
820
|
-
"Modifies
|
|
918
|
+
"Modifies a user's deposit balance and the backing vault position for the given mint.",
|
|
919
|
+
"",
|
|
920
|
+
"For non-USDC mints, this is a direct vault transfer: if `args.increase` is true, `args.amount`",
|
|
921
|
+
"is transferred from the user's token account to the vault token account and added to",
|
|
922
|
+
"`deposit.amount`. If false, `args.amount` is transferred from the vault token account back to",
|
|
923
|
+
"the user's token account and subtracted from `deposit.amount`.",
|
|
821
924
|
"",
|
|
822
|
-
"
|
|
823
|
-
"If
|
|
925
|
+
"For USDC, liquidity is routed through Kamino Lending instead of being left idle in the vault.",
|
|
926
|
+
"If `args.increase` is true, `args.amount` USDC is transferred into the vault token account,",
|
|
927
|
+
"supplied to the configured Kamino reserve, and `deposit.amount` is increased by the Kamino",
|
|
928
|
+
"reserve collateral shares (kTokens) minted to the vault. If false, `args.amount` is",
|
|
929
|
+
"interpreted as the Kamino share amount to redeem; the reserve returns the corresponding USDC",
|
|
930
|
+
"at the current exchange rate, that USDC is transferred from the vault token account to the",
|
|
931
|
+
"user's token account, and `deposit.amount` is decreased by the burned share amount."
|
|
824
932
|
],
|
|
825
933
|
discriminator: [
|
|
826
934
|
148,
|
|
@@ -1657,6 +1765,11 @@ var telegram_private_transfer_default = {
|
|
|
1657
1765
|
code: 6013,
|
|
1658
1766
|
name: "InvalidAmount",
|
|
1659
1767
|
msg: "Invalid amount"
|
|
1768
|
+
},
|
|
1769
|
+
{
|
|
1770
|
+
code: 6014,
|
|
1771
|
+
name: "NonZeroDeposit",
|
|
1772
|
+
msg: "Deposit account must have zero amount before it can be closed"
|
|
1660
1773
|
}
|
|
1661
1774
|
],
|
|
1662
1775
|
types: [
|
|
@@ -1823,14 +1936,14 @@ import {
|
|
|
1823
1936
|
LAMPORTS_PER_SOL,
|
|
1824
1937
|
SYSVAR_INSTRUCTIONS_PUBKEY
|
|
1825
1938
|
} from "@solana/web3.js";
|
|
1826
|
-
var ER_VALIDATOR_DEVNET = new PublicKey("
|
|
1939
|
+
var ER_VALIDATOR_DEVNET = new PublicKey("MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo");
|
|
1827
1940
|
var ER_VALIDATOR_MAINNET = new PublicKey("MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo");
|
|
1828
1941
|
var ER_VALIDATOR = ER_VALIDATOR_DEVNET;
|
|
1829
1942
|
function getErValidatorForSolanaEnv(env) {
|
|
1830
1943
|
return env === "mainnet" ? ER_VALIDATOR_MAINNET : ER_VALIDATOR_DEVNET;
|
|
1831
1944
|
}
|
|
1832
|
-
function getErValidatorForRpcEndpoint(
|
|
1833
|
-
return
|
|
1945
|
+
function getErValidatorForRpcEndpoint(perRpcEndpoint) {
|
|
1946
|
+
return perRpcEndpoint.includes("mainnet-tee") ? ER_VALIDATOR_MAINNET : ER_VALIDATOR_DEVNET;
|
|
1834
1947
|
}
|
|
1835
1948
|
function getKaminoModifyBalanceAccountsForTokenMint(tokenMint) {
|
|
1836
1949
|
if (tokenMint.equals(USDC_MINT_MAINNET)) {
|
|
@@ -1889,6 +2002,125 @@ function lamportsToSol(lamports) {
|
|
|
1889
2002
|
return lamports / LAMPORTS_PER_SOL;
|
|
1890
2003
|
}
|
|
1891
2004
|
|
|
2005
|
+
// src/enumerate-deposits.ts
|
|
2006
|
+
import { AnchorProvider, Program } from "@coral-xyz/anchor";
|
|
2007
|
+
import {
|
|
2008
|
+
PublicKey as PublicKey2
|
|
2009
|
+
} from "@solana/web3.js";
|
|
2010
|
+
var DEPOSIT_ACCOUNT_SIZE = 80;
|
|
2011
|
+
var DEPOSIT_USER_OFFSET = 8;
|
|
2012
|
+
var DEPOSIT_MINT_OFFSET = 40;
|
|
2013
|
+
var DEPOSIT_AMOUNT_OFFSET = 72;
|
|
2014
|
+
|
|
2015
|
+
class ReadOnlyWallet {
|
|
2016
|
+
publicKey;
|
|
2017
|
+
constructor(publicKey) {
|
|
2018
|
+
this.publicKey = publicKey;
|
|
2019
|
+
}
|
|
2020
|
+
async signTransaction(_tx) {
|
|
2021
|
+
throw new Error("ReadOnlyWallet cannot sign transactions; construct a real client for write paths.");
|
|
2022
|
+
}
|
|
2023
|
+
async signAllTransactions(_txs) {
|
|
2024
|
+
throw new Error("ReadOnlyWallet cannot sign transactions; construct a real client for write paths.");
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
function createReadOnlyDepositProgram(connection) {
|
|
2028
|
+
const wallet = new ReadOnlyWallet(PublicKey2.default);
|
|
2029
|
+
const provider = new AnchorProvider(connection, wallet, {
|
|
2030
|
+
commitment: connection.commitment ?? "confirmed"
|
|
2031
|
+
});
|
|
2032
|
+
return new Program(telegram_private_transfer_default, provider);
|
|
2033
|
+
}
|
|
2034
|
+
function readDepositAmount(data) {
|
|
2035
|
+
let value = BigInt(0);
|
|
2036
|
+
for (let i = 0;i < 8; i++) {
|
|
2037
|
+
value += BigInt(data[DEPOSIT_AMOUNT_OFFSET + i] ?? 0) << BigInt(i * 8);
|
|
2038
|
+
}
|
|
2039
|
+
return value;
|
|
2040
|
+
}
|
|
2041
|
+
async function readDelegatedDepositsOnBase(baseConnection, user) {
|
|
2042
|
+
const accounts = await baseConnection.getProgramAccounts(DELEGATION_PROGRAM_ID, {
|
|
2043
|
+
filters: [
|
|
2044
|
+
{ dataSize: DEPOSIT_ACCOUNT_SIZE },
|
|
2045
|
+
{
|
|
2046
|
+
memcmp: {
|
|
2047
|
+
offset: DEPOSIT_USER_OFFSET,
|
|
2048
|
+
bytes: user.toBase58()
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
]
|
|
2052
|
+
});
|
|
2053
|
+
const result = [];
|
|
2054
|
+
for (const { pubkey, account } of accounts) {
|
|
2055
|
+
const data = account.data;
|
|
2056
|
+
if (data.length < DEPOSIT_ACCOUNT_SIZE)
|
|
2057
|
+
continue;
|
|
2058
|
+
const tokenMint = new PublicKey2(data.subarray(DEPOSIT_MINT_OFFSET, DEPOSIT_MINT_OFFSET + 32));
|
|
2059
|
+
result.push({
|
|
2060
|
+
user,
|
|
2061
|
+
tokenMint,
|
|
2062
|
+
amount: readDepositAmount(data),
|
|
2063
|
+
address: pubkey
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
return result;
|
|
2067
|
+
}
|
|
2068
|
+
async function enumerateDepositsByUser(args) {
|
|
2069
|
+
const userFilter = [
|
|
2070
|
+
{
|
|
2071
|
+
memcmp: {
|
|
2072
|
+
offset: DEPOSIT_USER_OFFSET,
|
|
2073
|
+
bytes: args.user.toBase58()
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
];
|
|
2077
|
+
const baseProgram = createReadOnlyDepositProgram(args.baseConnection);
|
|
2078
|
+
const ephemeralProgram = args.ephemeralConnection ? createReadOnlyDepositProgram(args.ephemeralConnection) : null;
|
|
2079
|
+
const [baseUndelegatedResult, baseDelegatedResult, ephemeralResult] = await Promise.allSettled([
|
|
2080
|
+
baseProgram.account.deposit.all(userFilter),
|
|
2081
|
+
readDelegatedDepositsOnBase(args.baseConnection, args.user),
|
|
2082
|
+
ephemeralProgram ? ephemeralProgram.account.deposit.all(userFilter) : Promise.resolve([])
|
|
2083
|
+
]);
|
|
2084
|
+
const byPda = new Map;
|
|
2085
|
+
const ingestAnchor = (results, preferOverwrite) => {
|
|
2086
|
+
for (const { publicKey, account } of results) {
|
|
2087
|
+
const key = publicKey.toBase58();
|
|
2088
|
+
if (!preferOverwrite && byPda.has(key))
|
|
2089
|
+
continue;
|
|
2090
|
+
byPda.set(key, {
|
|
2091
|
+
user: account.user,
|
|
2092
|
+
tokenMint: account.tokenMint,
|
|
2093
|
+
amount: BigInt(account.amount.toString()),
|
|
2094
|
+
address: publicKey
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
};
|
|
2098
|
+
const ingestRaw = (results, preferOverwrite) => {
|
|
2099
|
+
for (const data of results) {
|
|
2100
|
+
const key = data.address.toBase58();
|
|
2101
|
+
if (!preferOverwrite && byPda.has(key))
|
|
2102
|
+
continue;
|
|
2103
|
+
byPda.set(key, data);
|
|
2104
|
+
}
|
|
2105
|
+
};
|
|
2106
|
+
if (baseUndelegatedResult.status === "fulfilled") {
|
|
2107
|
+
ingestAnchor(baseUndelegatedResult.value, false);
|
|
2108
|
+
} else {
|
|
2109
|
+
console.warn("[enumerateDepositsByUser] base undelegated enumeration failed", baseUndelegatedResult.reason);
|
|
2110
|
+
}
|
|
2111
|
+
if (baseDelegatedResult.status === "fulfilled") {
|
|
2112
|
+
ingestRaw(baseDelegatedResult.value, true);
|
|
2113
|
+
} else {
|
|
2114
|
+
console.warn("[enumerateDepositsByUser] base delegated enumeration failed", baseDelegatedResult.reason);
|
|
2115
|
+
}
|
|
2116
|
+
if (ephemeralResult.status === "fulfilled" && ephemeralProgram) {
|
|
2117
|
+
ingestAnchor(ephemeralResult.value, true);
|
|
2118
|
+
} else if (ephemeralProgram && ephemeralResult.status === "rejected") {
|
|
2119
|
+
console.warn("[enumerateDepositsByUser] ephemeral enumeration failed", ephemeralResult.reason);
|
|
2120
|
+
}
|
|
2121
|
+
return Array.from(byPda.values());
|
|
2122
|
+
}
|
|
2123
|
+
|
|
1892
2124
|
// src/kamino.ts
|
|
1893
2125
|
var KAMINO_RESERVE_DISCRIMINATOR = Buffer.from([
|
|
1894
2126
|
43,
|
|
@@ -1902,14 +2134,24 @@ var KAMINO_RESERVE_DISCRIMINATOR = Buffer.from([
|
|
|
1902
2134
|
]);
|
|
1903
2135
|
var KAMINO_FRACTION_BITS = 60n;
|
|
1904
2136
|
var KAMINO_FRACTION_SCALE = 1n << KAMINO_FRACTION_BITS;
|
|
2137
|
+
function bytesEqual(a, b) {
|
|
2138
|
+
if (a.length !== b.length)
|
|
2139
|
+
return false;
|
|
2140
|
+
for (let i = 0;i < a.length; i++) {
|
|
2141
|
+
if (a[i] !== b[i])
|
|
2142
|
+
return false;
|
|
2143
|
+
}
|
|
2144
|
+
return true;
|
|
2145
|
+
}
|
|
2146
|
+
var KAMINO_DISCRIMINATOR_OFFSET = 8;
|
|
1905
2147
|
var KAMINO_RESERVE_LAYOUT_OFFSETS = {
|
|
1906
|
-
liquidityAvailableAmount: 216,
|
|
1907
|
-
liquidityBorrowedAmountSf: 224,
|
|
1908
|
-
liquidityMintDecimals: 264,
|
|
1909
|
-
liquidityAccumulatedProtocolFeesSf: 336,
|
|
1910
|
-
liquidityAccumulatedReferrerFeesSf: 352,
|
|
1911
|
-
liquidityPendingReferrerFeesSf: 368,
|
|
1912
|
-
collateralMintTotalSupply: 2584
|
|
2148
|
+
liquidityAvailableAmount: KAMINO_DISCRIMINATOR_OFFSET + 216,
|
|
2149
|
+
liquidityBorrowedAmountSf: KAMINO_DISCRIMINATOR_OFFSET + 224,
|
|
2150
|
+
liquidityMintDecimals: KAMINO_DISCRIMINATOR_OFFSET + 264,
|
|
2151
|
+
liquidityAccumulatedProtocolFeesSf: KAMINO_DISCRIMINATOR_OFFSET + 336,
|
|
2152
|
+
liquidityAccumulatedReferrerFeesSf: KAMINO_DISCRIMINATOR_OFFSET + 352,
|
|
2153
|
+
liquidityPendingReferrerFeesSf: KAMINO_DISCRIMINATOR_OFFSET + 368,
|
|
2154
|
+
collateralMintTotalSupply: KAMINO_DISCRIMINATOR_OFFSET + 2584
|
|
1913
2155
|
};
|
|
1914
2156
|
function readUint64LE(data, offset) {
|
|
1915
2157
|
return data.readBigUInt64LE(offset);
|
|
@@ -1929,22 +2171,22 @@ function divCeil(numerator, denominator) {
|
|
|
1929
2171
|
return (numerator + denominator - 1n) / denominator;
|
|
1930
2172
|
}
|
|
1931
2173
|
function parseKaminoReserveSnapshotFromAccountData(args) {
|
|
1932
|
-
const {
|
|
1933
|
-
|
|
2174
|
+
const { reserve, tokenMint } = args;
|
|
2175
|
+
const data = Buffer.isBuffer(args.data) ? args.data : Buffer.from(args.data);
|
|
2176
|
+
if (data.length < 8 || !bytesEqual(data.subarray(0, 8), KAMINO_RESERVE_DISCRIMINATOR)) {
|
|
1934
2177
|
throw new Error(`Kamino reserve ${reserve.toBase58()} has an invalid discriminator`);
|
|
1935
2178
|
}
|
|
1936
|
-
const accountData = data.subarray(8);
|
|
1937
2179
|
const requiredLength = KAMINO_RESERVE_LAYOUT_OFFSETS.collateralMintTotalSupply + 8;
|
|
1938
|
-
if (
|
|
2180
|
+
if (data.length < requiredLength) {
|
|
1939
2181
|
throw new Error(`Kamino reserve ${reserve.toBase58()} is too small: expected at least ${requiredLength} bytes`);
|
|
1940
2182
|
}
|
|
1941
|
-
const liquidityAvailableAmount = readUint64LE(
|
|
1942
|
-
const liquidityBorrowedAmountSf = readUint128LE(
|
|
1943
|
-
const liquidityAccumulatedProtocolFeesSf = readUint128LE(
|
|
1944
|
-
const liquidityAccumulatedReferrerFeesSf = readUint128LE(
|
|
1945
|
-
const liquidityPendingReferrerFeesSf = readUint128LE(
|
|
1946
|
-
const collateralSupplyRaw = readUint64LE(
|
|
1947
|
-
const liquidityDecimals = Number(readUint64LE(
|
|
2183
|
+
const liquidityAvailableAmount = readUint64LE(data, KAMINO_RESERVE_LAYOUT_OFFSETS.liquidityAvailableAmount);
|
|
2184
|
+
const liquidityBorrowedAmountSf = readUint128LE(data, KAMINO_RESERVE_LAYOUT_OFFSETS.liquidityBorrowedAmountSf);
|
|
2185
|
+
const liquidityAccumulatedProtocolFeesSf = readUint128LE(data, KAMINO_RESERVE_LAYOUT_OFFSETS.liquidityAccumulatedProtocolFeesSf);
|
|
2186
|
+
const liquidityAccumulatedReferrerFeesSf = readUint128LE(data, KAMINO_RESERVE_LAYOUT_OFFSETS.liquidityAccumulatedReferrerFeesSf);
|
|
2187
|
+
const liquidityPendingReferrerFeesSf = readUint128LE(data, KAMINO_RESERVE_LAYOUT_OFFSETS.liquidityPendingReferrerFeesSf);
|
|
2188
|
+
const collateralSupplyRaw = readUint64LE(data, KAMINO_RESERVE_LAYOUT_OFFSETS.collateralMintTotalSupply);
|
|
2189
|
+
const liquidityDecimals = Number(readUint64LE(data, KAMINO_RESERVE_LAYOUT_OFFSETS.liquidityMintDecimals));
|
|
1948
2190
|
const grossLiquiditySupplyScaled = (liquidityAvailableAmount << KAMINO_FRACTION_BITS) + liquidityBorrowedAmountSf;
|
|
1949
2191
|
const totalFeeAmountScaled = liquidityAccumulatedProtocolFeesSf + liquidityAccumulatedReferrerFeesSf + liquidityPendingReferrerFeesSf;
|
|
1950
2192
|
return {
|
|
@@ -2019,41 +2261,98 @@ async function fetchKaminoReserveSnapshot(args) {
|
|
|
2019
2261
|
}
|
|
2020
2262
|
|
|
2021
2263
|
// src/pda.ts
|
|
2022
|
-
import { PublicKey as
|
|
2264
|
+
import { PublicKey as PublicKey4 } from "@solana/web3.js";
|
|
2023
2265
|
|
|
2024
2266
|
// src/utils.ts
|
|
2267
|
+
import { PublicKey as PublicKey3 } from "@solana/web3.js";
|
|
2268
|
+
function prettyStringify(obj) {
|
|
2269
|
+
const json = JSON.stringify(obj, (_key, value) => {
|
|
2270
|
+
if (value instanceof PublicKey3)
|
|
2271
|
+
return value.toBase58();
|
|
2272
|
+
if (typeof value === "bigint")
|
|
2273
|
+
return value.toString();
|
|
2274
|
+
return value;
|
|
2275
|
+
}, 2);
|
|
2276
|
+
return json.replace(/\[\s+(\d[\d,\s]*\d)\s+\]/g, (_match, inner) => {
|
|
2277
|
+
const items = inner.split(/,\s*/).map((s) => s.trim());
|
|
2278
|
+
return `[${items.join(", ")}]`;
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
function waitForAccountOwnerChange(connection, account, expectedOwner, timeoutMs = 15000, intervalMs = 1000) {
|
|
2282
|
+
let skipWait;
|
|
2283
|
+
const subId = connection.onAccountChange(account, (accountInfo) => {
|
|
2284
|
+
if (accountInfo.owner.equals(expectedOwner) && skipWait) {
|
|
2285
|
+
console.log(`waitForAccountOwnerChange: ${account.toString()} - short-circuit polling wait`);
|
|
2286
|
+
skipWait();
|
|
2287
|
+
}
|
|
2288
|
+
}, { commitment: "confirmed" });
|
|
2289
|
+
const cleanup = async () => {
|
|
2290
|
+
await connection.removeAccountChangeListener(subId);
|
|
2291
|
+
};
|
|
2292
|
+
const wait = async () => {
|
|
2293
|
+
try {
|
|
2294
|
+
const start = Date.now();
|
|
2295
|
+
while (Date.now() - start < timeoutMs) {
|
|
2296
|
+
const info = await connection.getAccountInfo(account, "confirmed");
|
|
2297
|
+
if (info && info.owner.equals(expectedOwner)) {
|
|
2298
|
+
console.log(`waitForAccountOwnerChange: ${account.toString()} appeared with owner ${expectedOwner.toString()} after ${Date.now() - start}ms`);
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
if (info) {
|
|
2302
|
+
console.log(`waitForAccountOwnerChange: ${account.toString()} exists but owner is ${info.owner.toString()}, expected ${expectedOwner.toString()}`);
|
|
2303
|
+
}
|
|
2304
|
+
await new Promise((r) => {
|
|
2305
|
+
skipWait = r;
|
|
2306
|
+
setTimeout(r, intervalMs);
|
|
2307
|
+
});
|
|
2308
|
+
}
|
|
2309
|
+
throw new Error(`waitForAccountOwnerChange: ${account.toString()} did not appear with owner ${expectedOwner.toString()} after ${timeoutMs}ms`);
|
|
2310
|
+
} finally {
|
|
2311
|
+
await cleanup();
|
|
2312
|
+
}
|
|
2313
|
+
};
|
|
2314
|
+
return { wait, cancel: cleanup };
|
|
2315
|
+
}
|
|
2025
2316
|
async function sha256hash(data) {
|
|
2026
2317
|
const encoded = Uint8Array.from(new TextEncoder().encode(data));
|
|
2027
2318
|
const hash = await crypto.subtle.digest("SHA-256", encoded);
|
|
2028
2319
|
return Array.from(new Uint8Array(hash));
|
|
2029
2320
|
}
|
|
2321
|
+
function validateUsername(username) {
|
|
2322
|
+
if (!username || username.length < 5 || username.length > 32) {
|
|
2323
|
+
throw new Error("Username must be between 5 and 32 characters");
|
|
2324
|
+
}
|
|
2325
|
+
if (!/^[a-z0-9_]+$/.test(username)) {
|
|
2326
|
+
throw new Error("Username can only contain lowercase alphanumeric characters and underscores");
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2030
2329
|
|
|
2031
2330
|
// src/pda.ts
|
|
2032
2331
|
function findDepositPda(user, tokenMint, programId = PROGRAM_ID) {
|
|
2033
|
-
return
|
|
2332
|
+
return PublicKey4.findProgramAddressSync([DEPOSIT_SEED_BYTES, user.toBuffer(), tokenMint.toBuffer()], programId);
|
|
2034
2333
|
}
|
|
2035
2334
|
async function findUsernameDepositPda(username, tokenMint, programId = PROGRAM_ID) {
|
|
2036
2335
|
const usernameHash = await sha256hash(username);
|
|
2037
|
-
return
|
|
2336
|
+
return PublicKey4.findProgramAddressSync([
|
|
2038
2337
|
USERNAME_DEPOSIT_SEED_BYTES,
|
|
2039
2338
|
Buffer.from(usernameHash),
|
|
2040
2339
|
tokenMint.toBuffer()
|
|
2041
2340
|
], programId);
|
|
2042
2341
|
}
|
|
2043
2342
|
function findVaultPda(tokenMint, programId = PROGRAM_ID) {
|
|
2044
|
-
return
|
|
2343
|
+
return PublicKey4.findProgramAddressSync([VAULT_SEED_BYTES, tokenMint.toBuffer()], programId);
|
|
2045
2344
|
}
|
|
2046
2345
|
function findPermissionPda(account, permissionProgramId = PERMISSION_PROGRAM_ID) {
|
|
2047
|
-
return
|
|
2346
|
+
return PublicKey4.findProgramAddressSync([PERMISSION_SEED_BYTES, account.toBuffer()], permissionProgramId);
|
|
2048
2347
|
}
|
|
2049
2348
|
function findDelegationRecordPda(account, delegationProgramId = DELEGATION_PROGRAM_ID) {
|
|
2050
|
-
return
|
|
2349
|
+
return PublicKey4.findProgramAddressSync([Buffer.from("delegation"), account.toBuffer()], delegationProgramId);
|
|
2051
2350
|
}
|
|
2052
2351
|
function findDelegationMetadataPda(account, delegationProgramId = DELEGATION_PROGRAM_ID) {
|
|
2053
|
-
return
|
|
2352
|
+
return PublicKey4.findProgramAddressSync([Buffer.from("delegation-metadata"), account.toBuffer()], delegationProgramId);
|
|
2054
2353
|
}
|
|
2055
2354
|
function findBufferPda(account, ownerProgramId = PROGRAM_ID) {
|
|
2056
|
-
return
|
|
2355
|
+
return PublicKey4.findProgramAddressSync([Buffer.from("buffer"), account.toBuffer()], ownerProgramId);
|
|
2057
2356
|
}
|
|
2058
2357
|
|
|
2059
2358
|
// src/wallet-adapter.ts
|
|
@@ -2192,61 +2491,1621 @@ function createKeypairMessageSigner(keypair) {
|
|
|
2192
2491
|
};
|
|
2193
2492
|
}
|
|
2194
2493
|
|
|
2195
|
-
// src/
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2494
|
+
// src/actions/shieldTokens.ts
|
|
2495
|
+
import { NATIVE_MINT as NATIVE_MINT2 } from "@solana/spl-token";
|
|
2496
|
+
import {
|
|
2497
|
+
Transaction as Transaction4
|
|
2498
|
+
} from "@solana/web3.js";
|
|
2499
|
+
|
|
2500
|
+
// src/rent-estimate.ts
|
|
2501
|
+
import { ACCOUNT_SIZE, getAssociatedTokenAddressSync } from "@solana/spl-token";
|
|
2502
|
+
var DISCRIMINATOR_SIZE = 8;
|
|
2503
|
+
var PUBLIC_KEY_SIZE = 32;
|
|
2504
|
+
var U64_SIZE = 8;
|
|
2505
|
+
var U8_SIZE = 1;
|
|
2506
|
+
var BOOL_SIZE = 1;
|
|
2507
|
+
var VEC_PREFIX_SIZE = 4;
|
|
2508
|
+
var MAGICBLOCK_UNDELEGATE_SESSION_FEE_LAMPORTS = 300000;
|
|
2509
|
+
var DEPOSIT_ACCOUNT_SIZE2 = DISCRIMINATOR_SIZE + PUBLIC_KEY_SIZE + PUBLIC_KEY_SIZE + U64_SIZE;
|
|
2510
|
+
var VAULT_ACCOUNT_SIZE = DISCRIMINATOR_SIZE + U8_SIZE;
|
|
2511
|
+
var PERMISSION_ACCOUNT_SIZE = 567;
|
|
2512
|
+
var DELEGATION_RECORD_ACCOUNT_SIZE = DISCRIMINATOR_SIZE + PUBLIC_KEY_SIZE + PUBLIC_KEY_SIZE + U64_SIZE * 3;
|
|
2513
|
+
function getDelegationMetadataAccountSize(seeds) {
|
|
2514
|
+
return DISCRIMINATOR_SIZE + U64_SIZE + BOOL_SIZE + PUBLIC_KEY_SIZE + VEC_PREFIX_SIZE + seeds.reduce((total, seed) => total + VEC_PREFIX_SIZE + seed.byteLength, 0);
|
|
2515
|
+
}
|
|
2516
|
+
async function estimateNewAccountRentLamports(params) {
|
|
2517
|
+
if (params.accounts.length === 0) {
|
|
2518
|
+
return 0;
|
|
2519
|
+
}
|
|
2520
|
+
const spaces = Array.from(new Set(params.accounts.map((account) => account.space)));
|
|
2521
|
+
const [accountInfos, rentEntries] = await Promise.all([
|
|
2522
|
+
params.connection.getMultipleAccountsInfo(params.accounts.map((account) => account.address)),
|
|
2523
|
+
Promise.all(spaces.map(async (space) => [
|
|
2524
|
+
space,
|
|
2525
|
+
await params.connection.getMinimumBalanceForRentExemption(space)
|
|
2526
|
+
]))
|
|
2527
|
+
]);
|
|
2528
|
+
const rentBySpace = new Map(rentEntries);
|
|
2529
|
+
return params.accounts.reduce((total, account, index) => {
|
|
2530
|
+
if (!account.forceCreate && accountInfos[index]) {
|
|
2531
|
+
return total;
|
|
2532
|
+
}
|
|
2533
|
+
return total + (rentBySpace.get(account.space) ?? 0);
|
|
2534
|
+
}, 0);
|
|
2535
|
+
}
|
|
2536
|
+
async function estimateExistingAccountLamports(params) {
|
|
2537
|
+
if (params.accounts.length === 0) {
|
|
2538
|
+
return 0;
|
|
2539
|
+
}
|
|
2540
|
+
const accountInfos = await params.connection.getMultipleAccountsInfo(params.accounts);
|
|
2541
|
+
return accountInfos.reduce((total, accountInfo) => total + (accountInfo?.lamports ?? 0), 0);
|
|
2542
|
+
}
|
|
2543
|
+
async function estimateDepositRentLamports(params) {
|
|
2544
|
+
return estimateNewAccountRentLamports({
|
|
2545
|
+
connection: params.connection,
|
|
2546
|
+
accounts: [
|
|
2547
|
+
{
|
|
2548
|
+
address: params.depositPda,
|
|
2549
|
+
space: DEPOSIT_ACCOUNT_SIZE2,
|
|
2550
|
+
forceCreate: params.forceCreate
|
|
2551
|
+
}
|
|
2552
|
+
]
|
|
2210
2553
|
});
|
|
2211
2554
|
}
|
|
2212
|
-
function
|
|
2213
|
-
const
|
|
2214
|
-
const
|
|
2215
|
-
|
|
2216
|
-
|
|
2555
|
+
async function estimateModifyBalanceRentLamports(params) {
|
|
2556
|
+
const [vaultPda] = findVaultPda(params.tokenMint);
|
|
2557
|
+
const userTokenAccount = getAssociatedTokenAddressSync(params.tokenMint, params.user);
|
|
2558
|
+
const vaultTokenAccount = getAssociatedTokenAddressSync(params.tokenMint, vaultPda, true);
|
|
2559
|
+
const accounts = [
|
|
2560
|
+
{ address: vaultPda, space: VAULT_ACCOUNT_SIZE },
|
|
2561
|
+
{ address: vaultTokenAccount, space: ACCOUNT_SIZE }
|
|
2562
|
+
];
|
|
2563
|
+
if (!params.isNativeSol) {
|
|
2564
|
+
accounts.push({ address: userTokenAccount, space: ACCOUNT_SIZE });
|
|
2565
|
+
}
|
|
2566
|
+
return estimateNewAccountRentLamports({
|
|
2567
|
+
connection: params.connection,
|
|
2568
|
+
accounts
|
|
2217
2569
|
});
|
|
2218
|
-
|
|
2219
|
-
|
|
2570
|
+
}
|
|
2571
|
+
async function estimatePermissionRentLamports(params) {
|
|
2572
|
+
return estimateNewAccountRentLamports({
|
|
2573
|
+
connection: params.connection,
|
|
2574
|
+
accounts: [
|
|
2575
|
+
{
|
|
2576
|
+
address: params.permissionPda,
|
|
2577
|
+
space: PERMISSION_ACCOUNT_SIZE,
|
|
2578
|
+
forceCreate: params.forceCreate
|
|
2579
|
+
}
|
|
2580
|
+
]
|
|
2220
2581
|
});
|
|
2221
|
-
return new Program(telegram_private_transfer_default, baseProvider);
|
|
2222
2582
|
}
|
|
2223
|
-
function
|
|
2224
|
-
|
|
2583
|
+
async function estimateDepositDelegationRentLamports(params) {
|
|
2584
|
+
const [delegationRecordPda] = findDelegationRecordPda(params.depositPda);
|
|
2585
|
+
const [delegationMetadataPda] = findDelegationMetadataPda(params.depositPda);
|
|
2586
|
+
const metadataSize = getDelegationMetadataAccountSize([
|
|
2587
|
+
DEPOSIT_SEED_BYTES,
|
|
2588
|
+
params.user.toBuffer(),
|
|
2589
|
+
params.tokenMint.toBuffer()
|
|
2590
|
+
]);
|
|
2591
|
+
return estimateNewAccountRentLamports({
|
|
2592
|
+
connection: params.connection,
|
|
2593
|
+
accounts: [
|
|
2594
|
+
{
|
|
2595
|
+
address: delegationRecordPda,
|
|
2596
|
+
space: DELEGATION_RECORD_ACCOUNT_SIZE,
|
|
2597
|
+
forceCreate: params.forceCreate
|
|
2598
|
+
},
|
|
2599
|
+
{
|
|
2600
|
+
address: delegationMetadataPda,
|
|
2601
|
+
space: metadataSize,
|
|
2602
|
+
forceCreate: params.forceCreate
|
|
2603
|
+
}
|
|
2604
|
+
]
|
|
2605
|
+
});
|
|
2225
2606
|
}
|
|
2226
|
-
function
|
|
2227
|
-
|
|
2228
|
-
|
|
2607
|
+
async function estimateDepositDelegationRentCreditLamports(params) {
|
|
2608
|
+
const [delegationRecordPda] = findDelegationRecordPda(params.depositPda);
|
|
2609
|
+
const [delegationMetadataPda] = findDelegationMetadataPda(params.depositPda);
|
|
2610
|
+
const delegationAccountLamports = await estimateExistingAccountLamports({
|
|
2611
|
+
connection: params.connection,
|
|
2612
|
+
accounts: [delegationRecordPda, delegationMetadataPda]
|
|
2613
|
+
});
|
|
2614
|
+
const refundableLamports = Math.max(0, delegationAccountLamports - MAGICBLOCK_UNDELEGATE_SESSION_FEE_LAMPORTS);
|
|
2615
|
+
return refundableLamports === 0 ? 0 : -refundableLamports;
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
// src/checks/enshureChecks.ts
|
|
2619
|
+
import { DELEGATION_PROGRAM_ID as DELEGATION_PROGRAM_ID2 } from "@magicblock-labs/ephemeral-rollups-sdk";
|
|
2620
|
+
var ENSURE_FETCH_MAX_ATTEMPTS = 3;
|
|
2621
|
+
var ENSURE_FETCH_INITIAL_DELAY_MS = 150;
|
|
2622
|
+
var ENSURE_FETCH_MAX_DELAY_MS = 1000;
|
|
2623
|
+
var ENSURE_FETCH_BACKOFF_MULTIPLIER = 2;
|
|
2624
|
+
var ENSURE_FETCH_JITTER_RATIO = 0.2;
|
|
2625
|
+
var MULTIPLE_ACCOUNTS_CHUNK_SIZE = 5;
|
|
2626
|
+
async function processEnsureChecks(baseConnection, perConnection, ensure) {
|
|
2627
|
+
const mergedChecks = new Map;
|
|
2628
|
+
for (const { address, delegated, passNotExist, label } of ensure) {
|
|
2629
|
+
const addressKey = address.toBase58();
|
|
2630
|
+
const existing = mergedChecks.get(addressKey);
|
|
2631
|
+
if (!existing) {
|
|
2632
|
+
mergedChecks.set(addressKey, {
|
|
2633
|
+
address,
|
|
2634
|
+
delegated,
|
|
2635
|
+
passNotExist,
|
|
2636
|
+
labels: [label]
|
|
2637
|
+
});
|
|
2638
|
+
continue;
|
|
2639
|
+
}
|
|
2640
|
+
existing.labels.push(label);
|
|
2641
|
+
if (existing.delegated !== delegated) {
|
|
2642
|
+
throw new Error(`Conflicting ensure delegation requirements: ${existing.labels.join(", ")} - ${addressKey}`);
|
|
2643
|
+
}
|
|
2644
|
+
existing.passNotExist = existing.passNotExist && passNotExist;
|
|
2229
2645
|
}
|
|
2230
|
-
|
|
2231
|
-
|
|
2646
|
+
const cache = {
|
|
2647
|
+
baseAccountInfos: new Map,
|
|
2648
|
+
delegationStatuses: new Map,
|
|
2649
|
+
ephemeralAccountInfos: new Map
|
|
2650
|
+
};
|
|
2651
|
+
const uniqueChecks = [...mergedChecks.values()];
|
|
2652
|
+
await primeEnsureBatchCache(baseConnection, perConnection, uniqueChecks, cache);
|
|
2653
|
+
for (const { address, delegated, passNotExist, labels } of uniqueChecks) {
|
|
2654
|
+
const displayLabels = labels.join(", ");
|
|
2655
|
+
if (delegated) {
|
|
2656
|
+
await ensureDelegated(baseConnection, perConnection, address, displayLabels, undefined, cache);
|
|
2657
|
+
} else {
|
|
2658
|
+
await ensureNotDelegated(baseConnection, perConnection, address, displayLabels, passNotExist, cache);
|
|
2659
|
+
}
|
|
2232
2660
|
}
|
|
2233
|
-
return BigInt(value);
|
|
2234
2661
|
}
|
|
2235
|
-
async function
|
|
2236
|
-
const
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2662
|
+
async function primeEnsureBatchCache(baseConnection, perConnection, checks, cache) {
|
|
2663
|
+
const addresses = checks.map((check) => check.address);
|
|
2664
|
+
const [baseAccountInfos, ephemeralAccountInfos] = await Promise.all([
|
|
2665
|
+
getMultipleAccountsInfoWithRetry(baseConnection, addresses, "base-getMultipleAccountsInfo"),
|
|
2666
|
+
getMultipleAccountsInfoWithRetry(perConnection, addresses, "ephemeral-getMultipleAccountsInfo")
|
|
2667
|
+
]);
|
|
2668
|
+
for (let index = 0;index < checks.length; index += 1) {
|
|
2669
|
+
const addressKey = checks[index].address.toBase58();
|
|
2670
|
+
cache.baseAccountInfos.set(addressKey, baseAccountInfos[index] ?? null);
|
|
2671
|
+
cache.ephemeralAccountInfos.set(addressKey, ephemeralAccountInfos[index] ?? null);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
async function getMultipleAccountsInfoWithRetry(connection, accounts, label) {
|
|
2675
|
+
if (accounts.length === 0) {
|
|
2676
|
+
return [];
|
|
2677
|
+
}
|
|
2678
|
+
const chunks = [];
|
|
2679
|
+
for (let start = 0;start < accounts.length; start += MULTIPLE_ACCOUNTS_CHUNK_SIZE) {
|
|
2680
|
+
chunks.push(accounts.slice(start, start + MULTIPLE_ACCOUNTS_CHUNK_SIZE));
|
|
2681
|
+
}
|
|
2682
|
+
const chunkResults = await Promise.all(chunks.map((chunk, index) => runEnsureFetchWithRetry(`${label}-chunk-${index + 1}`, () => connection.getMultipleAccountsInfo(chunk))));
|
|
2683
|
+
return chunkResults.flat();
|
|
2684
|
+
}
|
|
2685
|
+
async function ensureNotDelegated(baseConnection, perConnection, account, name, passNotExist, cache) {
|
|
2686
|
+
const baseAccountInfo = await getEnsureBaseAccountInfo(baseConnection, account, cache);
|
|
2687
|
+
if (!baseAccountInfo) {
|
|
2688
|
+
if (passNotExist) {
|
|
2689
|
+
return;
|
|
2242
2690
|
}
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
throw new Error(`Kamino reserve metrics request failed with status ${response.status}`);
|
|
2691
|
+
const displayName2 = formatEnsureDisplayName(name);
|
|
2692
|
+
throw new Error(`Account is not exists: ${displayName2}${account.toString()}`);
|
|
2246
2693
|
}
|
|
2247
|
-
const
|
|
2248
|
-
|
|
2249
|
-
|
|
2694
|
+
const isDelegated = baseAccountInfo.owner.equals(DELEGATION_PROGRAM_ID2);
|
|
2695
|
+
const displayName = formatEnsureDisplayName(name);
|
|
2696
|
+
if (isDelegated) {
|
|
2697
|
+
const [ephemeralAccountInfo, delegationStatus] = await Promise.all([
|
|
2698
|
+
getEnsureEphemeralAccountInfo(perConnection, account, cache),
|
|
2699
|
+
getEnsureDelegationStatus(perConnection.rpcEndpoint, account, cache)
|
|
2700
|
+
]);
|
|
2701
|
+
console.error(`Account is delegated to ER: ${displayName}${account.toString()}`);
|
|
2702
|
+
console.error("/getDelegationStatus", JSON.stringify(delegationStatus, null, 2));
|
|
2703
|
+
console.error("baseAccountInfo", prettyStringify(baseAccountInfo));
|
|
2704
|
+
console.error("ephemeralAccountInfo", prettyStringify(ephemeralAccountInfo));
|
|
2705
|
+
const expectedValidator = getErValidatorForRpcEndpoint(perConnection.rpcEndpoint);
|
|
2706
|
+
const authority = delegationStatus.result?.delegationRecord?.authority;
|
|
2707
|
+
if (authority && authority !== expectedValidator.toString()) {
|
|
2708
|
+
console.error(`Account is delegated on wrong validator: ${displayName}${account.toString()} - validator: ${authority}`);
|
|
2709
|
+
}
|
|
2710
|
+
throw new Error(`Account is delegated to ER: ${displayName}${account.toString()}`);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
async function ensureDelegated(baseConnection, perConnection, account, name, skipValidatorCheck, cache) {
|
|
2714
|
+
const baseAccountInfo = await getEnsureBaseAccountInfo(baseConnection, account, cache);
|
|
2715
|
+
if (!baseAccountInfo) {
|
|
2716
|
+
const displayName2 = formatEnsureDisplayName(name);
|
|
2717
|
+
throw new Error(`Account is not exists: ${displayName2}${account.toString()}`);
|
|
2718
|
+
}
|
|
2719
|
+
const isDelegated = baseAccountInfo.owner.equals(DELEGATION_PROGRAM_ID2);
|
|
2720
|
+
const displayName = formatEnsureDisplayName(name);
|
|
2721
|
+
if (!isDelegated) {
|
|
2722
|
+
const [ephemeralAccountInfo, delegationStatus] = await Promise.all([
|
|
2723
|
+
getEnsureEphemeralAccountInfo(perConnection, account, cache),
|
|
2724
|
+
getEnsureDelegationStatus(perConnection.rpcEndpoint, account, cache)
|
|
2725
|
+
]);
|
|
2726
|
+
console.error(`Account is not delegated to ER: ${displayName}${account.toString()}`);
|
|
2727
|
+
console.error("/getDelegationStatus:", JSON.stringify(delegationStatus, null, 2));
|
|
2728
|
+
console.error("baseAccountInfo", prettyStringify(baseAccountInfo));
|
|
2729
|
+
console.error("ephemeralAccountInfo", prettyStringify(ephemeralAccountInfo));
|
|
2730
|
+
throw new Error(`Account is not delegated to ER: ${displayName}${account.toString()}`);
|
|
2731
|
+
}
|
|
2732
|
+
if (!skipValidatorCheck) {
|
|
2733
|
+
const [ephemeralAccountInfo, delegationStatus] = await Promise.all([
|
|
2734
|
+
getEnsureEphemeralAccountInfo(perConnection, account, cache),
|
|
2735
|
+
getEnsureDelegationStatus(perConnection.rpcEndpoint, account, cache)
|
|
2736
|
+
]);
|
|
2737
|
+
if (delegationStatus.result.delegationRecord.authority !== getErValidatorForRpcEndpoint(perConnection.rpcEndpoint).toString()) {
|
|
2738
|
+
console.error(`Account is delegated on wrong validator: ${displayName}${account.toString()} - validator: ${delegationStatus.result.delegationRecord.authority}`);
|
|
2739
|
+
console.error("/getDelegationStatus:", JSON.stringify(delegationStatus, null, 2));
|
|
2740
|
+
console.error("baseAccountInfo", prettyStringify(baseAccountInfo));
|
|
2741
|
+
console.error("ephemeralAccountInfo", prettyStringify(ephemeralAccountInfo));
|
|
2742
|
+
throw new Error(`Account is delegated on wrong validator: ${displayName}${account.toString()} - validator: ${delegationStatus.result.delegationRecord.authority}`);
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
async function getEnsureBaseAccountInfo(baseConnection, account, cache) {
|
|
2747
|
+
const addressKey = account.toBase58();
|
|
2748
|
+
if (cache?.baseAccountInfos.has(addressKey)) {
|
|
2749
|
+
return cache.baseAccountInfos.get(addressKey) ?? null;
|
|
2750
|
+
}
|
|
2751
|
+
const baseAccountInfo = await baseConnection.getAccountInfo(account);
|
|
2752
|
+
cache?.baseAccountInfos.set(addressKey, baseAccountInfo);
|
|
2753
|
+
return baseAccountInfo;
|
|
2754
|
+
}
|
|
2755
|
+
async function getEnsureEphemeralAccountInfo(perConnection, account, cache) {
|
|
2756
|
+
const addressKey = account.toBase58();
|
|
2757
|
+
if (cache?.ephemeralAccountInfos.has(addressKey)) {
|
|
2758
|
+
return cache.ephemeralAccountInfos.get(addressKey) ?? null;
|
|
2759
|
+
}
|
|
2760
|
+
const ephemeralAccountInfo = await perConnection.getAccountInfo(account);
|
|
2761
|
+
cache?.ephemeralAccountInfos.set(addressKey, ephemeralAccountInfo);
|
|
2762
|
+
return ephemeralAccountInfo;
|
|
2763
|
+
}
|
|
2764
|
+
async function getEnsureDelegationStatus(perRpcEndpoint, account, cache) {
|
|
2765
|
+
const addressKey = account.toBase58();
|
|
2766
|
+
const cachedPromise = cache?.delegationStatuses.get(addressKey);
|
|
2767
|
+
if (cachedPromise) {
|
|
2768
|
+
return cachedPromise;
|
|
2769
|
+
}
|
|
2770
|
+
const request = getDelegationStatus(perRpcEndpoint, account);
|
|
2771
|
+
cache?.delegationStatuses.set(addressKey, request);
|
|
2772
|
+
return request;
|
|
2773
|
+
}
|
|
2774
|
+
async function runEnsureFetchWithRetry(label, task) {
|
|
2775
|
+
let nextDelayMs = ENSURE_FETCH_INITIAL_DELAY_MS;
|
|
2776
|
+
let lastError;
|
|
2777
|
+
for (let attempt = 1;attempt <= ENSURE_FETCH_MAX_ATTEMPTS; attempt += 1) {
|
|
2778
|
+
try {
|
|
2779
|
+
return await task();
|
|
2780
|
+
} catch (error) {
|
|
2781
|
+
lastError = error;
|
|
2782
|
+
if (attempt === ENSURE_FETCH_MAX_ATTEMPTS) {
|
|
2783
|
+
break;
|
|
2784
|
+
}
|
|
2785
|
+
console.warn(`[ensure] ${label} attempt ${attempt}/${ENSURE_FETCH_MAX_ATTEMPTS} failed: ${error?.message ?? String(error)}`);
|
|
2786
|
+
const jitter = nextDelayMs * ENSURE_FETCH_JITTER_RATIO;
|
|
2787
|
+
const jitteredDelay = Math.max(0, Math.round(nextDelayMs + (Math.random() * 2 - 1) * jitter));
|
|
2788
|
+
await new Promise((resolve) => setTimeout(resolve, jitteredDelay));
|
|
2789
|
+
nextDelayMs = Math.min(ENSURE_FETCH_MAX_DELAY_MS, Math.round(nextDelayMs * ENSURE_FETCH_BACKOFF_MULTIPLIER));
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
throw new Error(`[ensure] ${label} failed after ${ENSURE_FETCH_MAX_ATTEMPTS} attempts: ${lastError?.message ?? String(lastError)}`);
|
|
2793
|
+
}
|
|
2794
|
+
async function getDelegationStatus(perRpcEndpoint, account) {
|
|
2795
|
+
const body = JSON.stringify({
|
|
2796
|
+
jsonrpc: "2.0",
|
|
2797
|
+
id: 1,
|
|
2798
|
+
method: "getDelegationStatus",
|
|
2799
|
+
params: [account.toString()]
|
|
2800
|
+
});
|
|
2801
|
+
const options = {
|
|
2802
|
+
method: "POST",
|
|
2803
|
+
headers: { "Content-Type": "application/json" },
|
|
2804
|
+
body
|
|
2805
|
+
};
|
|
2806
|
+
const expectedValidator = getErValidatorForRpcEndpoint(perRpcEndpoint);
|
|
2807
|
+
const isMainnet = perRpcEndpoint.includes("mainnet-tee");
|
|
2808
|
+
const teeBaseUrl = isMainnet ? "https://mainnet-tee.magicblock.app/" : "https://tee.magicblock.app/";
|
|
2809
|
+
try {
|
|
2810
|
+
const teeRes = await fetch(teeBaseUrl, options);
|
|
2811
|
+
const teeData = await teeRes.json();
|
|
2812
|
+
if (teeData.result?.isDelegated) {
|
|
2813
|
+
return {
|
|
2814
|
+
...teeData,
|
|
2815
|
+
result: {
|
|
2816
|
+
...teeData.result,
|
|
2817
|
+
delegationRecord: {
|
|
2818
|
+
authority: expectedValidator.toString()
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
} catch (e) {
|
|
2824
|
+
console.error("[getDelegationStatus] TEE fetch failed, falling back to devnet-router: Options:", options, "Error:", e);
|
|
2825
|
+
}
|
|
2826
|
+
const routerBaseUrl = isMainnet ? "https://router.magicblock.app/" : "https://devnet-router.magicblock.app/";
|
|
2827
|
+
const res = await fetch(routerBaseUrl, options);
|
|
2828
|
+
const routerData = await res.json();
|
|
2829
|
+
if (routerData.error?.message?.includes(expectedValidator.toString())) {
|
|
2830
|
+
return {
|
|
2831
|
+
...routerData,
|
|
2832
|
+
result: {
|
|
2833
|
+
isDelegated: true,
|
|
2834
|
+
delegationRecord: {
|
|
2835
|
+
authority: expectedValidator.toString()
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
return routerData;
|
|
2841
|
+
}
|
|
2842
|
+
function formatEnsureDisplayName(name) {
|
|
2843
|
+
return name ? `${name} - ` : "";
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// src/wsol.ts
|
|
2847
|
+
import {
|
|
2848
|
+
createAssociatedTokenAccountIdempotentInstruction,
|
|
2849
|
+
createCloseAccountInstruction,
|
|
2850
|
+
createSyncNativeInstruction,
|
|
2851
|
+
getAssociatedTokenAddressSync as getAssociatedTokenAddressSync2,
|
|
2852
|
+
NATIVE_MINT
|
|
2853
|
+
} from "@solana/spl-token";
|
|
2854
|
+
import {
|
|
2855
|
+
SystemProgram
|
|
2856
|
+
} from "@solana/web3.js";
|
|
2857
|
+
function wrapSolToWsolIx({
|
|
2858
|
+
user,
|
|
2859
|
+
payer,
|
|
2860
|
+
lamports
|
|
2861
|
+
}) {
|
|
2862
|
+
const wsolAta = getAssociatedTokenAddressSync2(NATIVE_MINT, user);
|
|
2863
|
+
return [
|
|
2864
|
+
createAssociatedTokenAccountIdempotentInstruction(payer, wsolAta, user, NATIVE_MINT),
|
|
2865
|
+
SystemProgram.transfer({
|
|
2866
|
+
fromPubkey: user,
|
|
2867
|
+
toPubkey: wsolAta,
|
|
2868
|
+
lamports
|
|
2869
|
+
}),
|
|
2870
|
+
createSyncNativeInstruction(wsolAta)
|
|
2871
|
+
];
|
|
2872
|
+
}
|
|
2873
|
+
function createWsolAta({
|
|
2874
|
+
user,
|
|
2875
|
+
payer
|
|
2876
|
+
}) {
|
|
2877
|
+
const wsolAta = getAssociatedTokenAddressSync2(NATIVE_MINT, user);
|
|
2878
|
+
return createAssociatedTokenAccountIdempotentInstruction(payer, wsolAta, user, NATIVE_MINT);
|
|
2879
|
+
}
|
|
2880
|
+
function closeWsolAta({
|
|
2881
|
+
user,
|
|
2882
|
+
destination
|
|
2883
|
+
}) {
|
|
2884
|
+
const wsolAta = getAssociatedTokenAddressSync2(NATIVE_MINT, user);
|
|
2885
|
+
return createCloseAccountInstruction(wsolAta, destination, user);
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
// src/transaction-debug.ts
|
|
2889
|
+
import {
|
|
2890
|
+
SendTransactionError
|
|
2891
|
+
} from "@solana/web3.js";
|
|
2892
|
+
var MULTIPLE_ACCOUNTS_CHUNK_SIZE2 = 10;
|
|
2893
|
+
function describeAccountInfo(accountInfo) {
|
|
2894
|
+
if (!accountInfo) {
|
|
2895
|
+
return {
|
|
2896
|
+
exists: false,
|
|
2897
|
+
owner: null,
|
|
2898
|
+
executable: null,
|
|
2899
|
+
lamports: null,
|
|
2900
|
+
dataLength: null,
|
|
2901
|
+
rentEpoch: null
|
|
2902
|
+
};
|
|
2903
|
+
}
|
|
2904
|
+
return {
|
|
2905
|
+
exists: true,
|
|
2906
|
+
owner: accountInfo.owner.toBase58(),
|
|
2907
|
+
executable: accountInfo.executable,
|
|
2908
|
+
lamports: accountInfo.lamports,
|
|
2909
|
+
dataLength: accountInfo.data?.length ?? null,
|
|
2910
|
+
rentEpoch: accountInfo.rentEpoch ?? null
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
function extractInlineTransactionLogs(error) {
|
|
2914
|
+
const logs = error?.logs ?? error?.transactionLogs;
|
|
2915
|
+
return Array.isArray(logs) ? logs : undefined;
|
|
2916
|
+
}
|
|
2917
|
+
async function getTransactionErrorLogs(error, connection) {
|
|
2918
|
+
const inlineLogs = extractInlineTransactionLogs(error);
|
|
2919
|
+
if (inlineLogs) {
|
|
2920
|
+
return inlineLogs;
|
|
2921
|
+
}
|
|
2922
|
+
if (error instanceof SendTransactionError) {
|
|
2923
|
+
try {
|
|
2924
|
+
const fetchedLogs = await error.getLogs(connection);
|
|
2925
|
+
if (Array.isArray(fetchedLogs)) {
|
|
2926
|
+
return fetchedLogs;
|
|
2927
|
+
}
|
|
2928
|
+
} catch (fetchError) {
|
|
2929
|
+
console.error("[tx-debug] failed to fetch logs via SendTransactionError.getLogs()", {
|
|
2930
|
+
errorName: fetchError?.name ?? "UnknownError",
|
|
2931
|
+
errorMessage: fetchError?.message ?? String(fetchError)
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
function collectTransactionAccounts(tx) {
|
|
2938
|
+
const uniqueAccounts = new Map;
|
|
2939
|
+
if (tx.feePayer) {
|
|
2940
|
+
uniqueAccounts.set(tx.feePayer.toBase58(), tx.feePayer);
|
|
2941
|
+
}
|
|
2942
|
+
for (const instruction of tx.instructions) {
|
|
2943
|
+
uniqueAccounts.set(instruction.programId.toBase58(), instruction.programId);
|
|
2944
|
+
for (const key of instruction.keys) {
|
|
2945
|
+
uniqueAccounts.set(key.pubkey.toBase58(), key.pubkey);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
return [...uniqueAccounts.values()];
|
|
2949
|
+
}
|
|
2950
|
+
function describeTransaction(tx) {
|
|
2951
|
+
const compiledMessage = tx.compileMessage();
|
|
2952
|
+
const accountKeys = compiledMessage.accountKeys;
|
|
2953
|
+
const signedWritableCount = compiledMessage.header.numRequiredSignatures - compiledMessage.header.numReadonlySignedAccounts;
|
|
2954
|
+
const unsignedWritableEndExclusive = accountKeys.length - compiledMessage.header.numReadonlyUnsignedAccounts;
|
|
2955
|
+
const isAccountWritable = (index) => {
|
|
2956
|
+
if (index < compiledMessage.header.numRequiredSignatures) {
|
|
2957
|
+
return index < signedWritableCount;
|
|
2958
|
+
}
|
|
2959
|
+
return index < unsignedWritableEndExclusive;
|
|
2960
|
+
};
|
|
2961
|
+
return {
|
|
2962
|
+
feePayer: tx.feePayer?.toBase58() ?? null,
|
|
2963
|
+
recentBlockhash: tx.recentBlockhash ?? null,
|
|
2964
|
+
lastValidBlockHeight: tx.lastValidBlockHeight,
|
|
2965
|
+
signatureBase64: tx.signature ? Buffer.from(tx.signature).toString("base64") : null,
|
|
2966
|
+
instructionCount: tx.instructions.length,
|
|
2967
|
+
accountKeys: accountKeys.map((account, index) => ({
|
|
2968
|
+
index,
|
|
2969
|
+
pubkey: account.toBase58(),
|
|
2970
|
+
isSigner: index < compiledMessage.header.numRequiredSignatures,
|
|
2971
|
+
isWritable: isAccountWritable(index)
|
|
2972
|
+
})),
|
|
2973
|
+
instructions: tx.instructions.map((instruction, index) => ({
|
|
2974
|
+
index,
|
|
2975
|
+
programId: instruction.programId.toBase58(),
|
|
2976
|
+
programIdIndex: accountKeys.findIndex((account) => account.equals(instruction.programId)),
|
|
2977
|
+
dataLength: instruction.data.length,
|
|
2978
|
+
dataBase64: Buffer.from(instruction.data).toString("base64"),
|
|
2979
|
+
keys: instruction.keys.map((key, keyIndex) => ({
|
|
2980
|
+
index: keyIndex,
|
|
2981
|
+
accountIndex: accountKeys.findIndex((account) => account.equals(key.pubkey)),
|
|
2982
|
+
pubkey: key.pubkey.toBase58(),
|
|
2983
|
+
isSigner: key.isSigner,
|
|
2984
|
+
isWritable: key.isWritable
|
|
2985
|
+
}))
|
|
2986
|
+
}))
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
async function getMultipleAccountsInfoInChunks(connection, accounts) {
|
|
2990
|
+
if (accounts.length === 0) {
|
|
2991
|
+
return [];
|
|
2992
|
+
}
|
|
2993
|
+
const chunks = [];
|
|
2994
|
+
for (let start = 0;start < accounts.length; start += MULTIPLE_ACCOUNTS_CHUNK_SIZE2) {
|
|
2995
|
+
chunks.push(accounts.slice(start, start + MULTIPLE_ACCOUNTS_CHUNK_SIZE2));
|
|
2996
|
+
}
|
|
2997
|
+
const results = await Promise.all(chunks.map((chunk) => connection.getMultipleAccountsInfo(chunk)));
|
|
2998
|
+
return results.flat();
|
|
2999
|
+
}
|
|
3000
|
+
async function logFailedTransactionDiagnostics(params) {
|
|
3001
|
+
const { label, connection, tx, error, extraContext } = params;
|
|
3002
|
+
const txAccounts = collectTransactionAccounts(tx);
|
|
3003
|
+
const [errorLogs, accountInfos] = await Promise.all([
|
|
3004
|
+
getTransactionErrorLogs(error, connection),
|
|
3005
|
+
getMultipleAccountsInfoInChunks(connection, txAccounts)
|
|
3006
|
+
]);
|
|
3007
|
+
console.error(`[${label}] sendAndConfirm failed`, prettyStringify({
|
|
3008
|
+
errorName: error?.name ?? "UnknownError",
|
|
3009
|
+
errorMessage: error?.message ?? String(error),
|
|
3010
|
+
errorLogs,
|
|
3011
|
+
extraContext,
|
|
3012
|
+
transaction: describeTransaction(tx),
|
|
3013
|
+
accountSnapshots: txAccounts.map((account, index) => ({
|
|
3014
|
+
pubkey: account,
|
|
3015
|
+
...describeAccountInfo(accountInfos[index] ?? null)
|
|
3016
|
+
}))
|
|
3017
|
+
}));
|
|
3018
|
+
try {
|
|
3019
|
+
const simulation = await connection.simulateTransaction(tx);
|
|
3020
|
+
console.error(`[${label}] simulateTransaction result`, prettyStringify({
|
|
3021
|
+
contextSlot: simulation.context.slot,
|
|
3022
|
+
err: simulation.value.err,
|
|
3023
|
+
logs: simulation.value.logs,
|
|
3024
|
+
unitsConsumed: simulation.value.unitsConsumed,
|
|
3025
|
+
returnData: simulation.value.returnData
|
|
3026
|
+
}));
|
|
3027
|
+
} catch (simulationError) {
|
|
3028
|
+
const simulationLogs = await getTransactionErrorLogs(simulationError, connection);
|
|
3029
|
+
console.error(`[${label}] simulateTransaction failed`, prettyStringify({
|
|
3030
|
+
errorName: simulationError?.name ?? "UnknownError",
|
|
3031
|
+
errorMessage: simulationError?.message ?? String(simulationError),
|
|
3032
|
+
logs: simulationLogs
|
|
3033
|
+
}));
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
var COMMITMENT_ORDER = {
|
|
3037
|
+
processed: 0,
|
|
3038
|
+
confirmed: 1,
|
|
3039
|
+
finalized: 2
|
|
3040
|
+
};
|
|
3041
|
+
function meetsCommitment(observed, required) {
|
|
3042
|
+
if (!observed)
|
|
3043
|
+
return false;
|
|
3044
|
+
const o = COMMITMENT_ORDER[observed];
|
|
3045
|
+
const r = COMMITMENT_ORDER[required];
|
|
3046
|
+
return o !== undefined && r !== undefined && o >= r;
|
|
3047
|
+
}
|
|
3048
|
+
async function pollForLanding(connection, signature, lastValidBlockHeight, commitment) {
|
|
3049
|
+
const pollIntervalMs = 1500;
|
|
3050
|
+
const maxWallClockMs = 90000;
|
|
3051
|
+
const start = Date.now();
|
|
3052
|
+
while (Date.now() - start < maxWallClockMs) {
|
|
3053
|
+
let status = null;
|
|
3054
|
+
try {
|
|
3055
|
+
const res = await connection.getSignatureStatuses([signature], {
|
|
3056
|
+
searchTransactionHistory: true
|
|
3057
|
+
});
|
|
3058
|
+
status = res.value[0];
|
|
3059
|
+
} catch {}
|
|
3060
|
+
if (status?.err) {
|
|
3061
|
+
throw new Error(`Transaction failed on-chain: ${JSON.stringify(status.err)}`);
|
|
3062
|
+
}
|
|
3063
|
+
if (meetsCommitment(status?.confirmationStatus, commitment)) {
|
|
3064
|
+
return signature;
|
|
3065
|
+
}
|
|
3066
|
+
try {
|
|
3067
|
+
const currentHeight = await connection.getBlockHeight(commitment);
|
|
3068
|
+
if (currentHeight > lastValidBlockHeight && !status) {
|
|
3069
|
+
throw new Error(`Transaction dropped: ${signature} (blockhash expired without landing)`);
|
|
3070
|
+
}
|
|
3071
|
+
} catch {}
|
|
3072
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
3073
|
+
}
|
|
3074
|
+
throw new Error(`Transaction confirmation timed out after 90s: ${signature}`);
|
|
3075
|
+
}
|
|
3076
|
+
async function sendAndConfirmWithDiagnostics(params) {
|
|
3077
|
+
const { label, provider, tx, signers, rpcOptions, extraContext } = params;
|
|
3078
|
+
const connection = provider.connection;
|
|
3079
|
+
const wallet = provider.wallet;
|
|
3080
|
+
if (!wallet) {
|
|
3081
|
+
throw new Error(`[${label}] Provider has no wallet`);
|
|
3082
|
+
}
|
|
3083
|
+
const preflightCommitment = rpcOptions?.preflightCommitment ?? "confirmed";
|
|
3084
|
+
const confirmCommitment = preflightCommitment;
|
|
3085
|
+
const blockhashInfo = await connection.getLatestBlockhash(preflightCommitment);
|
|
3086
|
+
if (!tx.feePayer)
|
|
3087
|
+
tx.feePayer = wallet.publicKey;
|
|
3088
|
+
tx.recentBlockhash = blockhashInfo.blockhash;
|
|
3089
|
+
tx.lastValidBlockHeight = blockhashInfo.lastValidBlockHeight;
|
|
3090
|
+
let signedTx;
|
|
3091
|
+
try {
|
|
3092
|
+
signedTx = await wallet.signTransaction(tx);
|
|
3093
|
+
} catch (error) {
|
|
3094
|
+
throw error;
|
|
3095
|
+
}
|
|
3096
|
+
for (const extraSigner of signers ?? []) {
|
|
3097
|
+
signedTx.partialSign(extraSigner);
|
|
3098
|
+
}
|
|
3099
|
+
let signature;
|
|
3100
|
+
try {
|
|
3101
|
+
signature = await connection.sendRawTransaction(signedTx.serialize(), {
|
|
3102
|
+
skipPreflight: rpcOptions?.skipPreflight ?? false,
|
|
3103
|
+
preflightCommitment,
|
|
3104
|
+
maxRetries: rpcOptions?.maxRetries ?? 3
|
|
3105
|
+
});
|
|
3106
|
+
} catch (error) {
|
|
3107
|
+
await logFailedTransactionDiagnostics({
|
|
3108
|
+
label,
|
|
3109
|
+
connection,
|
|
3110
|
+
tx: signedTx,
|
|
3111
|
+
error,
|
|
3112
|
+
extraContext
|
|
3113
|
+
}).catch((debugError) => {
|
|
3114
|
+
console.error(`[${label}] failed to log transaction diagnostics`, {
|
|
3115
|
+
errorName: debugError?.name ?? "UnknownError",
|
|
3116
|
+
errorMessage: debugError?.message ?? String(debugError)
|
|
3117
|
+
});
|
|
3118
|
+
});
|
|
3119
|
+
throw error;
|
|
3120
|
+
}
|
|
3121
|
+
try {
|
|
3122
|
+
return await pollForLanding(connection, signature, blockhashInfo.lastValidBlockHeight, confirmCommitment);
|
|
3123
|
+
} catch (error) {
|
|
3124
|
+
await logFailedTransactionDiagnostics({
|
|
3125
|
+
label,
|
|
3126
|
+
connection,
|
|
3127
|
+
tx: signedTx,
|
|
3128
|
+
error,
|
|
3129
|
+
extraContext: { ...extraContext, signature }
|
|
3130
|
+
}).catch((debugError) => {
|
|
3131
|
+
console.error(`[${label}] failed to log transaction diagnostics`, {
|
|
3132
|
+
errorName: debugError?.name ?? "UnknownError",
|
|
3133
|
+
errorMessage: debugError?.message ?? String(debugError)
|
|
3134
|
+
});
|
|
3135
|
+
});
|
|
3136
|
+
throw error;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
// src/instructions/modifyBalance.ts
|
|
3141
|
+
import { BN } from "@coral-xyz/anchor";
|
|
3142
|
+
import {
|
|
3143
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
3144
|
+
getAssociatedTokenAddressSync as getAssociatedTokenAddressSync3,
|
|
3145
|
+
TOKEN_PROGRAM_ID
|
|
3146
|
+
} from "@solana/spl-token";
|
|
3147
|
+
import { SystemProgram as SystemProgram2 } from "@solana/web3.js";
|
|
3148
|
+
async function modifyBalanceIx(program, params) {
|
|
3149
|
+
const { user, tokenMint, amount, increase, payer, passNotExist } = params;
|
|
3150
|
+
const [depositPda] = findDepositPda(user, tokenMint);
|
|
3151
|
+
const [vaultPda] = findVaultPda(tokenMint);
|
|
3152
|
+
const userTokenAccount = getAssociatedTokenAddressSync3(tokenMint, user);
|
|
3153
|
+
const vaultTokenAccount = getAssociatedTokenAddressSync3(tokenMint, vaultPda, true);
|
|
3154
|
+
const kaminoAccounts = getKaminoModifyBalanceAccountsForTokenMint(tokenMint);
|
|
3155
|
+
const vaultCollateralTokenAccount = kaminoAccounts ? getAssociatedTokenAddressSync3(kaminoAccounts.reserveCollateralMint, vaultPda, true) : null;
|
|
3156
|
+
const accounts = {
|
|
3157
|
+
payer,
|
|
3158
|
+
user,
|
|
3159
|
+
vault: vaultPda,
|
|
3160
|
+
deposit: depositPda,
|
|
3161
|
+
userTokenAccount,
|
|
3162
|
+
vaultTokenAccount,
|
|
3163
|
+
tokenMint,
|
|
3164
|
+
tokenProgram: TOKEN_PROGRAM_ID,
|
|
3165
|
+
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
3166
|
+
systemProgram: SystemProgram2.programId
|
|
3167
|
+
};
|
|
3168
|
+
const remainingAccounts = kaminoAccounts && vaultCollateralTokenAccount ? [
|
|
3169
|
+
{
|
|
3170
|
+
pubkey: kaminoAccounts.lendingMarket,
|
|
3171
|
+
isSigner: false,
|
|
3172
|
+
isWritable: false
|
|
3173
|
+
},
|
|
3174
|
+
{
|
|
3175
|
+
pubkey: kaminoAccounts.lendingMarketAuthority,
|
|
3176
|
+
isSigner: false,
|
|
3177
|
+
isWritable: false
|
|
3178
|
+
},
|
|
3179
|
+
{
|
|
3180
|
+
pubkey: kaminoAccounts.reserve,
|
|
3181
|
+
isSigner: false,
|
|
3182
|
+
isWritable: true
|
|
3183
|
+
},
|
|
3184
|
+
{
|
|
3185
|
+
pubkey: kaminoAccounts.reserveLiquiditySupply,
|
|
3186
|
+
isSigner: false,
|
|
3187
|
+
isWritable: true
|
|
3188
|
+
},
|
|
3189
|
+
{
|
|
3190
|
+
pubkey: kaminoAccounts.reserveCollateralMint,
|
|
3191
|
+
isSigner: false,
|
|
3192
|
+
isWritable: true
|
|
3193
|
+
},
|
|
3194
|
+
{
|
|
3195
|
+
pubkey: vaultCollateralTokenAccount,
|
|
3196
|
+
isSigner: false,
|
|
3197
|
+
isWritable: true
|
|
3198
|
+
},
|
|
3199
|
+
{
|
|
3200
|
+
pubkey: kaminoAccounts.instructionSysvarAccount,
|
|
3201
|
+
isSigner: false,
|
|
3202
|
+
isWritable: false
|
|
3203
|
+
},
|
|
3204
|
+
{
|
|
3205
|
+
pubkey: kaminoAccounts.klendProgram,
|
|
3206
|
+
isSigner: false,
|
|
3207
|
+
isWritable: false
|
|
3208
|
+
}
|
|
3209
|
+
] : [];
|
|
3210
|
+
const ix = await program.methods.modifyBalance({ amount: new BN(amount.toString()), increase }).accountsPartial(accounts).remainingAccounts(remainingAccounts).instruction();
|
|
3211
|
+
return {
|
|
3212
|
+
ix,
|
|
3213
|
+
ensure: [
|
|
3214
|
+
{
|
|
3215
|
+
address: depositPda,
|
|
3216
|
+
delegated: false,
|
|
3217
|
+
passNotExist: passNotExist === undefined ? false : passNotExist,
|
|
3218
|
+
label: "modifyBalance-depositPda"
|
|
3219
|
+
}
|
|
3220
|
+
]
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
// src/instructions/initializeDeposit.ts
|
|
3225
|
+
import { TOKEN_PROGRAM_ID as TOKEN_PROGRAM_ID2 } from "@solana/spl-token";
|
|
3226
|
+
import { SystemProgram as SystemProgram3 } from "@solana/web3.js";
|
|
3227
|
+
async function initializeDepositIx(program, params) {
|
|
3228
|
+
const { user, tokenMint, payer } = params;
|
|
3229
|
+
const [depositPda] = findDepositPda(user, tokenMint);
|
|
3230
|
+
const ix = await program.methods.initializeDeposit().accountsPartial({
|
|
3231
|
+
payer,
|
|
3232
|
+
user,
|
|
3233
|
+
tokenMint,
|
|
3234
|
+
tokenProgram: TOKEN_PROGRAM_ID2,
|
|
3235
|
+
systemProgram: SystemProgram3.programId
|
|
3236
|
+
}).instruction();
|
|
3237
|
+
return {
|
|
3238
|
+
ix,
|
|
3239
|
+
ensure: [
|
|
3240
|
+
{
|
|
3241
|
+
address: depositPda,
|
|
3242
|
+
delegated: false,
|
|
3243
|
+
passNotExist: true,
|
|
3244
|
+
label: "initializeDeposit-depositPda"
|
|
3245
|
+
}
|
|
3246
|
+
]
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
// src/instructions/createPermission.ts
|
|
3251
|
+
import { PERMISSION_PROGRAM_ID as PERMISSION_PROGRAM_ID2 } from "@magicblock-labs/ephemeral-rollups-sdk";
|
|
3252
|
+
async function createPermissionIx(program, params) {
|
|
3253
|
+
const { user, tokenMint, payer, passNotExist } = params;
|
|
3254
|
+
const [depositPda] = findDepositPda(user, tokenMint);
|
|
3255
|
+
const [permissionPda] = findPermissionPda(depositPda);
|
|
3256
|
+
const ix = await program.methods.createPermission().accountsPartial({
|
|
3257
|
+
payer,
|
|
3258
|
+
user,
|
|
3259
|
+
deposit: depositPda,
|
|
3260
|
+
permission: permissionPda,
|
|
3261
|
+
permissionProgram: PERMISSION_PROGRAM_ID2
|
|
3262
|
+
}).instruction();
|
|
3263
|
+
return {
|
|
3264
|
+
ix,
|
|
3265
|
+
ensure: [
|
|
3266
|
+
{
|
|
3267
|
+
address: depositPda,
|
|
3268
|
+
delegated: false,
|
|
3269
|
+
passNotExist: passNotExist === undefined ? true : passNotExist,
|
|
3270
|
+
label: "createPermission-depositPda"
|
|
3271
|
+
},
|
|
3272
|
+
{
|
|
3273
|
+
address: permissionPda,
|
|
3274
|
+
delegated: false,
|
|
3275
|
+
passNotExist: true,
|
|
3276
|
+
label: "createPermission-permissionPda"
|
|
3277
|
+
}
|
|
3278
|
+
]
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
// src/instructions/delegateDeposit.ts
|
|
3283
|
+
async function delegateDepositIx(program, params) {
|
|
3284
|
+
const { user, tokenMint, payer, validator, passNotExist } = params;
|
|
3285
|
+
const [depositPda] = findDepositPda(user, tokenMint);
|
|
3286
|
+
const [bufferPda] = findBufferPda(depositPda);
|
|
3287
|
+
const [delegationRecordPda] = findDelegationRecordPda(depositPda);
|
|
3288
|
+
const [delegationMetadataPda] = findDelegationMetadataPda(depositPda);
|
|
3289
|
+
const ix = await program.methods.delegate(user, tokenMint).accountsPartial({
|
|
3290
|
+
payer,
|
|
3291
|
+
bufferDeposit: bufferPda,
|
|
3292
|
+
delegationRecordDeposit: delegationRecordPda,
|
|
3293
|
+
delegationMetadataDeposit: delegationMetadataPda,
|
|
3294
|
+
deposit: depositPda,
|
|
3295
|
+
validator,
|
|
3296
|
+
ownerProgram: PROGRAM_ID,
|
|
3297
|
+
delegationProgram: DELEGATION_PROGRAM_ID
|
|
3298
|
+
}).instruction();
|
|
3299
|
+
return {
|
|
3300
|
+
ix,
|
|
3301
|
+
ensure: [
|
|
3302
|
+
{
|
|
3303
|
+
address: depositPda,
|
|
3304
|
+
delegated: false,
|
|
3305
|
+
passNotExist: passNotExist === undefined ? false : passNotExist,
|
|
3306
|
+
label: "delegateDeposit-depositPda"
|
|
3307
|
+
}
|
|
3308
|
+
]
|
|
3309
|
+
};
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
// src/instructions/undelegateDeposit.ts
|
|
3313
|
+
async function undelegateDepositIx(perProgram, params) {
|
|
3314
|
+
const { user, tokenMint, payer, sessionToken, magicProgram, magicContext } = params;
|
|
3315
|
+
const [depositPda] = findDepositPda(user, tokenMint);
|
|
3316
|
+
const accounts = {
|
|
3317
|
+
user,
|
|
3318
|
+
payer,
|
|
3319
|
+
deposit: depositPda,
|
|
3320
|
+
magicProgram,
|
|
3321
|
+
magicContext
|
|
3322
|
+
};
|
|
3323
|
+
accounts.sessionToken = sessionToken ?? null;
|
|
3324
|
+
const ix = await perProgram.methods.undelegate().accountsPartial(accounts).instruction();
|
|
3325
|
+
return {
|
|
3326
|
+
ix,
|
|
3327
|
+
ensure: [
|
|
3328
|
+
{
|
|
3329
|
+
address: depositPda,
|
|
3330
|
+
delegated: true,
|
|
3331
|
+
passNotExist: false,
|
|
3332
|
+
label: "undelegateDeposit-depositPda"
|
|
3333
|
+
}
|
|
3334
|
+
]
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
// src/actions/undelegateDeposit.ts
|
|
3339
|
+
import {
|
|
3340
|
+
Transaction as Transaction3
|
|
3341
|
+
} from "@solana/web3.js";
|
|
3342
|
+
async function sendPlannedUndelegateDepositTransaction(params) {
|
|
3343
|
+
const { baseProgram, perProgram, transaction, user, tokenMint, rpcOptions } = params;
|
|
3344
|
+
await processEnsureChecks(baseProgram.provider.connection, perProgram.provider.connection, transaction.checks);
|
|
3345
|
+
const [depositPda] = findDepositPda(user, tokenMint);
|
|
3346
|
+
const delegationWatcher = waitForAccountOwnerChange(baseProgram.provider.connection, depositPda, PROGRAM_ID);
|
|
3347
|
+
const tx = new Transaction3().add(...transaction.instructions.map(({ ix }) => ix));
|
|
3348
|
+
let signature;
|
|
3349
|
+
try {
|
|
3350
|
+
signature = await sendAndConfirmWithDiagnostics({
|
|
3351
|
+
label: transaction.label,
|
|
3352
|
+
provider: perProgram.provider,
|
|
3353
|
+
tx,
|
|
3354
|
+
rpcOptions,
|
|
3355
|
+
extraContext: {
|
|
3356
|
+
user,
|
|
3357
|
+
tokenMint,
|
|
3358
|
+
depositPda
|
|
3359
|
+
}
|
|
3360
|
+
});
|
|
3361
|
+
} catch (e) {
|
|
3362
|
+
await delegationWatcher.cancel();
|
|
3363
|
+
throw e;
|
|
3364
|
+
}
|
|
3365
|
+
try {
|
|
3366
|
+
await delegationWatcher.wait();
|
|
3367
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
3368
|
+
} catch (err) {
|
|
3369
|
+
console.warn(`[${transaction.label}] delegation watcher did not observe owner change (signature=${signature}); continuing`, err);
|
|
3370
|
+
}
|
|
3371
|
+
return signature;
|
|
3372
|
+
}
|
|
3373
|
+
async function undelegateDeposit(baseProgram, perProgram, params) {
|
|
3374
|
+
const { user, tokenMint } = params;
|
|
3375
|
+
const { ix, ensure } = await undelegateDepositIx(perProgram, params);
|
|
3376
|
+
return sendPlannedUndelegateDepositTransaction({
|
|
3377
|
+
baseProgram,
|
|
3378
|
+
perProgram,
|
|
3379
|
+
transaction: {
|
|
3380
|
+
label: "undelegateDeposit",
|
|
3381
|
+
instructions: [{ ix }],
|
|
3382
|
+
checks: ensure
|
|
3383
|
+
},
|
|
3384
|
+
user,
|
|
3385
|
+
tokenMint,
|
|
3386
|
+
rpcOptions: params.rpcOptions
|
|
3387
|
+
});
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
// src/actions/shieldTokens.ts
|
|
3391
|
+
function labelTransactionInstructions(prefix, instructions) {
|
|
3392
|
+
const labels = [
|
|
3393
|
+
`${prefix}:createAssociatedTokenAccount`,
|
|
3394
|
+
`${prefix}:transfer`,
|
|
3395
|
+
`${prefix}:syncNative`
|
|
3396
|
+
];
|
|
3397
|
+
return instructions.map((ix, index) => ({
|
|
3398
|
+
label: labels[index] ?? `${prefix}:${index}`,
|
|
3399
|
+
ix
|
|
3400
|
+
}));
|
|
3401
|
+
}
|
|
3402
|
+
async function buildShieldTokensInstructionPlan(params) {
|
|
3403
|
+
const { user, payer, tokenMint, amount, baseProgram, perProgram } = params;
|
|
3404
|
+
const baseConnection = baseProgram.provider.connection;
|
|
3405
|
+
const perRpcEndpoint = perProgram.provider.connection.rpcEndpoint;
|
|
3406
|
+
const isNativeSol = tokenMint.equals(NATIVE_MINT2);
|
|
3407
|
+
const validator = params.validator ?? getErValidatorForRpcEndpoint(perRpcEndpoint);
|
|
3408
|
+
const [depositPda] = findDepositPda(user, tokenMint);
|
|
3409
|
+
const [permissionPda] = findPermissionPda(depositPda);
|
|
3410
|
+
const accountInfos = await getMultipleAccountsInfoWithRetry(baseConnection, [depositPda, permissionPda], "base-getMultipleAccountsInfo");
|
|
3411
|
+
const depositAccountInfo = accountInfos[0] ?? null;
|
|
3412
|
+
const permissionAccountInfo = accountInfos[1] ?? null;
|
|
3413
|
+
const needsUndelegate = depositAccountInfo?.owner.equals(DELEGATION_PROGRAM_ID) ?? false;
|
|
3414
|
+
const [
|
|
3415
|
+
depositRentLamports,
|
|
3416
|
+
modifyBalanceRentLamports,
|
|
3417
|
+
permissionRentLamports,
|
|
3418
|
+
delegationRentLamports
|
|
3419
|
+
] = await Promise.all([
|
|
3420
|
+
!depositAccountInfo ? estimateDepositRentLamports({ connection: baseConnection, depositPda }) : Promise.resolve(0),
|
|
3421
|
+
estimateModifyBalanceRentLamports({
|
|
3422
|
+
connection: baseConnection,
|
|
3423
|
+
user,
|
|
3424
|
+
tokenMint,
|
|
3425
|
+
isNativeSol
|
|
3426
|
+
}),
|
|
3427
|
+
!permissionAccountInfo ? estimatePermissionRentLamports({
|
|
3428
|
+
connection: baseConnection,
|
|
3429
|
+
permissionPda
|
|
3430
|
+
}) : Promise.resolve(0),
|
|
3431
|
+
estimateDepositDelegationRentLamports({
|
|
3432
|
+
connection: baseConnection,
|
|
3433
|
+
user,
|
|
3434
|
+
tokenMint,
|
|
3435
|
+
depositPda,
|
|
3436
|
+
forceCreate: needsUndelegate
|
|
3437
|
+
})
|
|
3438
|
+
]);
|
|
3439
|
+
const instructions = [];
|
|
3440
|
+
const checks = [];
|
|
3441
|
+
if (isNativeSol) {
|
|
3442
|
+
const wrapSolInstructions = labelTransactionInstructions("wrapSol", wrapSolToWsolIx({
|
|
3443
|
+
user,
|
|
3444
|
+
payer,
|
|
3445
|
+
lamports: amount
|
|
3446
|
+
}));
|
|
3447
|
+
const transferInstruction = wrapSolInstructions.find((instruction) => instruction.label === "wrapSol:transfer");
|
|
3448
|
+
if (transferInstruction) {
|
|
3449
|
+
transferInstruction.nativeLamports = Number(amount);
|
|
3450
|
+
}
|
|
3451
|
+
instructions.push(...wrapSolInstructions);
|
|
3452
|
+
}
|
|
3453
|
+
if (!depositAccountInfo) {
|
|
3454
|
+
const initializeDepositIxs = await initializeDepositIx(baseProgram, {
|
|
3455
|
+
tokenMint,
|
|
3456
|
+
user,
|
|
3457
|
+
payer
|
|
3458
|
+
});
|
|
3459
|
+
instructions.push({
|
|
3460
|
+
label: "initializeDeposit",
|
|
3461
|
+
ix: initializeDepositIxs.ix,
|
|
3462
|
+
rentLamports: depositRentLamports
|
|
3463
|
+
});
|
|
3464
|
+
checks.push(...initializeDepositIxs.ensure);
|
|
3465
|
+
}
|
|
3466
|
+
const modifyBalanceIxs = await modifyBalanceIx(baseProgram, {
|
|
3467
|
+
tokenMint,
|
|
3468
|
+
user,
|
|
3469
|
+
payer,
|
|
3470
|
+
amount,
|
|
3471
|
+
increase: true,
|
|
3472
|
+
passNotExist: true
|
|
3473
|
+
});
|
|
3474
|
+
instructions.push({
|
|
3475
|
+
label: "modifyBalanceIncrease",
|
|
3476
|
+
ix: modifyBalanceIxs.ix,
|
|
3477
|
+
rentLamports: modifyBalanceRentLamports
|
|
3478
|
+
});
|
|
3479
|
+
checks.push(...modifyBalanceIxs.ensure);
|
|
3480
|
+
if (!permissionAccountInfo) {
|
|
3481
|
+
const createPermissionIxs = await createPermissionIx(baseProgram, {
|
|
3482
|
+
tokenMint,
|
|
3483
|
+
user,
|
|
3484
|
+
payer,
|
|
3485
|
+
passNotExist: true
|
|
3486
|
+
});
|
|
3487
|
+
instructions.push({
|
|
3488
|
+
label: "createPermission",
|
|
3489
|
+
ix: createPermissionIxs.ix,
|
|
3490
|
+
rentLamports: permissionRentLamports
|
|
3491
|
+
});
|
|
3492
|
+
checks.push(...createPermissionIxs.ensure);
|
|
3493
|
+
}
|
|
3494
|
+
const delegateDepositIxs = await delegateDepositIx(baseProgram, {
|
|
3495
|
+
tokenMint,
|
|
3496
|
+
user,
|
|
3497
|
+
payer,
|
|
3498
|
+
validator,
|
|
3499
|
+
passNotExist: true
|
|
3500
|
+
});
|
|
3501
|
+
instructions.push({
|
|
3502
|
+
label: "delegateDeposit",
|
|
3503
|
+
ix: delegateDepositIxs.ix,
|
|
3504
|
+
rentLamports: delegationRentLamports
|
|
3505
|
+
});
|
|
3506
|
+
checks.push(...delegateDepositIxs.ensure);
|
|
3507
|
+
if (isNativeSol) {
|
|
3508
|
+
instructions.push({
|
|
3509
|
+
label: "closeWsolAta",
|
|
3510
|
+
ix: closeWsolAta({
|
|
3511
|
+
user,
|
|
3512
|
+
destination: user
|
|
3513
|
+
})
|
|
3514
|
+
});
|
|
3515
|
+
}
|
|
3516
|
+
return {
|
|
3517
|
+
instructions,
|
|
3518
|
+
checks,
|
|
3519
|
+
needsUndelegate,
|
|
3520
|
+
context: {
|
|
3521
|
+
isNativeSol,
|
|
3522
|
+
validator,
|
|
3523
|
+
depositPda,
|
|
3524
|
+
permissionPda,
|
|
3525
|
+
depositAccountInfo,
|
|
3526
|
+
permissionAccountInfo
|
|
3527
|
+
}
|
|
3528
|
+
};
|
|
3529
|
+
}
|
|
3530
|
+
async function buildShieldTokensTransactionPlan(params) {
|
|
3531
|
+
const instructionPlan = await buildShieldTokensInstructionPlan(params);
|
|
3532
|
+
let preUndelegateTransaction = null;
|
|
3533
|
+
if (instructionPlan.needsUndelegate) {
|
|
3534
|
+
const undelegateRentLamports = await estimateDepositDelegationRentCreditLamports({
|
|
3535
|
+
connection: params.baseProgram.provider.connection,
|
|
3536
|
+
depositPda: instructionPlan.context.depositPda
|
|
3537
|
+
});
|
|
3538
|
+
const undelegateIxs = await undelegateDepositIx(params.perProgram, {
|
|
3539
|
+
user: params.user,
|
|
3540
|
+
payer: params.payer,
|
|
3541
|
+
tokenMint: params.tokenMint,
|
|
3542
|
+
sessionToken: params.sessionToken,
|
|
3543
|
+
magicProgram: params.magicProgram ?? MAGIC_PROGRAM_ID,
|
|
3544
|
+
magicContext: params.magicContext ?? MAGIC_CONTEXT_ID
|
|
3545
|
+
});
|
|
3546
|
+
preUndelegateTransaction = {
|
|
3547
|
+
label: "shield:preUndelegate",
|
|
3548
|
+
cluster: "ephemeral",
|
|
3549
|
+
instructions: [
|
|
3550
|
+
{
|
|
3551
|
+
label: "undelegateDeposit",
|
|
3552
|
+
ix: undelegateIxs.ix,
|
|
3553
|
+
rentLamports: undelegateRentLamports
|
|
3554
|
+
}
|
|
3555
|
+
],
|
|
3556
|
+
checks: undelegateIxs.ensure
|
|
3557
|
+
};
|
|
3558
|
+
}
|
|
3559
|
+
return {
|
|
3560
|
+
preUndelegateTransaction,
|
|
3561
|
+
baseTransaction: {
|
|
3562
|
+
label: "shield",
|
|
3563
|
+
cluster: "base",
|
|
3564
|
+
instructions: instructionPlan.instructions,
|
|
3565
|
+
checks: instructionPlan.checks
|
|
3566
|
+
},
|
|
3567
|
+
context: instructionPlan.context
|
|
3568
|
+
};
|
|
3569
|
+
}
|
|
3570
|
+
async function shieldTokens(params) {
|
|
3571
|
+
const {
|
|
3572
|
+
user,
|
|
3573
|
+
payer,
|
|
3574
|
+
tokenMint,
|
|
3575
|
+
amount,
|
|
3576
|
+
baseProgram,
|
|
3577
|
+
perProgram,
|
|
3578
|
+
rpcOptions
|
|
3579
|
+
} = params;
|
|
3580
|
+
const baseConnection = baseProgram.provider.connection;
|
|
3581
|
+
const perConnection = perProgram.provider.connection;
|
|
3582
|
+
const plan = await buildShieldTokensTransactionPlan({
|
|
3583
|
+
user,
|
|
3584
|
+
payer,
|
|
3585
|
+
tokenMint,
|
|
3586
|
+
amount,
|
|
3587
|
+
baseProgram,
|
|
3588
|
+
perProgram,
|
|
3589
|
+
validator: params.validator,
|
|
3590
|
+
sessionToken: params.sessionToken,
|
|
3591
|
+
magicProgram: params.magicProgram,
|
|
3592
|
+
magicContext: params.magicContext
|
|
3593
|
+
});
|
|
3594
|
+
const {
|
|
3595
|
+
context: {
|
|
3596
|
+
validator,
|
|
3597
|
+
depositPda,
|
|
3598
|
+
permissionPda,
|
|
3599
|
+
depositAccountInfo,
|
|
3600
|
+
permissionAccountInfo,
|
|
3601
|
+
isNativeSol
|
|
3602
|
+
}
|
|
3603
|
+
} = plan;
|
|
3604
|
+
if (plan.preUndelegateTransaction) {
|
|
3605
|
+
await sendPlannedUndelegateDepositTransaction({
|
|
3606
|
+
baseProgram,
|
|
3607
|
+
perProgram,
|
|
3608
|
+
transaction: plan.preUndelegateTransaction,
|
|
3609
|
+
user,
|
|
3610
|
+
tokenMint,
|
|
3611
|
+
rpcOptions
|
|
3612
|
+
});
|
|
3613
|
+
}
|
|
3614
|
+
await processEnsureChecks(baseConnection, perConnection, plan.baseTransaction.checks);
|
|
3615
|
+
const delegationWatcher = waitForAccountOwnerChange(baseConnection, depositPda, DELEGATION_PROGRAM_ID);
|
|
3616
|
+
const tx = new Transaction4().add(...plan.baseTransaction.instructions.map(({ ix }) => ix));
|
|
3617
|
+
let signature;
|
|
3618
|
+
try {
|
|
3619
|
+
signature = await sendAndConfirmWithDiagnostics({
|
|
3620
|
+
label: "shieldTokens",
|
|
3621
|
+
provider: baseProgram.provider,
|
|
3622
|
+
tx,
|
|
3623
|
+
rpcOptions,
|
|
3624
|
+
extraContext: {
|
|
3625
|
+
user,
|
|
3626
|
+
payer,
|
|
3627
|
+
tokenMint,
|
|
3628
|
+
amount,
|
|
3629
|
+
isNativeSol,
|
|
3630
|
+
validator,
|
|
3631
|
+
depositPda,
|
|
3632
|
+
permissionPda,
|
|
3633
|
+
depositAccountInfo,
|
|
3634
|
+
permissionAccountInfo
|
|
3635
|
+
}
|
|
3636
|
+
});
|
|
3637
|
+
} catch (e) {
|
|
3638
|
+
await delegationWatcher.cancel();
|
|
3639
|
+
throw e;
|
|
3640
|
+
}
|
|
3641
|
+
try {
|
|
3642
|
+
await delegationWatcher.wait();
|
|
3643
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
3644
|
+
} catch (err) {
|
|
3645
|
+
console.warn(`[shieldTokens] delegation watcher did not observe owner change (signature=${signature}); continuing`, err);
|
|
3646
|
+
}
|
|
3647
|
+
return signature;
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
// src/actions/unshieldTokens.ts
|
|
3651
|
+
import { NATIVE_MINT as NATIVE_MINT3 } from "@solana/spl-token";
|
|
3652
|
+
import { Transaction as Transaction5 } from "@solana/web3.js";
|
|
3653
|
+
|
|
3654
|
+
// src/instructions/closeDeposit.ts
|
|
3655
|
+
async function closeDepositIx(program, params) {
|
|
3656
|
+
const { user, tokenMint } = params;
|
|
3657
|
+
const [depositPda] = findDepositPda(user, tokenMint);
|
|
3658
|
+
const ix = await program.methods.closeDeposit().accountsPartial({
|
|
3659
|
+
user,
|
|
3660
|
+
deposit: depositPda,
|
|
3661
|
+
tokenMint
|
|
3662
|
+
}).instruction();
|
|
3663
|
+
return {
|
|
3664
|
+
ix,
|
|
3665
|
+
ensure: [
|
|
3666
|
+
{
|
|
3667
|
+
address: depositPda,
|
|
3668
|
+
delegated: false,
|
|
3669
|
+
passNotExist: false,
|
|
3670
|
+
label: "closeDeposit-depositPda"
|
|
3671
|
+
}
|
|
3672
|
+
]
|
|
3673
|
+
};
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
// src/instructions/closePermission.ts
|
|
3677
|
+
import {
|
|
3678
|
+
createClosePermissionInstruction
|
|
3679
|
+
} from "@magicblock-labs/ephemeral-rollups-sdk";
|
|
3680
|
+
async function closePermissionIx(params) {
|
|
3681
|
+
const { user, tokenMint } = params;
|
|
3682
|
+
const [depositPda] = findDepositPda(user, tokenMint);
|
|
3683
|
+
const [permissionPda] = findPermissionPda(depositPda);
|
|
3684
|
+
const ix = createClosePermissionInstruction({
|
|
3685
|
+
payer: user,
|
|
3686
|
+
authority: [user, true],
|
|
3687
|
+
permissionedAccount: [depositPda, false]
|
|
3688
|
+
});
|
|
3689
|
+
return {
|
|
3690
|
+
ix,
|
|
3691
|
+
ensure: [
|
|
3692
|
+
{
|
|
3693
|
+
address: permissionPda,
|
|
3694
|
+
delegated: false,
|
|
3695
|
+
passNotExist: false,
|
|
3696
|
+
label: "closePermission-permissionPda"
|
|
3697
|
+
}
|
|
3698
|
+
]
|
|
3699
|
+
};
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
// src/actions/unshieldTokens.ts
|
|
3703
|
+
async function buildUnshieldTokensInstructionPlan(params) {
|
|
3704
|
+
const { user, payer, tokenMint, amount, baseProgram, perProgram } = params;
|
|
3705
|
+
const baseConnection = baseProgram.provider.connection;
|
|
3706
|
+
const perRpcEndpoint = perProgram.provider.connection.rpcEndpoint;
|
|
3707
|
+
const isNativeSol = tokenMint.equals(NATIVE_MINT3);
|
|
3708
|
+
const validator = params.validator ?? getErValidatorForRpcEndpoint(perRpcEndpoint);
|
|
3709
|
+
const [depositPda] = findDepositPda(user, tokenMint);
|
|
3710
|
+
const [permissionPda] = findPermissionPda(depositPda);
|
|
3711
|
+
const depositAccountInfoPromise = baseConnection.getAccountInfo(depositPda);
|
|
3712
|
+
const permissionAccountInfoPromise = baseConnection.getAccountInfo(permissionPda);
|
|
3713
|
+
const modifyBalanceRentLamportsPromise = estimateModifyBalanceRentLamports({
|
|
3714
|
+
connection: baseConnection,
|
|
3715
|
+
user,
|
|
3716
|
+
tokenMint,
|
|
3717
|
+
isNativeSol
|
|
3718
|
+
});
|
|
3719
|
+
const [depositAccountInfo, permissionAccountInfo] = await Promise.all([
|
|
3720
|
+
depositAccountInfoPromise,
|
|
3721
|
+
permissionAccountInfoPromise
|
|
3722
|
+
]);
|
|
3723
|
+
const needsUndelegate = depositAccountInfo?.owner.equals(DELEGATION_PROGRAM_ID) ?? false;
|
|
3724
|
+
const currentDepositAccount = needsUndelegate ? await perProgram.account.deposit.fetchNullable(depositPda) : depositAccountInfo?.owner.equals(PROGRAM_ID) ? await baseProgram.account.deposit.fetchNullable(depositPda) : null;
|
|
3725
|
+
const currentDepositAmount = currentDepositAccount ? BigInt(currentDepositAccount.amount.toString()) : null;
|
|
3726
|
+
const shouldRedelegate = currentDepositAmount !== null && currentDepositAmount - amount > 0n;
|
|
3727
|
+
const closePermissionRentLamports = shouldRedelegate ? 0 : -(permissionAccountInfo?.lamports ?? 0);
|
|
3728
|
+
const closeDepositRentLamports = shouldRedelegate ? 0 : -(depositAccountInfo?.lamports ?? 0);
|
|
3729
|
+
const [modifyBalanceRentLamports, delegationRentLamports] = await Promise.all([
|
|
3730
|
+
modifyBalanceRentLamportsPromise,
|
|
3731
|
+
shouldRedelegate ? estimateDepositDelegationRentLamports({
|
|
3732
|
+
connection: baseConnection,
|
|
3733
|
+
user,
|
|
3734
|
+
tokenMint,
|
|
3735
|
+
depositPda,
|
|
3736
|
+
forceCreate: needsUndelegate
|
|
3737
|
+
}) : Promise.resolve(0)
|
|
3738
|
+
]);
|
|
3739
|
+
const instructions = [];
|
|
3740
|
+
const checks = [];
|
|
3741
|
+
if (isNativeSol) {
|
|
3742
|
+
instructions.push(...labelTransactionInstructions("ensureWsolAta", [
|
|
3743
|
+
createWsolAta({ user, payer })
|
|
3744
|
+
]));
|
|
3745
|
+
}
|
|
3746
|
+
const modifyBalanceIxs = await modifyBalanceIx(baseProgram, {
|
|
3747
|
+
tokenMint,
|
|
3748
|
+
user,
|
|
3749
|
+
payer,
|
|
3750
|
+
amount,
|
|
3751
|
+
increase: false
|
|
3752
|
+
});
|
|
3753
|
+
instructions.push({
|
|
3754
|
+
label: "modifyBalanceDecrease",
|
|
3755
|
+
ix: modifyBalanceIxs.ix,
|
|
3756
|
+
rentLamports: modifyBalanceRentLamports,
|
|
3757
|
+
nativeLamports: isNativeSol ? -Number(amount) : undefined
|
|
3758
|
+
});
|
|
3759
|
+
checks.push(...modifyBalanceIxs.ensure);
|
|
3760
|
+
if (isNativeSol) {
|
|
3761
|
+
instructions.push({
|
|
3762
|
+
label: "closeWsolAta",
|
|
3763
|
+
ix: closeWsolAta({
|
|
3764
|
+
user,
|
|
3765
|
+
destination: user
|
|
3766
|
+
})
|
|
3767
|
+
});
|
|
3768
|
+
}
|
|
3769
|
+
if (shouldRedelegate) {
|
|
3770
|
+
const delegateDepositIxs = await delegateDepositIx(baseProgram, {
|
|
3771
|
+
tokenMint,
|
|
3772
|
+
user,
|
|
3773
|
+
payer,
|
|
3774
|
+
validator
|
|
3775
|
+
});
|
|
3776
|
+
instructions.push({
|
|
3777
|
+
label: "redelegateDeposit",
|
|
3778
|
+
ix: delegateDepositIxs.ix,
|
|
3779
|
+
rentLamports: delegationRentLamports
|
|
3780
|
+
});
|
|
3781
|
+
checks.push(...delegateDepositIxs.ensure);
|
|
3782
|
+
} else {
|
|
3783
|
+
const closePermissionIxs = await closePermissionIx({ user, tokenMint });
|
|
3784
|
+
instructions.push({
|
|
3785
|
+
label: "closePermission",
|
|
3786
|
+
ix: closePermissionIxs.ix,
|
|
3787
|
+
rentLamports: closePermissionRentLamports
|
|
3788
|
+
});
|
|
3789
|
+
checks.push(...closePermissionIxs.ensure);
|
|
3790
|
+
const closeDepositIxs = await closeDepositIx(baseProgram, {
|
|
3791
|
+
user,
|
|
3792
|
+
tokenMint
|
|
3793
|
+
});
|
|
3794
|
+
instructions.push({
|
|
3795
|
+
label: "closeDeposit",
|
|
3796
|
+
ix: closeDepositIxs.ix,
|
|
3797
|
+
rentLamports: closeDepositRentLamports
|
|
3798
|
+
});
|
|
3799
|
+
checks.push(...closeDepositIxs.ensure);
|
|
3800
|
+
}
|
|
3801
|
+
return {
|
|
3802
|
+
instructions,
|
|
3803
|
+
checks,
|
|
3804
|
+
needsUndelegate,
|
|
3805
|
+
shouldRedelegate,
|
|
3806
|
+
context: {
|
|
3807
|
+
isNativeSol,
|
|
3808
|
+
validator,
|
|
3809
|
+
depositPda,
|
|
3810
|
+
currentDepositAmount
|
|
3811
|
+
}
|
|
3812
|
+
};
|
|
3813
|
+
}
|
|
3814
|
+
async function buildUnshieldTokensTransactionPlan(params) {
|
|
3815
|
+
const instructionPlan = await buildUnshieldTokensInstructionPlan(params);
|
|
3816
|
+
let preUndelegateTransaction = null;
|
|
3817
|
+
if (instructionPlan.needsUndelegate) {
|
|
3818
|
+
preUndelegateTransaction = {
|
|
3819
|
+
label: "unshield:undelegate",
|
|
3820
|
+
cluster: "ephemeral",
|
|
3821
|
+
instructions: [],
|
|
3822
|
+
checks: []
|
|
3823
|
+
};
|
|
3824
|
+
const undelegateDepositIxs = await undelegateDepositIx(params.perProgram, {
|
|
3825
|
+
user: params.user,
|
|
3826
|
+
payer: params.payer,
|
|
3827
|
+
tokenMint: params.tokenMint,
|
|
3828
|
+
sessionToken: params.sessionToken,
|
|
3829
|
+
magicProgram: params.magicProgram ?? MAGIC_PROGRAM_ID,
|
|
3830
|
+
magicContext: params.magicContext ?? MAGIC_CONTEXT_ID
|
|
3831
|
+
});
|
|
3832
|
+
const undelegateRentLamports = await estimateDepositDelegationRentCreditLamports({
|
|
3833
|
+
connection: params.baseProgram.provider.connection,
|
|
3834
|
+
depositPda: instructionPlan.context.depositPda
|
|
3835
|
+
});
|
|
3836
|
+
preUndelegateTransaction.instructions.push({
|
|
3837
|
+
label: "undelegateDeposit",
|
|
3838
|
+
ix: undelegateDepositIxs.ix,
|
|
3839
|
+
rentLamports: undelegateRentLamports
|
|
3840
|
+
});
|
|
3841
|
+
preUndelegateTransaction.checks.push(...undelegateDepositIxs.ensure);
|
|
3842
|
+
}
|
|
3843
|
+
return {
|
|
3844
|
+
preUndelegateTransaction,
|
|
3845
|
+
baseTransaction: {
|
|
3846
|
+
label: "unshield",
|
|
3847
|
+
cluster: "base",
|
|
3848
|
+
instructions: instructionPlan.instructions,
|
|
3849
|
+
checks: instructionPlan.checks
|
|
3850
|
+
},
|
|
3851
|
+
shouldRedelegate: instructionPlan.shouldRedelegate,
|
|
3852
|
+
context: instructionPlan.context
|
|
3853
|
+
};
|
|
3854
|
+
}
|
|
3855
|
+
async function unshieldTokens(params) {
|
|
3856
|
+
const {
|
|
3857
|
+
user,
|
|
3858
|
+
payer,
|
|
3859
|
+
tokenMint,
|
|
3860
|
+
amount,
|
|
3861
|
+
baseProgram,
|
|
3862
|
+
perProgram,
|
|
3863
|
+
rpcOptions
|
|
3864
|
+
} = params;
|
|
3865
|
+
const baseConnection = baseProgram.provider.connection;
|
|
3866
|
+
const perConnection = perProgram.provider.connection;
|
|
3867
|
+
const plan = await buildUnshieldTokensTransactionPlan({
|
|
3868
|
+
user,
|
|
3869
|
+
payer,
|
|
3870
|
+
tokenMint,
|
|
3871
|
+
amount,
|
|
3872
|
+
baseProgram,
|
|
3873
|
+
perProgram,
|
|
3874
|
+
validator: params.validator,
|
|
3875
|
+
sessionToken: params.sessionToken,
|
|
3876
|
+
magicProgram: params.magicProgram,
|
|
3877
|
+
magicContext: params.magicContext
|
|
3878
|
+
});
|
|
3879
|
+
const {
|
|
3880
|
+
shouldRedelegate,
|
|
3881
|
+
context: { validator, depositPda, isNativeSol, currentDepositAmount }
|
|
3882
|
+
} = plan;
|
|
3883
|
+
if (plan.preUndelegateTransaction) {
|
|
3884
|
+
await sendPlannedUndelegateDepositTransaction({
|
|
3885
|
+
baseProgram,
|
|
3886
|
+
perProgram,
|
|
3887
|
+
transaction: plan.preUndelegateTransaction,
|
|
3888
|
+
user,
|
|
3889
|
+
tokenMint,
|
|
3890
|
+
rpcOptions
|
|
3891
|
+
});
|
|
3892
|
+
}
|
|
3893
|
+
await processEnsureChecks(baseConnection, perConnection, plan.baseTransaction.checks);
|
|
3894
|
+
const delegationWatcher = shouldRedelegate ? waitForAccountOwnerChange(baseConnection, depositPda, DELEGATION_PROGRAM_ID) : null;
|
|
3895
|
+
const tx = new Transaction5().add(...plan.baseTransaction.instructions.map(({ ix }) => ix));
|
|
3896
|
+
let signature;
|
|
3897
|
+
try {
|
|
3898
|
+
signature = await sendAndConfirmWithDiagnostics({
|
|
3899
|
+
label: "unshieldTokens",
|
|
3900
|
+
provider: baseProgram.provider,
|
|
3901
|
+
tx,
|
|
3902
|
+
rpcOptions,
|
|
3903
|
+
extraContext: {
|
|
3904
|
+
user,
|
|
3905
|
+
payer,
|
|
3906
|
+
tokenMint,
|
|
3907
|
+
amount,
|
|
3908
|
+
isNativeSol,
|
|
3909
|
+
validator,
|
|
3910
|
+
depositPda,
|
|
3911
|
+
currentDepositAmount,
|
|
3912
|
+
shouldRedelegate
|
|
3913
|
+
}
|
|
3914
|
+
});
|
|
3915
|
+
if (delegationWatcher) {
|
|
3916
|
+
await delegationWatcher.wait();
|
|
3917
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
3918
|
+
}
|
|
3919
|
+
} catch (e) {
|
|
3920
|
+
await delegationWatcher?.cancel();
|
|
3921
|
+
throw e;
|
|
3922
|
+
}
|
|
3923
|
+
return signature;
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3926
|
+
// src/instructions/initializeUsernameDeposit.ts
|
|
3927
|
+
import { TOKEN_PROGRAM_ID as TOKEN_PROGRAM_ID3 } from "@solana/spl-token";
|
|
3928
|
+
import { SystemProgram as SystemProgram4 } from "@solana/web3.js";
|
|
3929
|
+
async function initializeUsernameDepositIx(program, params) {
|
|
3930
|
+
const { username, tokenMint, payer } = params;
|
|
3931
|
+
validateUsername(username);
|
|
3932
|
+
const [usernameDepositPda] = await findUsernameDepositPda(username, tokenMint);
|
|
3933
|
+
const usernameHash = await sha256hash(username);
|
|
3934
|
+
const ix = await program.methods.initializeUsernameDeposit(usernameHash).accountsPartial({
|
|
3935
|
+
payer,
|
|
3936
|
+
tokenMint,
|
|
3937
|
+
tokenProgram: TOKEN_PROGRAM_ID3,
|
|
3938
|
+
systemProgram: SystemProgram4.programId
|
|
3939
|
+
}).instruction();
|
|
3940
|
+
return {
|
|
3941
|
+
ix,
|
|
3942
|
+
ensure: [
|
|
3943
|
+
{
|
|
3944
|
+
address: usernameDepositPda,
|
|
3945
|
+
delegated: false,
|
|
3946
|
+
passNotExist: true,
|
|
3947
|
+
label: "initializeUsernameDeposit-usernameDepositPda"
|
|
3948
|
+
}
|
|
3949
|
+
]
|
|
3950
|
+
};
|
|
3951
|
+
}
|
|
3952
|
+
|
|
3953
|
+
// src/fee-estimate.ts
|
|
3954
|
+
import {
|
|
3955
|
+
Transaction as Transaction6
|
|
3956
|
+
} from "@solana/web3.js";
|
|
3957
|
+
function createUnsignedTransaction(params) {
|
|
3958
|
+
return new Transaction6({
|
|
3959
|
+
feePayer: params.feePayer,
|
|
3960
|
+
recentBlockhash: params.blockhash
|
|
3961
|
+
}).add(...params.instructions);
|
|
3962
|
+
}
|
|
3963
|
+
async function getMessageFeeLamports(params) {
|
|
3964
|
+
const fee = await params.connection.getFeeForMessage(params.transaction.compileMessage(), params.commitment);
|
|
3965
|
+
if (fee.value === null) {
|
|
3966
|
+
throw new Error("RPC returned null fee for transaction message");
|
|
3967
|
+
}
|
|
3968
|
+
return fee.value;
|
|
3969
|
+
}
|
|
3970
|
+
async function estimatePlannedTransactionFees(params) {
|
|
3971
|
+
const transactionEstimates = await Promise.all(params.transactions.map(async (transactionPlan, index) => {
|
|
3972
|
+
if (transactionPlan.instructions.length === 0) {
|
|
3973
|
+
throw new Error(`Cannot estimate empty transaction: ${transactionPlan.label}`);
|
|
3974
|
+
}
|
|
3975
|
+
const blockhash = await transactionPlan.connection.getLatestBlockhash(params.commitment);
|
|
3976
|
+
const transaction = createUnsignedTransaction({
|
|
3977
|
+
feePayer: transactionPlan.feePayer,
|
|
3978
|
+
blockhash: blockhash.blockhash,
|
|
3979
|
+
instructions: transactionPlan.instructions.map(({ ix }) => ix)
|
|
3980
|
+
});
|
|
3981
|
+
const feeLamports = await getMessageFeeLamports({
|
|
3982
|
+
connection: transactionPlan.connection,
|
|
3983
|
+
transaction,
|
|
3984
|
+
commitment: params.commitment
|
|
3985
|
+
});
|
|
3986
|
+
const instructions = transactionPlan.instructions.map((instructionPlan, instructionIndex) => ({
|
|
3987
|
+
transactionIndex: index,
|
|
3988
|
+
instructionIndex,
|
|
3989
|
+
label: instructionPlan.label,
|
|
3990
|
+
programId: instructionPlan.ix.programId,
|
|
3991
|
+
rentLamports: instructionPlan.rentLamports ?? 0,
|
|
3992
|
+
nativeLamports: instructionPlan.nativeLamports ?? 0
|
|
3993
|
+
}));
|
|
3994
|
+
const rentLamports = instructions.reduce((total, instruction) => total + instruction.rentLamports, 0);
|
|
3995
|
+
const nativeLamports = instructions.reduce((total, instruction) => total + instruction.nativeLamports, 0);
|
|
3996
|
+
return {
|
|
3997
|
+
index,
|
|
3998
|
+
label: transactionPlan.label,
|
|
3999
|
+
cluster: transactionPlan.cluster,
|
|
4000
|
+
feePayer: transactionPlan.feePayer,
|
|
4001
|
+
blockhash: blockhash.blockhash,
|
|
4002
|
+
lastValidBlockHeight: blockhash.lastValidBlockHeight,
|
|
4003
|
+
instructionCount: transactionPlan.instructions.length,
|
|
4004
|
+
feeLamports,
|
|
4005
|
+
rentLamports,
|
|
4006
|
+
nativeLamports,
|
|
4007
|
+
totalLamports: feeLamports + rentLamports + nativeLamports,
|
|
4008
|
+
instructions
|
|
4009
|
+
};
|
|
4010
|
+
}));
|
|
4011
|
+
const instructionEstimates = transactionEstimates.flatMap((transaction) => transaction.instructions);
|
|
4012
|
+
return {
|
|
4013
|
+
transactions: transactionEstimates,
|
|
4014
|
+
instructions: instructionEstimates,
|
|
4015
|
+
totalFeeLamports: transactionEstimates.reduce((total, transaction) => total + transaction.feeLamports, 0),
|
|
4016
|
+
totalRentLamports: instructionEstimates.reduce((total, instruction) => total + instruction.rentLamports, 0),
|
|
4017
|
+
totalNativeLamports: instructionEstimates.reduce((total, instruction) => total + instruction.nativeLamports, 0)
|
|
4018
|
+
};
|
|
4019
|
+
}
|
|
4020
|
+
|
|
4021
|
+
// src/instructions/closeUsernameDeposit.ts
|
|
4022
|
+
async function closeUsernameDepositIx(program, params) {
|
|
4023
|
+
const { username, tokenMint, authority, session } = params;
|
|
4024
|
+
validateUsername(username);
|
|
4025
|
+
const [depositPda] = await findUsernameDepositPda(username, tokenMint);
|
|
4026
|
+
const ix = await program.methods.closeUsernameDeposit().accountsPartial({
|
|
4027
|
+
authority,
|
|
4028
|
+
deposit: depositPda,
|
|
4029
|
+
tokenMint,
|
|
4030
|
+
session
|
|
4031
|
+
}).instruction();
|
|
4032
|
+
return {
|
|
4033
|
+
ix,
|
|
4034
|
+
ensure: [
|
|
4035
|
+
{
|
|
4036
|
+
address: depositPda,
|
|
4037
|
+
delegated: false,
|
|
4038
|
+
passNotExist: false,
|
|
4039
|
+
label: "closeUsernameDeposit-depositPda"
|
|
4040
|
+
}
|
|
4041
|
+
]
|
|
4042
|
+
};
|
|
4043
|
+
}
|
|
4044
|
+
|
|
4045
|
+
// src/LoyalPrivateTransactionsClient.ts
|
|
4046
|
+
var KAMINO_API_BASE_URL = "https://api.kamino.finance";
|
|
4047
|
+
var KAMINO_MAINNET_ENV = "mainnet-beta";
|
|
4048
|
+
var KAMINO_DEVNET_ENV = "devnet";
|
|
4049
|
+
function prettyStringify2(obj) {
|
|
4050
|
+
const json = JSON.stringify(obj, (_key, value) => {
|
|
4051
|
+
if (value instanceof PublicKey7)
|
|
4052
|
+
return value.toBase58();
|
|
4053
|
+
if (typeof value === "bigint")
|
|
4054
|
+
return value.toString();
|
|
4055
|
+
return value;
|
|
4056
|
+
}, 2);
|
|
4057
|
+
return json.replace(/\[\s+(\d[\d,\s]*\d)\s+\]/g, (_match, inner) => {
|
|
4058
|
+
const items = inner.split(/,\s*/).map((s) => s.trim());
|
|
4059
|
+
return `[${items.join(", ")}]`;
|
|
4060
|
+
});
|
|
4061
|
+
}
|
|
4062
|
+
function programFromRpc(signer, commitment, rpcEndpoint, wsEndpoint) {
|
|
4063
|
+
const adapter = InternalWalletAdapter.from(signer);
|
|
4064
|
+
const baseConnection = new Connection3(rpcEndpoint, {
|
|
4065
|
+
wsEndpoint,
|
|
4066
|
+
commitment
|
|
4067
|
+
});
|
|
4068
|
+
const baseProvider = new AnchorProvider2(baseConnection, adapter, {
|
|
4069
|
+
commitment
|
|
4070
|
+
});
|
|
4071
|
+
return new Program2(telegram_private_transfer_default, baseProvider);
|
|
4072
|
+
}
|
|
4073
|
+
function getKaminoApiEnv(accounts) {
|
|
4074
|
+
return accounts && isKaminoMainnetModifyBalanceAccounts(accounts) ? KAMINO_MAINNET_ENV : KAMINO_DEVNET_ENV;
|
|
4075
|
+
}
|
|
4076
|
+
function normalizeBigInt(value) {
|
|
4077
|
+
if (typeof value === "bigint") {
|
|
4078
|
+
return value;
|
|
4079
|
+
}
|
|
4080
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
4081
|
+
throw new Error(`Expected a non-negative integer amount, received ${value}`);
|
|
4082
|
+
}
|
|
4083
|
+
return BigInt(value);
|
|
4084
|
+
}
|
|
4085
|
+
function toShieldFlowTransactionPlan(plan) {
|
|
4086
|
+
return {
|
|
4087
|
+
label: plan.label,
|
|
4088
|
+
cluster: plan.cluster,
|
|
4089
|
+
instructions: plan.instructions,
|
|
4090
|
+
checks: plan.checks,
|
|
4091
|
+
postSendOwnerChange: plan.postSendOwnerChange
|
|
4092
|
+
};
|
|
4093
|
+
}
|
|
4094
|
+
async function fetchKaminoReserveMetrics(args) {
|
|
4095
|
+
const url = new URL(`/kamino-market/${args.lendingMarket.toBase58()}/reserves/metrics`, KAMINO_API_BASE_URL);
|
|
4096
|
+
url.searchParams.set("env", args.env);
|
|
4097
|
+
const response = await fetch(url.toString(), {
|
|
4098
|
+
method: "GET",
|
|
4099
|
+
headers: {
|
|
4100
|
+
accept: "application/json"
|
|
4101
|
+
}
|
|
4102
|
+
});
|
|
4103
|
+
if (!response.ok) {
|
|
4104
|
+
throw new Error(`Kamino reserve metrics request failed with status ${response.status}`);
|
|
4105
|
+
}
|
|
4106
|
+
const payload = await response.json();
|
|
4107
|
+
if (!Array.isArray(payload)) {
|
|
4108
|
+
throw new Error("Kamino reserve metrics response was not an array");
|
|
2250
4109
|
}
|
|
2251
4110
|
const reserveAddress = args.reserve.toBase58();
|
|
2252
4111
|
const reserveMetrics = payload.find((item) => {
|
|
@@ -2286,7 +4145,7 @@ function deriveMessageSigner(signer) {
|
|
|
2286
4145
|
}
|
|
2287
4146
|
throw new Error("Wallet does not support signMessage, required for PER auth");
|
|
2288
4147
|
}
|
|
2289
|
-
function
|
|
4148
|
+
function waitForAccountOwnerChange2(connection, account, expectedOwner, timeoutMs = 15000, intervalMs = 1000) {
|
|
2290
4149
|
let skipWait;
|
|
2291
4150
|
const subId = connection.onAccountChange(account, (accountInfo) => {
|
|
2292
4151
|
if (accountInfo.owner.equals(expectedOwner) && skipWait) {
|
|
@@ -2359,10 +4218,7 @@ class LoyalPrivateTransactionsClient {
|
|
|
2359
4218
|
let expiresAt;
|
|
2360
4219
|
if (!authToken) {
|
|
2361
4220
|
try {
|
|
2362
|
-
|
|
2363
|
-
if (!isVerified) {
|
|
2364
|
-
console.error("[LoyalClient] TEE RPC integrity verification returned false");
|
|
2365
|
-
}
|
|
4221
|
+
await verifyTeeIntegrity(ephemeralRpcEndpoint);
|
|
2366
4222
|
} catch (e) {
|
|
2367
4223
|
console.error("[LoyalClient] TEE RPC integrity verification error:", e);
|
|
2368
4224
|
}
|
|
@@ -2377,123 +4233,328 @@ class LoyalPrivateTransactionsClient {
|
|
|
2377
4233
|
const ephemeralProgram = programFromRpc(signer, commitment, finalEphemeralRpcEndpoint, finalEphemeralWsEndpoint);
|
|
2378
4234
|
return new LoyalPrivateTransactionsClient(baseProgram, ephemeralProgram, adapter);
|
|
2379
4235
|
}
|
|
2380
|
-
async
|
|
2381
|
-
const
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
const signature = await this.baseProgram.methods.initializeDeposit().accountsPartial({
|
|
4236
|
+
async shieldTokens(params) {
|
|
4237
|
+
const payer = params.payer ?? params.user;
|
|
4238
|
+
return shieldTokens({
|
|
4239
|
+
user: params.user,
|
|
2385
4240
|
payer,
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
4241
|
+
tokenMint: params.tokenMint,
|
|
4242
|
+
amount: normalizeBigInt(params.amount),
|
|
4243
|
+
baseProgram: this.baseProgram,
|
|
4244
|
+
perProgram: this.ephemeralProgram,
|
|
4245
|
+
validator: params.validator,
|
|
4246
|
+
sessionToken: params.sessionToken,
|
|
4247
|
+
magicProgram: params.magicProgram,
|
|
4248
|
+
magicContext: params.magicContext,
|
|
4249
|
+
rpcOptions: params.rpcOptions
|
|
4250
|
+
});
|
|
2392
4251
|
}
|
|
2393
|
-
async
|
|
2394
|
-
const
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
await this.ensureNotDelegated(usernameDepositPda, "modifyBalance-depositPda", true);
|
|
2398
|
-
const usernameHash = await sha256hash(username);
|
|
2399
|
-
const signature = await this.baseProgram.methods.initializeUsernameDeposit(usernameHash).accountsPartial({
|
|
4252
|
+
async unshieldTokens(params) {
|
|
4253
|
+
const payer = params.payer ?? params.user;
|
|
4254
|
+
return unshieldTokens({
|
|
4255
|
+
user: params.user,
|
|
2400
4256
|
payer,
|
|
2401
|
-
tokenMint,
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
4257
|
+
tokenMint: params.tokenMint,
|
|
4258
|
+
amount: normalizeBigInt(params.amount),
|
|
4259
|
+
baseProgram: this.baseProgram,
|
|
4260
|
+
perProgram: this.ephemeralProgram,
|
|
4261
|
+
validator: params.validator,
|
|
4262
|
+
sessionToken: params.sessionToken,
|
|
4263
|
+
magicProgram: params.magicProgram,
|
|
4264
|
+
magicContext: params.magicContext,
|
|
4265
|
+
rpcOptions: params.rpcOptions
|
|
4266
|
+
});
|
|
2406
4267
|
}
|
|
2407
|
-
async
|
|
2408
|
-
const
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
4268
|
+
async buildShieldFlowTransactionPlan(params) {
|
|
4269
|
+
const amount = normalizeBigInt(params.amount);
|
|
4270
|
+
const payer = params.payer ?? params.user;
|
|
4271
|
+
const validator = params.validator ?? this.getExpectedErValidator();
|
|
4272
|
+
const magicProgram = params.magicProgram ?? MAGIC_PROGRAM_ID;
|
|
4273
|
+
const magicContext = params.magicContext ?? MAGIC_CONTEXT_ID;
|
|
4274
|
+
const transactions = [];
|
|
4275
|
+
if (params.kind === "shield") {
|
|
4276
|
+
const shieldPlan = await buildShieldTokensTransactionPlan({
|
|
4277
|
+
user: params.user,
|
|
4278
|
+
payer,
|
|
4279
|
+
tokenMint: params.tokenMint,
|
|
4280
|
+
amount,
|
|
4281
|
+
baseProgram: this.baseProgram,
|
|
4282
|
+
perProgram: this.ephemeralProgram,
|
|
4283
|
+
validator,
|
|
4284
|
+
sessionToken: params.sessionToken,
|
|
4285
|
+
magicProgram,
|
|
4286
|
+
magicContext
|
|
4287
|
+
});
|
|
4288
|
+
if (shieldPlan.preUndelegateTransaction) {
|
|
4289
|
+
transactions.push(toShieldFlowTransactionPlan({
|
|
4290
|
+
...shieldPlan.preUndelegateTransaction,
|
|
4291
|
+
postSendOwnerChange: {
|
|
4292
|
+
address: shieldPlan.context.depositPda,
|
|
4293
|
+
owner: PROGRAM_ID,
|
|
4294
|
+
bestEffort: true
|
|
4295
|
+
}
|
|
4296
|
+
}));
|
|
4297
|
+
}
|
|
4298
|
+
transactions.push(toShieldFlowTransactionPlan({
|
|
4299
|
+
...shieldPlan.baseTransaction,
|
|
4300
|
+
postSendOwnerChange: {
|
|
4301
|
+
address: shieldPlan.context.depositPda,
|
|
4302
|
+
owner: DELEGATION_PROGRAM_ID,
|
|
4303
|
+
bestEffort: true
|
|
4304
|
+
}
|
|
4305
|
+
}));
|
|
4306
|
+
} else {
|
|
4307
|
+
const unshieldPlan = await buildUnshieldTokensTransactionPlan({
|
|
4308
|
+
user: params.user,
|
|
4309
|
+
payer,
|
|
4310
|
+
tokenMint: params.tokenMint,
|
|
4311
|
+
amount,
|
|
4312
|
+
baseProgram: this.baseProgram,
|
|
4313
|
+
perProgram: this.ephemeralProgram,
|
|
4314
|
+
validator,
|
|
4315
|
+
sessionToken: params.sessionToken,
|
|
4316
|
+
magicProgram,
|
|
4317
|
+
magicContext
|
|
4318
|
+
});
|
|
4319
|
+
if (unshieldPlan.preUndelegateTransaction) {
|
|
4320
|
+
transactions.push(toShieldFlowTransactionPlan({
|
|
4321
|
+
...unshieldPlan.preUndelegateTransaction,
|
|
4322
|
+
postSendOwnerChange: {
|
|
4323
|
+
address: unshieldPlan.context.depositPda,
|
|
4324
|
+
owner: PROGRAM_ID,
|
|
4325
|
+
bestEffort: true
|
|
4326
|
+
}
|
|
4327
|
+
}));
|
|
4328
|
+
}
|
|
4329
|
+
transactions.push(toShieldFlowTransactionPlan({
|
|
4330
|
+
...unshieldPlan.baseTransaction,
|
|
4331
|
+
postSendOwnerChange: unshieldPlan.shouldRedelegate ? {
|
|
4332
|
+
address: unshieldPlan.context.depositPda,
|
|
4333
|
+
owner: DELEGATION_PROGRAM_ID,
|
|
4334
|
+
bestEffort: true
|
|
4335
|
+
} : undefined
|
|
4336
|
+
}));
|
|
4337
|
+
}
|
|
4338
|
+
return {
|
|
4339
|
+
kind: params.kind,
|
|
4340
|
+
user: params.user,
|
|
2413
4341
|
payer,
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
console.log("modifyBalance", {
|
|
2424
|
-
payer: payer.toString(),
|
|
2425
|
-
user: user.toString(),
|
|
2426
|
-
vault: vaultPda.toString(),
|
|
2427
|
-
deposit: depositPda.toString(),
|
|
2428
|
-
userTokenAccount: userTokenAccount.toString(),
|
|
2429
|
-
vaultTokenAccount: vaultTokenAccount.toString(),
|
|
2430
|
-
tokenMint: tokenMint.toString(),
|
|
2431
|
-
kaminoAccounts: kaminoAccounts ? {
|
|
2432
|
-
lendingMarket: kaminoAccounts.lendingMarket.toString(),
|
|
2433
|
-
lendingMarketAuthority: kaminoAccounts.lendingMarketAuthority.toString(),
|
|
2434
|
-
reserve: kaminoAccounts.reserve.toString(),
|
|
2435
|
-
reserveLiquiditySupply: kaminoAccounts.reserveLiquiditySupply.toString(),
|
|
2436
|
-
reserveCollateralMint: kaminoAccounts.reserveCollateralMint.toString(),
|
|
2437
|
-
vaultCollateralTokenAccount: vaultCollateralTokenAccount?.toString() ?? null
|
|
2438
|
-
} : null
|
|
4342
|
+
tokenMint: params.tokenMint,
|
|
4343
|
+
amount,
|
|
4344
|
+
transactions
|
|
4345
|
+
};
|
|
4346
|
+
}
|
|
4347
|
+
async buildShieldTokensTransactionPlan(params) {
|
|
4348
|
+
return this.buildShieldFlowTransactionPlan({
|
|
4349
|
+
...params,
|
|
4350
|
+
kind: "shield"
|
|
2439
4351
|
});
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
userTokenAccount,
|
|
2446
|
-
vaultTokenAccount,
|
|
2447
|
-
tokenMint,
|
|
2448
|
-
tokenProgram: TOKEN_PROGRAM_ID,
|
|
2449
|
-
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
2450
|
-
systemProgram: SystemProgram.programId
|
|
4352
|
+
}
|
|
4353
|
+
async buildUnshieldTokensTransactionPlan(params) {
|
|
4354
|
+
return this.buildShieldFlowTransactionPlan({
|
|
4355
|
+
...params,
|
|
4356
|
+
kind: "unshield"
|
|
2451
4357
|
});
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
4358
|
+
}
|
|
4359
|
+
async estimateShieldFlowFee(params) {
|
|
4360
|
+
const transactions = params.plan.transactions.map((transaction) => ({
|
|
4361
|
+
label: transaction.label,
|
|
4362
|
+
cluster: transaction.cluster,
|
|
4363
|
+
connection: transaction.cluster === "base" ? this.baseProgram.provider.connection : this.ephemeralProgram.provider.connection,
|
|
4364
|
+
feePayer: params.plan.payer,
|
|
4365
|
+
instructions: transaction.instructions
|
|
4366
|
+
}));
|
|
4367
|
+
if (transactions.length === 0) {
|
|
4368
|
+
throw new Error("Cannot estimate an empty shield flow plan");
|
|
4369
|
+
}
|
|
4370
|
+
const estimate = await estimatePlannedTransactionFees({
|
|
4371
|
+
transactions,
|
|
4372
|
+
commitment: params.commitment
|
|
4373
|
+
});
|
|
4374
|
+
return {
|
|
4375
|
+
kind: params.plan.kind,
|
|
4376
|
+
user: params.plan.user,
|
|
4377
|
+
payer: params.plan.payer,
|
|
4378
|
+
tokenMint: params.plan.tokenMint,
|
|
4379
|
+
amount: params.plan.amount,
|
|
4380
|
+
totalFeeLamports: estimate.totalFeeLamports,
|
|
4381
|
+
totalRentLamports: estimate.totalRentLamports,
|
|
4382
|
+
totalNativeLamports: estimate.totalNativeLamports,
|
|
4383
|
+
feeAndRentLamports: estimate.totalFeeLamports + estimate.totalRentLamports,
|
|
4384
|
+
totalLamports: estimate.totalFeeLamports + estimate.totalRentLamports + estimate.totalNativeLamports,
|
|
4385
|
+
transactions: estimate.transactions,
|
|
4386
|
+
instructions: estimate.instructions,
|
|
4387
|
+
note: "Solana charges protocol fees per transaction message. Instruction rows expose net rent changes (positive locks rent, negative reclaims rent) and nativeLamports for native-SOL token value movement. totalLamports is a cost-style net SOL impact for the common payer=user flow: positive values are debits/costs and negative values are credits/gains. feeAndRentLamports excludes native token principal. If payer differs from user, nativeLamports belongs to the token owner while fees/rent may belong to the payer."
|
|
4388
|
+
};
|
|
4389
|
+
}
|
|
4390
|
+
async estimateShieldTokensFee(params) {
|
|
4391
|
+
if (params.plan.kind !== "shield") {
|
|
4392
|
+
throw new Error("estimateShieldTokensFee expected a shield plan");
|
|
4393
|
+
}
|
|
4394
|
+
return this.estimateShieldFlowFee(params);
|
|
4395
|
+
}
|
|
4396
|
+
async estimateUnshieldTokensFee(params) {
|
|
4397
|
+
if (params.plan.kind !== "unshield") {
|
|
4398
|
+
throw new Error("estimateUnshieldTokensFee expected an unshield plan");
|
|
4399
|
+
}
|
|
4400
|
+
return this.estimateShieldFlowFee(params);
|
|
4401
|
+
}
|
|
4402
|
+
async executeShieldFlowTransactionPlan(params) {
|
|
4403
|
+
if (params.plan.transactions.length === 0) {
|
|
4404
|
+
throw new Error("Cannot execute an empty shield flow plan");
|
|
4405
|
+
}
|
|
4406
|
+
const signatures = [];
|
|
4407
|
+
for (let transactionIndex = 0;transactionIndex < params.plan.transactions.length; transactionIndex += 1) {
|
|
4408
|
+
const transactionPlan = params.plan.transactions[transactionIndex];
|
|
4409
|
+
if (transactionPlan.instructions.length === 0) {
|
|
4410
|
+
throw new Error(`Cannot execute empty transaction plan: ${transactionPlan.label}`);
|
|
4411
|
+
}
|
|
4412
|
+
await processEnsureChecks(this.baseProgram.provider.connection, this.ephemeralProgram.provider.connection, transactionPlan.checks ?? []);
|
|
4413
|
+
const ownerChangeWatcher = transactionPlan.postSendOwnerChange ? waitForAccountOwnerChange2(this.baseProgram.provider.connection, transactionPlan.postSendOwnerChange.address, transactionPlan.postSendOwnerChange.owner) : null;
|
|
4414
|
+
const provider = transactionPlan.cluster === "base" ? this.baseProgram.provider : this.ephemeralProgram.provider;
|
|
4415
|
+
const tx = new Transaction7().add(...transactionPlan.instructions.map(({ ix }) => ix));
|
|
4416
|
+
let signature;
|
|
4417
|
+
try {
|
|
4418
|
+
signature = await sendAndConfirmWithDiagnostics({
|
|
4419
|
+
label: transactionPlan.label,
|
|
4420
|
+
provider,
|
|
4421
|
+
tx,
|
|
4422
|
+
rpcOptions: params.rpcOptions,
|
|
4423
|
+
extraContext: {
|
|
4424
|
+
kind: params.plan.kind,
|
|
4425
|
+
user: params.plan.user,
|
|
4426
|
+
payer: params.plan.payer,
|
|
4427
|
+
tokenMint: params.plan.tokenMint,
|
|
4428
|
+
amount: params.plan.amount,
|
|
4429
|
+
transactionIndex,
|
|
4430
|
+
cluster: transactionPlan.cluster,
|
|
4431
|
+
postSendOwnerChange: transactionPlan.postSendOwnerChange
|
|
4432
|
+
}
|
|
4433
|
+
});
|
|
4434
|
+
} catch (e) {
|
|
4435
|
+
await ownerChangeWatcher?.cancel();
|
|
4436
|
+
throw e;
|
|
4437
|
+
}
|
|
4438
|
+
if (ownerChangeWatcher) {
|
|
4439
|
+
try {
|
|
4440
|
+
await ownerChangeWatcher.wait();
|
|
4441
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
4442
|
+
} catch (err) {
|
|
4443
|
+
if (!transactionPlan.postSendOwnerChange?.bestEffort) {
|
|
4444
|
+
throw err;
|
|
4445
|
+
}
|
|
4446
|
+
console.warn(`[${transactionPlan.label}] owner-change watcher did not observe expected owner (signature=${signature}); continuing`, err);
|
|
2493
4447
|
}
|
|
2494
|
-
|
|
4448
|
+
}
|
|
4449
|
+
signatures.push({
|
|
4450
|
+
index: transactionIndex,
|
|
4451
|
+
label: transactionPlan.label,
|
|
4452
|
+
cluster: transactionPlan.cluster,
|
|
4453
|
+
signature
|
|
4454
|
+
});
|
|
4455
|
+
}
|
|
4456
|
+
return {
|
|
4457
|
+
kind: params.plan.kind,
|
|
4458
|
+
user: params.plan.user,
|
|
4459
|
+
payer: params.plan.payer,
|
|
4460
|
+
tokenMint: params.plan.tokenMint,
|
|
4461
|
+
amount: params.plan.amount,
|
|
4462
|
+
signatures
|
|
4463
|
+
};
|
|
4464
|
+
}
|
|
4465
|
+
async executeShieldTokensTransactionPlan(params) {
|
|
4466
|
+
if (params.plan.kind !== "shield") {
|
|
4467
|
+
throw new Error("executeShieldTokensTransactionPlan expected a shield plan");
|
|
2495
4468
|
}
|
|
2496
|
-
|
|
4469
|
+
return this.executeShieldFlowTransactionPlan(params);
|
|
4470
|
+
}
|
|
4471
|
+
async executeUnshieldTokensTransactionPlan(params) {
|
|
4472
|
+
if (params.plan.kind !== "unshield") {
|
|
4473
|
+
throw new Error("executeUnshieldTokensTransactionPlan expected an unshield plan");
|
|
4474
|
+
}
|
|
4475
|
+
return this.executeShieldFlowTransactionPlan(params);
|
|
4476
|
+
}
|
|
4477
|
+
async initializeDeposit(params) {
|
|
4478
|
+
const { ix, ensure } = await initializeDepositIx(this.baseProgram, params);
|
|
4479
|
+
await processEnsureChecks(this.baseProgram.provider.connection, this.ephemeralProgram.provider.connection, ensure);
|
|
4480
|
+
const tx = new Transaction7().add(ix);
|
|
4481
|
+
return await sendAndConfirmWithDiagnostics({
|
|
4482
|
+
label: "initializeDeposit",
|
|
4483
|
+
provider: this.baseProgram.provider,
|
|
4484
|
+
tx,
|
|
4485
|
+
rpcOptions: params.rpcOptions,
|
|
4486
|
+
extraContext: {
|
|
4487
|
+
user: params.user,
|
|
4488
|
+
tokenMint: params.tokenMint
|
|
4489
|
+
}
|
|
4490
|
+
});
|
|
4491
|
+
}
|
|
4492
|
+
async initializeUsernameDeposit(params) {
|
|
4493
|
+
const { ix, ensure } = await initializeUsernameDepositIx(this.baseProgram, params);
|
|
4494
|
+
await processEnsureChecks(this.baseProgram.provider.connection, this.ephemeralProgram.provider.connection, ensure);
|
|
4495
|
+
const tx = new Transaction7().add(ix);
|
|
4496
|
+
return await sendAndConfirmWithDiagnostics({
|
|
4497
|
+
label: "initializeUsernameDeposit",
|
|
4498
|
+
provider: this.baseProgram.provider,
|
|
4499
|
+
tx,
|
|
4500
|
+
rpcOptions: params.rpcOptions,
|
|
4501
|
+
extraContext: {
|
|
4502
|
+
username: params.username,
|
|
4503
|
+
tokenMint: params.tokenMint
|
|
4504
|
+
}
|
|
4505
|
+
});
|
|
4506
|
+
}
|
|
4507
|
+
async closeDeposit(params) {
|
|
4508
|
+
const { user, tokenMint } = params;
|
|
4509
|
+
const { ix, ensure } = await closeDepositIx(this.baseProgram, params);
|
|
4510
|
+
await processEnsureChecks(this.baseProgram.provider.connection, this.ephemeralProgram.provider.connection, ensure);
|
|
4511
|
+
const tx = new Transaction7().add(ix);
|
|
4512
|
+
return await sendAndConfirmWithDiagnostics({
|
|
4513
|
+
label: "closeDeposit",
|
|
4514
|
+
provider: this.baseProgram.provider,
|
|
4515
|
+
tx,
|
|
4516
|
+
rpcOptions: params.rpcOptions,
|
|
4517
|
+
extraContext: {
|
|
4518
|
+
user,
|
|
4519
|
+
tokenMint
|
|
4520
|
+
}
|
|
4521
|
+
});
|
|
4522
|
+
}
|
|
4523
|
+
async closeUsernameDeposit(params) {
|
|
4524
|
+
const { username, tokenMint, authority, session } = params;
|
|
4525
|
+
const { ix, ensure } = await closeUsernameDepositIx(this.baseProgram, params);
|
|
4526
|
+
await processEnsureChecks(this.baseProgram.provider.connection, this.ephemeralProgram.provider.connection, ensure);
|
|
4527
|
+
const tx = new Transaction7().add(ix);
|
|
4528
|
+
return await sendAndConfirmWithDiagnostics({
|
|
4529
|
+
label: "closeUsernameDeposit",
|
|
4530
|
+
provider: this.baseProgram.provider,
|
|
4531
|
+
tx,
|
|
4532
|
+
rpcOptions: params.rpcOptions,
|
|
4533
|
+
extraContext: {
|
|
4534
|
+
username,
|
|
4535
|
+
tokenMint,
|
|
4536
|
+
authority,
|
|
4537
|
+
session
|
|
4538
|
+
}
|
|
4539
|
+
});
|
|
4540
|
+
}
|
|
4541
|
+
async modifyBalance(params) {
|
|
4542
|
+
const { user, tokenMint } = params;
|
|
4543
|
+
const { ix, ensure } = await modifyBalanceIx(this.baseProgram, params);
|
|
4544
|
+
await processEnsureChecks(this.baseProgram.provider.connection, this.ephemeralProgram.provider.connection, ensure);
|
|
4545
|
+
const tx = new Transaction7().add(ix);
|
|
4546
|
+
const signature = await sendAndConfirmWithDiagnostics({
|
|
4547
|
+
label: "modifyBalance",
|
|
4548
|
+
provider: this.baseProgram.provider,
|
|
4549
|
+
tx,
|
|
4550
|
+
rpcOptions: params.rpcOptions,
|
|
4551
|
+
extraContext: {
|
|
4552
|
+
user,
|
|
4553
|
+
tokenMint,
|
|
4554
|
+
amount: params.amount,
|
|
4555
|
+
increase: params.increase
|
|
4556
|
+
}
|
|
4557
|
+
});
|
|
2497
4558
|
const deposit = await this.getBaseDeposit(user, tokenMint);
|
|
2498
4559
|
if (!deposit) {
|
|
2499
4560
|
throw new Error("Failed to fetch deposit after modification");
|
|
@@ -2502,7 +4563,7 @@ class LoyalPrivateTransactionsClient {
|
|
|
2502
4563
|
}
|
|
2503
4564
|
async claimUsernameDepositToDeposit(params) {
|
|
2504
4565
|
const { username, tokenMint, amount, recipient, session, rpcOptions } = params;
|
|
2505
|
-
|
|
4566
|
+
validateUsername(username);
|
|
2506
4567
|
const [sourceUsernameDeposit] = await findUsernameDepositPda(username, tokenMint);
|
|
2507
4568
|
const [destinationDeposit] = findDepositPda(recipient, tokenMint);
|
|
2508
4569
|
await this.ensureDelegated(sourceUsernameDeposit, "claimUsernameDepositToDeposit-sourceUsernameDeposit");
|
|
@@ -2513,16 +4574,16 @@ class LoyalPrivateTransactionsClient {
|
|
|
2513
4574
|
destinationDeposit,
|
|
2514
4575
|
tokenMint,
|
|
2515
4576
|
session,
|
|
2516
|
-
tokenProgram:
|
|
4577
|
+
tokenProgram: TOKEN_PROGRAM_ID4
|
|
2517
4578
|
};
|
|
2518
|
-
console.log("claimUsernameDepositToDeposit accounts:",
|
|
4579
|
+
console.log("claimUsernameDepositToDeposit accounts:", prettyStringify2(accounts));
|
|
2519
4580
|
const connection = this.baseProgram.provider.connection;
|
|
2520
4581
|
const [srcInfo, dstInfo, sessionInfo] = await Promise.all([
|
|
2521
4582
|
connection.getAccountInfo(sourceUsernameDeposit),
|
|
2522
4583
|
connection.getAccountInfo(destinationDeposit),
|
|
2523
4584
|
connection.getAccountInfo(session)
|
|
2524
4585
|
]);
|
|
2525
|
-
console.log("claimUsernameDepositToDeposit sourceUsernameDeposit accountInfo:",
|
|
4586
|
+
console.log("claimUsernameDepositToDeposit sourceUsernameDeposit accountInfo:", prettyStringify2({
|
|
2526
4587
|
address: sourceUsernameDeposit.toBase58(),
|
|
2527
4588
|
exists: !!srcInfo,
|
|
2528
4589
|
owner: srcInfo?.owner?.toBase58(),
|
|
@@ -2530,7 +4591,7 @@ class LoyalPrivateTransactionsClient {
|
|
|
2530
4591
|
dataLen: srcInfo?.data?.length,
|
|
2531
4592
|
executable: srcInfo?.executable
|
|
2532
4593
|
}));
|
|
2533
|
-
console.log("claimUsernameDepositToDeposit destinationDeposit accountInfo:",
|
|
4594
|
+
console.log("claimUsernameDepositToDeposit destinationDeposit accountInfo:", prettyStringify2({
|
|
2534
4595
|
address: destinationDeposit.toBase58(),
|
|
2535
4596
|
exists: !!dstInfo,
|
|
2536
4597
|
owner: dstInfo?.owner?.toBase58(),
|
|
@@ -2538,7 +4599,7 @@ class LoyalPrivateTransactionsClient {
|
|
|
2538
4599
|
dataLen: dstInfo?.data?.length,
|
|
2539
4600
|
executable: dstInfo?.executable
|
|
2540
4601
|
}));
|
|
2541
|
-
console.log("claimUsernameDepositToDeposit session accountInfo:",
|
|
4602
|
+
console.log("claimUsernameDepositToDeposit session accountInfo:", prettyStringify2({
|
|
2542
4603
|
address: session.toBase58(),
|
|
2543
4604
|
exists: !!sessionInfo,
|
|
2544
4605
|
owner: sessionInfo?.owner?.toBase58(),
|
|
@@ -2547,50 +4608,40 @@ class LoyalPrivateTransactionsClient {
|
|
|
2547
4608
|
executable: sessionInfo?.executable
|
|
2548
4609
|
}));
|
|
2549
4610
|
try {
|
|
2550
|
-
const sim = await this.ephemeralProgram.methods.claimUsernameDepositToDeposit(new
|
|
4611
|
+
const sim = await this.ephemeralProgram.methods.claimUsernameDepositToDeposit(new BN2(amount.toString())).accountsPartial(accounts).simulate();
|
|
2551
4612
|
console.log("claimUsernameDepositToDeposit simulation logs:", sim.raw);
|
|
2552
4613
|
} catch (simErr) {
|
|
2553
4614
|
const simResponse = simErr.simulationResponse;
|
|
2554
4615
|
console.error("claimUsernameDepositToDeposit simulate FAILED");
|
|
2555
4616
|
console.error(" error message:", simErr instanceof Error ? simErr.message : String(simErr));
|
|
2556
4617
|
if (simResponse) {
|
|
2557
|
-
console.error(" simulation err:",
|
|
2558
|
-
console.error(" simulation logs:",
|
|
4618
|
+
console.error(" simulation err:", prettyStringify2(simResponse.err));
|
|
4619
|
+
console.error(" simulation logs:", prettyStringify2(simResponse.logs));
|
|
2559
4620
|
console.error(" unitsConsumed:", simResponse.unitsConsumed);
|
|
2560
4621
|
}
|
|
2561
4622
|
throw simErr;
|
|
2562
4623
|
}
|
|
2563
|
-
const signature = await this.ephemeralProgram.methods.claimUsernameDepositToDeposit(new
|
|
4624
|
+
const signature = await this.ephemeralProgram.methods.claimUsernameDepositToDeposit(new BN2(amount.toString())).accountsPartial(accounts).rpc({ skipPreflight: true, commitment: "confirmed" });
|
|
2564
4625
|
return signature;
|
|
2565
4626
|
}
|
|
2566
4627
|
async createPermission(params) {
|
|
2567
|
-
const {
|
|
2568
|
-
|
|
2569
|
-
const
|
|
2570
|
-
await
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
deposit: depositPda,
|
|
2579
|
-
permission: permissionPda,
|
|
2580
|
-
permissionProgram: PERMISSION_PROGRAM_ID,
|
|
2581
|
-
systemProgram: SystemProgram.programId
|
|
2582
|
-
}).rpc(rpcOptions);
|
|
2583
|
-
return signature;
|
|
2584
|
-
} catch (err) {
|
|
2585
|
-
if (this.isAccountAlreadyInUse(err)) {
|
|
2586
|
-
return "permission-exists";
|
|
4628
|
+
const { ix, ensure } = await createPermissionIx(this.baseProgram, params);
|
|
4629
|
+
await processEnsureChecks(this.baseProgram.provider.connection, this.ephemeralProgram.provider.connection, ensure);
|
|
4630
|
+
const tx = new Transaction7().add(ix);
|
|
4631
|
+
return await sendAndConfirmWithDiagnostics({
|
|
4632
|
+
label: "createPermission",
|
|
4633
|
+
provider: this.baseProgram.provider,
|
|
4634
|
+
tx,
|
|
4635
|
+
rpcOptions: params.rpcOptions,
|
|
4636
|
+
extraContext: {
|
|
4637
|
+
user: params.user,
|
|
4638
|
+
tokenMint: params.tokenMint
|
|
2587
4639
|
}
|
|
2588
|
-
|
|
2589
|
-
}
|
|
4640
|
+
});
|
|
2590
4641
|
}
|
|
2591
4642
|
async createUsernamePermission(params) {
|
|
2592
4643
|
const { username, tokenMint, session, authority, payer, rpcOptions } = params;
|
|
2593
|
-
|
|
4644
|
+
validateUsername(username);
|
|
2594
4645
|
const [depositPda] = await findUsernameDepositPda(username, tokenMint);
|
|
2595
4646
|
const [permissionPda] = findPermissionPda(depositPda);
|
|
2596
4647
|
await this.ensureNotDelegated(depositPda, "createUsernamePermission-depositPda");
|
|
@@ -2605,7 +4656,7 @@ class LoyalPrivateTransactionsClient {
|
|
|
2605
4656
|
session,
|
|
2606
4657
|
permission: permissionPda,
|
|
2607
4658
|
permissionProgram: PERMISSION_PROGRAM_ID,
|
|
2608
|
-
systemProgram:
|
|
4659
|
+
systemProgram: SystemProgram5.programId
|
|
2609
4660
|
}).rpc(rpcOptions);
|
|
2610
4661
|
return signature;
|
|
2611
4662
|
} catch (err) {
|
|
@@ -2616,35 +4667,35 @@ class LoyalPrivateTransactionsClient {
|
|
|
2616
4667
|
}
|
|
2617
4668
|
}
|
|
2618
4669
|
async delegateDeposit(params) {
|
|
2619
|
-
const { user, tokenMint
|
|
4670
|
+
const { user, tokenMint } = params;
|
|
4671
|
+
const { ix, ensure } = await delegateDepositIx(this.baseProgram, params);
|
|
4672
|
+
await processEnsureChecks(this.baseProgram.provider.connection, this.ephemeralProgram.provider.connection, ensure);
|
|
2620
4673
|
const [depositPda] = findDepositPda(user, tokenMint);
|
|
2621
|
-
const
|
|
2622
|
-
const [delegationRecordPda] = findDelegationRecordPda(depositPda);
|
|
2623
|
-
const [delegationMetadataPda] = findDelegationMetadataPda(depositPda);
|
|
2624
|
-
await this.ensureNotDelegated(depositPda, "delegateDeposit-depositPda");
|
|
2625
|
-
const accounts = {
|
|
2626
|
-
payer,
|
|
2627
|
-
bufferDeposit: bufferPda,
|
|
2628
|
-
delegationRecordDeposit: delegationRecordPda,
|
|
2629
|
-
delegationMetadataDeposit: delegationMetadataPda,
|
|
2630
|
-
deposit: depositPda,
|
|
2631
|
-
validator,
|
|
2632
|
-
ownerProgram: PROGRAM_ID,
|
|
2633
|
-
delegationProgram: DELEGATION_PROGRAM_ID,
|
|
2634
|
-
systemProgram: SystemProgram.programId
|
|
2635
|
-
};
|
|
2636
|
-
const delegationWatcher = waitForAccountOwnerChange(this.baseProgram.provider.connection, depositPda, DELEGATION_PROGRAM_ID);
|
|
4674
|
+
const delegationWatcher = waitForAccountOwnerChange2(this.baseProgram.provider.connection, depositPda, DELEGATION_PROGRAM_ID);
|
|
2637
4675
|
let signature;
|
|
2638
4676
|
try {
|
|
2639
|
-
|
|
2640
|
-
signature = await
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
4677
|
+
const tx = new Transaction7().add(ix);
|
|
4678
|
+
signature = await sendAndConfirmWithDiagnostics({
|
|
4679
|
+
label: "delegateDeposit",
|
|
4680
|
+
provider: this.baseProgram.provider,
|
|
4681
|
+
tx,
|
|
4682
|
+
rpcOptions: params.rpcOptions,
|
|
4683
|
+
extraContext: {
|
|
4684
|
+
user,
|
|
4685
|
+
tokenMint,
|
|
4686
|
+
depositPda
|
|
4687
|
+
}
|
|
4688
|
+
});
|
|
2644
4689
|
} catch (e) {
|
|
2645
4690
|
await delegationWatcher.cancel();
|
|
2646
4691
|
throw e;
|
|
2647
4692
|
}
|
|
4693
|
+
try {
|
|
4694
|
+
await delegationWatcher.wait();
|
|
4695
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
4696
|
+
} catch (err) {
|
|
4697
|
+
console.warn(`[delegateDeposit] delegation watcher did not observe owner change (signature=${signature}); continuing`, err);
|
|
4698
|
+
}
|
|
2648
4699
|
return signature;
|
|
2649
4700
|
}
|
|
2650
4701
|
async delegateUsernameDeposit(params) {
|
|
@@ -2655,7 +4706,7 @@ class LoyalPrivateTransactionsClient {
|
|
|
2655
4706
|
validator,
|
|
2656
4707
|
rpcOptions
|
|
2657
4708
|
} = params;
|
|
2658
|
-
|
|
4709
|
+
validateUsername(username);
|
|
2659
4710
|
const [depositPda] = await findUsernameDepositPda(username, tokenMint);
|
|
2660
4711
|
const [bufferPda] = findBufferPda(depositPda);
|
|
2661
4712
|
const [delegationRecordPda] = findDelegationRecordPda(depositPda);
|
|
@@ -2670,56 +4721,30 @@ class LoyalPrivateTransactionsClient {
|
|
|
2670
4721
|
deposit: depositPda,
|
|
2671
4722
|
ownerProgram: PROGRAM_ID,
|
|
2672
4723
|
delegationProgram: DELEGATION_PROGRAM_ID,
|
|
2673
|
-
systemProgram:
|
|
4724
|
+
systemProgram: SystemProgram5.programId
|
|
2674
4725
|
};
|
|
2675
4726
|
accounts.validator = validator ?? null;
|
|
2676
|
-
const delegationWatcher =
|
|
4727
|
+
const delegationWatcher = waitForAccountOwnerChange2(this.baseProgram.provider.connection, depositPda, DELEGATION_PROGRAM_ID);
|
|
2677
4728
|
let signature;
|
|
2678
4729
|
try {
|
|
2679
|
-
console.log("delegateUsernameDeposit Accounts:",
|
|
4730
|
+
console.log("delegateUsernameDeposit Accounts:", prettyStringify2(accounts));
|
|
2680
4731
|
signature = await this.baseProgram.methods.delegateUsernameDeposit(usernameHash, tokenMint).accountsPartial(accounts).rpc(rpcOptions);
|
|
2681
|
-
console.log("delegateUsernameDeposit: waiting for depositPda owner to be DELEGATION_PROGRAM_ID on base connection...");
|
|
2682
|
-
await delegationWatcher.wait();
|
|
2683
|
-
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
2684
4732
|
} catch (e) {
|
|
2685
4733
|
await delegationWatcher.cancel();
|
|
2686
4734
|
throw e;
|
|
2687
4735
|
}
|
|
2688
|
-
return signature;
|
|
2689
|
-
}
|
|
2690
|
-
async undelegateDeposit(params) {
|
|
2691
|
-
const {
|
|
2692
|
-
user,
|
|
2693
|
-
tokenMint,
|
|
2694
|
-
payer,
|
|
2695
|
-
sessionToken,
|
|
2696
|
-
magicProgram,
|
|
2697
|
-
magicContext,
|
|
2698
|
-
rpcOptions
|
|
2699
|
-
} = params;
|
|
2700
|
-
const [depositPda] = findDepositPda(user, tokenMint);
|
|
2701
|
-
await this.ensureDelegated(depositPda, "undelegateDeposit-depositPda", true);
|
|
2702
|
-
const accounts = {
|
|
2703
|
-
user,
|
|
2704
|
-
payer,
|
|
2705
|
-
deposit: depositPda,
|
|
2706
|
-
magicProgram,
|
|
2707
|
-
magicContext
|
|
2708
|
-
};
|
|
2709
|
-
accounts.sessionToken = sessionToken ?? null;
|
|
2710
|
-
const delegationWatcher = waitForAccountOwnerChange(this.baseProgram.provider.connection, depositPda, PROGRAM_ID);
|
|
2711
|
-
let signature;
|
|
2712
4736
|
try {
|
|
2713
|
-
console.log("
|
|
2714
|
-
signature = await this.ephemeralProgram.methods.undelegate().accountsPartial(accounts).rpc(rpcOptions);
|
|
2715
|
-
console.log("undelegateDeposit: waiting for depositPda owner to be PROGRAM_ID on base connection...");
|
|
4737
|
+
console.log("delegateUsernameDeposit: waiting for depositPda owner to be DELEGATION_PROGRAM_ID on base connection...");
|
|
2716
4738
|
await delegationWatcher.wait();
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
4739
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
4740
|
+
} catch (err) {
|
|
4741
|
+
console.warn(`[delegateUsernameDeposit] delegation watcher did not observe owner change (signature=${signature}); continuing`, err);
|
|
2720
4742
|
}
|
|
2721
4743
|
return signature;
|
|
2722
4744
|
}
|
|
4745
|
+
async undelegateDeposit(params) {
|
|
4746
|
+
return await undelegateDeposit(this.baseProgram, this.ephemeralProgram, params);
|
|
4747
|
+
}
|
|
2723
4748
|
async undelegateUsernameDeposit(params) {
|
|
2724
4749
|
const {
|
|
2725
4750
|
username,
|
|
@@ -2730,7 +4755,7 @@ class LoyalPrivateTransactionsClient {
|
|
|
2730
4755
|
magicContext,
|
|
2731
4756
|
rpcOptions
|
|
2732
4757
|
} = params;
|
|
2733
|
-
|
|
4758
|
+
validateUsername(username);
|
|
2734
4759
|
const [depositPda] = await findUsernameDepositPda(username, tokenMint);
|
|
2735
4760
|
await this.ensureDelegated(depositPda, "undelegateUsernameDeposit-depositPda");
|
|
2736
4761
|
const usernameHash = await sha256hash(username);
|
|
@@ -2763,7 +4788,7 @@ class LoyalPrivateTransactionsClient {
|
|
|
2763
4788
|
sourceDeposit: sourceDepositPda,
|
|
2764
4789
|
destinationDeposit: destinationDepositPda,
|
|
2765
4790
|
tokenMint,
|
|
2766
|
-
systemProgram:
|
|
4791
|
+
systemProgram: SystemProgram5.programId
|
|
2767
4792
|
};
|
|
2768
4793
|
accounts.sessionToken = sessionToken ?? null;
|
|
2769
4794
|
console.log("transferDeposit Accounts:");
|
|
@@ -2771,7 +4796,7 @@ class LoyalPrivateTransactionsClient {
|
|
|
2771
4796
|
console.log(key, value && value.toString());
|
|
2772
4797
|
});
|
|
2773
4798
|
console.log("-----");
|
|
2774
|
-
const signature = await this.ephemeralProgram.methods.transferDeposit(new
|
|
4799
|
+
const signature = await this.ephemeralProgram.methods.transferDeposit(new BN2(amount.toString())).accountsPartial(accounts).rpc(rpcOptions);
|
|
2775
4800
|
return signature;
|
|
2776
4801
|
}
|
|
2777
4802
|
async transferToUsernameDeposit(params) {
|
|
@@ -2784,7 +4809,7 @@ class LoyalPrivateTransactionsClient {
|
|
|
2784
4809
|
sessionToken,
|
|
2785
4810
|
rpcOptions
|
|
2786
4811
|
} = params;
|
|
2787
|
-
|
|
4812
|
+
validateUsername(username);
|
|
2788
4813
|
const [sourceDepositPda] = findDepositPda(user, tokenMint);
|
|
2789
4814
|
const [destinationDepositPda] = await findUsernameDepositPda(username, tokenMint);
|
|
2790
4815
|
await this.ensureDelegated(sourceDepositPda, "transferToUsernameDeposit-sourceDepositPda");
|
|
@@ -2795,10 +4820,10 @@ class LoyalPrivateTransactionsClient {
|
|
|
2795
4820
|
sourceDeposit: sourceDepositPda,
|
|
2796
4821
|
destinationDeposit: destinationDepositPda,
|
|
2797
4822
|
tokenMint,
|
|
2798
|
-
systemProgram:
|
|
4823
|
+
systemProgram: SystemProgram5.programId
|
|
2799
4824
|
};
|
|
2800
4825
|
accounts.sessionToken = sessionToken ?? null;
|
|
2801
|
-
const signature = await this.ephemeralProgram.methods.transferToUsernameDeposit(new
|
|
4826
|
+
const signature = await this.ephemeralProgram.methods.transferToUsernameDeposit(new BN2(amount.toString())).accountsPartial(accounts).rpc(rpcOptions);
|
|
2802
4827
|
return signature;
|
|
2803
4828
|
}
|
|
2804
4829
|
async getBaseDeposit(user, tokenMint) {
|
|
@@ -2830,43 +4855,11 @@ class LoyalPrivateTransactionsClient {
|
|
|
2830
4855
|
}
|
|
2831
4856
|
}
|
|
2832
4857
|
async getAllDepositsByUser(user) {
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
}
|
|
2839
|
-
}
|
|
2840
|
-
];
|
|
2841
|
-
const [baseResults, ephemeralResults] = await Promise.allSettled([
|
|
2842
|
-
this.baseProgram.account.deposit.all(userFilter),
|
|
2843
|
-
this.ephemeralProgram.account.deposit.all(userFilter)
|
|
2844
|
-
]);
|
|
2845
|
-
const byPda = new Map;
|
|
2846
|
-
const ingest = (results, preferOverwrite) => {
|
|
2847
|
-
for (const { publicKey, account } of results) {
|
|
2848
|
-
const key = publicKey.toBase58();
|
|
2849
|
-
if (!preferOverwrite && byPda.has(key))
|
|
2850
|
-
continue;
|
|
2851
|
-
byPda.set(key, {
|
|
2852
|
-
user: account.user,
|
|
2853
|
-
tokenMint: account.tokenMint,
|
|
2854
|
-
amount: BigInt(account.amount.toString()),
|
|
2855
|
-
address: publicKey
|
|
2856
|
-
});
|
|
2857
|
-
}
|
|
2858
|
-
};
|
|
2859
|
-
if (baseResults.status === "fulfilled") {
|
|
2860
|
-
ingest(baseResults.value, false);
|
|
2861
|
-
} else {
|
|
2862
|
-
console.warn("[getAllDepositsByUser] base program enumeration failed", baseResults.reason);
|
|
2863
|
-
}
|
|
2864
|
-
if (ephemeralResults.status === "fulfilled") {
|
|
2865
|
-
ingest(ephemeralResults.value, true);
|
|
2866
|
-
} else {
|
|
2867
|
-
console.warn("[getAllDepositsByUser] ephemeral program enumeration failed", ephemeralResults.reason);
|
|
2868
|
-
}
|
|
2869
|
-
return Array.from(byPda.values());
|
|
4858
|
+
return enumerateDepositsByUser({
|
|
4859
|
+
user,
|
|
4860
|
+
baseConnection: this.baseProgram.provider.connection,
|
|
4861
|
+
ephemeralConnection: this.ephemeralProgram.provider.connection
|
|
4862
|
+
});
|
|
2870
4863
|
}
|
|
2871
4864
|
async getBaseUsernameDeposit(username, tokenMint) {
|
|
2872
4865
|
const [depositPda] = await findUsernameDepositPda(username, tokenMint);
|
|
@@ -2972,14 +4965,6 @@ class LoyalPrivateTransactionsClient {
|
|
|
2972
4965
|
getProgramId() {
|
|
2973
4966
|
return PROGRAM_ID;
|
|
2974
4967
|
}
|
|
2975
|
-
validateUsername(username) {
|
|
2976
|
-
if (!username || username.length < 5 || username.length > 32) {
|
|
2977
|
-
throw new Error("Username must be between 5 and 32 characters");
|
|
2978
|
-
}
|
|
2979
|
-
if (!/^[a-z0-9_]+$/.test(username)) {
|
|
2980
|
-
throw new Error("Username can only contain lowercase alphanumeric characters and underscores");
|
|
2981
|
-
}
|
|
2982
|
-
}
|
|
2983
4968
|
async permissionAccountExists(permission) {
|
|
2984
4969
|
const info = await this.baseProgram.provider.connection.getAccountInfo(permission);
|
|
2985
4970
|
return !!info && info.owner.equals(PERMISSION_PROGRAM_ID);
|
|
@@ -3011,8 +4996,8 @@ class LoyalPrivateTransactionsClient {
|
|
|
3011
4996
|
console.error(`Account is delegated to ER: ${displayName}${account.toString()}`);
|
|
3012
4997
|
const delegationStatus = await this.getDelegationStatus(account);
|
|
3013
4998
|
console.error("/getDelegationStatus", JSON.stringify(delegationStatus, null, 2));
|
|
3014
|
-
console.error("baseAccountInfo",
|
|
3015
|
-
console.error("ephemeralAccountInfo",
|
|
4999
|
+
console.error("baseAccountInfo", prettyStringify2(baseAccountInfo));
|
|
5000
|
+
console.error("ephemeralAccountInfo", prettyStringify2(ephemeralAccountInfo));
|
|
3016
5001
|
const expectedValidator = this.getExpectedErValidator();
|
|
3017
5002
|
const authority = delegationStatus.result?.delegationRecord?.authority;
|
|
3018
5003
|
if (authority && authority !== expectedValidator.toString()) {
|
|
@@ -3034,14 +5019,14 @@ class LoyalPrivateTransactionsClient {
|
|
|
3034
5019
|
if (!isDelegated) {
|
|
3035
5020
|
console.error(`Account is not delegated to ER: ${displayName}${account.toString()}`);
|
|
3036
5021
|
console.error("/getDelegationStatus:", JSON.stringify(delegationStatus, null, 2));
|
|
3037
|
-
console.error("baseAccountInfo",
|
|
3038
|
-
console.error("ephemeralAccountInfo",
|
|
5022
|
+
console.error("baseAccountInfo", prettyStringify2(baseAccountInfo));
|
|
5023
|
+
console.error("ephemeralAccountInfo", prettyStringify2(ephemeralAccountInfo));
|
|
3039
5024
|
throw new Error(`Account is not delegated to ER: ${displayName}${account.toString()}`);
|
|
3040
5025
|
} else if (!skipValidatorCheck && delegationStatus.result.delegationRecord.authority !== this.getExpectedErValidator().toString()) {
|
|
3041
5026
|
console.error(`Account is delegated on wrong validator: ${displayName}${account.toString()} - validator: ${delegationStatus.result.delegationRecord.authority}`);
|
|
3042
5027
|
console.error("/getDelegationStatus:", JSON.stringify(delegationStatus, null, 2));
|
|
3043
|
-
console.error("baseAccountInfo",
|
|
3044
|
-
console.error("ephemeralAccountInfo",
|
|
5028
|
+
console.error("baseAccountInfo", prettyStringify2(baseAccountInfo));
|
|
5029
|
+
console.error("ephemeralAccountInfo", prettyStringify2(ephemeralAccountInfo));
|
|
3045
5030
|
throw new Error(`Account is delegated on wrong validator: ${displayName}${account.toString()} - validator: ${delegationStatus.result.delegationRecord.authority}`);
|
|
3046
5031
|
}
|
|
3047
5032
|
}
|
|
@@ -3097,12 +5082,17 @@ class LoyalPrivateTransactionsClient {
|
|
|
3097
5082
|
// index.ts
|
|
3098
5083
|
var IDL = telegram_private_transfer_default;
|
|
3099
5084
|
export {
|
|
3100
|
-
waitForAccountOwnerChange,
|
|
5085
|
+
waitForAccountOwnerChange2 as waitForAccountOwnerChange,
|
|
5086
|
+
unshieldTokens,
|
|
3101
5087
|
solToLamports,
|
|
5088
|
+
shieldTokens,
|
|
5089
|
+
parseKaminoReserveSnapshotFromAccountData,
|
|
3102
5090
|
lamportsToSol,
|
|
3103
5091
|
isWalletLike,
|
|
3104
5092
|
isKeypair,
|
|
5093
|
+
isKaminoMainnetModifyBalanceAccounts,
|
|
3105
5094
|
isAnchorProvider,
|
|
5095
|
+
getKaminoModifyBalanceAccountsForTokenMint,
|
|
3106
5096
|
getErValidatorForSolanaEnv,
|
|
3107
5097
|
getErValidatorForRpcEndpoint,
|
|
3108
5098
|
findVaultPda,
|
|
@@ -3112,10 +5102,18 @@ export {
|
|
|
3112
5102
|
findDelegationRecordPda,
|
|
3113
5103
|
findDelegationMetadataPda,
|
|
3114
5104
|
findBufferPda,
|
|
5105
|
+
fetchKaminoReserveSnapshot,
|
|
5106
|
+
enumerateDepositsByUser,
|
|
5107
|
+
calculateKaminoShareAmountForLiquidityAmountRaw,
|
|
5108
|
+
calculateKaminoRedeemableLiquidityAmountRaw,
|
|
5109
|
+
calculateKaminoCollateralValuation,
|
|
5110
|
+
calculateKaminoCollateralExchangeRateSfFromAmounts,
|
|
3115
5111
|
VAULT_SEED_BYTES,
|
|
3116
5112
|
VAULT_SEED,
|
|
3117
5113
|
USERNAME_DEPOSIT_SEED_BYTES,
|
|
3118
5114
|
USERNAME_DEPOSIT_SEED,
|
|
5115
|
+
USDC_MINT_MAINNET,
|
|
5116
|
+
USDC_MINT_DEVNET,
|
|
3119
5117
|
PROGRAM_ID,
|
|
3120
5118
|
PERMISSION_SEED_BYTES,
|
|
3121
5119
|
PERMISSION_SEED,
|
|
@@ -3124,6 +5122,7 @@ export {
|
|
|
3124
5122
|
MAGIC_CONTEXT_ID,
|
|
3125
5123
|
LoyalPrivateTransactionsClient,
|
|
3126
5124
|
LAMPORTS_PER_SOL,
|
|
5125
|
+
KLEND_PROGRAM_ID,
|
|
3127
5126
|
IDL,
|
|
3128
5127
|
ER_VALIDATOR_MAINNET,
|
|
3129
5128
|
ER_VALIDATOR_DEVNET,
|