@xchainjs/xchain-solana 0.0.4 → 0.0.6
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/lib/client.d.ts +72 -3
- package/lib/index.esm.js +413 -207
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +414 -204
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +2 -1
- package/package.json +4 -4
package/lib/index.esm.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { mplTokenMetadata,
|
|
1
|
+
import { mplTokenMetadata, fetchDigitalAsset, fetchAllDigitalAsset } from '@metaplex-foundation/mpl-token-metadata';
|
|
2
2
|
import { publicKey } from '@metaplex-foundation/umi';
|
|
3
3
|
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
|
|
4
4
|
import { isAddress } from '@solana/addresses';
|
|
5
5
|
import { TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, getAccount, createTransferInstruction, TokenAccountNotFoundError, TokenInvalidAccountOwnerError, getOrCreateAssociatedTokenAccount } from '@solana/spl-token';
|
|
6
6
|
import { Connection, clusterApiUrl, Keypair, PublicKey, Transaction, SystemProgram, TransactionInstruction, ComputeBudgetProgram, SendTransactionError } from '@solana/web3.js';
|
|
7
|
-
import { ExplorerProvider, Network, BaseXChainClient, FeeType, FeeOption
|
|
7
|
+
import { ExplorerProvider, Network, BaseXChainClient, TxType, FeeType, FeeOption } from '@xchainjs/xchain-client';
|
|
8
8
|
import { getSeed } from '@xchainjs/xchain-crypto';
|
|
9
|
-
import { AssetType, baseAmount,
|
|
9
|
+
import { AssetType, baseAmount, assetToBase, assetAmount, assetFromStringEx, eqAsset, getContractAddressFromAsset } from '@xchainjs/xchain-util';
|
|
10
10
|
import bs58 from 'bs58';
|
|
11
11
|
import { HDKey } from 'micro-ed25519-hdkey';
|
|
12
12
|
|
|
@@ -24,7 +24,7 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
24
24
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
25
25
|
PERFORMANCE OF THIS SOFTWARE.
|
|
26
26
|
***************************************************************************** */
|
|
27
|
-
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
27
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
function __awaiter(thisArg, _arguments, P, generator) {
|
|
@@ -87,8 +87,55 @@ class Client extends BaseXChainClient {
|
|
|
87
87
|
constructor(params = defaultSolanaParams) {
|
|
88
88
|
super(SOLChain, Object.assign(Object.assign({}, defaultSolanaParams), params));
|
|
89
89
|
this.explorerProviders = params.explorerProviders;
|
|
90
|
-
this.
|
|
91
|
-
|
|
90
|
+
this.clientUrls = params.clientUrls;
|
|
91
|
+
if (!this.clientUrls) {
|
|
92
|
+
const solanaProvider = new Connection(clusterApiUrl(getSolanaNetwork(this.getNetwork())));
|
|
93
|
+
const umiProvider = createUmi(solanaProvider).use(mplTokenMetadata());
|
|
94
|
+
this.providers = [
|
|
95
|
+
{
|
|
96
|
+
solanaProvider,
|
|
97
|
+
umiProvider,
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this.providers = this.clientUrls[this.getNetwork()].map((url) => {
|
|
103
|
+
const solanaProvider = new Connection(url);
|
|
104
|
+
const umiProvider = createUmi(solanaProvider).use(mplTokenMetadata());
|
|
105
|
+
return {
|
|
106
|
+
solanaProvider,
|
|
107
|
+
umiProvider,
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Set or update the current network.
|
|
114
|
+
* @param {Network} network The network to set
|
|
115
|
+
* @returns {void}
|
|
116
|
+
*/
|
|
117
|
+
setNetwork(network) {
|
|
118
|
+
super.setNetwork(network);
|
|
119
|
+
if (!this.clientUrls) {
|
|
120
|
+
const solanaProvider = new Connection(clusterApiUrl(getSolanaNetwork(this.getNetwork())));
|
|
121
|
+
const umiProvider = createUmi(solanaProvider).use(mplTokenMetadata());
|
|
122
|
+
this.providers = [
|
|
123
|
+
{
|
|
124
|
+
solanaProvider,
|
|
125
|
+
umiProvider,
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
this.providers = this.clientUrls[this.getNetwork()].map((url) => {
|
|
131
|
+
const solanaProvider = new Connection(url);
|
|
132
|
+
const umiProvider = createUmi(solanaProvider).use(mplTokenMetadata());
|
|
133
|
+
return {
|
|
134
|
+
solanaProvider,
|
|
135
|
+
umiProvider,
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
}
|
|
92
139
|
}
|
|
93
140
|
/**
|
|
94
141
|
* Get information about the native asset of the Solana.
|
|
@@ -173,39 +220,7 @@ class Client extends BaseXChainClient {
|
|
|
173
220
|
*/
|
|
174
221
|
getBalance(address, assets) {
|
|
175
222
|
return __awaiter(this, void 0, void 0, function* () {
|
|
176
|
-
|
|
177
|
-
const nativeBalance = yield this.connection.getBalance(new PublicKey(address));
|
|
178
|
-
balances.push({
|
|
179
|
-
asset: SOLAsset,
|
|
180
|
-
amount: baseAmount(nativeBalance, SOL_DECIMALS),
|
|
181
|
-
});
|
|
182
|
-
const tokenBalances = yield this.connection.getParsedTokenAccountsByOwner(new PublicKey(address), {
|
|
183
|
-
programId: TOKEN_PROGRAM_ID,
|
|
184
|
-
});
|
|
185
|
-
const tokensToRequest = !assets
|
|
186
|
-
? tokenBalances.value
|
|
187
|
-
: tokenBalances.value.filter((tokenBalance) => {
|
|
188
|
-
const tokenData = tokenBalance.account.data.parsed;
|
|
189
|
-
return (assets.findIndex((asset) => {
|
|
190
|
-
return asset.symbol.toLowerCase().includes(tokenData.info.mint.toLowerCase());
|
|
191
|
-
}) !== -1);
|
|
192
|
-
});
|
|
193
|
-
const mintPublicKeys = tokensToRequest.map((tokenBalance) => {
|
|
194
|
-
const tokenData = tokenBalance.account.data.parsed;
|
|
195
|
-
return publicKey(tokenData.info.mint);
|
|
196
|
-
});
|
|
197
|
-
const assetsData = yield fetchAllDigitalAsset(this.umi, mintPublicKeys);
|
|
198
|
-
tokenBalances.value.forEach((balance) => {
|
|
199
|
-
const parsedData = balance.account.data.parsed;
|
|
200
|
-
const assetData = assetsData.find((assetData) => assetData.publicKey.toString() === parsedData.info.mint);
|
|
201
|
-
if (assetData) {
|
|
202
|
-
balances.push({
|
|
203
|
-
amount: baseAmount(parsedData.info.tokenAmount.amount, parsedData.info.tokenAmount.decimals),
|
|
204
|
-
asset: assetFromStringEx(`SOL.${assetData.metadata.symbol.trim()}-${parsedData.info.mint}`),
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
return balances;
|
|
223
|
+
return this.roundRobinGetBalance(address, assets);
|
|
209
224
|
});
|
|
210
225
|
}
|
|
211
226
|
/**
|
|
@@ -219,64 +234,7 @@ class Client extends BaseXChainClient {
|
|
|
219
234
|
return __awaiter(this, void 0, void 0, function* () {
|
|
220
235
|
if (!params)
|
|
221
236
|
throw new Error('Params need to be passed');
|
|
222
|
-
|
|
223
|
-
const toPubkey = new PublicKey(params.recipient);
|
|
224
|
-
const transaction = new Transaction();
|
|
225
|
-
transaction.recentBlockhash = yield this.connection.getLatestBlockhash().then((block) => block.blockhash);
|
|
226
|
-
transaction.feePayer = sender.publicKey;
|
|
227
|
-
let createAccountTxFee = 0;
|
|
228
|
-
if (!params.asset || eqAsset(params.asset, this.getAssetInfo().asset)) {
|
|
229
|
-
// Native transfer
|
|
230
|
-
transaction.add(SystemProgram.transfer({
|
|
231
|
-
fromPubkey: sender.publicKey,
|
|
232
|
-
toPubkey,
|
|
233
|
-
lamports: params.amount.amount().toNumber(),
|
|
234
|
-
}));
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
// Token transfer
|
|
238
|
-
const mintAddress = new PublicKey(getContractAddressFromAsset(params.asset));
|
|
239
|
-
const associatedTokenAddress = getAssociatedTokenAddressSync(mintAddress, toPubkey);
|
|
240
|
-
try {
|
|
241
|
-
yield getAccount(this.connection, associatedTokenAddress, undefined, TOKEN_PROGRAM_ID);
|
|
242
|
-
transaction.add(createTransferInstruction(sender.publicKey, // Should be Token account, but as it new KeyPair for estimation, sender public key
|
|
243
|
-
associatedTokenAddress, sender.publicKey, params.amount.amount().toNumber()));
|
|
244
|
-
}
|
|
245
|
-
catch (error) {
|
|
246
|
-
if (error instanceof TokenAccountNotFoundError || error instanceof TokenInvalidAccountOwnerError) {
|
|
247
|
-
// recipient token account has to be created
|
|
248
|
-
const dataLength = 165; // Normally used for Token accounts
|
|
249
|
-
createAccountTxFee = yield this.connection.getMinimumBalanceForRentExemption(dataLength);
|
|
250
|
-
transaction.add(createTransferInstruction(sender.publicKey, // Should be Token account, but as it new KeyPair for estimation, sender public key
|
|
251
|
-
toPubkey, // Should be Token account, but as recipient token account should be created, recipient public key
|
|
252
|
-
sender.publicKey, params.amount.amount().toNumber()));
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (params.memo) {
|
|
257
|
-
transaction.add(new TransactionInstruction({
|
|
258
|
-
keys: [{ pubkey: sender.publicKey, isSigner: true, isWritable: true }],
|
|
259
|
-
data: Buffer.from(params.memo, 'utf-8'),
|
|
260
|
-
programId: new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'),
|
|
261
|
-
}));
|
|
262
|
-
}
|
|
263
|
-
if (params.priorityFee) {
|
|
264
|
-
transaction.add(ComputeBudgetProgram.setComputeUnitPrice({
|
|
265
|
-
microLamports: params.priorityFee.amount().toNumber() / Math.pow(10, 3),
|
|
266
|
-
}));
|
|
267
|
-
}
|
|
268
|
-
if (params.limit) {
|
|
269
|
-
transaction.add(ComputeBudgetProgram.setComputeUnitLimit({
|
|
270
|
-
units: params.limit,
|
|
271
|
-
}));
|
|
272
|
-
}
|
|
273
|
-
const fee = (yield transaction.getEstimatedFee(this.connection)) || 0;
|
|
274
|
-
return {
|
|
275
|
-
type: FeeType.FlatFee,
|
|
276
|
-
[FeeOption.Average]: baseAmount(fee + createAccountTxFee, SOL_DECIMALS),
|
|
277
|
-
[FeeOption.Fast]: baseAmount(fee + createAccountTxFee, SOL_DECIMALS),
|
|
278
|
-
[FeeOption.Fastest]: baseAmount(fee + createAccountTxFee, SOL_DECIMALS),
|
|
279
|
-
};
|
|
237
|
+
return this.roundRobinGetFees(params);
|
|
280
238
|
});
|
|
281
239
|
}
|
|
282
240
|
/**
|
|
@@ -287,29 +245,21 @@ class Client extends BaseXChainClient {
|
|
|
287
245
|
*/
|
|
288
246
|
getTransactionData(txId) {
|
|
289
247
|
return __awaiter(this, void 0, void 0, function* () {
|
|
290
|
-
const transaction = yield this.
|
|
248
|
+
const transaction = yield this.roundRobinGetTransactionData(txId);
|
|
291
249
|
if (!transaction)
|
|
292
250
|
throw Error('Can not find transaction');
|
|
293
251
|
return this.parseTransaction(transaction);
|
|
294
252
|
});
|
|
295
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Get the transaction history of a given address with pagination options.
|
|
256
|
+
*
|
|
257
|
+
* @param {TxHistoryParams} params The options to get transaction history.
|
|
258
|
+
* @returns {TxsPage} The transaction history.
|
|
259
|
+
*/
|
|
296
260
|
getTransactions(params) {
|
|
297
261
|
return __awaiter(this, void 0, void 0, function* () {
|
|
298
|
-
|
|
299
|
-
const transactions = yield this.connection.getParsedTransactions(signatures.map(({ signature }) => signature));
|
|
300
|
-
const results = yield Promise.allSettled(transactions
|
|
301
|
-
.filter((transaction) => !!transaction)
|
|
302
|
-
.map((transaction) => this.parseTransaction(transaction)));
|
|
303
|
-
const txs = [];
|
|
304
|
-
results.forEach((result) => {
|
|
305
|
-
if (result.status === 'fulfilled') {
|
|
306
|
-
txs.push(result.value);
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
return {
|
|
310
|
-
txs,
|
|
311
|
-
total: txs.length,
|
|
312
|
-
};
|
|
262
|
+
return this.roundRobinGetTransactions(params);
|
|
313
263
|
});
|
|
314
264
|
}
|
|
315
265
|
/**
|
|
@@ -320,14 +270,8 @@ class Client extends BaseXChainClient {
|
|
|
320
270
|
*/
|
|
321
271
|
transfer({ walletIndex, recipient, asset, amount, memo, limit, priorityFee, }) {
|
|
322
272
|
return __awaiter(this, void 0, void 0, function* () {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
// Check if receipt token account is created, otherwise, create it
|
|
326
|
-
const mintAddress = new PublicKey(getContractAddressFromAsset(asset));
|
|
327
|
-
yield getOrCreateAssociatedTokenAccount(this.connection, senderKeyPair, mintAddress, new PublicKey(recipient));
|
|
328
|
-
}
|
|
329
|
-
const { rawUnsignedTx } = yield this.prepareTx({
|
|
330
|
-
sender: senderKeyPair.publicKey.toBase58(),
|
|
273
|
+
return this.roundRobinTransfer({
|
|
274
|
+
walletIndex,
|
|
331
275
|
recipient,
|
|
332
276
|
asset,
|
|
333
277
|
amount,
|
|
@@ -335,9 +279,6 @@ class Client extends BaseXChainClient {
|
|
|
335
279
|
limit,
|
|
336
280
|
priorityFee,
|
|
337
281
|
});
|
|
338
|
-
const transaction = Transaction.from(bs58.decode(rawUnsignedTx));
|
|
339
|
-
transaction.sign(senderKeyPair);
|
|
340
|
-
return this.broadcastTx(bs58.encode(transaction.serialize()));
|
|
341
282
|
});
|
|
342
283
|
}
|
|
343
284
|
/**
|
|
@@ -347,16 +288,7 @@ class Client extends BaseXChainClient {
|
|
|
347
288
|
*/
|
|
348
289
|
broadcastTx(txHex) {
|
|
349
290
|
return __awaiter(this, void 0, void 0, function* () {
|
|
350
|
-
|
|
351
|
-
const transaction = Transaction.from(bs58.decode(txHex));
|
|
352
|
-
return yield this.connection.sendRawTransaction(transaction.serialize());
|
|
353
|
-
}
|
|
354
|
-
catch (e) {
|
|
355
|
-
if (e instanceof SendTransactionError) {
|
|
356
|
-
console.log(yield e.getLogs(this.connection));
|
|
357
|
-
}
|
|
358
|
-
throw Error('Can not broadcast transaction. Unknown error');
|
|
359
|
-
}
|
|
291
|
+
return this.roundRobinBroadcastTx(txHex);
|
|
360
292
|
});
|
|
361
293
|
}
|
|
362
294
|
/**
|
|
@@ -367,64 +299,15 @@ class Client extends BaseXChainClient {
|
|
|
367
299
|
*/
|
|
368
300
|
prepareTx({ sender, recipient, asset, amount, memo, limit, priorityFee, }) {
|
|
369
301
|
return __awaiter(this, void 0, void 0, function* () {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
toPubkey,
|
|
380
|
-
lamports: amount.amount().toNumber(),
|
|
381
|
-
}));
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
// Token transfer
|
|
385
|
-
const mintAddress = new PublicKey(getContractAddressFromAsset(asset));
|
|
386
|
-
const fromAssociatedAccount = getAssociatedTokenAddressSync(mintAddress, fromPubkey);
|
|
387
|
-
let fromTokenAccount;
|
|
388
|
-
try {
|
|
389
|
-
fromTokenAccount = yield getAccount(this.connection, fromAssociatedAccount);
|
|
390
|
-
}
|
|
391
|
-
catch (error) {
|
|
392
|
-
if (error instanceof TokenAccountNotFoundError || error instanceof TokenInvalidAccountOwnerError) {
|
|
393
|
-
throw Error('Can not find sender Token account');
|
|
394
|
-
}
|
|
395
|
-
throw error;
|
|
396
|
-
}
|
|
397
|
-
const toAssociatedAccount = getAssociatedTokenAddressSync(mintAddress, toPubkey);
|
|
398
|
-
let toTokenAccount;
|
|
399
|
-
try {
|
|
400
|
-
toTokenAccount = yield getAccount(this.connection, toAssociatedAccount);
|
|
401
|
-
}
|
|
402
|
-
catch (error) {
|
|
403
|
-
if (error instanceof TokenAccountNotFoundError || error instanceof TokenInvalidAccountOwnerError) {
|
|
404
|
-
throw Error('Can not find recipient Token account. Create it first');
|
|
405
|
-
}
|
|
406
|
-
throw error;
|
|
407
|
-
}
|
|
408
|
-
transaction.add(createTransferInstruction(fromTokenAccount.address, toTokenAccount.address, fromPubkey, amount.amount().toNumber()));
|
|
409
|
-
}
|
|
410
|
-
if (memo) {
|
|
411
|
-
transaction.add(new TransactionInstruction({
|
|
412
|
-
keys: [{ pubkey: fromPubkey, isSigner: true, isWritable: true }],
|
|
413
|
-
data: Buffer.from(memo, 'utf-8'),
|
|
414
|
-
programId: new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'),
|
|
415
|
-
}));
|
|
416
|
-
}
|
|
417
|
-
if (priorityFee) {
|
|
418
|
-
transaction.add(ComputeBudgetProgram.setComputeUnitPrice({
|
|
419
|
-
microLamports: priorityFee.amount().toNumber() / Math.pow(10, 3),
|
|
420
|
-
}));
|
|
421
|
-
}
|
|
422
|
-
if (limit) {
|
|
423
|
-
transaction.add(ComputeBudgetProgram.setComputeUnitLimit({
|
|
424
|
-
units: limit,
|
|
425
|
-
}));
|
|
426
|
-
}
|
|
427
|
-
return { rawUnsignedTx: bs58.encode(transaction.serialize({ verifySignatures: false })) };
|
|
302
|
+
return this.roundRobinPrepareTx({
|
|
303
|
+
sender,
|
|
304
|
+
recipient,
|
|
305
|
+
asset,
|
|
306
|
+
amount,
|
|
307
|
+
memo,
|
|
308
|
+
limit,
|
|
309
|
+
priorityFee,
|
|
310
|
+
});
|
|
428
311
|
});
|
|
429
312
|
}
|
|
430
313
|
getPrivateKeyPair(index) {
|
|
@@ -473,23 +356,28 @@ class Client extends BaseXChainClient {
|
|
|
473
356
|
const assetDecimals = tx.meta.postTokenBalances[i].uiTokenAmount.decimals;
|
|
474
357
|
const mintAddress = tx.meta.postTokenBalances[i].mint;
|
|
475
358
|
const owner = tx.meta.postTokenBalances[i].owner;
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
359
|
+
try {
|
|
360
|
+
for (const provider of this.providers) {
|
|
361
|
+
const tokenMetadata = yield fetchDigitalAsset(provider.umiProvider, publicKey(mintAddress));
|
|
362
|
+
if (owner) {
|
|
363
|
+
if (postBalanceAmount > preBalanceAmount) {
|
|
364
|
+
to.push({
|
|
365
|
+
amount: assetToBase(assetAmount(postBalanceAmount - preBalanceAmount, assetDecimals)),
|
|
366
|
+
asset: assetFromStringEx(`SOL.${tokenMetadata.metadata.symbol.trim()}-${mintAddress}`),
|
|
367
|
+
to: owner,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
else if (preBalanceAmount > postBalanceAmount) {
|
|
371
|
+
from.push({
|
|
372
|
+
amount: assetToBase(assetAmount(preBalanceAmount - postBalanceAmount, assetDecimals)),
|
|
373
|
+
asset: assetFromStringEx(`SOL.${tokenMetadata.metadata.symbol.trim()}-${mintAddress}`),
|
|
374
|
+
from: owner,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
491
378
|
}
|
|
492
379
|
}
|
|
380
|
+
catch (_d) { }
|
|
493
381
|
}
|
|
494
382
|
}
|
|
495
383
|
}
|
|
@@ -503,6 +391,324 @@ class Client extends BaseXChainClient {
|
|
|
503
391
|
};
|
|
504
392
|
});
|
|
505
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Retrieves the balance of a given address making a round robin over the providers.
|
|
396
|
+
*
|
|
397
|
+
* @param {Address} address - The address to retrieve the balance for.
|
|
398
|
+
* @param {TokenAsset[]} assets - Assets to retrieve the balance for (optional).
|
|
399
|
+
* @returns {Promise<Balance[]>} An array containing the balance of the address.
|
|
400
|
+
* @throws {Error} if there is no provider able to retrieve the balances
|
|
401
|
+
*/
|
|
402
|
+
roundRobinGetBalance(address, assets) {
|
|
403
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
404
|
+
try {
|
|
405
|
+
for (const provider of this.providers) {
|
|
406
|
+
const balances = [];
|
|
407
|
+
const nativeBalance = yield provider.solanaProvider.getBalance(new PublicKey(address));
|
|
408
|
+
balances.push({
|
|
409
|
+
asset: SOLAsset,
|
|
410
|
+
amount: baseAmount(nativeBalance, SOL_DECIMALS),
|
|
411
|
+
});
|
|
412
|
+
const tokenBalances = yield provider.solanaProvider.getParsedTokenAccountsByOwner(new PublicKey(address), {
|
|
413
|
+
programId: TOKEN_PROGRAM_ID,
|
|
414
|
+
});
|
|
415
|
+
const tokensToRequest = !assets
|
|
416
|
+
? tokenBalances.value
|
|
417
|
+
: tokenBalances.value.filter((tokenBalance) => {
|
|
418
|
+
const tokenData = tokenBalance.account.data.parsed;
|
|
419
|
+
return (assets.findIndex((asset) => {
|
|
420
|
+
return asset.symbol.toLowerCase().includes(tokenData.info.mint.toLowerCase());
|
|
421
|
+
}) !== -1);
|
|
422
|
+
});
|
|
423
|
+
const mintPublicKeys = tokensToRequest.map((tokenBalance) => {
|
|
424
|
+
const tokenData = tokenBalance.account.data.parsed;
|
|
425
|
+
return publicKey(tokenData.info.mint);
|
|
426
|
+
});
|
|
427
|
+
const assetsData = yield fetchAllDigitalAsset(provider.umiProvider, mintPublicKeys);
|
|
428
|
+
tokenBalances.value.forEach((balance) => {
|
|
429
|
+
const parsedData = balance.account.data.parsed;
|
|
430
|
+
const assetData = assetsData.find((assetData) => assetData.publicKey.toString() === parsedData.info.mint);
|
|
431
|
+
if (assetData) {
|
|
432
|
+
balances.push({
|
|
433
|
+
amount: baseAmount(parsedData.info.tokenAmount.amount, parsedData.info.tokenAmount.decimals),
|
|
434
|
+
asset: assetFromStringEx(`SOL.${assetData.metadata.symbol.trim()}-${parsedData.info.mint}`),
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
return balances;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
catch (_a) { }
|
|
442
|
+
throw Error('No provider able to get balances.');
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Get transaction fees making a round robin over the providers.
|
|
447
|
+
*
|
|
448
|
+
* @param {TxParams} params - The transaction parameters.
|
|
449
|
+
* @returns {Fees} The average, fast, and fastest fees.
|
|
450
|
+
* @throws {Error} if there is no provider able to retrieve the fees
|
|
451
|
+
*/
|
|
452
|
+
roundRobinGetFees(params) {
|
|
453
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
454
|
+
try {
|
|
455
|
+
for (const provider of this.providers) {
|
|
456
|
+
const sender = Keypair.generate();
|
|
457
|
+
const toPubkey = new PublicKey(params.recipient);
|
|
458
|
+
const transaction = new Transaction();
|
|
459
|
+
transaction.recentBlockhash = yield provider.solanaProvider
|
|
460
|
+
.getLatestBlockhash()
|
|
461
|
+
.then((block) => block.blockhash);
|
|
462
|
+
transaction.feePayer = sender.publicKey;
|
|
463
|
+
let createAccountTxFee = 0;
|
|
464
|
+
if (!params.asset || eqAsset(params.asset, this.getAssetInfo().asset)) {
|
|
465
|
+
// Native transfer
|
|
466
|
+
transaction.add(SystemProgram.transfer({
|
|
467
|
+
fromPubkey: sender.publicKey,
|
|
468
|
+
toPubkey,
|
|
469
|
+
lamports: params.amount.amount().toNumber(),
|
|
470
|
+
}));
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
// Token transfer
|
|
474
|
+
const mintAddress = new PublicKey(getContractAddressFromAsset(params.asset));
|
|
475
|
+
const associatedTokenAddress = getAssociatedTokenAddressSync(mintAddress, toPubkey);
|
|
476
|
+
try {
|
|
477
|
+
yield getAccount(provider.solanaProvider, associatedTokenAddress, undefined, TOKEN_PROGRAM_ID);
|
|
478
|
+
transaction.add(createTransferInstruction(sender.publicKey, // Should be Token account, but as it new KeyPair for estimation, sender public key
|
|
479
|
+
associatedTokenAddress, sender.publicKey, params.amount.amount().toNumber()));
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
if (error instanceof TokenAccountNotFoundError || error instanceof TokenInvalidAccountOwnerError) {
|
|
483
|
+
// recipient token account has to be created
|
|
484
|
+
const dataLength = 165; // Normally used for Token accounts
|
|
485
|
+
createAccountTxFee = yield provider.solanaProvider.getMinimumBalanceForRentExemption(dataLength);
|
|
486
|
+
transaction.add(createTransferInstruction(sender.publicKey, // Should be Token account, but as it new KeyPair for estimation, sender public key
|
|
487
|
+
toPubkey, // Should be Token account, but as recipient token account should be created, recipient public key
|
|
488
|
+
sender.publicKey, params.amount.amount().toNumber()));
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (params.memo) {
|
|
493
|
+
transaction.add(new TransactionInstruction({
|
|
494
|
+
keys: [{ pubkey: sender.publicKey, isSigner: true, isWritable: true }],
|
|
495
|
+
data: Buffer.from(params.memo, 'utf-8'),
|
|
496
|
+
programId: new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'),
|
|
497
|
+
}));
|
|
498
|
+
}
|
|
499
|
+
if (params.priorityFee) {
|
|
500
|
+
transaction.add(ComputeBudgetProgram.setComputeUnitPrice({
|
|
501
|
+
microLamports: params.priorityFee.amount().toNumber() / Math.pow(10, 3),
|
|
502
|
+
}));
|
|
503
|
+
}
|
|
504
|
+
if (params.limit) {
|
|
505
|
+
transaction.add(ComputeBudgetProgram.setComputeUnitLimit({
|
|
506
|
+
units: params.limit,
|
|
507
|
+
}));
|
|
508
|
+
}
|
|
509
|
+
const fee = (yield transaction.getEstimatedFee(provider.solanaProvider)) || 0;
|
|
510
|
+
return {
|
|
511
|
+
type: FeeType.FlatFee,
|
|
512
|
+
[FeeOption.Average]: baseAmount(fee + createAccountTxFee, SOL_DECIMALS),
|
|
513
|
+
[FeeOption.Fast]: baseAmount(fee + createAccountTxFee, SOL_DECIMALS),
|
|
514
|
+
[FeeOption.Fastest]: baseAmount(fee + createAccountTxFee, SOL_DECIMALS),
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
catch (_a) { }
|
|
519
|
+
throw Error('No provider able to get fees.');
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Get the transaction details of a given transaction ID making a round robin over the providers.
|
|
524
|
+
*
|
|
525
|
+
* @param {string} txId The transaction ID.
|
|
526
|
+
* @returns {Tx} The transaction details.
|
|
527
|
+
* @throws {Error} if there is no provider able to retrieve the transaction data
|
|
528
|
+
*/
|
|
529
|
+
roundRobinGetTransactionData(txId) {
|
|
530
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
531
|
+
try {
|
|
532
|
+
for (const provider of this.providers) {
|
|
533
|
+
const transaction = yield provider.solanaProvider.getParsedTransaction(txId);
|
|
534
|
+
return transaction;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
catch (_a) { }
|
|
538
|
+
throw Error('No provider able to get transaction data.');
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Get the transaction history of a given address with pagination options making a round robin over the providers.
|
|
543
|
+
*
|
|
544
|
+
* @param {TxHistoryParams} params The options to get transaction history.
|
|
545
|
+
* @returns {TxsPage} The transaction history.
|
|
546
|
+
* @throws {Error} if there is no provider able to retrieve the transactions
|
|
547
|
+
*/
|
|
548
|
+
roundRobinGetTransactions(params) {
|
|
549
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
550
|
+
try {
|
|
551
|
+
for (const provider of this.providers) {
|
|
552
|
+
const signatures = yield provider.solanaProvider.getSignaturesForAddress(new PublicKey((params === null || params === void 0 ? void 0 : params.address) || (yield this.getAddressAsync())));
|
|
553
|
+
const transactions = yield provider.solanaProvider.getParsedTransactions(signatures.map(({ signature }) => signature));
|
|
554
|
+
const results = yield Promise.allSettled(transactions
|
|
555
|
+
.filter((transaction) => !!transaction)
|
|
556
|
+
.map((transaction) => this.parseTransaction(transaction)));
|
|
557
|
+
const txs = [];
|
|
558
|
+
results.forEach((result) => {
|
|
559
|
+
if (result.status === 'fulfilled') {
|
|
560
|
+
txs.push(result.value);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
return {
|
|
564
|
+
txs,
|
|
565
|
+
total: txs.length,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
catch (_a) { }
|
|
570
|
+
throw Error('No provider able to get transactions.');
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Transfers SOL or Solana token making a round robin over the providers
|
|
575
|
+
*
|
|
576
|
+
* @param {TxParams} params The transfer options.
|
|
577
|
+
* @returns {TxHash} The transaction hash.
|
|
578
|
+
* @throws {Error} if there is no provider able to make the transfer
|
|
579
|
+
*/
|
|
580
|
+
roundRobinTransfer({ walletIndex, recipient, asset, amount, memo, limit, priorityFee, }) {
|
|
581
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
582
|
+
try {
|
|
583
|
+
const senderKeyPair = this.getPrivateKeyPair(walletIndex || 0);
|
|
584
|
+
for (const provider of this.providers) {
|
|
585
|
+
if (asset && !eqAsset(asset, this.getAssetInfo().asset)) {
|
|
586
|
+
// Check if receipt token account is created, otherwise, create it
|
|
587
|
+
const mintAddress = new PublicKey(getContractAddressFromAsset(asset));
|
|
588
|
+
yield getOrCreateAssociatedTokenAccount(provider.solanaProvider, senderKeyPair, mintAddress, new PublicKey(recipient));
|
|
589
|
+
}
|
|
590
|
+
const { rawUnsignedTx } = yield this.prepareTx({
|
|
591
|
+
sender: senderKeyPair.publicKey.toBase58(),
|
|
592
|
+
recipient,
|
|
593
|
+
asset,
|
|
594
|
+
amount,
|
|
595
|
+
memo,
|
|
596
|
+
limit,
|
|
597
|
+
priorityFee,
|
|
598
|
+
});
|
|
599
|
+
const transaction = Transaction.from(bs58.decode(rawUnsignedTx));
|
|
600
|
+
transaction.sign(senderKeyPair);
|
|
601
|
+
return this.broadcastTx(bs58.encode(transaction.serialize()));
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
catch (_a) { }
|
|
605
|
+
throw Error('No provider able to transfer.');
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Broadcast a transaction to the network making a round robin over the providers
|
|
610
|
+
*
|
|
611
|
+
* @param {string} txHex Raw transaction to broadcast
|
|
612
|
+
* @returns {TxHash} The hash of the transaction broadcasted
|
|
613
|
+
* @throws {Error} if there is no provider able to broadcast transaction
|
|
614
|
+
*/
|
|
615
|
+
roundRobinBroadcastTx(txHex) {
|
|
616
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
617
|
+
try {
|
|
618
|
+
for (const provider of this.providers) {
|
|
619
|
+
try {
|
|
620
|
+
const transaction = Transaction.from(bs58.decode(txHex));
|
|
621
|
+
return yield provider.solanaProvider.sendRawTransaction(transaction.serialize());
|
|
622
|
+
}
|
|
623
|
+
catch (e) {
|
|
624
|
+
if (e instanceof SendTransactionError) {
|
|
625
|
+
console.log(yield e.getLogs(provider.solanaProvider));
|
|
626
|
+
}
|
|
627
|
+
throw Error('Can not broadcast transaction. Unknown error');
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
catch (_a) { }
|
|
632
|
+
throw Error('No provider able to broadcast transaction.');
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Prepares a transaction for transfer making round robin over the providers.
|
|
637
|
+
*
|
|
638
|
+
* @param {TxParams&Address} params - The transfer options.
|
|
639
|
+
* @returns {Promise<PreparedTx>} The raw unsigned transaction.
|
|
640
|
+
* @throws {Error} if there is no provider able to prepare transaction
|
|
641
|
+
*/
|
|
642
|
+
roundRobinPrepareTx({ sender, recipient, asset, amount, memo, limit, priorityFee, }) {
|
|
643
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
644
|
+
try {
|
|
645
|
+
for (const provider of this.providers) {
|
|
646
|
+
const transaction = new Transaction();
|
|
647
|
+
const fromPubkey = new PublicKey(sender);
|
|
648
|
+
const toPubkey = new PublicKey(recipient);
|
|
649
|
+
transaction.recentBlockhash = yield provider.solanaProvider
|
|
650
|
+
.getLatestBlockhash()
|
|
651
|
+
.then((block) => block.blockhash);
|
|
652
|
+
transaction.feePayer = new PublicKey(sender);
|
|
653
|
+
if (!asset || eqAsset(asset, this.getAssetInfo().asset)) {
|
|
654
|
+
// Native transfer
|
|
655
|
+
transaction.add(SystemProgram.transfer({
|
|
656
|
+
fromPubkey,
|
|
657
|
+
toPubkey,
|
|
658
|
+
lamports: amount.amount().toNumber(),
|
|
659
|
+
}));
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
// Token transfer
|
|
663
|
+
const mintAddress = new PublicKey(getContractAddressFromAsset(asset));
|
|
664
|
+
const fromAssociatedAccount = getAssociatedTokenAddressSync(mintAddress, fromPubkey);
|
|
665
|
+
let fromTokenAccount;
|
|
666
|
+
try {
|
|
667
|
+
fromTokenAccount = yield getAccount(provider.solanaProvider, fromAssociatedAccount);
|
|
668
|
+
}
|
|
669
|
+
catch (error) {
|
|
670
|
+
if (error instanceof TokenAccountNotFoundError || error instanceof TokenInvalidAccountOwnerError) {
|
|
671
|
+
throw Error('Can not find sender Token account');
|
|
672
|
+
}
|
|
673
|
+
throw error;
|
|
674
|
+
}
|
|
675
|
+
const toAssociatedAccount = getAssociatedTokenAddressSync(mintAddress, toPubkey);
|
|
676
|
+
let toTokenAccount;
|
|
677
|
+
try {
|
|
678
|
+
toTokenAccount = yield getAccount(provider.solanaProvider, toAssociatedAccount);
|
|
679
|
+
}
|
|
680
|
+
catch (error) {
|
|
681
|
+
if (error instanceof TokenAccountNotFoundError || error instanceof TokenInvalidAccountOwnerError) {
|
|
682
|
+
throw Error('Can not find recipient Token account. Create it first');
|
|
683
|
+
}
|
|
684
|
+
throw error;
|
|
685
|
+
}
|
|
686
|
+
transaction.add(createTransferInstruction(fromTokenAccount.address, toTokenAccount.address, fromPubkey, amount.amount().toNumber()));
|
|
687
|
+
}
|
|
688
|
+
if (memo) {
|
|
689
|
+
transaction.add(new TransactionInstruction({
|
|
690
|
+
keys: [{ pubkey: fromPubkey, isSigner: true, isWritable: true }],
|
|
691
|
+
data: Buffer.from(memo, 'utf-8'),
|
|
692
|
+
programId: new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'),
|
|
693
|
+
}));
|
|
694
|
+
}
|
|
695
|
+
if (priorityFee) {
|
|
696
|
+
transaction.add(ComputeBudgetProgram.setComputeUnitPrice({
|
|
697
|
+
microLamports: priorityFee.amount().toNumber() / Math.pow(10, 3),
|
|
698
|
+
}));
|
|
699
|
+
}
|
|
700
|
+
if (limit) {
|
|
701
|
+
transaction.add(ComputeBudgetProgram.setComputeUnitLimit({
|
|
702
|
+
units: limit,
|
|
703
|
+
}));
|
|
704
|
+
}
|
|
705
|
+
return { rawUnsignedTx: bs58.encode(transaction.serialize({ verifySignatures: false })) };
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
catch (_a) { }
|
|
709
|
+
throw Error('No provider able to prepare transaction');
|
|
710
|
+
});
|
|
711
|
+
}
|
|
506
712
|
}
|
|
507
713
|
|
|
508
714
|
export { Client, SOLAsset, SOLChain, SOL_DECIMALS, defaultSolanaParams };
|