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