@atomiqlabs/chain-solana 10.0.0-dev.3 → 11.0.0
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/LICENSE +201 -201
- package/dist/index.d.ts +29 -29
- package/dist/index.js +45 -45
- package/dist/solana/SolanaChainType.d.ts +10 -10
- package/dist/solana/SolanaChainType.js +2 -2
- package/dist/solana/SolanaChains.d.ts +20 -20
- package/dist/solana/SolanaChains.js +25 -25
- package/dist/solana/SolanaInitializer.d.ts +18 -18
- package/dist/solana/SolanaInitializer.js +63 -63
- package/dist/solana/btcrelay/SolanaBtcRelay.d.ts +228 -228
- package/dist/solana/btcrelay/SolanaBtcRelay.js +441 -441
- package/dist/solana/btcrelay/headers/SolanaBtcHeader.d.ts +29 -29
- package/dist/solana/btcrelay/headers/SolanaBtcHeader.js +34 -34
- package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.d.ts +46 -46
- package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.js +78 -78
- package/dist/solana/btcrelay/program/programIdl.json +671 -671
- package/dist/solana/chain/SolanaAction.d.ts +26 -26
- package/dist/solana/chain/SolanaAction.js +86 -86
- package/dist/solana/chain/SolanaChainInterface.d.ts +58 -58
- package/dist/solana/chain/SolanaChainInterface.js +112 -112
- package/dist/solana/chain/SolanaModule.d.ts +14 -14
- package/dist/solana/chain/SolanaModule.js +13 -13
- package/dist/solana/chain/modules/SolanaAddresses.d.ts +8 -8
- package/dist/solana/chain/modules/SolanaAddresses.js +22 -22
- package/dist/solana/chain/modules/SolanaBlocks.d.ts +28 -28
- package/dist/solana/chain/modules/SolanaBlocks.js +72 -72
- package/dist/solana/chain/modules/SolanaEvents.d.ts +25 -25
- package/dist/solana/chain/modules/SolanaEvents.js +58 -58
- package/dist/solana/chain/modules/SolanaFees.d.ts +121 -121
- package/dist/solana/chain/modules/SolanaFees.js +379 -379
- package/dist/solana/chain/modules/SolanaSignatures.d.ts +23 -23
- package/dist/solana/chain/modules/SolanaSignatures.js +39 -39
- package/dist/solana/chain/modules/SolanaSlots.d.ts +31 -31
- package/dist/solana/chain/modules/SolanaSlots.js +68 -68
- package/dist/solana/chain/modules/SolanaTokens.d.ts +136 -136
- package/dist/solana/chain/modules/SolanaTokens.js +248 -248
- package/dist/solana/chain/modules/SolanaTransactions.d.ts +124 -124
- package/dist/solana/chain/modules/SolanaTransactions.js +332 -332
- package/dist/solana/events/SolanaChainEvents.d.ts +88 -88
- package/dist/solana/events/SolanaChainEvents.js +256 -256
- package/dist/solana/events/SolanaChainEventsBrowser.d.ts +85 -85
- package/dist/solana/events/SolanaChainEventsBrowser.js +194 -194
- package/dist/solana/program/SolanaProgramBase.d.ts +40 -40
- package/dist/solana/program/SolanaProgramBase.js +43 -43
- package/dist/solana/program/SolanaProgramModule.d.ts +8 -8
- package/dist/solana/program/SolanaProgramModule.js +11 -11
- package/dist/solana/program/modules/SolanaProgramEvents.d.ts +59 -59
- package/dist/solana/program/modules/SolanaProgramEvents.js +103 -103
- package/dist/solana/swaps/SolanaSwapData.d.ts +59 -59
- package/dist/solana/swaps/SolanaSwapData.js +267 -267
- package/dist/solana/swaps/SolanaSwapModule.d.ts +10 -10
- package/dist/solana/swaps/SolanaSwapModule.js +11 -11
- package/dist/solana/swaps/SolanaSwapProgram.d.ts +202 -202
- package/dist/solana/swaps/SolanaSwapProgram.js +470 -463
- package/dist/solana/swaps/SwapTypeEnum.d.ts +11 -11
- package/dist/solana/swaps/SwapTypeEnum.js +42 -42
- package/dist/solana/swaps/modules/SolanaDataAccount.d.ts +94 -94
- package/dist/solana/swaps/modules/SolanaDataAccount.js +231 -231
- package/dist/solana/swaps/modules/SolanaLpVault.d.ts +71 -71
- package/dist/solana/swaps/modules/SolanaLpVault.js +173 -173
- package/dist/solana/swaps/modules/SwapClaim.d.ts +129 -129
- package/dist/solana/swaps/modules/SwapClaim.js +291 -291
- package/dist/solana/swaps/modules/SwapInit.d.ts +217 -217
- package/dist/solana/swaps/modules/SwapInit.js +519 -519
- package/dist/solana/swaps/modules/SwapRefund.d.ts +82 -82
- package/dist/solana/swaps/modules/SwapRefund.js +252 -252
- package/dist/solana/swaps/programIdl.json +945 -945
- package/dist/solana/swaps/programTypes.d.ts +943 -943
- package/dist/solana/swaps/programTypes.js +945 -945
- package/dist/solana/wallet/SolanaKeypairWallet.d.ts +9 -9
- package/dist/solana/wallet/SolanaKeypairWallet.js +33 -33
- package/dist/solana/wallet/SolanaSigner.d.ts +10 -10
- package/dist/solana/wallet/SolanaSigner.js +16 -16
- package/dist/utils/Utils.d.ts +53 -53
- package/dist/utils/Utils.js +170 -170
- package/package.json +41 -41
- package/src/index.ts +36 -36
- package/src/solana/SolanaChainType.ts +25 -25
- package/src/solana/SolanaChains.ts +23 -23
- package/src/solana/SolanaInitializer.ts +102 -102
- package/src/solana/btcrelay/SolanaBtcRelay.ts +589 -588
- package/src/solana/btcrelay/headers/SolanaBtcHeader.ts +57 -57
- package/src/solana/btcrelay/headers/SolanaBtcStoredHeader.ts +102 -102
- package/src/solana/btcrelay/program/programIdl.json +670 -670
- package/src/solana/chain/SolanaAction.ts +108 -108
- package/src/solana/chain/SolanaChainInterface.ts +174 -174
- package/src/solana/chain/SolanaModule.ts +20 -20
- package/src/solana/chain/modules/SolanaAddresses.ts +20 -20
- package/src/solana/chain/modules/SolanaBlocks.ts +78 -78
- package/src/solana/chain/modules/SolanaEvents.ts +56 -56
- package/src/solana/chain/modules/SolanaFees.ts +450 -450
- package/src/solana/chain/modules/SolanaSignatures.ts +39 -39
- package/src/solana/chain/modules/SolanaSlots.ts +82 -82
- package/src/solana/chain/modules/SolanaTokens.ts +307 -307
- package/src/solana/chain/modules/SolanaTransactions.ts +370 -370
- package/src/solana/events/SolanaChainEvents.ts +299 -299
- package/src/solana/events/SolanaChainEventsBrowser.ts +256 -256
- package/src/solana/program/SolanaProgramBase.ts +79 -79
- package/src/solana/program/SolanaProgramModule.ts +15 -15
- package/src/solana/program/modules/SolanaProgramEvents.ts +140 -140
- package/src/solana/swaps/SolanaSwapData.ts +379 -379
- package/src/solana/swaps/SolanaSwapModule.ts +16 -16
- package/src/solana/swaps/SolanaSwapProgram.ts +697 -692
- package/src/solana/swaps/SwapTypeEnum.ts +29 -29
- package/src/solana/swaps/modules/SolanaDataAccount.ts +307 -307
- package/src/solana/swaps/modules/SolanaLpVault.ts +215 -215
- package/src/solana/swaps/modules/SwapClaim.ts +389 -389
- package/src/solana/swaps/modules/SwapInit.ts +663 -663
- package/src/solana/swaps/modules/SwapRefund.ts +312 -312
- package/src/solana/swaps/programIdl.json +944 -944
- package/src/solana/swaps/programTypes.ts +1885 -1885
- package/src/solana/wallet/SolanaKeypairWallet.ts +36 -36
- package/src/solana/wallet/SolanaSigner.ts +23 -23
- package/src/utils/Utils.ts +180 -180
|
@@ -1,308 +1,308 @@
|
|
|
1
|
-
import {SolanaModule} from "../SolanaModule";
|
|
2
|
-
import {PublicKey, SystemProgram} from "@solana/web3.js";
|
|
3
|
-
import {
|
|
4
|
-
Account, createAssociatedTokenAccountInstruction,
|
|
5
|
-
createCloseAccountInstruction, createSyncNativeInstruction, createTransferInstruction,
|
|
6
|
-
getAccount, getAssociatedTokenAddressSync,
|
|
7
|
-
TokenAccountNotFoundError
|
|
8
|
-
} from "@solana/spl-token";
|
|
9
|
-
import {SolanaTx} from "./SolanaTransactions";
|
|
10
|
-
import {SolanaAction} from "../SolanaAction";
|
|
11
|
-
import {tryWithRetries} from "../../../utils/Utils";
|
|
12
|
-
|
|
13
|
-
export class SolanaTokens extends SolanaModule {
|
|
14
|
-
|
|
15
|
-
public static readonly CUCosts = {
|
|
16
|
-
WRAP_SOL: 10000,
|
|
17
|
-
ATA_CLOSE: 10000,
|
|
18
|
-
ATA_INIT: 40000,
|
|
19
|
-
TRANSFER: 50000,
|
|
20
|
-
TRANSFER_SOL: 5000
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Creates an ATA for a specific public key & token, the ATA creation is paid for by the underlying provider's
|
|
25
|
-
* public key
|
|
26
|
-
*
|
|
27
|
-
* @param signer
|
|
28
|
-
* @param publicKey public key address of the user for which to initiate the ATA
|
|
29
|
-
* @param token token identification for which the ATA should be initialized
|
|
30
|
-
* @param requiredAta optional required ata address to use, if the address doesn't match it returns null
|
|
31
|
-
* @constructor
|
|
32
|
-
*/
|
|
33
|
-
public InitAta(signer: PublicKey, publicKey: PublicKey, token: PublicKey, requiredAta?: PublicKey): SolanaAction | null {
|
|
34
|
-
const ata = getAssociatedTokenAddressSync(token, publicKey, true);
|
|
35
|
-
if(requiredAta!=null && !ata.equals(requiredAta)) return null;
|
|
36
|
-
return new SolanaAction(
|
|
37
|
-
signer,
|
|
38
|
-
this.root,
|
|
39
|
-
createAssociatedTokenAccountInstruction(
|
|
40
|
-
signer,
|
|
41
|
-
ata,
|
|
42
|
-
publicKey,
|
|
43
|
-
token
|
|
44
|
-
),
|
|
45
|
-
SolanaTokens.CUCosts.ATA_INIT
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Action for wrapping SOL to WSOL for a specific public key
|
|
51
|
-
*
|
|
52
|
-
* @param publicKey public key of the user for which to wrap the SOL
|
|
53
|
-
* @param amount amount of SOL in lamports (smallest unit) to wrap
|
|
54
|
-
* @param initAta whether we should also initialize the ATA before depositing SOL
|
|
55
|
-
* @constructor
|
|
56
|
-
*/
|
|
57
|
-
public Wrap(publicKey: PublicKey, amount: bigint, initAta: boolean): SolanaAction {
|
|
58
|
-
const ata = getAssociatedTokenAddressSync(SolanaTokens.WSOL_ADDRESS, publicKey, true);
|
|
59
|
-
const action = new SolanaAction(publicKey, this.root);
|
|
60
|
-
if(initAta) action.addIx(
|
|
61
|
-
createAssociatedTokenAccountInstruction(publicKey, ata, publicKey, SolanaTokens.WSOL_ADDRESS),
|
|
62
|
-
SolanaTokens.CUCosts.ATA_INIT
|
|
63
|
-
);
|
|
64
|
-
action.addIx(
|
|
65
|
-
SystemProgram.transfer({
|
|
66
|
-
fromPubkey: publicKey,
|
|
67
|
-
toPubkey: ata,
|
|
68
|
-
lamports: amount
|
|
69
|
-
}),
|
|
70
|
-
SolanaTokens.CUCosts.WRAP_SOL
|
|
71
|
-
);
|
|
72
|
-
action.addIx(createSyncNativeInstruction(ata));
|
|
73
|
-
return action;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Action for unwrapping WSOL to SOL for a specific public key
|
|
78
|
-
*
|
|
79
|
-
* @param publicKey public key of the user for which to unwrap the sol
|
|
80
|
-
* @constructor
|
|
81
|
-
*/
|
|
82
|
-
public Unwrap(publicKey: PublicKey): SolanaAction {
|
|
83
|
-
const ata = getAssociatedTokenAddressSync(SolanaTokens.WSOL_ADDRESS, publicKey, true);
|
|
84
|
-
return new SolanaAction(
|
|
85
|
-
publicKey,
|
|
86
|
-
this.root,
|
|
87
|
-
createCloseAccountInstruction(ata, publicKey, publicKey),
|
|
88
|
-
SolanaTokens.CUCosts.ATA_CLOSE
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
public static readonly WSOL_ADDRESS = new PublicKey("So11111111111111111111111111111111111111112");
|
|
93
|
-
public static readonly SPL_ATA_RENT_EXEMPT = 2039280;
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Action for transferring the native SOL token, uses provider's public key as a sender
|
|
97
|
-
*
|
|
98
|
-
* @param signer
|
|
99
|
-
* @param recipient
|
|
100
|
-
* @param amount
|
|
101
|
-
* @constructor
|
|
102
|
-
* @private
|
|
103
|
-
*/
|
|
104
|
-
private SolTransfer(signer: PublicKey, recipient: PublicKey, amount: bigint): SolanaAction {
|
|
105
|
-
return new SolanaAction(signer, this.root,
|
|
106
|
-
SystemProgram.transfer({
|
|
107
|
-
fromPubkey: signer,
|
|
108
|
-
toPubkey: recipient,
|
|
109
|
-
lamports: amount
|
|
110
|
-
}),
|
|
111
|
-
SolanaTokens.CUCosts.TRANSFER_SOL
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Action for transferring the SPL token, uses provider's public key as a sender
|
|
117
|
-
*
|
|
118
|
-
* @param signer
|
|
119
|
-
* @param recipient
|
|
120
|
-
* @param token
|
|
121
|
-
* @param amount
|
|
122
|
-
* @constructor
|
|
123
|
-
* @private
|
|
124
|
-
*/
|
|
125
|
-
private Transfer(signer: PublicKey, recipient: PublicKey, token: PublicKey, amount: bigint): SolanaAction {
|
|
126
|
-
const srcAta = getAssociatedTokenAddressSync(token, signer, true)
|
|
127
|
-
const dstAta = getAssociatedTokenAddressSync(token, recipient, true);
|
|
128
|
-
return new SolanaAction(signer, this.root,
|
|
129
|
-
createTransferInstruction(
|
|
130
|
-
srcAta,
|
|
131
|
-
dstAta,
|
|
132
|
-
signer,
|
|
133
|
-
amount
|
|
134
|
-
),
|
|
135
|
-
SolanaTokens.CUCosts.TRANSFER
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Creates transactions for sending SOL (the native token)
|
|
141
|
-
*
|
|
142
|
-
* @param signer
|
|
143
|
-
* @param amount amount of the SOL in lamports (smallest unit) to send
|
|
144
|
-
* @param recipient recipient's address
|
|
145
|
-
* @param feeRate fee rate to use for the transactions
|
|
146
|
-
* @private
|
|
147
|
-
*/
|
|
148
|
-
private async txsTransferSol(signer: PublicKey, amount: bigint, recipient: PublicKey, feeRate?: string): Promise<SolanaTx[]> {
|
|
149
|
-
const wsolAta = getAssociatedTokenAddressSync(SolanaTokens.WSOL_ADDRESS, signer, true);
|
|
150
|
-
|
|
151
|
-
const shouldUnwrap = await this.ataExists(wsolAta);
|
|
152
|
-
const action = new SolanaAction(signer, this.root);
|
|
153
|
-
if(shouldUnwrap) {
|
|
154
|
-
feeRate = feeRate || await this.root.Fees.getFeeRate([signer, recipient, wsolAta]);
|
|
155
|
-
action.add(this.Unwrap(signer));
|
|
156
|
-
} else {
|
|
157
|
-
feeRate = feeRate || await this.root.Fees.getFeeRate([signer, recipient]);
|
|
158
|
-
}
|
|
159
|
-
action.add(this.SolTransfer(signer, recipient, amount));
|
|
160
|
-
|
|
161
|
-
this.logger.debug("txsTransferSol(): transfer native solana TX created, recipient: "+recipient.toString()+
|
|
162
|
-
" amount: "+amount.toString(10)+" unwrapping: "+shouldUnwrap);
|
|
163
|
-
|
|
164
|
-
return [await action.tx(feeRate)];
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Creates transactions for sending the over the tokens
|
|
169
|
-
*
|
|
170
|
-
* @param signer
|
|
171
|
-
* @param token token to send
|
|
172
|
-
* @param amount amount of the token to send
|
|
173
|
-
* @param recipient recipient's address
|
|
174
|
-
* @param feeRate fee rate to use for the transactions
|
|
175
|
-
* @private
|
|
176
|
-
*/
|
|
177
|
-
private async txsTransferTokens(signer: PublicKey, token: PublicKey, amount: bigint, recipient: PublicKey, feeRate?: string) {
|
|
178
|
-
const srcAta = getAssociatedTokenAddressSync(token, signer, true);
|
|
179
|
-
const dstAta = getAssociatedTokenAddressSync(token, recipient, true);
|
|
180
|
-
|
|
181
|
-
feeRate = feeRate || await this.root.Fees.getFeeRate([signer, srcAta, dstAta]);
|
|
182
|
-
|
|
183
|
-
const initAta = !await this.ataExists(dstAta);
|
|
184
|
-
const action = new SolanaAction(signer, this.root);
|
|
185
|
-
if(initAta) {
|
|
186
|
-
action.add(this.InitAta(signer, recipient, token));
|
|
187
|
-
}
|
|
188
|
-
action.add(this.Transfer(signer, recipient, token, amount));
|
|
189
|
-
|
|
190
|
-
this.logger.debug("txsTransferTokens(): transfer TX created, recipient: "+recipient.toString()+
|
|
191
|
-
" token: "+token.toString()+ " amount: "+amount.toString(10)+" initAta: "+initAta);
|
|
192
|
-
|
|
193
|
-
return [await action.tx(feeRate)];
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
///////////////////
|
|
197
|
-
//// Tokens
|
|
198
|
-
/**
|
|
199
|
-
* Checks if the provided string is a valid solana token
|
|
200
|
-
*
|
|
201
|
-
* @param token
|
|
202
|
-
*/
|
|
203
|
-
public isValidToken(token: string) {
|
|
204
|
-
try {
|
|
205
|
-
new PublicKey(token);
|
|
206
|
-
return true;
|
|
207
|
-
} catch (e) {
|
|
208
|
-
return false;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Returns the specific ATA or null if it doesn't exist
|
|
214
|
-
*
|
|
215
|
-
* @param ata
|
|
216
|
-
*/
|
|
217
|
-
public getATAOrNull(ata: PublicKey): Promise<Account | null> {
|
|
218
|
-
return getAccount(this.connection, ata).catch(e => {
|
|
219
|
-
if(e instanceof TokenAccountNotFoundError) return null;
|
|
220
|
-
throw e;
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Checks whether the specific ATA exists, uses tryWithRetries so retries on failure
|
|
226
|
-
*
|
|
227
|
-
* @param ata
|
|
228
|
-
*/
|
|
229
|
-
public async ataExists(ata: PublicKey) {
|
|
230
|
-
const account = await tryWithRetries<Account>(
|
|
231
|
-
() => this.getATAOrNull(ata),
|
|
232
|
-
this.retryPolicy
|
|
233
|
-
);
|
|
234
|
-
return account!=null;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Returns the rent exempt deposit required to initiate the ATA
|
|
239
|
-
*/
|
|
240
|
-
public getATARentExemptLamports(): Promise<bigint> {
|
|
241
|
-
return Promise.resolve(BigInt(SolanaTokens.SPL_ATA_RENT_EXEMPT));
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Returns the token balance of the public key
|
|
246
|
-
*
|
|
247
|
-
* @param publicKey
|
|
248
|
-
* @param token
|
|
249
|
-
*/
|
|
250
|
-
public async getTokenBalance(publicKey: PublicKey, token: PublicKey): Promise<{balance: bigint, ataExists: boolean}> {
|
|
251
|
-
const ata: PublicKey = getAssociatedTokenAddressSync(token, publicKey, true);
|
|
252
|
-
const [ataAccount, balance] = await Promise.all<[Promise<Account>, Promise<number>]>([
|
|
253
|
-
this.getATAOrNull(ata),
|
|
254
|
-
(token!=null && token.equals(SolanaTokens.WSOL_ADDRESS)) ? this.connection.getBalance(publicKey) : Promise.resolve(null)
|
|
255
|
-
]);
|
|
256
|
-
|
|
257
|
-
let ataExists: boolean = ataAccount!=null;
|
|
258
|
-
let sum: bigint = 0n;
|
|
259
|
-
if(ataExists) {
|
|
260
|
-
sum += ataAccount.amount;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if(balance!=null) {
|
|
264
|
-
let balanceLamports: bigint = BigInt(balance);
|
|
265
|
-
if(!ataExists) balanceLamports = balanceLamports - await this.getATARentExemptLamports();
|
|
266
|
-
if(balanceLamports >= 0n) sum += balanceLamports;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
this.logger.debug("getTokenBalance(): token balance fetched, token: "+token.toString()+
|
|
270
|
-
" address: "+publicKey.toString()+" amount: "+sum.toString());
|
|
271
|
-
|
|
272
|
-
return {balance: sum, ataExists};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Returns the native currency address, we use WSOL address as placeholder for SOL
|
|
277
|
-
*/
|
|
278
|
-
public getNativeCurrencyAddress(): PublicKey {
|
|
279
|
-
return SolanaTokens.WSOL_ADDRESS;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Parses string base58 representation of the token address to a PublicKey object
|
|
284
|
-
* @param address
|
|
285
|
-
*/
|
|
286
|
-
public toTokenAddress(address: string): PublicKey {
|
|
287
|
-
return new PublicKey(address);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
///////////////////
|
|
291
|
-
//// Transfers
|
|
292
|
-
/**
|
|
293
|
-
* Create transactions for sending a specific token to a destination address
|
|
294
|
-
*
|
|
295
|
-
* @param signer
|
|
296
|
-
* @param token token to use for the transfer
|
|
297
|
-
* @param amount amount of token in base units to transfer
|
|
298
|
-
* @param dstAddress destination address of the recipient
|
|
299
|
-
* @param feeRate fee rate to use for the transaction
|
|
300
|
-
*/
|
|
301
|
-
public txsTransfer(signer:PublicKey, token: PublicKey, amount: bigint, dstAddress: PublicKey, feeRate?: string): Promise<SolanaTx[]> {
|
|
302
|
-
if(SolanaTokens.WSOL_ADDRESS.equals(token)) {
|
|
303
|
-
return this.txsTransferSol(signer, amount, dstAddress, feeRate);
|
|
304
|
-
}
|
|
305
|
-
return this.txsTransferTokens(signer, token, amount, dstAddress, feeRate);
|
|
306
|
-
}
|
|
307
|
-
|
|
1
|
+
import {SolanaModule} from "../SolanaModule";
|
|
2
|
+
import {PublicKey, SystemProgram} from "@solana/web3.js";
|
|
3
|
+
import {
|
|
4
|
+
Account, createAssociatedTokenAccountInstruction,
|
|
5
|
+
createCloseAccountInstruction, createSyncNativeInstruction, createTransferInstruction,
|
|
6
|
+
getAccount, getAssociatedTokenAddressSync,
|
|
7
|
+
TokenAccountNotFoundError
|
|
8
|
+
} from "@solana/spl-token";
|
|
9
|
+
import {SolanaTx} from "./SolanaTransactions";
|
|
10
|
+
import {SolanaAction} from "../SolanaAction";
|
|
11
|
+
import {tryWithRetries} from "../../../utils/Utils";
|
|
12
|
+
|
|
13
|
+
export class SolanaTokens extends SolanaModule {
|
|
14
|
+
|
|
15
|
+
public static readonly CUCosts = {
|
|
16
|
+
WRAP_SOL: 10000,
|
|
17
|
+
ATA_CLOSE: 10000,
|
|
18
|
+
ATA_INIT: 40000,
|
|
19
|
+
TRANSFER: 50000,
|
|
20
|
+
TRANSFER_SOL: 5000
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates an ATA for a specific public key & token, the ATA creation is paid for by the underlying provider's
|
|
25
|
+
* public key
|
|
26
|
+
*
|
|
27
|
+
* @param signer
|
|
28
|
+
* @param publicKey public key address of the user for which to initiate the ATA
|
|
29
|
+
* @param token token identification for which the ATA should be initialized
|
|
30
|
+
* @param requiredAta optional required ata address to use, if the address doesn't match it returns null
|
|
31
|
+
* @constructor
|
|
32
|
+
*/
|
|
33
|
+
public InitAta(signer: PublicKey, publicKey: PublicKey, token: PublicKey, requiredAta?: PublicKey): SolanaAction | null {
|
|
34
|
+
const ata = getAssociatedTokenAddressSync(token, publicKey, true);
|
|
35
|
+
if(requiredAta!=null && !ata.equals(requiredAta)) return null;
|
|
36
|
+
return new SolanaAction(
|
|
37
|
+
signer,
|
|
38
|
+
this.root,
|
|
39
|
+
createAssociatedTokenAccountInstruction(
|
|
40
|
+
signer,
|
|
41
|
+
ata,
|
|
42
|
+
publicKey,
|
|
43
|
+
token
|
|
44
|
+
),
|
|
45
|
+
SolanaTokens.CUCosts.ATA_INIT
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Action for wrapping SOL to WSOL for a specific public key
|
|
51
|
+
*
|
|
52
|
+
* @param publicKey public key of the user for which to wrap the SOL
|
|
53
|
+
* @param amount amount of SOL in lamports (smallest unit) to wrap
|
|
54
|
+
* @param initAta whether we should also initialize the ATA before depositing SOL
|
|
55
|
+
* @constructor
|
|
56
|
+
*/
|
|
57
|
+
public Wrap(publicKey: PublicKey, amount: bigint, initAta: boolean): SolanaAction {
|
|
58
|
+
const ata = getAssociatedTokenAddressSync(SolanaTokens.WSOL_ADDRESS, publicKey, true);
|
|
59
|
+
const action = new SolanaAction(publicKey, this.root);
|
|
60
|
+
if(initAta) action.addIx(
|
|
61
|
+
createAssociatedTokenAccountInstruction(publicKey, ata, publicKey, SolanaTokens.WSOL_ADDRESS),
|
|
62
|
+
SolanaTokens.CUCosts.ATA_INIT
|
|
63
|
+
);
|
|
64
|
+
action.addIx(
|
|
65
|
+
SystemProgram.transfer({
|
|
66
|
+
fromPubkey: publicKey,
|
|
67
|
+
toPubkey: ata,
|
|
68
|
+
lamports: amount
|
|
69
|
+
}),
|
|
70
|
+
SolanaTokens.CUCosts.WRAP_SOL
|
|
71
|
+
);
|
|
72
|
+
action.addIx(createSyncNativeInstruction(ata));
|
|
73
|
+
return action;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Action for unwrapping WSOL to SOL for a specific public key
|
|
78
|
+
*
|
|
79
|
+
* @param publicKey public key of the user for which to unwrap the sol
|
|
80
|
+
* @constructor
|
|
81
|
+
*/
|
|
82
|
+
public Unwrap(publicKey: PublicKey): SolanaAction {
|
|
83
|
+
const ata = getAssociatedTokenAddressSync(SolanaTokens.WSOL_ADDRESS, publicKey, true);
|
|
84
|
+
return new SolanaAction(
|
|
85
|
+
publicKey,
|
|
86
|
+
this.root,
|
|
87
|
+
createCloseAccountInstruction(ata, publicKey, publicKey),
|
|
88
|
+
SolanaTokens.CUCosts.ATA_CLOSE
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public static readonly WSOL_ADDRESS = new PublicKey("So11111111111111111111111111111111111111112");
|
|
93
|
+
public static readonly SPL_ATA_RENT_EXEMPT = 2039280;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Action for transferring the native SOL token, uses provider's public key as a sender
|
|
97
|
+
*
|
|
98
|
+
* @param signer
|
|
99
|
+
* @param recipient
|
|
100
|
+
* @param amount
|
|
101
|
+
* @constructor
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
private SolTransfer(signer: PublicKey, recipient: PublicKey, amount: bigint): SolanaAction {
|
|
105
|
+
return new SolanaAction(signer, this.root,
|
|
106
|
+
SystemProgram.transfer({
|
|
107
|
+
fromPubkey: signer,
|
|
108
|
+
toPubkey: recipient,
|
|
109
|
+
lamports: amount
|
|
110
|
+
}),
|
|
111
|
+
SolanaTokens.CUCosts.TRANSFER_SOL
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Action for transferring the SPL token, uses provider's public key as a sender
|
|
117
|
+
*
|
|
118
|
+
* @param signer
|
|
119
|
+
* @param recipient
|
|
120
|
+
* @param token
|
|
121
|
+
* @param amount
|
|
122
|
+
* @constructor
|
|
123
|
+
* @private
|
|
124
|
+
*/
|
|
125
|
+
private Transfer(signer: PublicKey, recipient: PublicKey, token: PublicKey, amount: bigint): SolanaAction {
|
|
126
|
+
const srcAta = getAssociatedTokenAddressSync(token, signer, true)
|
|
127
|
+
const dstAta = getAssociatedTokenAddressSync(token, recipient, true);
|
|
128
|
+
return new SolanaAction(signer, this.root,
|
|
129
|
+
createTransferInstruction(
|
|
130
|
+
srcAta,
|
|
131
|
+
dstAta,
|
|
132
|
+
signer,
|
|
133
|
+
amount
|
|
134
|
+
),
|
|
135
|
+
SolanaTokens.CUCosts.TRANSFER
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Creates transactions for sending SOL (the native token)
|
|
141
|
+
*
|
|
142
|
+
* @param signer
|
|
143
|
+
* @param amount amount of the SOL in lamports (smallest unit) to send
|
|
144
|
+
* @param recipient recipient's address
|
|
145
|
+
* @param feeRate fee rate to use for the transactions
|
|
146
|
+
* @private
|
|
147
|
+
*/
|
|
148
|
+
private async txsTransferSol(signer: PublicKey, amount: bigint, recipient: PublicKey, feeRate?: string): Promise<SolanaTx[]> {
|
|
149
|
+
const wsolAta = getAssociatedTokenAddressSync(SolanaTokens.WSOL_ADDRESS, signer, true);
|
|
150
|
+
|
|
151
|
+
const shouldUnwrap = await this.ataExists(wsolAta);
|
|
152
|
+
const action = new SolanaAction(signer, this.root);
|
|
153
|
+
if(shouldUnwrap) {
|
|
154
|
+
feeRate = feeRate || await this.root.Fees.getFeeRate([signer, recipient, wsolAta]);
|
|
155
|
+
action.add(this.Unwrap(signer));
|
|
156
|
+
} else {
|
|
157
|
+
feeRate = feeRate || await this.root.Fees.getFeeRate([signer, recipient]);
|
|
158
|
+
}
|
|
159
|
+
action.add(this.SolTransfer(signer, recipient, amount));
|
|
160
|
+
|
|
161
|
+
this.logger.debug("txsTransferSol(): transfer native solana TX created, recipient: "+recipient.toString()+
|
|
162
|
+
" amount: "+amount.toString(10)+" unwrapping: "+shouldUnwrap);
|
|
163
|
+
|
|
164
|
+
return [await action.tx(feeRate)];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Creates transactions for sending the over the tokens
|
|
169
|
+
*
|
|
170
|
+
* @param signer
|
|
171
|
+
* @param token token to send
|
|
172
|
+
* @param amount amount of the token to send
|
|
173
|
+
* @param recipient recipient's address
|
|
174
|
+
* @param feeRate fee rate to use for the transactions
|
|
175
|
+
* @private
|
|
176
|
+
*/
|
|
177
|
+
private async txsTransferTokens(signer: PublicKey, token: PublicKey, amount: bigint, recipient: PublicKey, feeRate?: string) {
|
|
178
|
+
const srcAta = getAssociatedTokenAddressSync(token, signer, true);
|
|
179
|
+
const dstAta = getAssociatedTokenAddressSync(token, recipient, true);
|
|
180
|
+
|
|
181
|
+
feeRate = feeRate || await this.root.Fees.getFeeRate([signer, srcAta, dstAta]);
|
|
182
|
+
|
|
183
|
+
const initAta = !await this.ataExists(dstAta);
|
|
184
|
+
const action = new SolanaAction(signer, this.root);
|
|
185
|
+
if(initAta) {
|
|
186
|
+
action.add(this.InitAta(signer, recipient, token));
|
|
187
|
+
}
|
|
188
|
+
action.add(this.Transfer(signer, recipient, token, amount));
|
|
189
|
+
|
|
190
|
+
this.logger.debug("txsTransferTokens(): transfer TX created, recipient: "+recipient.toString()+
|
|
191
|
+
" token: "+token.toString()+ " amount: "+amount.toString(10)+" initAta: "+initAta);
|
|
192
|
+
|
|
193
|
+
return [await action.tx(feeRate)];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
///////////////////
|
|
197
|
+
//// Tokens
|
|
198
|
+
/**
|
|
199
|
+
* Checks if the provided string is a valid solana token
|
|
200
|
+
*
|
|
201
|
+
* @param token
|
|
202
|
+
*/
|
|
203
|
+
public isValidToken(token: string) {
|
|
204
|
+
try {
|
|
205
|
+
new PublicKey(token);
|
|
206
|
+
return true;
|
|
207
|
+
} catch (e) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Returns the specific ATA or null if it doesn't exist
|
|
214
|
+
*
|
|
215
|
+
* @param ata
|
|
216
|
+
*/
|
|
217
|
+
public getATAOrNull(ata: PublicKey): Promise<Account | null> {
|
|
218
|
+
return getAccount(this.connection, ata).catch(e => {
|
|
219
|
+
if(e instanceof TokenAccountNotFoundError) return null;
|
|
220
|
+
throw e;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Checks whether the specific ATA exists, uses tryWithRetries so retries on failure
|
|
226
|
+
*
|
|
227
|
+
* @param ata
|
|
228
|
+
*/
|
|
229
|
+
public async ataExists(ata: PublicKey) {
|
|
230
|
+
const account = await tryWithRetries<Account>(
|
|
231
|
+
() => this.getATAOrNull(ata),
|
|
232
|
+
this.retryPolicy
|
|
233
|
+
);
|
|
234
|
+
return account!=null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Returns the rent exempt deposit required to initiate the ATA
|
|
239
|
+
*/
|
|
240
|
+
public getATARentExemptLamports(): Promise<bigint> {
|
|
241
|
+
return Promise.resolve(BigInt(SolanaTokens.SPL_ATA_RENT_EXEMPT));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Returns the token balance of the public key
|
|
246
|
+
*
|
|
247
|
+
* @param publicKey
|
|
248
|
+
* @param token
|
|
249
|
+
*/
|
|
250
|
+
public async getTokenBalance(publicKey: PublicKey, token: PublicKey): Promise<{balance: bigint, ataExists: boolean}> {
|
|
251
|
+
const ata: PublicKey = getAssociatedTokenAddressSync(token, publicKey, true);
|
|
252
|
+
const [ataAccount, balance] = await Promise.all<[Promise<Account>, Promise<number>]>([
|
|
253
|
+
this.getATAOrNull(ata),
|
|
254
|
+
(token!=null && token.equals(SolanaTokens.WSOL_ADDRESS)) ? this.connection.getBalance(publicKey) : Promise.resolve(null)
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
let ataExists: boolean = ataAccount!=null;
|
|
258
|
+
let sum: bigint = 0n;
|
|
259
|
+
if(ataExists) {
|
|
260
|
+
sum += ataAccount.amount;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if(balance!=null) {
|
|
264
|
+
let balanceLamports: bigint = BigInt(balance);
|
|
265
|
+
if(!ataExists) balanceLamports = balanceLamports - await this.getATARentExemptLamports();
|
|
266
|
+
if(balanceLamports >= 0n) sum += balanceLamports;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
this.logger.debug("getTokenBalance(): token balance fetched, token: "+token.toString()+
|
|
270
|
+
" address: "+publicKey.toString()+" amount: "+sum.toString());
|
|
271
|
+
|
|
272
|
+
return {balance: sum, ataExists};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Returns the native currency address, we use WSOL address as placeholder for SOL
|
|
277
|
+
*/
|
|
278
|
+
public getNativeCurrencyAddress(): PublicKey {
|
|
279
|
+
return SolanaTokens.WSOL_ADDRESS;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Parses string base58 representation of the token address to a PublicKey object
|
|
284
|
+
* @param address
|
|
285
|
+
*/
|
|
286
|
+
public toTokenAddress(address: string): PublicKey {
|
|
287
|
+
return new PublicKey(address);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
///////////////////
|
|
291
|
+
//// Transfers
|
|
292
|
+
/**
|
|
293
|
+
* Create transactions for sending a specific token to a destination address
|
|
294
|
+
*
|
|
295
|
+
* @param signer
|
|
296
|
+
* @param token token to use for the transfer
|
|
297
|
+
* @param amount amount of token in base units to transfer
|
|
298
|
+
* @param dstAddress destination address of the recipient
|
|
299
|
+
* @param feeRate fee rate to use for the transaction
|
|
300
|
+
*/
|
|
301
|
+
public txsTransfer(signer:PublicKey, token: PublicKey, amount: bigint, dstAddress: PublicKey, feeRate?: string): Promise<SolanaTx[]> {
|
|
302
|
+
if(SolanaTokens.WSOL_ADDRESS.equals(token)) {
|
|
303
|
+
return this.txsTransferSol(signer, amount, dstAddress, feeRate);
|
|
304
|
+
}
|
|
305
|
+
return this.txsTransferTokens(signer, token, amount, dstAddress, feeRate);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
308
|
}
|