diff --git a/.github/workflows/solana.yml b/.github/workflows/solana.yml
index 68950697..bb05b389 100644
--- a/.github/workflows/solana.yml
+++ b/.github/workflows/solana.yml
@@ -1,6 +1,7 @@
name: Solana Workflow runner
on:
+ workflow_dispatch:
push:
branches: [ disable ]
paths:
@@ -37,4 +38,4 @@ jobs:
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
build-args: |
- DOTNET_VERSION=${{ env.DOTNET_VERSION }}
\ No newline at end of file
+ DOTNET_VERSION=${{ env.DOTNET_VERSION }}
diff --git a/csharp/TrainSolver.slnx b/csharp/TrainSolver.slnx
index 96ce8ca9..e73875de 100644
--- a/csharp/TrainSolver.slnx
+++ b/csharp/TrainSolver.slnx
@@ -33,6 +33,7 @@
+
diff --git a/csharp/src/Infrastructure.Abstractions/Models/DetailedNetworkDto.cs b/csharp/src/Infrastructure.Abstractions/Models/DetailedNetworkDto.cs
index 8b325e87..0f578e82 100644
--- a/csharp/src/Infrastructure.Abstractions/Models/DetailedNetworkDto.cs
+++ b/csharp/src/Infrastructure.Abstractions/Models/DetailedNetworkDto.cs
@@ -1,6 +1,4 @@
-using Train.Solver.Common.Enums;
-
-namespace Train.Solver.Infrastructure.Abstractions.Models;
+namespace Train.Solver.Infrastructure.Abstractions.Models;
public class DetailedNetworkDto : ExtendedNetworkDto
{
diff --git a/csharp/src/Infrastructure.Abstractions/Models/ExtendedNetworkDto.cs b/csharp/src/Infrastructure.Abstractions/Models/ExtendedNetworkDto.cs
index 32ea271f..f1b3ac67 100644
--- a/csharp/src/Infrastructure.Abstractions/Models/ExtendedNetworkDto.cs
+++ b/csharp/src/Infrastructure.Abstractions/Models/ExtendedNetworkDto.cs
@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Train.Solver.Common.Enums;
+using Train.Solver.Common.Enums;
namespace Train.Solver.Infrastructure.Abstractions.Models;
diff --git a/csharp/src/SmartNodeInvoker/ISmartNodeInvoker.cs b/csharp/src/SmartNodeInvoker/ISmartNodeInvoker.cs
index e2d0b62d..dfcad8e2 100644
--- a/csharp/src/SmartNodeInvoker/ISmartNodeInvoker.cs
+++ b/csharp/src/SmartNodeInvoker/ISmartNodeInvoker.cs
@@ -1,11 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Train.Solver.Common.Enums;
-
-namespace Train.Solver.SmartNodeInvoker;
+namespace Train.Solver.SmartNodeInvoker;
public interface ISmartNodeInvoker
{
diff --git a/csharp/src/SmartNodeInvoker/SmartNodeInvoker.cs b/csharp/src/SmartNodeInvoker/SmartNodeInvoker.cs
index d24ded24..a4e59bf9 100644
--- a/csharp/src/SmartNodeInvoker/SmartNodeInvoker.cs
+++ b/csharp/src/SmartNodeInvoker/SmartNodeInvoker.cs
@@ -1,13 +1,5 @@
-using Microsoft.Extensions.Logging;
-using StackExchange.Redis;
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
+using StackExchange.Redis;
using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Train.Solver.Common.Enums;
using Train.Solver.Common.Helpers;
namespace Train.Solver.SmartNodeInvoker;
@@ -78,7 +70,6 @@ private async Task> OrderNodesByScoreAsync(string redisKey, IEnumer
.ToList();
}
-
private async Task IncrementScoreAsync(string redisKey, string node, int delta)
{
var newScore = await cache.SortedSetIncrementAsync(redisKey, node, delta);
diff --git a/csharp/src/Workflow.EVM/Models/EstimateFeeRequest.cs b/csharp/src/Workflow.Abstractions/Models/EstimateFeeRequest.cs
similarity index 79%
rename from csharp/src/Workflow.EVM/Models/EstimateFeeRequest.cs
rename to csharp/src/Workflow.Abstractions/Models/EstimateFeeRequest.cs
index e4ab8194..fb313bc1 100644
--- a/csharp/src/Workflow.EVM/Models/EstimateFeeRequest.cs
+++ b/csharp/src/Workflow.Abstractions/Models/EstimateFeeRequest.cs
@@ -1,7 +1,6 @@
using System.Numerics;
-using Train.Solver.Workflow.Abstractions.Models;
-namespace Train.Solver.Workflow.EVM.Models;
+namespace Train.Solver.Workflow.Abstractions.Models;
public class EstimateFeeRequest : BaseRequest
{
diff --git a/csharp/src/Workflow.EVM/Models/NextNonceRequest.cs b/csharp/src/Workflow.Abstractions/Models/NextNonceRequest.cs
similarity index 52%
rename from csharp/src/Workflow.EVM/Models/NextNonceRequest.cs
rename to csharp/src/Workflow.Abstractions/Models/NextNonceRequest.cs
index d912e6cd..c06e67f7 100644
--- a/csharp/src/Workflow.EVM/Models/NextNonceRequest.cs
+++ b/csharp/src/Workflow.Abstractions/Models/NextNonceRequest.cs
@@ -1,6 +1,4 @@
-using Train.Solver.Workflow.Abstractions.Models;
-
-namespace Train.Solver.Workflow.EVM.Models;
+namespace Train.Solver.Workflow.Abstractions.Models;
public class NextNonceRequest : BaseRequest
{
diff --git a/csharp/src/Workflow.Abstractions/Models/TransactionBuilderRequest.cs b/csharp/src/Workflow.Abstractions/Models/TransactionBuilderRequest.cs
index 3467f538..9e0c3459 100644
--- a/csharp/src/Workflow.Abstractions/Models/TransactionBuilderRequest.cs
+++ b/csharp/src/Workflow.Abstractions/Models/TransactionBuilderRequest.cs
@@ -7,4 +7,6 @@ public class TransactionBuilderRequest : BaseRequest
public TransactionType Type { get; set; }
public string PrepareArgs { get; set; } = null!;
+
+ public string? FromAddress { get; set; }
}
diff --git a/csharp/src/Workflow.EVM/Activities/EVMBlockchainActivities.cs b/csharp/src/Workflow.EVM/Activities/EVMBlockchainActivities.cs
index 24a3f30e..d535c0a4 100644
--- a/csharp/src/Workflow.EVM/Activities/EVMBlockchainActivities.cs
+++ b/csharp/src/Workflow.EVM/Activities/EVMBlockchainActivities.cs
@@ -109,7 +109,7 @@ public virtual async Task GetBatchTransactionAsync(GetBatch
[Activity]
public virtual Task BuildTransactionAsync(TransactionBuilderRequest request)
{
- PrepareTransactionDto result = request.Type switch
+ var result = request.Type switch
{
TransactionType.Transfer => EVMTransactionBuilder.BuildTransferTransaction(request.Network, request.PrepareArgs),
TransactionType.Approve => EVMTransactionBuilder.BuildApproveTransaction(request.Network, request.PrepareArgs),
diff --git a/csharp/src/Workflow.EVM/Program.cs b/csharp/src/Workflow.EVM/Program.cs
index 15253408..f9d77f22 100644
--- a/csharp/src/Workflow.EVM/Program.cs
+++ b/csharp/src/Workflow.EVM/Program.cs
@@ -1,7 +1,6 @@
using Train.Solver.Infrastructure.DependencyInjection;
using Train.Solver.Infrastructure.Logging.OpenTelemetry;
using Train.Solver.Infrastrucutre.Secret.Treasury.Extensions;
-using Train.Solver.Workflow.EVM.Activities;
using Train.Solver.Workflow.EVM.Extensions;
IHost host = Host.CreateDefaultBuilder(args)
diff --git a/csharp/src/Workflow.Solana/Activities/ISolanaBlockchainActivities.cs b/csharp/src/Workflow.Solana/Activities/ISolanaBlockchainActivities.cs
index a7806973..fff7a701 100644
--- a/csharp/src/Workflow.Solana/Activities/ISolanaBlockchainActivities.cs
+++ b/csharp/src/Workflow.Solana/Activities/ISolanaBlockchainActivities.cs
@@ -1,29 +1,28 @@
using Temporalio.Activities;
-using Train.Solver.Blockchain.Abstractions.Models;
using Train.Solver.Blockchain.Solana.Models;
+using Train.Solver.Infrastructure.Abstractions.Models;
+using Train.Solver.Workflow.Abstractions.Models;
+using Train.Solver.Workflow.Solana.Models;
-namespace Train.Solver.Blockchain.Solana.Activities;
+namespace Train.Solver.Workflow.Solana.Activities;
public interface ISolanaBlockchainActivities
{
[Activity]
- Task EstimateFeeAsync(EstimateFeeRequest request);
+ Task BuildTransactionAsync(TransactionBuilderRequest request);
[Activity]
- Task GetNextNonceAsync(NextNonceRequest request);
-
- [Activity]
- Task BuildTransactionAsync(TransactionBuilderRequest request);
-
- [Activity]
- Task GetTransactionAsync(GetTransactionRequest request);
+ Task GetTransactionAsync(SolanaGetReceiptRequest request);
[Activity]
Task SimulateTransactionAsync(SolanaPublishTransactionRequest request);
[Activity]
- Task ComposeSolanaTranscationAsync(SolanaComposeTransactionRequest request);
+ Task ComposeSolanaTranscationAsync(SolanaComposeTransactionRequest request);
[Activity]
Task PublishTransactionAsync(SolanaPublishTransactionRequest request);
+
+ [Activity]
+ Task SignTransactionAsync(SolanaSignTransactionRequest request);
}
diff --git a/csharp/src/Workflow.Solana/Activities/SolanaBlockchainActivities.cs b/csharp/src/Workflow.Solana/Activities/SolanaBlockchainActivities.cs
index b09398df..a252cfb2 100644
--- a/csharp/src/Workflow.Solana/Activities/SolanaBlockchainActivities.cs
+++ b/csharp/src/Workflow.Solana/Activities/SolanaBlockchainActivities.cs
@@ -1,232 +1,83 @@
-using System.Numerics;
+using Nethereum.Hex.HexConvertors.Extensions;
+using Nethereum.RPC.Eth.DTOs;
+using Nethereum.Web3;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
using Solnet.Rpc;
using Solnet.Rpc.Builders;
using Solnet.Rpc.Models;
using Solnet.Wallet;
using Solnet.Wallet.Utilities;
-using StackExchange.Redis;
+using System.Buffers;
+using System.Numerics;
using Temporalio.Activities;
-using static Train.Solver.Blockchain.Common.Helpers.ResilientNodeHelper;
-using Nethereum.Web3;
-using Train.Solver.Infrastructure.Abstractions.Exceptions;
-using Train.Solver.Data.Abstractions.Repositories;
-using Train.Solver.Data.Abstractions.Entities;
-using Train.Solver.Blockchain.Abstractions.Activities;
-using Train.Solver.Blockchain.Abstractions.Models;
-using Train.Solver.Util.Extensions;
-using Train.Solver.Infrastructure.Abstractions;
using Train.Solver.Blockchain.Solana.Extensions;
using Train.Solver.Blockchain.Solana.Helpers;
using Train.Solver.Blockchain.Solana.Models;
using Train.Solver.Blockchain.Solana.Programs;
-using Train.Solver.Blockchain.Common.Helpers;
-using Org.BouncyCastle.Crypto.Parameters;
-using Org.BouncyCastle.Crypto.Signers;
-using System.Buffers;
-using Nethereum.Hex.HexConvertors.Extensions;
-using System.Text;
+using Train.Solver.Common.Enums;
+using Train.Solver.Data.Abstractions.Repositories;
+using Train.Solver.Infrastructure.Abstractions;
+using Train.Solver.Infrastructure.Abstractions.Exceptions;
+using Train.Solver.Infrastructure.Abstractions.Models;
+using Train.Solver.Workflow.Abstractions.Activities;
+using Train.Solver.Workflow.Abstractions.Models;
+using Train.Solver.Workflow.Solana.Helpers;
+using Train.Solver.Workflow.Solana.Models;
+using Transaction = Solnet.Rpc.Models.Transaction;
-namespace Train.Solver.Blockchain.Solana.Activities;
+namespace Train.Solver.Workflow.Solana.Activities;
public class SolanaBlockchainActivities(
INetworkRepository networkRepository,
- IDatabase cache,
IPrivateKeyProvider privateKeyProvider) : ISolanaBlockchainActivities, IBlockchainActivities
{
private const int MaxConcurrentTaskCount = 4;
- private const int LamportsPerSignature = 5000;
private const int LamportsPerRent = 3000000;
private const int BlockhashNotFoundErrorCode = -32002;
[Activity]
- public virtual async Task BuildTransactionAsync(TransactionBuilderRequest request)
+ public virtual async Task BuildTransactionAsync(TransactionBuilderRequest request)
{
- var network = await networkRepository.GetAsync(request.NetworkName);
-
- if (network is null)
- {
- throw new ArgumentNullException(nameof(network), $"Network {request.NetworkName} not found");
- }
-
- var solverAccount = await networkRepository.GetSolverAccountAsync(network.Name);
-
- if (solverAccount is null)
- {
- throw new ArgumentNullException(nameof(network), $"Solver account for {request.NetworkName} not found");
- }
-
- PrepareTransactionResponse result;
+ PrepareTransactionDto result;
switch (request.Type)
{
case TransactionType.Transfer:
- result = await SolanaTransactionBuilder.BuildTransferTransactionAsync(network, request.Args);
+ result = await SolanaTransactionBuilder.BuildTransferTransactionAsync(request.Network, request.PrepareArgs);
break;
case TransactionType.HTLCLock:
- result = await SolanaTransactionBuilder.BuildHTLCLockTransactionAsync(network, solverAccount, request.Args);
+ result = await SolanaTransactionBuilder.BuildHTLCLockTransactionAsync(request.Network, request.FromAddress!, request.PrepareArgs);
break;
case TransactionType.HTLCRedeem:
- result = await SolanaTransactionBuilder.BuildHTLCRedeemTransactionAsync(network, solverAccount, request.Args);
+ result = await SolanaTransactionBuilder.BuildHTLCRedeemTransactionAsync(request.Network, request.FromAddress!, request.PrepareArgs);
break;
case TransactionType.HTLCRefund:
- result = await SolanaTransactionBuilder.BuildHTLCRefundTransactionAsync(network, solverAccount, request.Args);
+ result = await SolanaTransactionBuilder.BuildHTLCRefundTransactionAsync(request.Network, request.FromAddress!, request.PrepareArgs);
break;
case TransactionType.HTLCAddLockSig:
- result = await SolanaTransactionBuilder.BuildHTLCAddlockSigTransactionAsync(network, solverAccount, request.Args);
+ result = await SolanaTransactionBuilder.BuildHTLCAddlockSigTransactionAsync(request.Network, request.FromAddress!, request.PrepareArgs);
break;
default:
- throw new ArgumentOutOfRangeException(nameof(request.Type), $"Not supported transaction type {request.Type} for network {request.NetworkName}");
+ throw new ArgumentOutOfRangeException(nameof(request.Type), $"Not supported transaction type {request.Type} for network {request.Network.Name}");
}
return result;
}
- [Activity]
- public virtual async Task EstimateFeeAsync(EstimateFeeRequest request)
- {
- var result = new Dictionary();
-
- var network = await networkRepository.GetAsync(request.NetworkName);
-
- if (network is null)
- {
- throw new ArgumentNullException(nameof(network), $"Network {request.NetworkName} not found");
- }
-
- var currency = network.Tokens.SingleOrDefault(x => x.Asset == request.Asset);
-
- if (currency is null)
- {
- throw new ArgumentNullException(nameof(currency), $"Currency {request.Asset} for {request.NetworkName} is missing");
- }
-
- var privateKeyResult = await privateKeyProvider.GetAsync(request.FromAddress);
-
- var node = network.Nodes.FirstOrDefault();
-
- if (node is null)
- {
- throw new ArgumentNullException(nameof(node), $"Node for network: {network.Id} is not configured");
- }
-
- var rpcClient = ClientFactory.GetClient(node.Url);
-
- var solanaAccount = new Account(privateKeyResult, request.FromAddress);
-
- var balanceForRentExemptionResult = await rpcClient.GetMinimumBalanceForRentExemptionAsync(165);
-
- if (!balanceForRentExemptionResult.WasSuccessful)
- {
- throw new Exception($"Failed to get minimum balance for rent exemption in network: {request.NetworkName}");
- }
-
- var builder = new TransactionBuilder()
- .SetFeePayer(solanaAccount);
-
- var signers = new List { solanaAccount };
-
- var transaction = Convert.FromBase64String(request.CallData!);
- var tx = Solnet.Rpc.Models.Transaction.Deserialize(transaction);
-
- foreach (var instruction in tx.Instructions)
- {
- builder.AddInstruction(instruction);
- }
-
- if (tx.FeePayer != solanaAccount)
- {
- var managedAddressPrivateKey = await privateKeyProvider.GetAsync(tx.FeePayer);
- signers.Add(new Account(managedAddressPrivateKey, tx.FeePayer));
- }
-
- var latestBlockHashResponse = await rpcClient.GetLatestBlockHashAsync();
-
- if (!latestBlockHashResponse.WasSuccessful)
- {
- throw new Exception($"Failed to get latest block hash, error: {latestBlockHashResponse.RawRpcResponse}");
- }
-
- builder.SetRecentBlockHash(latestBlockHashResponse.Result.Value.Blockhash);
-
- var rawTx = builder.Build(signers);
-
- var simulatedTransaction = await rpcClient.SimulateTransactionAsync(rawTx);
-
- if (!simulatedTransaction.WasSuccessful || simulatedTransaction.Result.Value.Error != null)
- {
- if (!simulatedTransaction.WasSuccessful)
- {
- throw new Exception($"Failed to simulate transaction in network{request.NetworkName}: Reason {simulatedTransaction.Reason}");
- }
-
- throw new Exception($"Failed to simulate transaction in network{request.NetworkName}: Error Type {simulatedTransaction.Result.Value.Error.Type}");
- }
-
- var computeUnitsUsed = SolanaConstants.BaseLimit + TransactionLogExtension.ExtractTotalComputeUnitsUsed(simulatedTransaction.Result.Value.Logs.ToList());
-
- var baseFeeInLamports = balanceForRentExemptionResult.Result + ComputeRentFee(request.NetworkName, tx.Instructions) + signers.Count * LamportsPerSignature;
-
- var nativeCurrency = network.Tokens.SingleOrDefault(x => x.TokenContract is null);
-
- if (nativeCurrency is null)
- {
- throw new ArgumentNullException(nameof(nativeCurrency), $"Native currency is not configured on {request.NetworkName} network");
- }
-
- decimal computeUnitPrice = 0;
-
- if (!SolanaConstants.HighComputeUnitPrice.TryGetValue(request.NetworkName, out computeUnitPrice))
- {
- throw new($"High compute unit price is not configured on {request.NetworkName} network");
- }
-
- computeUnitsUsed = computeUnitsUsed.PercentageIncrease(200);
-
- var fee = new Fee(
- nativeCurrency.Asset,
- nativeCurrency.Decimals,
- new SolanaFeeData(
- computeUnitPrice.ToString(),
- computeUnitsUsed.ToString(),
- baseFeeInLamports.ToString()));
-
- var balance = await GetBalanceAsync(new BalanceRequest
- {
- NetworkName = request.NetworkName,
- Address = request.FromAddress,
- Asset = fee.Asset
- });
-
- var amount = BigInteger.Parse(fee.AmountInWei) + BigInteger.Parse(request.Amount);
-
- if (BigInteger.Parse(balance.AmountInWei) < amount)
- {
- throw new Exception($"Insufficient funds in {request.NetworkName}. {request.FromAddress}. Required {amount} {fee.Asset}");
- }
-
- return fee;
- }
-
[Activity]
public virtual async Task GetBalanceAsync(BalanceRequest request)
{
- var network = await networkRepository.GetAsync(request.NetworkName);
-
- if (network is null)
- {
- throw new ArgumentNullException(nameof(network), $"Network {request.NetworkName} not found");
- }
-
- var node = network.Nodes.FirstOrDefault();
+ var node = request.Network.Nodes.FirstOrDefault();
if (node is null)
{
- throw new ArgumentNullException(nameof(node), $"Primary node is not configured on {request.NetworkName} network");
+ throw new ArgumentNullException(nameof(node), $"Primary node is not configured on {request.Network.Name} network");
}
var rpcClient = ClientFactory.GetClient(node.Url);
- var currency = network.Tokens.SingleOrDefault(x => x.Asset.ToUpper() == request.Asset.ToUpper());
+ var currency = request.Network.Tokens.SingleOrDefault(x => x.Symbol.ToUpper() == request.Asset.ToUpper());
if (currency is null)
{
throw new ArgumentNullException(nameof(currency), $"Invalid currency");
@@ -236,9 +87,8 @@ public virtual async Task GetBalanceAsync(BalanceRequest reques
try
{
- if (currency.TokenContract is null)
+ if (currency.Contract is null)
{
-
var response = await rpcClient.GetBalanceAsync(request.Address);
if (!response.WasSuccessful)
@@ -250,7 +100,7 @@ public virtual async Task GetBalanceAsync(BalanceRequest reques
}
else
{
- var response = await rpcClient.GetTokenAccountsByOwnerAsync(request.Address, currency.TokenContract);
+ var response = await rpcClient.GetTokenAccountsByOwnerAsync(request.Address, currency.Contract);
if (!response.WasSuccessful)
{
@@ -265,45 +115,40 @@ public virtual async Task GetBalanceAsync(BalanceRequest reques
}
catch (Exception ex)
{
- throw new Exception($"Failed to get balance of {currency.Asset} on {request.Address} address in {request.NetworkName} network , message {ex.Message}");
+ throw new Exception($"Failed to get balance of {currency.Symbol} on {request.Address} address in {request.Network.Name} network , message {ex.Message}");
}
var balanceResponse = new BalanceResponse
{
- AmountInWei = balance.ToString(),
- Decimals = currency.Decimals,
+ Amount = balance,
};
return balanceResponse;
}
[Activity]
- public virtual async Task GetTransactionAsync(GetTransactionRequest request)
+ public virtual async Task GetTransactionAsync(SolanaGetReceiptRequest request)
{
- var network = await networkRepository.GetAsync(request.NetworkName);
+ var node = request.Network.Nodes.FirstOrDefault();
- if (network is null)
+ if (node is null)
{
- throw new ArgumentNullException(nameof(network), $"Network {request.NetworkName} not found");
+ throw new($"Primary node is not configured on {request.Network.Name} network");
}
- var nodes = network.Nodes;
-
- if (!nodes.Any())
- {
- throw new ArgumentException($"Primary node is not configured on {request.NetworkName} network", nameof(nodes));
- }
+ var rpcClient = ClientFactory.GetClient(node.Url);
- var epochInfoResponse = await GetDataFromNodesAsync(nodes,
- async url => await ClientFactory.GetClient(url).GetEpochInfoAsync());
+ var epochInfoResponse = await rpcClient.GetEpochInfoAsync();
TransactionResponse transaction;
+
try
{
- transaction = await GetDataFromNodesAsync(nodes,
- async url => await GetTransactionAsync(request.TransactionHash, network, epochInfoResponse.Result,
- ClientFactory.GetClient(url)));
-
+ transaction = await GetTransactionReceiptAsync(
+ rpcClient,
+ request.Network,
+ epochInfoResponse.Result,
+ request.TxHash);
}
catch (AggregateException ae)
{
@@ -314,12 +159,11 @@ public virtual async Task GetTransactionAsync(GetTransactio
throw transactionNotConfirmedException;
}
- var status = await GetDataFromNodesAsync(nodes,
- async url => await ClientFactory.GetClient(url).GetSignatureStatusAsync(request.TransactionHash));
+ var status = await rpcClient.GetSignatureStatusAsync(request.TxHash);
if (status.Result.Value.FirstOrDefault() != null)
{
- throw new TransactionNotComfirmedException($"Transaction is not confirmed yet, TxHash: {request.TransactionHash}.");
+ throw new TransactionNotComfirmedException($"Transaction is not confirmed yet, TxHash: {request.TxHash}.");
}
throw;
@@ -330,31 +174,19 @@ public virtual async Task GetTransactionAsync(GetTransactio
throw new TransactionFailedException("Transaction failed");
}
+ await CheckBlockHeightAsync(rpcClient, request.TransactionBlockHeight);
+
return transaction;
}
[Activity]
public virtual async Task GetEventsAsync(EventRequest request)
{
- var network = await networkRepository.GetAsync(request.NetworkName);
-
- if (network is null)
- {
- throw new ArgumentNullException(nameof(network), $"Chain for network: {request.NetworkName} is not configured");
- }
-
- var node = network!.Nodes.FirstOrDefault();
+ var node = request.Network.Nodes.FirstOrDefault();
if (node is null)
{
- throw new ArgumentNullException(nameof(node), $"Node for network: {request.NetworkName} is not configured");
- }
-
- var solverAccount = await networkRepository.GetSolverAccountAsync(network.Name);
-
- if (solverAccount is null)
- {
- throw new ArgumentNullException(nameof(network), $"Solver account for {request.NetworkName} not found");
+ throw new ArgumentNullException(nameof(node), $"Node for network: {request.Network.Name} is not configured");
}
var rpcClient = ClientFactory.GetClient(node.Url);
@@ -363,18 +195,15 @@ public virtual async Task GetEventsAsync(EventRequest re
var blocksForProcessing = Enumerable.Range((int)request.FromBlock, (int)(request.ToBlock - request.FromBlock) + 1).ToArray();
var events = new HTLCBlockEventResponse();
- var currencies = await networkRepository.GetTokensAsync();
-
foreach (var blockChunk in blocksForProcessing.Chunk(MaxConcurrentTaskCount))
{
foreach (var currentBlock in blockChunk)
{
blockProcessingTasks[currentBlock] = EventDecoder.GetBlockEventsAsync(
rpcClient,
- currentBlock,
- network,
- currencies,
- solverAccount);
+ request.Network,
+ request.WalletAddresses,
+ currentBlock);
}
await Task.WhenAll(blockProcessingTasks.Values);
@@ -394,18 +223,11 @@ public virtual async Task GetEventsAsync(EventRequest re
[Activity]
public virtual async Task GetLastConfirmedBlockNumberAsync(BaseRequest request)
{
- var network = await networkRepository.GetAsync(request.NetworkName);
-
- if (network is null)
- {
- throw new ArgumentNullException(nameof(network), $"Network {request.NetworkName} not found");
- }
-
- var node = network.Nodes.FirstOrDefault();
+ var node = request.Network.Nodes.FirstOrDefault();
if (node is null)
{
- throw new ArgumentNullException(nameof(node), $"Node for network: {request.NetworkName} is not configured");
+ throw new ArgumentNullException(nameof(node), $"Node for network: {request.Network.Name} is not configured");
}
var rpcClient = ClientFactory.GetClient(node.Url);
@@ -433,56 +255,21 @@ public virtual async Task GetLastConfirmedBlockNumberAsync(
};
}
- [Activity]
- public virtual async Task GetNextNonceAsync(NextNonceRequest request)
- {
- var network = await networkRepository.GetAsync(request.NetworkName);
-
- if (network == null)
- {
- throw new ArgumentNullException(nameof(network), $"Network {request.NetworkName} not found");
- }
-
- var node = network.Nodes.FirstOrDefault();
-
- if (node is null)
- {
- throw new ArgumentNullException(nameof(node), $"Node for network: {network.Name} is not configured");
- }
-
- var rpcClient = ClientFactory.GetClient(node.Url);
-
- var latestBlockHashResponse = await ClientFactory
- .GetClient(node.Url)
- .GetLatestBlockHashAsync();
-
- if (!latestBlockHashResponse.WasSuccessful)
- {
- throw new Exception($"Failed to get latest block hash, error: {latestBlockHashResponse.RawRpcResponse}");
- }
-
- await cache.StringSetAsync(RedisHelper.BuildNonceKey(request.NetworkName, request.Address),
- latestBlockHashResponse.Result.Value.LastValidBlockHeight,
- expiry: TimeSpan.FromDays(7));
-
- return latestBlockHashResponse.Result.Value.Blockhash;
- }
-
[Activity]
public async Task ValidateAddLockSignatureAsync(AddLockSignatureRequest request)
{
- var network = await networkRepository.GetAsync(request.NetworkName);
+ var network = await networkRepository.GetAsync(request.Network.Name);
if (network is null)
{
- throw new ArgumentNullException(nameof(network), $"Network {request.NetworkName} not found");
+ throw new ArgumentNullException(nameof(network), $"Network {request.Network.Name} not found");
}
var currency = network.Tokens.Single(x => x.Asset.ToUpper() == request.Asset.ToUpper());
if (currency is null)
{
- throw new ArgumentNullException(nameof(currency), $"Currency {request.Asset} for {request.NetworkName} is missing");
+ throw new ArgumentNullException(nameof(currency), $"Currency {request.Asset} for {request.Network.Name} is missing");
}
if (request.Signature is null)
@@ -493,7 +280,7 @@ public async Task ValidateAddLockSignatureAsync(AddLockSignatureRequest re
var message = Ed25519Program.CreateAddLockSigMessage(new()
{
Hashlock = request.Hashlock.HexToByteArray(),
- Id = request.Id.HexToByteArray(),
+ Id = request.CommitId.HexToByteArray(),
Timelock = request.Timelock,
SignerPublicKey = new PublicKey(request.SignerAddress),
});
@@ -512,18 +299,11 @@ public async Task ValidateAddLockSignatureAsync(AddLockSignatureRequest re
[Activity]
public async Task SimulateTransactionAsync(SolanaPublishTransactionRequest request)
{
- var network = await networkRepository.GetAsync(request.NetworkName);
-
- if (network == null)
- {
- throw new ArgumentNullException(nameof(network), $"Network {request.NetworkName} not found");
- }
-
- var node = network.Nodes.FirstOrDefault();
+ var node = request.Network.Nodes.FirstOrDefault();
if (node is null)
{
- throw new ArgumentNullException(nameof(node), $"Node for network: {network.Name} is not configured");
+ throw new ArgumentNullException(nameof(node), $"Node for network: {request.Network.Name} is not configured");
}
var rpcClient = ClientFactory.GetClient(node.Url);
@@ -534,21 +314,21 @@ public async Task SimulateTransactionAsync(SolanaPublishTransactionRequest reque
{
if (!simulatedTransaction.WasSuccessful)
{
- throw new Exception($"Failed to simulate transaction in network {network.Name}: Reason {simulatedTransaction.Reason}");
+ throw new Exception($"Failed to simulate transaction in network {request.Network.Name}: Reason {simulatedTransaction.Reason}");
}
if (simulatedTransaction.Result.Value.Error.Type == TransactionErrorType.BlockhashNotFound)
{
throw new NonceMissMatchException(
- $"Nonce mismatch error Failed to simulate transaction in network {network.Name}: Error Type {simulatedTransaction.Result.Value.Error.Type}");
+ $"Nonce mismatch error Failed to simulate transaction in network {request.Network.Name}: Error Type {simulatedTransaction.Result.Value.Error.Type}");
}
- throw new Exception($"Failed to simulate transaction in network {network.Name}: Error Type {simulatedTransaction.Result.Value.Error.Type}");
+ throw new Exception($"Failed to simulate transaction in network {request.Network.Name}: Error Type {simulatedTransaction.Result.Value.Error.Type}");
}
}
[Activity]
- public async Task ComposeSolanaTranscationAsync(SolanaComposeTransactionRequest request)
+ public async Task ComposeSolanaTranscationAsync(SolanaComposeTransactionRequest request)
{
var solanaAddress = new PublicKey(request.FromAddress);
@@ -563,7 +343,7 @@ public async Task ComposeSolanaTranscationAsync(SolanaComposeTransaction
}
var transactionBytes = Convert.FromBase64String(request.CallData);
- var tx = Solnet.Rpc.Models.Transaction.Deserialize(transactionBytes);
+ var tx = Transaction.Deserialize(transactionBytes);
foreach (var instruction in tx.Instructions)
{
builder.AddInstruction(instruction);
@@ -574,29 +354,53 @@ public async Task ComposeSolanaTranscationAsync(SolanaComposeTransaction
signers.Add(tx.FeePayer);
}
+ if (!SolanaConstants.MediumComputeUnitPrice.TryGetValue(request.Network.Name, out var computeUnitPrice))
+ {
+ throw new($"Compute unit is not configured for netwokr {request.Network.Name}");
+ }
+
+ var node = request.Network.Nodes.FirstOrDefault();
+
+ if (node is null)
+ {
+ throw new ArgumentNullException(nameof(node), $"Node for network: {request.Network.Name} is not configured");
+ }
+
+ var rpcClient = ClientFactory.GetClient(node.Url);
+
+ var latestBlockHashResponse = await rpcClient.GetLatestBlockHashAsync();
+
+ if (!latestBlockHashResponse.WasSuccessful)
+ {
+ throw new Exception($"Failed to get latest block hash, error: {latestBlockHashResponse.RawRpcResponse}");
+ }
+
builder
- .AddInstruction(ComputeBudgetProgram.SetComputeUnitLimit(uint.Parse(request.Fee.SolanaFeeData!.ComputeUnitLimit)))
- .AddInstruction(ComputeBudgetProgram.SetComputeUnitPrice((ulong)Web3.Convert.ToWei(decimal.Parse(request.Fee.SolanaFeeData.ComputeUnitPrice), SolanaConstants.MicroLamportsDecimal)))
- .SetRecentBlockHash(request.LastValidBlockHash);
+ .AddInstruction(ComputeBudgetProgram.SetComputeUnitPrice((ulong)Web3.Convert.ToWei(computeUnitPrice, SolanaConstants.MicroLamportsDecimal)))
+ .SetRecentBlockHash(latestBlockHashResponse.Result.Value.Blockhash);
- var rawTxResult = await SignSolanaTransactionAsync(builder, signers);
+ var rawTxResult = Convert.ToBase64String(builder.Serialize());
- return rawTxResult;
+ return new()
+ {
+ LastValidBlockHeight = latestBlockHashResponse.Result.Value.LastValidBlockHeight.ToString(),
+ RawTx = rawTxResult
+ };
}
[Activity]
public async Task PublishTransactionAsync(SolanaPublishTransactionRequest request)
{
- var network = await networkRepository.GetAsync(request.NetworkName);
+ var network = await networkRepository.GetAsync(request.Network.Name);
if (network is null)
{
- throw new ArgumentNullException(nameof(network), $"Network {request.NetworkName} not found");
+ throw new ArgumentNullException(nameof(network), $"Network {request.Network.Name} not found");
}
if (network == null)
{
- throw new($"Network {request.NetworkName} not found");
+ throw new($"Network {request.Network.Name} not found");
}
var node = network.Nodes.FirstOrDefault();
@@ -608,9 +412,11 @@ public async Task PublishTransactionAsync(SolanaPublishTransactionReques
var rpcClient = ClientFactory.GetClient(node.Url);
+ var signedRawData = Convert.FromBase64String(request.RawTx);
+
try
{
- var transactionResult = await rpcClient.SendSolanaTransactionAsync(request.RawTx);
+ var transactionResult = await rpcClient.SendSolanaTransactionAsync(signedRawData);
if (!transactionResult.WasSuccessful)
{
@@ -631,47 +437,30 @@ public async Task PublishTransactionAsync(SolanaPublishTransactionReques
{
}
- return CalculateTransactionHash(request.RawTx);
+ return CalculateTransactionHash(signedRawData);
}
private static bool ValidateAddress(string address)
=> PublicKey.IsValid(address);
- private async Task SignSolanaTransactionAsync(
- TransactionBuilder builder,
- List managedAddresses)
- {
- var signers = new List();
+ //private static BigInteger ComputeRentFee(
+ // string networkName,
+ // List instructions)
+ //{
+ // int accountCreationCount = 0;
- foreach (var address in managedAddresses)
- {
- var privateKeyResult = await privateKeyProvider.GetAsync(address);
+ // foreach (var instruction in instructions)
+ // {
+ // var lockDescriminator = FieldEncoder.Sighash(SolanaConstants.LockSighash);
- var solanaAccount = new Account(privateKeyResult, address);
- signers.Add(solanaAccount);
- }
+ // if (instruction.Data.Take(8).SequenceEqual(lockDescriminator))
+ // {
+ // accountCreationCount++;
+ // }
+ // }
- return builder.Build(signers);
- }
-
- private static BigInteger ComputeRentFee(
- string networkName,
- List instructions)
- {
- int accountCreationCount = 0;
-
- foreach (var instruction in instructions)
- {
- var lockDescriminator = FieldEncoder.Sighash(SolanaConstants.LockSighash);
-
- if (instruction.Data.Take(8).SequenceEqual(lockDescriminator))
- {
- accountCreationCount++;
- }
- }
-
- return accountCreationCount * LamportsPerRent;
- }
+ // return accountCreationCount * LamportsPerRent;
+ //}
private static string CalculateTransactionHash(byte[] rawTransactionBytes)
{
@@ -685,45 +474,34 @@ private static string CalculateTransactionHash(byte[] rawTransactionBytes)
}
private async Task CheckBlockHeightAsync(
- Network network,
- string fromAddress)
+ IRpcClient rpcClient,
+ string lastValidBlockHeight)
{
- var primaryNode = network.Nodes.FirstOrDefault();
-
- if (primaryNode is null)
- {
- throw new ArgumentNullException(nameof(primaryNode), $"Primary node is not configured on {network.Name} network");
- }
-
- var primaryRpcClient = ClientFactory.GetClient(primaryNode.Url);
-
- var epochInfoResponseResult = await primaryRpcClient.GetEpochInfoAsync();
+ var epochInfoResponseResult = await rpcClient.GetEpochInfoAsync();
if (!epochInfoResponseResult.WasSuccessful)
{
- throw new Exception($"Failed to get latestBlock for {network.Name} network");
+ throw new Exception($"Failed to get latestBlock");
}
- if (!string.IsNullOrEmpty(fromAddress))
+ if (ulong.Parse(lastValidBlockHeight) <= epochInfoResponseResult.Result.BlockHeight)
{
- var lastValidBlockHeight = await cache.StringGetAsync(
- RedisHelper.BuildNonceKey(network.Name, fromAddress));
-
- if (lastValidBlockHeight.HasValue && ulong.Parse(lastValidBlockHeight.ToString()) <= epochInfoResponseResult.Result.BlockHeight)
- {
- throw new TransactionFailedRetriableException("Transaction not found");
- }
+ throw new TransactionFailedRetriableException("Transaction not found");
}
}
- private static async Task GetTransactionAsync(
- string transactionId,
- Network network,
+ private static async Task GetTransactionReceiptAsync(
+ IRpcClient rpcClient,
+ DetailedNetworkDto network,
EpochInfo epochInfo,
- IRpcClient rpcClient)
+ string transactionId)
{
- var feeCurrency = network.Tokens
- .Single(x => string.IsNullOrEmpty(x.TokenContract));
+ var feeCurrency = network.NativeToken;
+
+ if (feeCurrency == null)
+ {
+ throw new($"Fee currency not cinfigured for network {network.Name}");
+ }
var transactionReceiptResult = await rpcClient.GetParsedTransactionAsync(transactionId);
@@ -737,18 +515,31 @@ private static async Task GetTransactionAsync(
var result = new TransactionResponse
{
TransactionHash = transactionId,
- FeeAmount = transactionReceiptResult.Result.Meta.Fee.ToString(),
- FeeAsset = feeCurrency.Asset,
+ FeeAmount = transactionReceiptResult.Result.Meta.Fee,
+ FeeAsset = feeCurrency.Symbol,
FeeDecimals = feeCurrency.Decimals,
Timestamp = DateTimeOffset.FromUnixTimeMilliseconds(transactionReceiptResult.Result.BlockTime * 1000),
Status = transactionReceiptResult.Result.Meta.Err is null ? TransactionStatus.Completed : TransactionStatus.Failed,
Confirmations = confirmations,
NetworkName = network.Name,
- Decimals = feeCurrency.Decimals,
};
return result;
}
- private static string FormatAddress(string address) => address;
+ public async Task SignTransactionAsync(SolanaSignTransactionRequest request)
+ {
+ var signedTransaction = await privateKeyProvider.SignAsync(
+ request.SignerAgentUrl,
+ request.Network.Type,
+ request.FromAddress,
+ request.UnsignRawTransaction);
+
+ if (string.IsNullOrEmpty(signedTransaction))
+ {
+ throw new Exception($"Failed to sign transaction for {request.FromAddress} on network {request.Network.Name}. RawTx {request.UnsignRawTransaction}");
+ }
+
+ return signedTransaction;
+ }
}
diff --git a/csharp/src/Workflow.Solana/Extensions/TrainSolverBuilderExtensions.cs b/csharp/src/Workflow.Solana/Extensions/TrainSolverBuilderExtensions.cs
index da873058..24c35ecf 100644
--- a/csharp/src/Workflow.Solana/Extensions/TrainSolverBuilderExtensions.cs
+++ b/csharp/src/Workflow.Solana/Extensions/TrainSolverBuilderExtensions.cs
@@ -1,24 +1,19 @@
using Temporalio.Extensions.Hosting;
-using Train.Solver.Blockchain.Common.Activities;
-using Train.Solver.Blockchain.Common.Worklows;
-using Train.Solver.Blockchain.Solana.Activities;
-using Train.Solver.Blockchain.Solana.Workflows;
-using Train.Solver.Data.Abstractions.Entities;
using Train.Solver.Infrastructure.DependencyInjection;
+using Train.Solver.Workflow.Solana.Activities;
+using Train.Solver.Workflow.Solana.Workflows;
-namespace Train.Solver.Blockchain.Solana.Extensions;
+namespace Train.Solver.Workflow.Solana.Extensions;
public static class TrainSolverBuilderExtensions
{
public static TrainSolverBuilder WithSolanaWorkflows(
this TrainSolverBuilder builder)
{
- var temporalBuilder = builder.Services
- .AddHostedTemporalWorker(nameof(NetworkType.Solana))
+ builder.Services
+ .AddHostedTemporalWorker(builder.Options.NetworkType)
.AddWorkflow()
- .AddWorkflow()
- .AddTransientActivities()
- .AddTransientActivities();
+ .AddTransientActivities();
return builder;
}
diff --git a/csharp/src/Workflow.Solana/Helpers/EventDecoder.cs b/csharp/src/Workflow.Solana/Helpers/EventDecoder.cs
index 4df25475..ef91c6db 100644
--- a/csharp/src/Workflow.Solana/Helpers/EventDecoder.cs
+++ b/csharp/src/Workflow.Solana/Helpers/EventDecoder.cs
@@ -1,15 +1,13 @@
-using System.Numerics;
-using Nethereum.Hex.HexConvertors.Extensions;
-using Nethereum.Web3;
+using Nethereum.Hex.HexConvertors.Extensions;
using Solnet.Rpc;
using Solnet.Rpc.Builders;
-using Solnet.Rpc.Models;
using Solnet.Wallet;
-using Train.Solver.Blockchain.Abstractions.Models;
+using System.Numerics;
using Train.Solver.Blockchain.Solana.Extensions;
using Train.Solver.Blockchain.Solana.Models;
using Train.Solver.Blockchain.Solana.Programs.HTLCProgram;
-using Train.Solver.Data.Abstractions.Entities;
+using Train.Solver.Infrastructure.Abstractions.Models;
+using Train.Solver.Workflow.Abstractions.Models;
namespace Train.Solver.Blockchain.Solana.Helpers;
@@ -17,10 +15,9 @@ public static class EventDecoder
{
public static async Task GetBlockEventsAsync(
IRpcClient rpcClient,
- int block,
- Network network,
- List currencies,
- string solverAccount)
+ DetailedNetworkDto network,
+ string[] solverAccounts,
+ int block)
{
var blockResponseResult = await rpcClient.GetParsedEventBlockAsync(block);
@@ -31,6 +28,8 @@ public static async Task GetBlockEventsAsync(
var result = new HTLCBlockEventResponse();
+ var currencies = network.Tokens;
+
if (blockResponseResult.Result != null && blockResponseResult.Result.Transactions.Any())
{
var htlcTokenContractAddress = network.HTLCTokenContractAddress;
@@ -48,6 +47,8 @@ public static async Task GetBlockEventsAsync(
var isLockEvent = transaction.Meta.LogMessages
.Any(x => x.Contains(SolanaConstants.HtlcConstants.addLockEventPrefixPattern));
+ var accountForSimulation = solverAccounts.First();
+
if (isCommitEvent)
{
var prefixPattern = "Program return: " + htlcTokenContractAddress + " ";
@@ -61,23 +62,25 @@ public static async Task GetBlockEventsAsync(
var commitEvent = await DeserializeCommitEventDataAsync(
rpcClient,
- id,
network,
- solverAccount);
+ id,
+ accountForSimulation);
if (commitEvent == null)
{
continue;
}
- if (commitEvent.ReceiverAddress != solverAccount)
+ var receiverAddress = commitEvent.ReceiverAddress;
+
+ if (!solverAccounts.Contains(receiverAddress))
{
continue;
}
var destinationCurrency = currencies
- .FirstOrDefault(x => x.Asset == commitEvent.DestinationAsset
- && x.Network.Name == commitEvent.DestinationNetwork);
+ .FirstOrDefault(x => x.Symbol == commitEvent.DestinationAsset
+ && network.Name == commitEvent.DestinationNetwork);
if (destinationCurrency is null)
{
@@ -85,7 +88,7 @@ public static async Task GetBlockEventsAsync(
}
var sourceCurrency = network.Tokens
- .FirstOrDefault(x => x.Asset == commitEvent.SourceAsset);
+ .FirstOrDefault(x => x.Symbol == commitEvent.SourceAsset);
if (sourceCurrency is null)
{
@@ -95,9 +98,9 @@ public static async Task GetBlockEventsAsync(
var commitEventMessage = new HTLCCommitEventMessage
{
TxId = transaction.Transaction.Signatures.First(),
- Id = commitEvent.Id,
- AmountInWei = commitEvent.AmountInWei,
- ReceiverAddress = solverAccount,
+ CommitId = commitEvent.Id,
+ Amount = BigInteger.Parse(commitEvent.AmountInWei),
+ ReceiverAddress = receiverAddress,
SourceNetwork = network.Name,
SourceAsset = commitEvent.SourceAsset,
DestinationAddress = commitEvent.DestinationAddress,
@@ -105,8 +108,6 @@ public static async Task GetBlockEventsAsync(
DestinationAsset = commitEvent.DestinationAsset,
SenderAddress = commitEvent.SenderAddress,
TimeLock = commitEvent.TimeLock,
- DestinationNetworkType = destinationCurrency.Network.Type,
- SourceNetworkType = sourceCurrency.Network.Type
};
result.HTLCCommitEventMessages.Add(commitEventMessage);
@@ -125,9 +126,9 @@ public static async Task GetBlockEventsAsync(
var addLockMessageResult = await DeserializeAddLockEventDataAsync(
rpcClient,
- id,
network,
- solverAccount);
+ id,
+ accountForSimulation);
if (addLockMessageResult == null)
{
@@ -146,8 +147,8 @@ public static async Task GetBlockEventsAsync(
private static async Task DeserializeCommitEventDataAsync(
IRpcClient rpcClient,
+ DetailedNetworkDto network,
string commitId,
- Network network,
string solverAccount)
{
if (solverAccount is null)
@@ -194,13 +195,12 @@ private static async Task DeserializeCommitEventData
response.Id = commitId;
return response;
-
}
private static async Task DeserializeAddLockEventDataAsync(
IRpcClient rpcClient,
- string id,
- Network network,
+ DetailedNetworkDto network,
+ string commitId,
string solverAccount)
{
try
@@ -218,7 +218,7 @@ private static async Task DeserializeCommitEventData
builder.SetGetDetailsInstruction(
new PublicKey(htlcContractAddress),
- id.HexToByteArray());
+ commitId.HexToByteArray());
var latestBlockHashResponse = await rpcClient.GetLatestBlockHashAsync();
@@ -248,7 +248,7 @@ private static async Task DeserializeCommitEventData
var response = new HTLCLockEventMessage();
- response.Id = id;
+ response.CommitId = commitId;
response.HashLock = ExtractString(
simulatedTransaction.Result.Value.Logs.FirstOrDefault(x =>
diff --git a/csharp/src/Workflow.Solana/Helpers/SolanaTransactionBuilder.cs b/csharp/src/Workflow.Solana/Helpers/SolanaTransactionBuilder.cs
index bf6628e6..85b4c84f 100644
--- a/csharp/src/Workflow.Solana/Helpers/SolanaTransactionBuilder.cs
+++ b/csharp/src/Workflow.Solana/Helpers/SolanaTransactionBuilder.cs
@@ -1,6 +1,4 @@
-using System.Numerics;
-using System.Text.Json;
-using Nethereum.Hex.HexConvertors.Extensions;
+using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Hex.HexTypes;
using Nethereum.Web3;
using Solnet.Extensions;
@@ -9,17 +7,20 @@
using Solnet.Rpc;
using Solnet.Rpc.Builders;
using Solnet.Wallet;
-using Train.Solver.Blockchain.Abstractions.Models;
-using Train.Solver.Data.Abstractions.Entities;
+using System.Numerics;
+using System.Text.Json;
using Train.Solver.Blockchain.Solana.Programs.HTLCProgram;
using Train.Solver.Blockchain.Solana.Programs.HTLCProgram.Models;
+using Train.Solver.Data.Abstractions.Entities;
+using Train.Solver.Infrastructure.Abstractions.Models;
+using Train.Solver.Workflow.Abstractions.Models;
-namespace Train.Solver.Blockchain.Solana.Helpers;
+namespace Train.Solver.Workflow.Solana.Helpers;
public static class SolanaTransactionBuilder
{
- public static async Task BuildHTLCLockTransactionAsync(
- Network network,
+ public static async Task BuildHTLCLockTransactionAsync(
+ DetailedNetworkDto network,
string solverAccount,
string args)
{
@@ -30,7 +31,7 @@ public static async Task BuildHTLCLockTransactionAsy
throw new Exception($"Occured exception during deserializing {args}");
}
- var currency = network.Tokens.SingleOrDefault(x => x.Asset.ToUpper() == request.SourceAsset.ToUpper());
+ var currency = network.Tokens.SingleOrDefault(x => x.Symbol.ToUpper() == request.SourceAsset.ToUpper());
if (currency is null)
{
@@ -38,20 +39,23 @@ public static async Task BuildHTLCLockTransactionAsy
$"Currency {request.SourceAsset} for {network.Name} is missing");
}
- var isNative = currency.Id == network.NativeTokenId;
+ var account = new Account();
+
+ var isNative = currency.Symbol.ToUpper() == network.NativeToken!.Symbol.ToUpper();
+
var node = network.Nodes.FirstOrDefault();
if (node is null)
{
- throw new ArgumentNullException(nameof(node), $"Node is not configured on {network.Name} network");
+ throw new($"Node is not configured on {network.Name} network");
}
+ var rpcClient = ClientFactory.GetClient(node.Url);
+
var htlcContractAddress = isNative
? network.HTLCNativeContractAddress
: network.HTLCTokenContractAddress;
- var rpcClient = ClientFactory.GetClient(node.Url);
-
var builder = new TransactionBuilder()
.SetFeePayer(new PublicKey(solverAccount));
@@ -67,50 +71,49 @@ await GetOrCreateAssociatedTokenAccount(
new HTLCLockRequest
{
Hashlock = request.Hashlock.HexToByteArray(),
- Id = request.Id.HexToByteArray(),
+ Id = request.CommitId.HexToByteArray(),
SignerPublicKey = new PublicKey(solverAccount),
ReceiverPublicKey = new PublicKey(request.Receiver),
- Amount = BigInteger.Parse(request.Amount),
+ Amount = request.Amount,
Timelock = new BigInteger(request.Timelock),
- SourceAsset = currency.Asset,
+ SourceAsset = currency.Symbol,
DestinationNetwork = request.DestinationNetwork,
SourceAddress = request.DestinationAddress,
DestinationAsset = request.DestinationAsset,
- SourceTokenPublicKey = new PublicKey(currency.TokenContract),
- Reward = BigInteger.Parse(request.Reward),
+ SourceTokenPublicKey = new PublicKey(currency.Contract),
+ Reward = request.Reward,
RewardTimelock = new BigInteger(request.RewardTimelock),
});
- var latestBlockHashResponse = await rpcClient.GetLatestBlockHashAsync();
+ var latestBlockResult = await rpcClient.GetLatestBlockHashAsync();
- if (!latestBlockHashResponse.WasSuccessful)
+ if (!latestBlockResult.WasSuccessful)
{
- throw new Exception($"Failed to get latest block hash, error: {latestBlockHashResponse.RawRpcResponse}");
+ throw new ($"Failed to get last valid block");
}
- builder.SetRecentBlockHash(latestBlockHashResponse.Result.Value.Blockhash);
+ builder.SetRecentBlockHash(latestBlockResult.Result.Value.Blockhash);
var serializedTx = Convert.ToBase64String(builder.Serialize());
- var response = new PrepareTransactionResponse
+
+ var response = new PrepareTransactionDto
{
Data = serializedTx,
ToAddress = htlcContractAddress,
- Asset = network.NativeToken.Asset,
- AmountInWei = "0",
- CallDataAmountInWei = request.Amount,
- CallDataAsset = currency.Asset,
+ Asset = network.NativeToken.Symbol,
+ Amount = 0
};
if (isNative)
{
- response.AmountInWei = request.Amount;
+ response.Amount = request.Amount;
}
return response;
}
- public static async Task BuildHTLCRedeemTransactionAsync(
- Network network,
+ public static async Task BuildHTLCRedeemTransactionAsync(
+ DetailedNetworkDto network,
string solverAccount,
string args)
{
@@ -131,7 +134,7 @@ public static async Task BuildHTLCRedeemTransactionA
throw new ArgumentNullException(nameof(request.SenderAddress), "Sender address is required");
}
- var currency = network.Tokens.SingleOrDefault(x => x.Asset.ToUpper() == request.Asset.ToUpper());
+ var currency = network.Tokens.SingleOrDefault(x => x.Symbol.ToUpper() == request.Asset.ToUpper());
if (currency is null)
{
@@ -139,37 +142,38 @@ public static async Task BuildHTLCRedeemTransactionA
$"Currency {request.Asset} for {network.Name} is missing");
}
- var isNative = currency.Id == network.NativeTokenId;
+ var isNative = currency.Symbol.ToUpper() == network.NativeToken!.Symbol.ToUpper();
+
var node = network.Nodes.FirstOrDefault();
if (node is null)
{
- throw new ArgumentNullException(nameof(node), $"Node is not configured on {network.Name} network");
+ throw new($"Node is not configured on {network.Name} network");
}
+ var rpcClient = ClientFactory.GetClient(node.Url);
+
var htlcContractAddress = isNative
? network.HTLCNativeContractAddress
: network.HTLCTokenContractAddress;
- var rpcClient = ClientFactory.GetClient(node.Url);
-
var builder = new TransactionBuilder()
.SetFeePayer(new PublicKey(solverAccount));
- await GetOrCreateAssociatedTokenAccount(
+ await GetOrCreateAssociatedTokenAccount(
rpcClient,
builder,
currency,
- new PublicKey(request.DestinationAddress),
+ new PublicKey(solverAccount),
new PublicKey(solverAccount));
builder.SetRedeemTransactionInstruction(
new PublicKey(htlcContractAddress),
new HTLCRedeemRequest
{
- Id = request.Id.HexToByteArray(),
+ Id = request.CommitId.HexToByteArray(),
Secret = BigInteger.Parse(request.Secret).ToHexBigInteger().HexValue.HexToByteArray(),
- SourceTokenPublicKey = new PublicKey(currency.TokenContract),
+ SourceTokenPublicKey = new PublicKey(currency.Contract),
SignerPublicKey = new PublicKey(solverAccount),
ReceiverPublicKey = new PublicKey(request.DestinationAddress),
SenderPublicKey = new PublicKey(request.SenderAddress),
@@ -178,31 +182,29 @@ await GetOrCreateAssociatedTokenAccount(
new PublicKey(request.SenderAddress),
});
- var latestBlockHashResponse = await rpcClient.GetLatestBlockHashAsync();
+ var latestBlockResult = await rpcClient.GetLatestBlockHashAsync();
- if (!latestBlockHashResponse.WasSuccessful)
+ if (!latestBlockResult.WasSuccessful)
{
- throw new Exception($"Failed to get latest block hash, error: {latestBlockHashResponse.RawRpcResponse}");
+ throw new($"Failed to get last valid block");
}
- builder.SetRecentBlockHash(latestBlockHashResponse.Result.Value.Blockhash);
+ builder.SetRecentBlockHash(latestBlockResult.Result.Value.Blockhash);
var serializedTx = Convert.ToBase64String(builder.Serialize());
- var response = new PrepareTransactionResponse
+ var response = new PrepareTransactionDto
{
Data = serializedTx,
ToAddress = htlcContractAddress,
- Asset = network.NativeToken.Asset,
- AmountInWei = "0",
- CallDataAsset = currency.Asset,
- CallDataAmountInWei = "0",
+ Asset = network.NativeToken.Symbol,
+ Amount = 0,
};
return response;
}
- public static async Task BuildHTLCRefundTransactionAsync(
- Network network,
+ public static async Task BuildHTLCRefundTransactionAsync(
+ DetailedNetworkDto network,
string solverAccount,
string args)
{
@@ -218,14 +220,15 @@ public static async Task BuildHTLCRefundTransactionA
throw new ArgumentNullException(nameof(request.DestinationAddress), "Receiver address is required");
}
- var currency = network.Tokens.SingleOrDefault(x => x.Asset.ToUpper() == request.Asset.ToUpper());
+ var currency = network.Tokens.SingleOrDefault(x => x.Symbol.ToUpper() == request.Asset.ToUpper());
if (currency is null)
{
throw new ArgumentNullException(nameof(currency), "Currency {request.Asset} for {network.Name} is missing");
}
- var isNative = currency.Id == network.NativeTokenId;
+ var isNative = currency.Symbol.ToUpper() == network.NativeToken!.Symbol.ToUpper();
+
var node = network.Nodes.FirstOrDefault();
if (node is null)
@@ -253,8 +256,8 @@ await GetOrCreateAssociatedTokenAccount(
new PublicKey(htlcContractAddress),
new HTLCRefundRequest
{
- Id = request.Id.HexToByteArray(),
- SourceTokenPublicKey = new PublicKey(currency.TokenContract),
+ Id = request.CommitId.HexToByteArray(),
+ SourceTokenPublicKey = new PublicKey(currency.Contract),
SignerPublicKey = new PublicKey(solverAccount),
ReceiverPublicKey = new PublicKey(request.DestinationAddress)
});
@@ -269,22 +272,21 @@ await GetOrCreateAssociatedTokenAccount(
builder.SetRecentBlockHash(latestBlockHashResponse.Result.Value.Blockhash);
var serializedTx = Convert.ToBase64String(builder.Serialize());
- var response = new PrepareTransactionResponse
+ var response = new PrepareTransactionDto
{
Data = serializedTx,
ToAddress = htlcContractAddress,
- Asset = network.NativeToken.Asset,
- AmountInWei = "0",
- CallDataAsset = currency.Asset,
- CallDataAmountInWei = "0",
+ Asset = network.NativeToken.Symbol,
+ Amount = 0,
};
return response;
}
- public static async Task BuildTransferTransactionAsync(Network network, string args)
+ public static async Task BuildTransferTransactionAsync(
+ DetailedNetworkDto network,
+ string args)
{
-
var request = JsonSerializer.Deserialize(args);
if (request is null)
@@ -299,7 +301,7 @@ public static async Task BuildTransferTransactionAsy
throw new ArgumentNullException(nameof(node), $"Node is not configured on {network.Name} network");
}
- var currency = network.Tokens.SingleOrDefault(x => x.Asset == request.Asset);
+ var currency = network.Tokens.SingleOrDefault(x => x.Symbol == request.Asset);
if (currency is null)
{
@@ -335,28 +337,21 @@ public static async Task BuildTransferTransactionAsy
builder.SetRecentBlockHash(latestBlockHashResponse.Result.Value.Blockhash);
- if (request.Memo != null)
- {
- builder.AddInstruction(MemoProgram.NewMemo(publicKeyFromAddress, request.Memo));
- }
-
var serializedTx = Convert.ToBase64String(builder.Serialize());
- var response = new PrepareTransactionResponse
+ var response = new PrepareTransactionDto
{
Data = serializedTx,
ToAddress = request.ToAddress,
Asset = request.Asset,
- AmountInWei = amountInBaseUnits.ToString(),
- CallDataAmountInWei = amountInBaseUnits.ToString(),
- CallDataAsset = currency.Asset,
+ Amount = request.Amount,
};
return response;
}
- public static async Task BuildHTLCAddlockSigTransactionAsync(
- Network network,
+ public static async Task BuildHTLCAddlockSigTransactionAsync(
+ DetailedNetworkDto network,
string solverAccount,
string args)
{
@@ -377,7 +372,7 @@ public static async Task BuildHTLCAddlockSigTransact
throw new ArgumentNullException(nameof(request.SignerAddress), "Sender address is required");
}
- var currency = network.Tokens.SingleOrDefault(x => x.Asset.ToUpper() == request.Asset.ToUpper());
+ var currency = network.Tokens.SingleOrDefault(x => x.Symbol.ToUpper() == request.Asset.ToUpper());
if (currency is null)
{
@@ -385,7 +380,7 @@ public static async Task BuildHTLCAddlockSigTransact
$"Currency {request.Asset} for {network.Name} is missing");
}
- var isNative = currency.Id == network.NativeTokenId;
+ var isNative = currency.Symbol.ToUpper() == network.NativeToken!.Symbol.ToUpper();
var node = network.Nodes.FirstOrDefault();
if (node is null)
@@ -408,7 +403,7 @@ public static async Task BuildHTLCAddlockSigTransact
{
AddLockSigMessageRequest = new()
{
- Id = request.Id.HexToByteArray(),
+ Id = request.CommitId.HexToByteArray(),
Hashlock = request.Hashlock.HexToByteArray(),
Timelock = request.Timelock,
SignerPublicKey = new PublicKey(request.SignerAddress),
@@ -427,14 +422,12 @@ public static async Task BuildHTLCAddlockSigTransact
builder.SetRecentBlockHash(latestBlockHashResponse.Result.Value.Blockhash);
var serializedTx = Convert.ToBase64String(builder.Serialize());
- var response = new PrepareTransactionResponse
+ var response = new PrepareTransactionDto
{
Data = serializedTx,
ToAddress = htlcContractAddress,
- Asset = network.NativeToken.Asset,
- AmountInWei = "0",
- CallDataAsset = currency.Asset,
- CallDataAmountInWei = "0",
+ Asset = network.NativeToken.Symbol,
+ Amount = 0,
};
return response;
@@ -442,7 +435,7 @@ public static async Task BuildHTLCAddlockSigTransact
public static async Task CreateTransactionInstructionAsync(
this TransactionBuilder builder,
- Token currency,
+ TokenDto currency,
IRpcClient rpcClient,
PublicKey publicKeyFromAddress,
string toAddress,
@@ -451,7 +444,7 @@ public static async Task CreateTransactionInstructionAsync(
{
var publicKeyToAddress = new PublicKey(toAddress);
- if (string.IsNullOrEmpty(currency.TokenContract))
+ if (string.IsNullOrEmpty(currency.Contract))
{
//SolanaTransactionProcessorWorkflow transfer
builder.AddInstruction(
@@ -466,9 +459,9 @@ public static async Task CreateTransactionInstructionAsync(
{
//SPL token transfer
var token = new TokenDef(
- currency.TokenContract,
- currency.Asset,
- currency.Asset,
+ currency.Contract,
+ currency.Symbol,
+ currency.Symbol,
currency.Decimals);
var tokenDefs = new TokenMintResolver();
@@ -483,18 +476,18 @@ public static async Task CreateTransactionInstructionAsync(
{
destination = destinationWallet.JitCreateAssociatedTokenAccount(
builder,
- currency.TokenContract,
+ currency.Contract,
publicKeyFromAddress);
}
else
{
destination = AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(publicKeyToAddress,
- new PublicKey(currency.TokenContract));
+ new PublicKey(currency.Contract));
}
var source = sourceWallet.JitCreateAssociatedTokenAccount(
builder,
- currency.TokenContract,
+ currency.Contract,
publicKeyFromAddress);
builder.AddInstruction(
@@ -513,19 +506,19 @@ public static async Task CreateTransactionInstructionAsync(
return builder;
}
- public async static Task GetOrCreateAssociatedTokenAccount(
+ private static async Task GetOrCreateAssociatedTokenAccount(
IRpcClient rpcClient,
TransactionBuilder builder,
- Token currency,
+ TokenDto currency,
PublicKey ownerPublicKey,
PublicKey feePayerPublicKey)
{
try
{
var token = new TokenDef(
- currency.TokenContract,
- currency.Asset,
- currency.Asset,
+ currency.Contract,
+ currency.Symbol,
+ currency.Symbol,
currency.Decimals);
var tokenDefs = new TokenMintResolver();
@@ -535,7 +528,7 @@ public async static Task GetOrCreateAssociatedTokenAccount(
wallet.JitCreateAssociatedTokenAccount(
builder,
- currency.TokenContract,
+ currency.Contract,
feePayerPublicKey);
}
catch (TokenWalletException ex)
diff --git a/csharp/src/Workflow.Solana/Models/SolanaComposeTransactionRequest.cs b/csharp/src/Workflow.Solana/Models/SolanaComposeTransactionRequest.cs
index cbd80ac0..c1eb1dd3 100644
--- a/csharp/src/Workflow.Solana/Models/SolanaComposeTransactionRequest.cs
+++ b/csharp/src/Workflow.Solana/Models/SolanaComposeTransactionRequest.cs
@@ -1,14 +1,12 @@
-using Train.Solver.Blockchain.Abstractions.Models;
+using Train.Solver.Infrastructure.Abstractions.Models;
namespace Train.Solver.Blockchain.Solana.Models;
public class SolanaComposeTransactionRequest
{
- public required Fee Fee { get; set; }
+ public required DetailedNetworkDto Network { get; set; } = null!;
public required string FromAddress { get; set; } = null!;
public required string CallData { get; set; } = null!;
-
- public required string LastValidBlockHash { get; set; } = null!;
}
diff --git a/csharp/src/Workflow.Solana/Models/SolanaComposeTransactionResponse.cs b/csharp/src/Workflow.Solana/Models/SolanaComposeTransactionResponse.cs
new file mode 100644
index 00000000..5d9f6990
--- /dev/null
+++ b/csharp/src/Workflow.Solana/Models/SolanaComposeTransactionResponse.cs
@@ -0,0 +1,8 @@
+namespace Train.Solver.Workflow.Solana.Models;
+
+public class SolanaComposeTransactionResponse
+{
+ public required string LastValidBlockHeight { get; set; } = null!;
+
+ public required string RawTx { get; set; } = null!;
+}
diff --git a/csharp/src/Workflow.Solana/Models/SolanaGetReceiptRequest.cs b/csharp/src/Workflow.Solana/Models/SolanaGetReceiptRequest.cs
new file mode 100644
index 00000000..3b873893
--- /dev/null
+++ b/csharp/src/Workflow.Solana/Models/SolanaGetReceiptRequest.cs
@@ -0,0 +1,12 @@
+using Train.Solver.Infrastructure.Abstractions.Models;
+
+namespace Train.Solver.Workflow.Solana.Models;
+
+public class SolanaGetReceiptRequest
+{
+ public required DetailedNetworkDto Network { get; set; } = null!;
+
+ public required string TxHash { get; set; } = null!;
+
+ public required string TransactionBlockHeight { get; set; } = null!;
+}
diff --git a/csharp/src/Workflow.Solana/Models/SolanaGetTransactionRequest.cs b/csharp/src/Workflow.Solana/Models/SolanaGetTransactionRequest.cs
deleted file mode 100644
index 67e00f98..00000000
--- a/csharp/src/Workflow.Solana/Models/SolanaGetTransactionRequest.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using Train.Solver.Blockchain.Abstractions.Models;
-
-namespace Train.Solver.Blockchain.Solana.Models;
-
-public class SolanaGetTransactionRequest : GetTransactionRequest
-{
- public required string FromAddress { get; set; } = null!;
-}
diff --git a/csharp/src/Workflow.Solana/Models/SolanaPublishTransactionRequest.cs b/csharp/src/Workflow.Solana/Models/SolanaPublishTransactionRequest.cs
index 0699597c..e98fc721 100644
--- a/csharp/src/Workflow.Solana/Models/SolanaPublishTransactionRequest.cs
+++ b/csharp/src/Workflow.Solana/Models/SolanaPublishTransactionRequest.cs
@@ -1,8 +1,8 @@
-using Train.Solver.Blockchain.Abstractions.Models;
+using Train.Solver.Workflow.Abstractions.Models;
namespace Train.Solver.Blockchain.Solana.Models;
public class SolanaPublishTransactionRequest : BaseRequest
{
- public byte[] RawTx { get; set; } = null!;
+ public string RawTx { get; set; } = null!;
}
diff --git a/csharp/src/Workflow.Solana/Models/SolanaSignTransactionRequest.cs b/csharp/src/Workflow.Solana/Models/SolanaSignTransactionRequest.cs
new file mode 100644
index 00000000..84842be8
--- /dev/null
+++ b/csharp/src/Workflow.Solana/Models/SolanaSignTransactionRequest.cs
@@ -0,0 +1,12 @@
+using Train.Solver.Workflow.Abstractions.Models;
+
+namespace Train.Solver.Workflow.Solana.Models;
+
+public class SolanaSignTransactionRequest : BaseRequest
+{
+ public required string SignerAgentUrl { get; set; }
+
+ public required string UnsignRawTransaction { get; set; } = null!;
+
+ public required string FromAddress { get; set; } = null!;
+}
diff --git a/csharp/src/Workflow.Solana/Program.cs b/csharp/src/Workflow.Solana/Program.cs
index 76aa76bc..f595ae5c 100644
--- a/csharp/src/Workflow.Solana/Program.cs
+++ b/csharp/src/Workflow.Solana/Program.cs
@@ -1,8 +1,7 @@
-using Train.Solver.Blockchain.Solana.Extensions;
-using Train.Solver.Infrastructure.Extensions;
-using Train.Solver.Data.Npgsql.Extensions;
+using Train.Solver.Data.Npgsql.Extensions;
using Train.Solver.Infrastructure.Logging.OpenTelemetry;
-using Train.Solver.Infrastructure.Secret.HashicorpKeyVault;
+using Train.Solver.Infrastructure.DependencyInjection;
+using Train.Solver.Workflow.Solana.Extensions;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(builder =>
@@ -17,7 +16,6 @@
.AddTrainSolver(hostContext.Configuration)
.WithOpenTelemetryLogging("Solana Runner")
.WithNpgsqlRepositories()
- .WithHashicorpKeyVault()
.WithSolanaWorkflows();
})
.Build();
diff --git a/csharp/src/Workflow.Solana/Workflows.Solana.csproj b/csharp/src/Workflow.Solana/Workflows.Solana.csproj
index 4cb028e4..46a42618 100644
--- a/csharp/src/Workflow.Solana/Workflows.Solana.csproj
+++ b/csharp/src/Workflow.Solana/Workflows.Solana.csproj
@@ -12,10 +12,11 @@
-
+
+
+
-
diff --git a/csharp/src/Workflow.Solana/Workflows/SolanaTransactionProcessor.cs b/csharp/src/Workflow.Solana/Workflows/SolanaTransactionProcessor.cs
index 467abe4c..7b447220 100644
--- a/csharp/src/Workflow.Solana/Workflows/SolanaTransactionProcessor.cs
+++ b/csharp/src/Workflow.Solana/Workflows/SolanaTransactionProcessor.cs
@@ -1,14 +1,15 @@
using Temporalio.Exceptions;
using Temporalio.Workflows;
-using Train.Solver.Blockchain.Abstractions.Models;
-using Train.Solver.Blockchain.Common.Extensions;
-using Train.Solver.Blockchain.Common.Helpers;
-using Train.Solver.Blockchain.Solana.Activities;
using Train.Solver.Infrastructure.Abstractions.Exceptions;
using Train.Solver.Blockchain.Solana.Models;
using static Temporalio.Workflows.Workflow;
+using Train.Solver.Workflow.Solana.Activities;
+using Train.Solver.Workflow.Abstractions.Models;
+using Train.Solver.Workflow.Common.Helpers;
+using Train.Solver.Workflow.Common.Extensions;
+using Train.Solver.Workflow.Solana.Models;
-namespace Train.Solver.Blockchain.Solana.Workflows;
+namespace Train.Solver.Workflow.Solana.Workflows;
[Workflow]
public class SolanaTransactionProcessor
@@ -18,55 +19,31 @@ public async Task RunAsync(TransactionRequest request, Tran
{
var preparedTransaction = await ExecuteActivityAsync(
(ISolanaBlockchainActivities x) => x.BuildTransactionAsync(new TransactionBuilderRequest
- {
- NetworkName = request.NetworkName,
- Args = request.PrepareArgs,
- Type = request.Type
- }
- ),
- TemporalHelper.DefaultActivityOptions(request.NetworkType));
-
- if (context.Fee == null)
- {
- var fee = await ExecuteActivityAsync(
- (ISolanaBlockchainActivities x) => x.EstimateFeeAsync(new EstimateFeeRequest
- {
- NetworkName = request.NetworkName,
- FromAddress = request.FromAddress!,
- ToAddress = preparedTransaction.ToAddress,
- Asset = preparedTransaction.Asset,
- Amount = preparedTransaction.AmountInWei,
- CallData = preparedTransaction.Data,
- }
- ),
- TemporalHelper.DefaultActivityOptions(request.NetworkType));
-
- if (fee is null)
{
- throw new("Unable to pay fees");
- }
-
- context.Fee = fee;
- }
-
- var lastValidBLockHash = await ExecuteActivityAsync(
- (ISolanaBlockchainActivities x) => x.GetNextNonceAsync(
- new NextNonceRequest()
- {
- Address = request.NetworkName,
- NetworkName = request.NetworkName,
- }),
- TemporalHelper.DefaultActivityOptions(request.NetworkType));
+ Network = request.Network,
+ PrepareArgs = request.PrepareArgs,
+ Type = request.Type
+ }),
+ TemporalHelper.DefaultActivityOptions(request.Network.Type));
- var rawTx = await ExecuteActivityAsync(
+ var composedTransaction = await ExecuteActivityAsync(
(ISolanaBlockchainActivities x) => x.ComposeSolanaTranscationAsync(new SolanaComposeTransactionRequest()
- {
- Fee = context.Fee,
- FromAddress = request.FromAddress,
- CallData = preparedTransaction.Data,
- LastValidBlockHash = lastValidBLockHash,
- }),
- TemporalHelper.DefaultActivityOptions(request.NetworkType));
+ {
+ Network = request.Network,
+ FromAddress = request.FromAddress,
+ CallData = preparedTransaction.Data!
+ }),
+ TemporalHelper.DefaultActivityOptions(request.Network.Type));
+
+ var signedTx = await ExecuteActivityAsync(
+ (ISolanaBlockchainActivities x) => x.SignTransactionAsync(new SolanaSignTransactionRequest()
+ {
+ Network = request.Network,
+ UnsignRawTransaction = composedTransaction.RawTx,
+ FromAddress = request.FromAddress,
+ SignerAgentUrl = request.SignerAgentUrl
+ }),
+ TemporalHelper.DefaultActivityOptions(request.Network.Type));
TransactionResponse confirmedTransaction;
@@ -76,11 +53,11 @@ public async Task RunAsync(TransactionRequest request, Tran
await ExecuteActivityAsync(
(ISolanaBlockchainActivities x) => x.SimulateTransactionAsync(
new SolanaPublishTransactionRequest()
- {
- RawTx = rawTx,
- NetworkName = request.NetworkName
- }),
- TemporalHelper.DefaultActivityOptions(request.NetworkType));
+ {
+ RawTx = composedTransaction.RawTx,
+ Network= request.Network
+ }),
+ TemporalHelper.DefaultActivityOptions(request.Network.Type));
//Send transaction
@@ -88,24 +65,21 @@ await ExecuteActivityAsync(
(ISolanaBlockchainActivities x) => x.PublishTransactionAsync(
new SolanaPublishTransactionRequest()
{
- RawTx = rawTx,
- NetworkName = request.NetworkName
+ RawTx = composedTransaction.RawTx,
+ Network = request.Network
}),
- TemporalHelper.DefaultActivityOptions(request.NetworkType));
+ TemporalHelper.DefaultActivityOptions(request.Network.Type));
//Wait for transaction receipt
confirmedTransaction = await ExecuteActivityAsync(
- (ISolanaBlockchainActivities x) => x.GetTransactionAsync(
- new GetTransactionRequest()
- {
- NetworkName = request.NetworkName,
- TransactionHash = transactionId
- }),
- TemporalHelper.DefaultActivityOptions(request.NetworkType));
-
- confirmedTransaction.Asset = preparedTransaction.CallDataAsset;
- confirmedTransaction.Amount = preparedTransaction.CallDataAmountInWei;
+ (ISolanaBlockchainActivities x) => x.GetTransactionAsync(new SolanaGetReceiptRequest
+ {
+ TxHash = transactionId,
+ Network = request.Network,
+ TransactionBlockHeight = composedTransaction.LastValidBlockHeight
+ }),
+ TemporalHelper.DefaultActivityOptions(request.Network.Type));
}
catch (ActivityFailureException ex)
{
diff --git a/treasury/package-lock.json b/treasury/package-lock.json
index 46b71242..43188f7c 100644
--- a/treasury/package-lock.json
+++ b/treasury/package-lock.json
@@ -17,6 +17,8 @@
"@nestjs/config": "^4.0.2",
"@nestjs/core": "11.1.2",
"@nestjs/platform-express": "^11.1.6",
+ "@solana/web3.js": "^1.98.4",
+ "bs58": "^6.0.0",
"class-transformer": "0.5.1",
"class-validator": "0.14.2",
"ethers": "^6.14.3",
@@ -1136,6 +1138,15 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
@@ -5389,6 +5400,139 @@
"@sinonjs/commons": "^3.0.0"
}
},
+ "node_modules/@solana/buffer-layout": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz",
+ "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "~6.0.3"
+ },
+ "engines": {
+ "node": ">=5.10"
+ }
+ },
+ "node_modules/@solana/codecs-core": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz",
+ "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==",
+ "license": "MIT",
+ "dependencies": {
+ "@solana/errors": "2.3.0"
+ },
+ "engines": {
+ "node": ">=20.18.0"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.3.3"
+ }
+ },
+ "node_modules/@solana/codecs-numbers": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz",
+ "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==",
+ "license": "MIT",
+ "dependencies": {
+ "@solana/codecs-core": "2.3.0",
+ "@solana/errors": "2.3.0"
+ },
+ "engines": {
+ "node": ">=20.18.0"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.3.3"
+ }
+ },
+ "node_modules/@solana/errors": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz",
+ "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^5.4.1",
+ "commander": "^14.0.0"
+ },
+ "bin": {
+ "errors": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": ">=20.18.0"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.3.3"
+ }
+ },
+ "node_modules/@solana/errors/node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/@solana/errors/node_modules/commander": {
+ "version": "14.0.2",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz",
+ "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@solana/web3.js": {
+ "version": "1.98.4",
+ "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz",
+ "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.25.0",
+ "@noble/curves": "^1.4.2",
+ "@noble/hashes": "^1.4.0",
+ "@solana/buffer-layout": "^4.0.1",
+ "@solana/codecs-numbers": "^2.1.0",
+ "agentkeepalive": "^4.5.0",
+ "bn.js": "^5.2.1",
+ "borsh": "^0.7.0",
+ "bs58": "^4.0.1",
+ "buffer": "6.0.3",
+ "fast-stable-stringify": "^1.0.0",
+ "jayson": "^4.1.1",
+ "node-fetch": "^2.7.0",
+ "rpc-websockets": "^9.0.2",
+ "superstruct": "^2.0.2"
+ }
+ },
+ "node_modules/@solana/web3.js/node_modules/base-x": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz",
+ "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/@solana/web3.js/node_modules/bs58": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
+ "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
+ "license": "MIT",
+ "dependencies": {
+ "base-x": "^3.0.2"
+ }
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.17",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
+ "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
"node_modules/@tokenizer/inflate": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
@@ -5525,7 +5669,6 @@
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
@@ -5800,12 +5943,27 @@
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
"license": "MIT"
},
+ "node_modules/@types/uuid": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
+ "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
+ "license": "MIT"
+ },
"node_modules/@types/validator": {
"version": "13.15.3",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.3.tgz",
"integrity": "sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==",
"license": "MIT"
},
+ "node_modules/@types/ws": {
+ "version": "7.4.7",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz",
+ "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/yargs": {
"version": "17.0.33",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
@@ -6550,6 +6708,18 @@
"node": ">= 14"
}
},
+ "node_modules/agentkeepalive": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
+ "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "humanize-ms": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -6997,6 +7167,12 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
+ "node_modules/base-x": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz",
+ "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==",
+ "license": "MIT"
+ },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -7153,6 +7329,35 @@
"node": ">=0.10.0"
}
},
+ "node_modules/borsh": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz",
+ "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bn.js": "^5.2.0",
+ "bs58": "^4.0.0",
+ "text-encoding-utf-8": "^1.0.2"
+ }
+ },
+ "node_modules/borsh/node_modules/base-x": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz",
+ "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/borsh/node_modules/bs58": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
+ "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
+ "license": "MIT",
+ "dependencies": {
+ "base-x": "^3.0.2"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -7223,6 +7428,15 @@
"node": ">= 6"
}
},
+ "node_modules/bs58": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz",
+ "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==",
+ "license": "MIT",
+ "dependencies": {
+ "base-x": "^5.0.0"
+ }
+ },
"node_modules/bser": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
@@ -7269,6 +7483,20 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
+ "node_modules/bufferutil": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz",
+ "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "node-gyp-build": "^4.3.0"
+ },
+ "engines": {
+ "node": ">=6.14.2"
+ }
+ },
"node_modules/bundle-require": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz",
@@ -8355,6 +8583,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/delay": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz",
+ "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -8664,6 +8904,21 @@
"node": ">= 0.4"
}
},
+ "node_modules/es6-promise": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+ "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
+ "license": "MIT"
+ },
+ "node_modules/es6-promisify": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+ "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es6-promise": "^4.0.3"
+ }
+ },
"node_modules/esbuild": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz",
@@ -9190,6 +9445,14 @@
],
"license": "MIT"
},
+ "node_modules/eyes": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
+ "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==",
+ "engines": {
+ "node": "> 0.1.90"
+ }
+ },
"node_modules/fast-copy": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz",
@@ -9260,6 +9523,12 @@
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
"license": "MIT"
},
+ "node_modules/fast-stable-stringify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz",
+ "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==",
+ "license": "MIT"
+ },
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
@@ -10498,6 +10767,15 @@
"node": ">=14.18.0"
}
},
+ "node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.0.0"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
@@ -10855,6 +11133,15 @@
"whatwg-fetch": "^3.4.1"
}
},
+ "node_modules/isomorphic-ws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
+ "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "ws": "*"
+ }
+ },
"node_modules/isows": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz",
@@ -11006,6 +11293,65 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/jayson": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.2.0.tgz",
+ "integrity": "sha512-VfJ9t1YLwacIubLhONk0KFeosUBwstRWQ0IRT1KDjEjnVnSOVHC3uwugyV7L0c7R9lpVyrUGT2XWiBA1UTtpyg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "^3.4.33",
+ "@types/node": "^12.12.54",
+ "@types/ws": "^7.4.4",
+ "commander": "^2.20.3",
+ "delay": "^5.0.0",
+ "es6-promisify": "^5.0.0",
+ "eyes": "^0.1.8",
+ "isomorphic-ws": "^4.0.1",
+ "json-stringify-safe": "^5.0.1",
+ "stream-json": "^1.9.1",
+ "uuid": "^8.3.2",
+ "ws": "^7.5.10"
+ },
+ "bin": {
+ "jayson": "bin/jayson.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jayson/node_modules/@types/node": {
+ "version": "12.20.55",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
+ "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
+ "license": "MIT"
+ },
+ "node_modules/jayson/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "license": "MIT"
+ },
+ "node_modules/jayson/node_modules/ws": {
+ "version": "7.5.10",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
+ "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
@@ -11977,9 +12323,9 @@
}
},
"node_modules/koa": {
- "version": "2.16.2",
- "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.2.tgz",
- "integrity": "sha512-+CCssgnrWKx9aI3OeZwroa/ckG4JICxvIFnSiOUyl2Uv+UTI+xIw0FfFrWS7cQFpoePpr9o8csss7KzsTzNL8Q==",
+ "version": "2.16.3",
+ "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.3.tgz",
+ "integrity": "sha512-zPPuIt+ku1iCpFBRwseMcPYQ1cJL8l60rSmKeOuGfOXyE6YnTBmf2aEFNL2HQGrD0cPcLO/t+v9RTgC+fwEh/g==",
"license": "MIT",
"dependencies": {
"accepts": "^1.3.5",
@@ -14423,6 +14769,44 @@
"node": ">= 18"
}
},
+ "node_modules/rpc-websockets": {
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.3.1.tgz",
+ "integrity": "sha512-bY6a+i/lEtBJ/mUxwsCTgevoV1P0foXTVA7UoThzaIWbM+3NDqorf8NBWs5DmqKTFeA1IoNzgvkWjFCPgnzUiQ==",
+ "license": "LGPL-3.0-only",
+ "dependencies": {
+ "@swc/helpers": "^0.5.11",
+ "@types/uuid": "^8.3.4",
+ "@types/ws": "^8.2.2",
+ "buffer": "^6.0.3",
+ "eventemitter3": "^5.0.1",
+ "uuid": "^8.3.2",
+ "ws": "^8.5.0"
+ },
+ "funding": {
+ "type": "paypal",
+ "url": "https://paypal.me/kozjak"
+ },
+ "optionalDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ }
+ },
+ "node_modules/rpc-websockets/node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/rpc-websockets/node_modules/eventemitter3": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
+ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
+ "license": "MIT"
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -14966,6 +15350,12 @@
"license": "MIT",
"peer": true
},
+ "node_modules/stream-chain": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz",
+ "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/stream-events": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz",
@@ -14975,6 +15365,15 @@
"stubs": "^3.0.0"
}
},
+ "node_modules/stream-json": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz",
+ "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "stream-chain": "^2.2.5"
+ }
+ },
"node_modules/stream-length": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz",
@@ -15261,6 +15660,15 @@
"node": ">=4.0.0"
}
},
+ "node_modules/superstruct": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz",
+ "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/supertest": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.1.tgz",
@@ -15667,6 +16075,11 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/text-encoding-utf-8": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz",
+ "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg=="
+ },
"node_modules/thread-stream": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
@@ -16155,7 +16568,6 @@
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
- "devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -16320,6 +16732,20 @@
"requires-port": "^1.0.0"
}
},
+ "node_modules/utf-8-validate": {
+ "version": "5.0.10",
+ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
+ "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "node-gyp-build": "^4.3.0"
+ },
+ "engines": {
+ "node": ">=6.14.2"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -16358,9 +16784,9 @@
}
},
"node_modules/validator": {
- "version": "13.15.15",
- "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
- "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==",
+ "version": "13.15.23",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz",
+ "integrity": "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
@@ -16474,9 +16900,9 @@
}
},
"node_modules/vite": {
- "version": "6.3.6",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
- "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
+ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"license": "MIT",
"peer": true,
"dependencies": {
diff --git a/treasury/package.json b/treasury/package.json
index e287548e..310e286e 100644
--- a/treasury/package.json
+++ b/treasury/package.json
@@ -27,6 +27,8 @@
"@nestjs/config": "^4.0.2",
"@nestjs/core": "11.1.2",
"@nestjs/platform-express": "^11.1.6",
+ "@solana/web3.js": "^1.98.4",
+ "bs58": "^6.0.0",
"class-transformer": "0.5.1",
"class-validator": "0.14.2",
"ethers": "^6.14.3",
diff --git a/treasury/src/treasury/shared/networks.types.ts b/treasury/src/treasury/shared/networks.types.ts
index 796f5379..0c885369 100644
--- a/treasury/src/treasury/shared/networks.types.ts
+++ b/treasury/src/treasury/shared/networks.types.ts
@@ -1 +1 @@
-export type Network = 'evm' | 'starknet' | 'solana' | 'fuel' | 'aztec';
\ No newline at end of file
+export type Network = 'evm' | 'starknet' | 'solana' | 'fuel' | 'aztec' | 'solana';
diff --git a/treasury/src/treasury/solana/solana.service.ts b/treasury/src/treasury/solana/solana.service.ts
new file mode 100644
index 00000000..0b925ef1
--- /dev/null
+++ b/treasury/src/treasury/solana/solana.service.ts
@@ -0,0 +1,38 @@
+import { Injectable } from '@nestjs/common';
+import { TreasuryService } from '../../app/interfaces/treasury.interface';
+import { Network } from '../shared/networks.types';
+import { BaseSignRequest, BaseSignResponse, GenerateResponse } from '../../app/dto/base.dto';
+import { PrivateKeyService } from '../../kv/vault.service';
+import { Keypair, Transaction } from "@solana/web3.js";
+import bs58 from "bs58";
+
+@Injectable()
+export class SolanaTreasuryService extends TreasuryService {
+
+ readonly network: Network = 'solana';
+
+ constructor(privateKeyService: PrivateKeyService) {
+ super(privateKeyService);
+ }
+
+ async sign(request: BaseSignRequest): Promise {
+
+ const privateKey = await this.privateKeyService.getAsync(request.address);
+
+ const account = Keypair.fromSecretKey(bs58.decode(privateKey));
+
+ let transaction: Transaction = Transaction.from(Buffer.from(request.unsignedTxn, 'base64'));
+
+ transaction.sign(account);
+
+ return { signedTxn: transaction.serialize().toString('base64') };
+ }
+
+ async generate(): Promise {
+ const keypair = Keypair.generate();
+
+ await this.privateKeyService.setAsync(keypair.publicKey.toBase58(), keypair.secretKey.toString());
+
+ return { address: keypair.publicKey.toBase58() };
+ }
+}
\ No newline at end of file