@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.js
CHANGED
|
@@ -12,6 +12,10 @@ var xchainUtil = require('@xchainjs/xchain-util');
|
|
|
12
12
|
var bs58 = require('bs58');
|
|
13
13
|
var microEd25519Hdkey = require('micro-ed25519-hdkey');
|
|
14
14
|
|
|
15
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
|
+
|
|
17
|
+
var bs58__default = /*#__PURE__*/_interopDefault(bs58);
|
|
18
|
+
|
|
15
19
|
/******************************************************************************
|
|
16
20
|
Copyright (c) Microsoft Corporation.
|
|
17
21
|
|
|
@@ -26,7 +30,7 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
26
30
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
27
31
|
PERFORMANCE OF THIS SOFTWARE.
|
|
28
32
|
***************************************************************************** */
|
|
29
|
-
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
33
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
30
34
|
|
|
31
35
|
|
|
32
36
|
function __awaiter(thisArg, _arguments, P, generator) {
|
|
@@ -89,8 +93,55 @@ class Client extends xchainClient.BaseXChainClient {
|
|
|
89
93
|
constructor(params = defaultSolanaParams) {
|
|
90
94
|
super(SOLChain, Object.assign(Object.assign({}, defaultSolanaParams), params));
|
|
91
95
|
this.explorerProviders = params.explorerProviders;
|
|
92
|
-
this.
|
|
93
|
-
|
|
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
|
+
}
|
|
94
145
|
}
|
|
95
146
|
/**
|
|
96
147
|
* Get information about the native asset of the Solana.
|
|
@@ -175,39 +226,7 @@ class Client extends xchainClient.BaseXChainClient {
|
|
|
175
226
|
*/
|
|
176
227
|
getBalance(address, assets) {
|
|
177
228
|
return __awaiter(this, void 0, void 0, function* () {
|
|
178
|
-
|
|
179
|
-
const nativeBalance = yield this.connection.getBalance(new web3_js.PublicKey(address));
|
|
180
|
-
balances.push({
|
|
181
|
-
asset: SOLAsset,
|
|
182
|
-
amount: xchainUtil.baseAmount(nativeBalance, SOL_DECIMALS),
|
|
183
|
-
});
|
|
184
|
-
const tokenBalances = yield this.connection.getParsedTokenAccountsByOwner(new web3_js.PublicKey(address), {
|
|
185
|
-
programId: splToken.TOKEN_PROGRAM_ID,
|
|
186
|
-
});
|
|
187
|
-
const tokensToRequest = !assets
|
|
188
|
-
? tokenBalances.value
|
|
189
|
-
: tokenBalances.value.filter((tokenBalance) => {
|
|
190
|
-
const tokenData = tokenBalance.account.data.parsed;
|
|
191
|
-
return (assets.findIndex((asset) => {
|
|
192
|
-
return asset.symbol.toLowerCase().includes(tokenData.info.mint.toLowerCase());
|
|
193
|
-
}) !== -1);
|
|
194
|
-
});
|
|
195
|
-
const mintPublicKeys = tokensToRequest.map((tokenBalance) => {
|
|
196
|
-
const tokenData = tokenBalance.account.data.parsed;
|
|
197
|
-
return umi.publicKey(tokenData.info.mint);
|
|
198
|
-
});
|
|
199
|
-
const assetsData = yield mplTokenMetadata.fetchAllDigitalAsset(this.umi, mintPublicKeys);
|
|
200
|
-
tokenBalances.value.forEach((balance) => {
|
|
201
|
-
const parsedData = balance.account.data.parsed;
|
|
202
|
-
const assetData = assetsData.find((assetData) => assetData.publicKey.toString() === parsedData.info.mint);
|
|
203
|
-
if (assetData) {
|
|
204
|
-
balances.push({
|
|
205
|
-
amount: xchainUtil.baseAmount(parsedData.info.tokenAmount.amount, parsedData.info.tokenAmount.decimals),
|
|
206
|
-
asset: xchainUtil.assetFromStringEx(`SOL.${assetData.metadata.symbol.trim()}-${parsedData.info.mint}`),
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
return balances;
|
|
229
|
+
return this.roundRobinGetBalance(address, assets);
|
|
211
230
|
});
|
|
212
231
|
}
|
|
213
232
|
/**
|
|
@@ -221,64 +240,7 @@ class Client extends xchainClient.BaseXChainClient {
|
|
|
221
240
|
return __awaiter(this, void 0, void 0, function* () {
|
|
222
241
|
if (!params)
|
|
223
242
|
throw new Error('Params need to be passed');
|
|
224
|
-
|
|
225
|
-
const toPubkey = new web3_js.PublicKey(params.recipient);
|
|
226
|
-
const transaction = new web3_js.Transaction();
|
|
227
|
-
transaction.recentBlockhash = yield this.connection.getLatestBlockhash().then((block) => block.blockhash);
|
|
228
|
-
transaction.feePayer = sender.publicKey;
|
|
229
|
-
let createAccountTxFee = 0;
|
|
230
|
-
if (!params.asset || xchainUtil.eqAsset(params.asset, this.getAssetInfo().asset)) {
|
|
231
|
-
// Native transfer
|
|
232
|
-
transaction.add(web3_js.SystemProgram.transfer({
|
|
233
|
-
fromPubkey: sender.publicKey,
|
|
234
|
-
toPubkey,
|
|
235
|
-
lamports: params.amount.amount().toNumber(),
|
|
236
|
-
}));
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
// Token transfer
|
|
240
|
-
const mintAddress = new web3_js.PublicKey(xchainUtil.getContractAddressFromAsset(params.asset));
|
|
241
|
-
const associatedTokenAddress = splToken.getAssociatedTokenAddressSync(mintAddress, toPubkey);
|
|
242
|
-
try {
|
|
243
|
-
yield splToken.getAccount(this.connection, associatedTokenAddress, undefined, splToken.TOKEN_PROGRAM_ID);
|
|
244
|
-
transaction.add(splToken.createTransferInstruction(sender.publicKey, // Should be Token account, but as it new KeyPair for estimation, sender public key
|
|
245
|
-
associatedTokenAddress, sender.publicKey, params.amount.amount().toNumber()));
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
if (error instanceof splToken.TokenAccountNotFoundError || error instanceof splToken.TokenInvalidAccountOwnerError) {
|
|
249
|
-
// recipient token account has to be created
|
|
250
|
-
const dataLength = 165; // Normally used for Token accounts
|
|
251
|
-
createAccountTxFee = yield this.connection.getMinimumBalanceForRentExemption(dataLength);
|
|
252
|
-
transaction.add(splToken.createTransferInstruction(sender.publicKey, // Should be Token account, but as it new KeyPair for estimation, sender public key
|
|
253
|
-
toPubkey, // Should be Token account, but as recipient token account should be created, recipient public key
|
|
254
|
-
sender.publicKey, params.amount.amount().toNumber()));
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
if (params.memo) {
|
|
259
|
-
transaction.add(new web3_js.TransactionInstruction({
|
|
260
|
-
keys: [{ pubkey: sender.publicKey, isSigner: true, isWritable: true }],
|
|
261
|
-
data: Buffer.from(params.memo, 'utf-8'),
|
|
262
|
-
programId: new web3_js.PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'),
|
|
263
|
-
}));
|
|
264
|
-
}
|
|
265
|
-
if (params.priorityFee) {
|
|
266
|
-
transaction.add(web3_js.ComputeBudgetProgram.setComputeUnitPrice({
|
|
267
|
-
microLamports: params.priorityFee.amount().toNumber() / Math.pow(10, 3),
|
|
268
|
-
}));
|
|
269
|
-
}
|
|
270
|
-
if (params.limit) {
|
|
271
|
-
transaction.add(web3_js.ComputeBudgetProgram.setComputeUnitLimit({
|
|
272
|
-
units: params.limit,
|
|
273
|
-
}));
|
|
274
|
-
}
|
|
275
|
-
const fee = (yield transaction.getEstimatedFee(this.connection)) || 0;
|
|
276
|
-
return {
|
|
277
|
-
type: xchainClient.FeeType.FlatFee,
|
|
278
|
-
[xchainClient.FeeOption.Average]: xchainUtil.baseAmount(fee + createAccountTxFee, SOL_DECIMALS),
|
|
279
|
-
[xchainClient.FeeOption.Fast]: xchainUtil.baseAmount(fee + createAccountTxFee, SOL_DECIMALS),
|
|
280
|
-
[xchainClient.FeeOption.Fastest]: xchainUtil.baseAmount(fee + createAccountTxFee, SOL_DECIMALS),
|
|
281
|
-
};
|
|
243
|
+
return this.roundRobinGetFees(params);
|
|
282
244
|
});
|
|
283
245
|
}
|
|
284
246
|
/**
|
|
@@ -289,29 +251,21 @@ class Client extends xchainClient.BaseXChainClient {
|
|
|
289
251
|
*/
|
|
290
252
|
getTransactionData(txId) {
|
|
291
253
|
return __awaiter(this, void 0, void 0, function* () {
|
|
292
|
-
const transaction = yield this.
|
|
254
|
+
const transaction = yield this.roundRobinGetTransactionData(txId);
|
|
293
255
|
if (!transaction)
|
|
294
256
|
throw Error('Can not find transaction');
|
|
295
257
|
return this.parseTransaction(transaction);
|
|
296
258
|
});
|
|
297
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
|
+
*/
|
|
298
266
|
getTransactions(params) {
|
|
299
267
|
return __awaiter(this, void 0, void 0, function* () {
|
|
300
|
-
|
|
301
|
-
const transactions = yield this.connection.getParsedTransactions(signatures.map(({ signature }) => signature));
|
|
302
|
-
const results = yield Promise.allSettled(transactions
|
|
303
|
-
.filter((transaction) => !!transaction)
|
|
304
|
-
.map((transaction) => this.parseTransaction(transaction)));
|
|
305
|
-
const txs = [];
|
|
306
|
-
results.forEach((result) => {
|
|
307
|
-
if (result.status === 'fulfilled') {
|
|
308
|
-
txs.push(result.value);
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
return {
|
|
312
|
-
txs,
|
|
313
|
-
total: txs.length,
|
|
314
|
-
};
|
|
268
|
+
return this.roundRobinGetTransactions(params);
|
|
315
269
|
});
|
|
316
270
|
}
|
|
317
271
|
/**
|
|
@@ -322,14 +276,8 @@ class Client extends xchainClient.BaseXChainClient {
|
|
|
322
276
|
*/
|
|
323
277
|
transfer({ walletIndex, recipient, asset, amount, memo, limit, priorityFee, }) {
|
|
324
278
|
return __awaiter(this, void 0, void 0, function* () {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
// Check if receipt token account is created, otherwise, create it
|
|
328
|
-
const mintAddress = new web3_js.PublicKey(xchainUtil.getContractAddressFromAsset(asset));
|
|
329
|
-
yield splToken.getOrCreateAssociatedTokenAccount(this.connection, senderKeyPair, mintAddress, new web3_js.PublicKey(recipient));
|
|
330
|
-
}
|
|
331
|
-
const { rawUnsignedTx } = yield this.prepareTx({
|
|
332
|
-
sender: senderKeyPair.publicKey.toBase58(),
|
|
279
|
+
return this.roundRobinTransfer({
|
|
280
|
+
walletIndex,
|
|
333
281
|
recipient,
|
|
334
282
|
asset,
|
|
335
283
|
amount,
|
|
@@ -337,9 +285,6 @@ class Client extends xchainClient.BaseXChainClient {
|
|
|
337
285
|
limit,
|
|
338
286
|
priorityFee,
|
|
339
287
|
});
|
|
340
|
-
const transaction = web3_js.Transaction.from(bs58.decode(rawUnsignedTx));
|
|
341
|
-
transaction.sign(senderKeyPair);
|
|
342
|
-
return this.broadcastTx(bs58.encode(transaction.serialize()));
|
|
343
288
|
});
|
|
344
289
|
}
|
|
345
290
|
/**
|
|
@@ -349,16 +294,7 @@ class Client extends xchainClient.BaseXChainClient {
|
|
|
349
294
|
*/
|
|
350
295
|
broadcastTx(txHex) {
|
|
351
296
|
return __awaiter(this, void 0, void 0, function* () {
|
|
352
|
-
|
|
353
|
-
const transaction = web3_js.Transaction.from(bs58.decode(txHex));
|
|
354
|
-
return yield this.connection.sendRawTransaction(transaction.serialize());
|
|
355
|
-
}
|
|
356
|
-
catch (e) {
|
|
357
|
-
if (e instanceof web3_js.SendTransactionError) {
|
|
358
|
-
console.log(yield e.getLogs(this.connection));
|
|
359
|
-
}
|
|
360
|
-
throw Error('Can not broadcast transaction. Unknown error');
|
|
361
|
-
}
|
|
297
|
+
return this.roundRobinBroadcastTx(txHex);
|
|
362
298
|
});
|
|
363
299
|
}
|
|
364
300
|
/**
|
|
@@ -369,64 +305,15 @@ class Client extends xchainClient.BaseXChainClient {
|
|
|
369
305
|
*/
|
|
370
306
|
prepareTx({ sender, recipient, asset, amount, memo, limit, priorityFee, }) {
|
|
371
307
|
return __awaiter(this, void 0, void 0, function* () {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
toPubkey,
|
|
382
|
-
lamports: amount.amount().toNumber(),
|
|
383
|
-
}));
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
// Token transfer
|
|
387
|
-
const mintAddress = new web3_js.PublicKey(xchainUtil.getContractAddressFromAsset(asset));
|
|
388
|
-
const fromAssociatedAccount = splToken.getAssociatedTokenAddressSync(mintAddress, fromPubkey);
|
|
389
|
-
let fromTokenAccount;
|
|
390
|
-
try {
|
|
391
|
-
fromTokenAccount = yield splToken.getAccount(this.connection, fromAssociatedAccount);
|
|
392
|
-
}
|
|
393
|
-
catch (error) {
|
|
394
|
-
if (error instanceof splToken.TokenAccountNotFoundError || error instanceof splToken.TokenInvalidAccountOwnerError) {
|
|
395
|
-
throw Error('Can not find sender Token account');
|
|
396
|
-
}
|
|
397
|
-
throw error;
|
|
398
|
-
}
|
|
399
|
-
const toAssociatedAccount = splToken.getAssociatedTokenAddressSync(mintAddress, toPubkey);
|
|
400
|
-
let toTokenAccount;
|
|
401
|
-
try {
|
|
402
|
-
toTokenAccount = yield splToken.getAccount(this.connection, toAssociatedAccount);
|
|
403
|
-
}
|
|
404
|
-
catch (error) {
|
|
405
|
-
if (error instanceof splToken.TokenAccountNotFoundError || error instanceof splToken.TokenInvalidAccountOwnerError) {
|
|
406
|
-
throw Error('Can not find recipient Token account. Create it first');
|
|
407
|
-
}
|
|
408
|
-
throw error;
|
|
409
|
-
}
|
|
410
|
-
transaction.add(splToken.createTransferInstruction(fromTokenAccount.address, toTokenAccount.address, fromPubkey, amount.amount().toNumber()));
|
|
411
|
-
}
|
|
412
|
-
if (memo) {
|
|
413
|
-
transaction.add(new web3_js.TransactionInstruction({
|
|
414
|
-
keys: [{ pubkey: fromPubkey, isSigner: true, isWritable: true }],
|
|
415
|
-
data: Buffer.from(memo, 'utf-8'),
|
|
416
|
-
programId: new web3_js.PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'),
|
|
417
|
-
}));
|
|
418
|
-
}
|
|
419
|
-
if (priorityFee) {
|
|
420
|
-
transaction.add(web3_js.ComputeBudgetProgram.setComputeUnitPrice({
|
|
421
|
-
microLamports: priorityFee.amount().toNumber() / Math.pow(10, 3),
|
|
422
|
-
}));
|
|
423
|
-
}
|
|
424
|
-
if (limit) {
|
|
425
|
-
transaction.add(web3_js.ComputeBudgetProgram.setComputeUnitLimit({
|
|
426
|
-
units: limit,
|
|
427
|
-
}));
|
|
428
|
-
}
|
|
429
|
-
return { rawUnsignedTx: bs58.encode(transaction.serialize({ verifySignatures: false })) };
|
|
308
|
+
return this.roundRobinPrepareTx({
|
|
309
|
+
sender,
|
|
310
|
+
recipient,
|
|
311
|
+
asset,
|
|
312
|
+
amount,
|
|
313
|
+
memo,
|
|
314
|
+
limit,
|
|
315
|
+
priorityFee,
|
|
316
|
+
});
|
|
430
317
|
});
|
|
431
318
|
}
|
|
432
319
|
getPrivateKeyPair(index) {
|
|
@@ -475,23 +362,28 @@ class Client extends xchainClient.BaseXChainClient {
|
|
|
475
362
|
const assetDecimals = tx.meta.postTokenBalances[i].uiTokenAmount.decimals;
|
|
476
363
|
const mintAddress = tx.meta.postTokenBalances[i].mint;
|
|
477
364
|
const owner = tx.meta.postTokenBalances[i].owner;
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
+
}
|
|
493
384
|
}
|
|
494
385
|
}
|
|
386
|
+
catch (_d) { }
|
|
495
387
|
}
|
|
496
388
|
}
|
|
497
389
|
}
|
|
@@ -505,6 +397,324 @@ class Client extends xchainClient.BaseXChainClient {
|
|
|
505
397
|
};
|
|
506
398
|
});
|
|
507
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
|
+
}
|
|
508
718
|
}
|
|
509
719
|
|
|
510
720
|
exports.Client = Client;
|