From 85aeb6521714d48a86e7e982224be81546155578 Mon Sep 17 00:00:00 2001
From: 0xFirekeeper <0xFirekeeper@gmail.com>
Date: Sat, 10 May 2025 03:05:47 +0700
Subject: [PATCH 1/2] ThirdwebBridge - Onramp Integration & Improved APIs
---
Thirdweb.Console/Program.cs | 199 ++++++-----
.../ThirdwebBridge.Extensions.cs | 58 +++-
.../Thirdweb.Bridge/ThirdwebBridge.Types.cs | 317 +++++++++++++++---
Thirdweb/Thirdweb.Bridge/ThirdwebBridge.cs | 152 ++++++++-
4 files changed, 576 insertions(+), 150 deletions(-)
diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs
index 50ff598..0d23258 100644
--- a/Thirdweb.Console/Program.cs
+++ b/Thirdweb.Console/Program.cs
@@ -45,88 +45,123 @@
#region Bridge
-// // Create a ThirdwebBridge instance
-// var bridge = await ThirdwebBridge.Create(client);
-
-// // Buy - Get a quote for buying a specific amount of tokens
-// var buyQuote = await bridge.Buy_Quote(
-// originChainId: 1,
-// originTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
-// destinationChainId: 324,
-// destinationTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
-// buyAmountWei: BigInteger.Parse("0.1".ToWei())
-// );
-// Console.WriteLine($"Buy quote: {JsonConvert.SerializeObject(buyQuote, Formatting.Indented)}");
-
-// // Buy - Get an executable set of transactions (alongside a quote) for buying a specific amount of tokens
-// var preparedBuy = await bridge.Buy_Prepare(
-// originChainId: 1,
-// originTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
-// destinationChainId: 324,
-// destinationTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
-// buyAmountWei: BigInteger.Parse("0.1".ToWei()),
-// sender: await Utils.GetAddressFromENS(client, "vitalik.eth"),
-// receiver: await myWallet.GetAddress()
-// );
-// Console.WriteLine($"Prepared Buy contains {preparedBuy.Transactions.Count} transaction(s)!");
-
-// // Sell - Get a quote for selling a specific amount of tokens
-// var sellQuote = await bridge.Sell_Quote(
-// originChainId: 324,
-// originTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
-// destinationChainId: 1,
-// destinationTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
-// sellAmountWei: BigInteger.Parse("0.1".ToWei())
-// );
-// Console.WriteLine($"Sell quote: {JsonConvert.SerializeObject(sellQuote, Formatting.Indented)}");
-
-// // Sell - Get an executable set of transactions (alongside a quote) for selling a specific amount of tokens
-// var preparedSell = await bridge.Sell_Prepare(
-// originChainId: 324,
-// originTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
-// destinationChainId: 1,
-// destinationTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
-// sellAmountWei: BigInteger.Parse("0.1".ToWei()),
-// sender: await Utils.GetAddressFromENS(client, "vitalik.eth"),
-// receiver: await myWallet.GetAddress()
-// );
-// Console.WriteLine($"Prepared Sell contains {preparedSell.Transactions.Count} transaction(s)!");
-
-// // Transfer - Get an executable transaction for transferring a specific amount of tokens
-// var preparedTransfer = await bridge.Transfer_Prepare(
-// chainId: 137,
-// tokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
-// transferAmountWei: BigInteger.Parse("0.1".ToWei()),
-// sender: await Utils.GetAddressFromENS(client, "vitalik.eth"),
-// receiver: await myWallet.GetAddress()
-// );
-// Console.WriteLine($"Prepared Transfer: {JsonConvert.SerializeObject(preparedTransfer, Formatting.Indented)}");
-
-// // You may use our extensions to execute yourself...
-// var myTx = await preparedTransfer.Transactions[0].ToThirdwebTransaction(myWallet);
-// var myHash = await ThirdwebTransaction.Send(myTx);
-
-// // ...and poll for the status...
-// var status = await bridge.Status(transactionHash: myHash, chainId: 1);
-// var isComplete = status.StatusType == StatusType.COMPLETED;
-// Console.WriteLine($"Status: {JsonConvert.SerializeObject(status, Formatting.Indented)}");
-
-// // Or use our Execute extensions directly to handle everything for you!
-
-// // Execute a prepared Buy
-// var buyResult = await bridge.Execute(myWallet, preparedBuy);
-// var buyHashes = buyResult.Select(receipt => receipt.TransactionHash).ToList();
-// Console.WriteLine($"Buy hashes: {JsonConvert.SerializeObject(buyHashes, Formatting.Indented)}");
-
-// // Execute a prepared Sell
-// var sellResult = await bridge.Execute(myWallet, preparedSell);
-// var sellHashes = sellResult.Select(receipt => receipt.TransactionHash).ToList();
-// Console.WriteLine($"Sell hashes: {JsonConvert.SerializeObject(sellHashes, Formatting.Indented)}");
-
-// // Execute a prepared Transfer
-// var transferResult = await bridge.Execute(myWallet, preparedTransfer);
-// var transferHashes = transferResult.Select(receipt => receipt.TransactionHash).ToList();
-// Console.WriteLine($"Transfer hashes: {JsonConvert.SerializeObject(transferHashes, Formatting.Indented)}");
+var myWallet = await PrivateKeyWallet.Generate(client);
+
+// Create a ThirdwebBridge instance
+var bridge = await ThirdwebBridge.Create(client);
+
+// Buy - Get a quote for buying a specific amount of tokens
+var buyQuote = await bridge.Buy_Quote(
+ originChainId: 1,
+ originTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
+ destinationChainId: 324,
+ destinationTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
+ buyAmountWei: BigInteger.Parse("0.01".ToWei())
+);
+Console.WriteLine($"Buy quote: {JsonConvert.SerializeObject(buyQuote, Formatting.Indented)}");
+
+// Buy - Get an executable set of transactions (alongside a quote) for buying a specific amount of tokens
+var preparedBuy = await bridge.Buy_Prepare(
+ originChainId: 1,
+ originTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
+ destinationChainId: 324,
+ destinationTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
+ buyAmountWei: BigInteger.Parse("0.01".ToWei()),
+ sender: await Utils.GetAddressFromENS(client, "vitalik.eth"),
+ receiver: await myWallet.GetAddress()
+);
+Console.WriteLine($"Prepared Buy contains {preparedBuy.Steps.Count} steps(s) with a total of {preparedBuy.Steps.Sum(step => step.Transactions.Count)} transactions!");
+
+// Sell - Get a quote for selling a specific amount of tokens
+var sellQuote = await bridge.Sell_Quote(
+ originChainId: 324,
+ originTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
+ destinationChainId: 1,
+ destinationTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
+ sellAmountWei: BigInteger.Parse("0.01".ToWei())
+);
+Console.WriteLine($"Sell quote: {JsonConvert.SerializeObject(sellQuote, Formatting.Indented)}");
+
+// Sell - Get an executable set of transactions (alongside a quote) for selling a specific amount of tokens
+var preparedSell = await bridge.Sell_Prepare(
+ originChainId: 324,
+ originTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
+ destinationChainId: 1,
+ destinationTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
+ sellAmountWei: BigInteger.Parse("0.01".ToWei()),
+ sender: await Utils.GetAddressFromENS(client, "vitalik.eth"),
+ receiver: await myWallet.GetAddress()
+);
+Console.WriteLine($"Prepared Sell contains {preparedBuy.Steps.Count} steps(s) with a total of {preparedBuy.Steps.Sum(step => step.Transactions.Count)} transactions!");
+
+// Transfer - Get an executable transaction for transferring a specific amount of tokens
+var preparedTransfer = await bridge.Transfer_Prepare(
+ chainId: 137,
+ tokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // POL on Polygon
+ transferAmountWei: BigInteger.Parse("0.01".ToWei()),
+ sender: await Utils.GetAddressFromENS(client, "vitalik.eth"),
+ receiver: await myWallet.GetAddress()
+);
+Console.WriteLine($"Prepared Transfer: {JsonConvert.SerializeObject(preparedTransfer, Formatting.Indented)}");
+
+// You may use our extensions to execute yourself...
+var myTx = await preparedTransfer.Transactions[0].ToThirdwebTransaction(myWallet);
+var myHash = await ThirdwebTransaction.Send(myTx);
+
+// ...and poll for the status...
+var status = await bridge.Status(transactionHash: myHash, chainId: 1);
+var isComplete = status.StatusType == StatusType.COMPLETED;
+Console.WriteLine($"Status: {JsonConvert.SerializeObject(status, Formatting.Indented)}");
+
+// Or use our Execute extensions directly to handle everything for you!
+
+// Execute a prepared Buy
+var buyResult = await bridge.Execute(myWallet, preparedBuy);
+var buyHashes = buyResult.Select(receipt => receipt.TransactionHash).ToList();
+Console.WriteLine($"Buy hashes: {JsonConvert.SerializeObject(buyHashes, Formatting.Indented)}");
+
+// Execute a prepared Sell
+var sellResult = await bridge.Execute(myWallet, preparedSell);
+var sellHashes = sellResult.Select(receipt => receipt.TransactionHash).ToList();
+Console.WriteLine($"Sell hashes: {JsonConvert.SerializeObject(sellHashes, Formatting.Indented)}");
+
+// Execute a prepared Transfer
+var transferResult = await bridge.Execute(myWallet, preparedTransfer);
+var transferHashes = transferResult.Select(receipt => receipt.TransactionHash).ToList();
+Console.WriteLine($"Transfer hashes: {JsonConvert.SerializeObject(transferHashes, Formatting.Indented)}");
+
+// Onramp - Get a quote for buying crypto with Fiat
+var preparedOnramp = await bridge.Onramp_Prepare(
+ onramp: OnrampProvider.Coinbase,
+ chainId: 8453,
+ tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
+ amount: "10000000",
+ receiver: await myWallet.GetAddress()
+);
+Console.WriteLine($"Onramp link: {preparedOnramp.Link}");
+Console.WriteLine($"Full onramp quote and steps data: {JsonConvert.SerializeObject(preparedOnramp, Formatting.Indented)}");
+
+while (true)
+{
+ var onrampStatus = await bridge.Onramp_Status(id: preparedOnramp.Id);
+ Console.WriteLine($"Full Onramp Status: {JsonConvert.SerializeObject(onrampStatus, Formatting.Indented)}");
+ if (onrampStatus.StatusType is StatusType.COMPLETED or StatusType.FAILED)
+ {
+ break;
+ }
+ await ThirdwebTask.Delay(5000);
+}
+
+if (preparedOnramp.IsSwapRequiredPostOnramp())
+{
+ // Execute additional steps that are required post-onramp to get to your token, manually or via the Execute extension
+ var receipts = await bridge.Execute(myWallet, preparedOnramp);
+ Console.WriteLine($"Onramp receipts: {JsonConvert.SerializeObject(receipts, Formatting.Indented)}");
+}
+else
+{
+ Console.WriteLine("No additional steps required post-onramp, you can use the tokens directly!");
+}
#endregion
diff --git a/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Extensions.cs b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Extensions.cs
index 04d1428..aa231a8 100644
--- a/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Extensions.cs
+++ b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Extensions.cs
@@ -16,7 +16,7 @@ public static class ThirdwebBridgeExtensions
/// The transaction receipts as a list of .
public static async Task> Execute(this ThirdwebBridge bridge, IThirdwebWallet executor, BuyPrepareData preparedBuy, CancellationToken cancellationToken = default)
{
- return await ExecuteInternal(bridge, executor, preparedBuy.Transactions, cancellationToken);
+ return await ExecuteInternal(bridge, executor, preparedBuy.Steps, cancellationToken);
}
///
@@ -34,7 +34,7 @@ public static async Task> Execute(
CancellationToken cancellationToken = default
)
{
- return await ExecuteInternal(bridge, executor, preparedSell.Transactions, cancellationToken);
+ return await ExecuteInternal(bridge, executor, preparedSell.Steps, cancellationToken);
}
///
@@ -52,23 +52,48 @@ public static Task> Execute(
CancellationToken cancellationToken = default
)
{
- return ExecuteInternal(bridge, executor, preparedTransfer.Transactions, cancellationToken);
+ var steps = new List() { new() { Transactions = preparedTransfer.Transactions } };
+ return ExecuteInternal(bridge, executor, steps, cancellationToken);
}
- private static async Task> ExecuteInternal(
- this ThirdwebBridge bridge,
- IThirdwebWallet executor,
- List transactions,
- CancellationToken cancellationToken = default
- )
+ ///
+ /// Executes a set of post-onramp transactions and handles status polling.
+ ///
+ /// The Thirdweb bridge.
+ /// The executor wallet.
+ /// The prepared onramp data.
+ /// The cancellation token.
+ /// The transaction receipts as a list of .
+ /// Note: This method is used for executing transactions after an onramp process.
+ public static Task> Execute(this ThirdwebBridge bridge, IThirdwebWallet executor, OnrampPrepareData preparedOnRamp, CancellationToken cancellationToken = default)
+ {
+ return ExecuteInternal(bridge, executor, preparedOnRamp.Steps, cancellationToken);
+ }
+
+ ///
+ /// Executes a set of transactions and handles status polling.
+ ///
+ /// /// The Thirdweb bridge.
+ /// The executor wallet.
+ /// The steps containing transactions to execute.
+ /// The cancellation token.
+ public static Task> Execute(this ThirdwebBridge bridge, IThirdwebWallet executor, List steps, CancellationToken cancellationToken = default)
+ {
+ return ExecuteInternal(bridge, executor, steps, cancellationToken);
+ }
+
+ private static async Task> ExecuteInternal(this ThirdwebBridge bridge, IThirdwebWallet executor, List steps, CancellationToken cancellationToken = default)
{
var receipts = new List();
- foreach (var tx in transactions)
+ foreach (var step in steps)
{
- var thirdwebTx = await tx.ToThirdwebTransaction(executor);
- var hash = await ThirdwebTransaction.Send(thirdwebTx);
- receipts.Add(await ThirdwebTransaction.WaitForTransactionReceipt(executor.Client, tx.ChainId, hash, cancellationToken));
- _ = await bridge.WaitForStatusCompletion(hash, tx.ChainId, cancellationToken);
+ foreach (var tx in step.Transactions)
+ {
+ var thirdwebTx = await tx.ToThirdwebTransaction(executor);
+ var hash = await ThirdwebTransaction.Send(thirdwebTx);
+ receipts.Add(await ThirdwebTransaction.WaitForTransactionReceipt(executor.Client, tx.ChainId, hash, cancellationToken));
+ _ = await bridge.WaitForStatusCompletion(hash, tx.ChainId, cancellationToken);
+ }
}
return receipts;
}
@@ -117,5 +142,10 @@ public static async Task WaitForStatusCompletion(this ThirdwebBridge
return status;
}
+ public static bool IsSwapRequiredPostOnramp(this OnrampPrepareData preparedOnramp)
+ {
+ return preparedOnramp.Steps == null || preparedOnramp.Steps.Count == 0 || !preparedOnramp.Steps.Any(step => step.Transactions?.Count > 0);
+ }
+
#endregion
}
diff --git a/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Types.cs b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Types.cs
index a5a7219..368bef6 100644
--- a/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Types.cs
+++ b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Types.cs
@@ -24,40 +24,46 @@ internal class ResponseModel
public class Intent
{
///
- /// The chain ID where the transaction originates.
+ /// The origin chain ID.
///
[JsonProperty("originChainId")]
public BigInteger OriginChainId { get; set; }
///
- /// The token address in the origin chain.
+ /// The origin token address.
///
[JsonProperty("originTokenAddress")]
public string OriginTokenAddress { get; set; }
///
- /// The chain ID where the transaction is executed.
+ /// The destination chain ID.
///
[JsonProperty("destinationChainId")]
public BigInteger DestinationChainId { get; set; }
///
- /// The token address in the destination chain.
+ /// The destination token address.
///
[JsonProperty("destinationTokenAddress")]
public string DestinationTokenAddress { get; set; }
///
- /// The amount involved in the transaction (buy, sell, or transfer) in wei.
+ /// The desired amount in wei.
///
- public virtual string AmountWei { get; set; }
+ [JsonProperty("amount")]
+ public string Amount { get; set; }
+
+ ///
+ /// The maximum number of steps in the returned route (optional).
+ ///
+ [JsonProperty("maxSteps", NullValueHandling = NullValueHandling.Ignore)]
+ public int? MaxSteps { get; set; } = 3;
}
///
/// Represents the common fields for both Buy and Sell transactions.
///
-public class QuoteData
- where TIntent : Intent
+public class QuoteData
{
///
/// The amount (in wei) of the input token that must be paid to receive the desired amount.
@@ -93,14 +99,80 @@ public class QuoteData
/// The intent object containing details about the transaction.
///
[JsonProperty("intent")]
- public TIntent Intent { get; set; }
+ public Intent Intent { get; set; }
+
+ [JsonProperty("steps")]
+ public List Steps { get; set; }
+
+ [JsonProperty("purchaseData", NullValueHandling = NullValueHandling.Ignore)]
+ public object PurchaseData { get; set; }
+}
+
+///
+/// Represents a single step in a transaction, including origin and destination tokens.
+///
+public class Step
+{
+ [JsonProperty("originToken")]
+ public TokenData OriginToken { get; set; }
+
+ [JsonProperty("destinationToken")]
+ public TokenData DestinationToken { get; set; }
+
+ [JsonProperty("transactions")]
+ public List Transactions { get; set; }
+
+ [JsonProperty("originAmount")]
+ public string OriginAmount { get; set; }
+
+ [JsonProperty("destinationAmount")]
+ public string DestinationAmount { get; set; }
+
+ [JsonProperty("nativeFee")]
+ public string NativeFee { get; set; }
+
+ [JsonProperty("estimatedExecutionTimeMs")]
+ public long EstimatedExecutionTimeMs { get; set; }
}
///
-/// Represents a transaction to be executed.
+/// Represents a token in a step, including metadata like chain ID, address, and pricing.
+///
+public class TokenData
+{
+ [JsonProperty("chainId")]
+ public BigInteger ChainId { get; set; }
+
+ [JsonProperty("address")]
+ public string Address { get; set; }
+
+ [JsonProperty("symbol")]
+ public string Symbol { get; set; }
+
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("decimals")]
+ public int Decimals { get; set; }
+
+ [JsonProperty("priceUsd")]
+ public decimal PriceUsd { get; set; }
+
+ [JsonProperty("iconUri")]
+ public string IconUri { get; set; }
+}
+
+///
+/// Represents a transaction ready to be executed.
///
public class Transaction
{
+ ///
+ /// The transaction ID, each step in a quoted payment will have a unique transaction ID.
+ ///
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
///
/// The chain ID where the transaction will take place.
///
@@ -108,17 +180,41 @@ public class Transaction
public BigInteger ChainId { get; set; }
///
- /// The address to which the transaction is sent, or null if not applicable.
+ /// The maximum priority fee per gas (EIP-1559).
+ ///
+ [JsonProperty("maxPriorityFeePerGas", NullValueHandling = NullValueHandling.Ignore)]
+ public string MaxPriorityFeePerGas { get; set; }
+
+ ///
+ /// The maximum fee per gas (EIP-1559).
///
- [JsonProperty("to", NullValueHandling = NullValueHandling.Ignore)]
+ [JsonProperty("maxFeePerGas", NullValueHandling = NullValueHandling.Ignore)]
+ public string MaxFeePerGas { get; set; }
+
+ ///
+ /// The address to which the transaction is sent.
+ ///
+ [JsonProperty("to")]
public string To { get; set; }
+ ///
+ /// The address from which the transaction is sent, or null if not applicable.
+ ///
+ [JsonProperty("from", NullValueHandling = NullValueHandling.Ignore)]
+ public string From { get; set; }
+
///
/// The value (amount) to be sent in the transaction.
///
- [JsonProperty("value")]
+ [JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)]
public string Value { get; set; }
+ ///
+ /// The gas limit for the transaction.
+ ///
+ [JsonProperty("gas", NullValueHandling = NullValueHandling.Ignore)]
+ public string Gas { get; set; }
+
///
/// The transaction data.
///
@@ -130,6 +226,12 @@ public class Transaction
///
[JsonProperty("type")]
public string Type { get; set; }
+
+ ///
+ /// The action type for the transaction (e.g., "approval", "transfer", "buy", "sell").
+ ///
+ [JsonProperty("action")]
+ public string Action { get; set; }
}
#endregion
@@ -139,16 +241,23 @@ public class Transaction
///
/// Represents the data returned in the buy quote response.
///
-public class BuyQuoteData : QuoteData { }
+public class BuyQuoteData : QuoteData { }
///
/// Represents the data returned in the buy prepare response.
///
-public class BuyPrepareData : QuoteData
+public class BuyPrepareData : QuoteData
{
+ ///
+ /// A hex ID associated with the quoted payment.
+ ///
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
///
/// An array of transactions to be executed to fulfill this quote (in order).
///
+ [Obsolete("Use Steps.Transactions instead.")]
[JsonProperty("transactions")]
public List Transactions { get; set; }
@@ -159,18 +268,6 @@ public class BuyPrepareData : QuoteData
public long? Expiration { get; set; }
}
-///
-/// Represents the intent object for a buy quote.
-///
-public class BuyIntent : Intent
-{
- ///
- /// The desired output amount in wei for buying.
- ///
- [JsonProperty("buyAmountWei")]
- public override string AmountWei { get; set; }
-}
-
#endregion
#region Sell
@@ -178,16 +275,23 @@ public class BuyIntent : Intent
///
/// Represents the data returned in the sell quote response.
///
-public class SellQuoteData : QuoteData { }
+public class SellQuoteData : QuoteData { }
///
/// Represents the data returned in the sell prepare response.
///
-public class SellPrepareData : QuoteData
+public class SellPrepareData : QuoteData
{
+ ///
+ /// A hex ID associated with the quoted payment.
+ ///
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
///
/// An array of transactions to be executed to fulfill this quote (in order).
///
+ [Obsolete("Use Steps.Transactions instead.")]
[JsonProperty("transactions")]
public List Transactions { get; set; }
@@ -198,18 +302,6 @@ public class SellPrepareData : QuoteData
public long? Expiration { get; set; }
}
-///
-/// Represents the intent object for a sell quote.
-///
-public class SellIntent : Intent
-{
- ///
- /// The amount to sell in wei.
- ///
- [JsonProperty("sellAmountWei")]
- public override string AmountWei { get; set; }
-}
-
#endregion
#region Transfer
@@ -234,6 +326,9 @@ public class TransferPrepareData
[JsonProperty("estimatedExecutionTimeMs")]
public long EstimatedExecutionTimeMs { get; set; }
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
[JsonProperty("transactions")]
public List Transactions { get; set; }
@@ -250,7 +345,7 @@ public class TransferPrepareData
public class TransferIntent
{
[JsonProperty("chainId")]
- public int ChainId { get; set; }
+ public BigInteger ChainId { get; set; }
[JsonProperty("tokenAddress")]
public string TokenAddress { get; set; }
@@ -263,6 +358,12 @@ public class TransferIntent
[JsonProperty("receiver")]
public string Receiver { get; set; }
+
+ [JsonProperty("feePayer")]
+ public string FeePayer { get; set; } = "sender";
+
+ [JsonProperty("purchaseData", NullValueHandling = NullValueHandling.Ignore)]
+ public object PurchaseData { get; set; }
}
#endregion
@@ -277,7 +378,10 @@ public enum StatusType
FAILED,
PENDING,
COMPLETED,
- NOT_FOUND
+ NOT_FOUND,
+ PROCESSING,
+ CREATED,
+ UNKNOWN
}
///
@@ -296,7 +400,7 @@ public class StatusData
"PENDING" => StatusType.PENDING,
"COMPLETED" => StatusType.COMPLETED,
"NOT_FOUND" => StatusType.NOT_FOUND,
- _ => throw new InvalidOperationException($"Unknown status: {this.Status}")
+ _ => StatusType.UNKNOWN
};
///
@@ -311,6 +415,18 @@ public class StatusData
[JsonProperty("transactions")]
public List Transactions { get; set; }
+ ///
+ /// The unique payment ID for the transaction.
+ ///
+ [JsonProperty("paymentId", NullValueHandling = NullValueHandling.Ignore)]
+ public string PaymentId { get; set; }
+
+ ///
+ /// The unique transaction ID for the transaction.
+ ///
+ [JsonProperty("transactionId", NullValueHandling = NullValueHandling.Ignore)]
+ public string TransactionId { get; set; }
+
///
/// The origin chain ID (for PENDING and COMPLETED statuses).
///
@@ -346,6 +462,12 @@ public class StatusData
///
[JsonProperty("destinationAmount", NullValueHandling = NullValueHandling.Ignore)]
public string DestinationAmount { get; set; }
+
+ ///
+ /// The purchase data, which can be null.
+ ///
+ [JsonProperty("purchaseData", NullValueHandling = NullValueHandling.Ignore)]
+ public object PurchaseData { get; set; }
}
///
@@ -367,3 +489,110 @@ public class TransactionStatus
}
#endregion
+
+#region Onramp
+
+public enum OnrampProvider
+{
+ Stripe,
+ Coinbase,
+ Transak
+}
+
+///
+/// Represents the core data of an onramp response.
+///
+public class OnrampPrepareData
+{
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
+ [JsonProperty("link")]
+ public string Link { get; set; }
+
+ [JsonProperty("currency")]
+ public string Currency { get; set; }
+
+ [JsonProperty("currencyAmount")]
+ public decimal CurrencyAmount { get; set; }
+
+ [JsonProperty("destinationAmount")]
+ public string DestinationAmount { get; set; }
+
+ [JsonProperty("timestamp", NullValueHandling = NullValueHandling.Ignore)]
+ public long? Timestamp { get; set; }
+
+ [JsonProperty("expiration", NullValueHandling = NullValueHandling.Ignore)]
+ public long? Expiration { get; set; }
+
+ [JsonProperty("steps")]
+ public List Steps { get; set; }
+
+ [JsonProperty("intent")]
+ public OnrampIntent Intent { get; set; }
+}
+
+///
+/// Represents the intent used to prepare the onramp.
+///
+public class OnrampIntent
+{
+ [JsonProperty("onramp")]
+ public OnrampProvider Onramp { get; set; }
+
+ [JsonProperty("chainId")]
+ public BigInteger ChainId { get; set; }
+
+ [JsonProperty("tokenAddress")]
+ public string TokenAddress { get; set; }
+
+ [JsonProperty("amount")]
+ public string Amount { get; set; }
+
+ [JsonProperty("receiver")]
+ public string Receiver { get; set; }
+
+ [JsonProperty("purchaseData", NullValueHandling = NullValueHandling.Ignore)]
+ public Dictionary PurchaseData { get; set; }
+
+ [JsonProperty("onrampTokenAddress", NullValueHandling = NullValueHandling.Ignore)]
+ public string OnrampTokenAddress { get; set; }
+
+ [JsonProperty("onrampChainId", NullValueHandling = NullValueHandling.Ignore)]
+ public BigInteger? OnrampChainId { get; set; }
+
+ [JsonProperty("currency", NullValueHandling = NullValueHandling.Ignore)]
+ public string Currency { get; set; } = "USD";
+
+ [JsonProperty("maxSteps", NullValueHandling = NullValueHandling.Ignore)]
+ public int? MaxSteps { get; set; } = 3;
+
+ [JsonProperty("excludeChainIds", NullValueHandling = NullValueHandling.Ignore)]
+ public List ExcludeChainIds { get; set; }
+}
+
+///
+/// Represents the status of an onramp transaction.
+///
+public class OnrampStatusData
+{
+ [JsonIgnore]
+ public StatusType StatusType =>
+ this.Status switch
+ {
+ "FAILED" => StatusType.FAILED,
+ "PENDING" => StatusType.PENDING,
+ "COMPLETED" => StatusType.COMPLETED,
+ "PROCESSING" => StatusType.PROCESSING,
+ "CREATED" => StatusType.CREATED,
+ _ => StatusType.UNKNOWN
+ };
+
+ [JsonProperty("status")]
+ public string Status { get; set; }
+
+ [JsonProperty("transactionHash")]
+ public string TransactionHash { get; set; }
+}
+
+#endregion
diff --git a/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.cs b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.cs
index 095fd8f..331a6f7 100644
--- a/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.cs
+++ b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.cs
@@ -32,9 +32,17 @@ public static Task Create(ThirdwebClient client)
/// The chain ID of the destination chain.
/// The address of the token on the destination chain.
/// The amount of tokens to buy in wei.
+ /// The maximum number of steps in the returned route.
/// A object representing the quote.
/// Thrown when one of the parameters is invalid.
- public async Task Buy_Quote(BigInteger originChainId, string originTokenAddress, BigInteger destinationChainId, string destinationTokenAddress, BigInteger buyAmountWei)
+ public async Task Buy_Quote(
+ BigInteger originChainId,
+ string originTokenAddress,
+ BigInteger destinationChainId,
+ string destinationTokenAddress,
+ BigInteger buyAmountWei,
+ int maxSteps = 3
+ )
{
if (originChainId <= 0)
{
@@ -63,7 +71,8 @@ public async Task Buy_Quote(BigInteger originChainId, string origi
{ "originTokenAddress", originTokenAddress },
{ "destinationChainId", destinationChainId.ToString() },
{ "destinationTokenAddress", destinationTokenAddress },
- { "buyAmountWei", buyAmountWei.ToString() }
+ { "buyAmountWei", buyAmountWei.ToString() },
+ { "maxSteps", maxSteps.ToString() }
};
url = AppendQueryParams(url, queryParams);
@@ -84,6 +93,8 @@ public async Task Buy_Quote(BigInteger originChainId, string origi
/// The amount of tokens to buy in wei.
/// The address of the sender.
/// The address of the receiver.
+ /// The maximum number of steps in the returned route.
+ /// Arbitrary purchase data to be included with the payment and returned with all webhooks and status checks.
/// A object representing the prepare data.
/// Thrown when one of the parameters is invalid.
public async Task Buy_Prepare(
@@ -93,7 +104,9 @@ public async Task Buy_Prepare(
string destinationTokenAddress,
BigInteger buyAmountWei,
string sender,
- string receiver
+ string receiver,
+ int maxSteps = 3,
+ object purchaseData = null
)
{
if (originChainId <= 0)
@@ -135,7 +148,9 @@ string receiver
{ "destinationTokenAddress", destinationTokenAddress },
{ "buyAmountWei", buyAmountWei.ToString() },
{ "sender", sender },
- { "receiver", receiver }
+ { "receiver", receiver },
+ { "maxSteps", maxSteps.ToString() },
+ { "purchaseData", purchaseData != null ? JsonConvert.SerializeObject(purchaseData) : null }
};
url = AppendQueryParams(url, queryParams);
@@ -158,9 +173,17 @@ string receiver
/// The chain ID of the destination chain.
/// The address of the token on the destination chain.
/// The amount of tokens to sell in wei.
+ /// The maximum number of steps in the returned route.
/// A object representing the quote.
/// Thrown when one of the parameters is invalid.
- public async Task Sell_Quote(BigInteger originChainId, string originTokenAddress, BigInteger destinationChainId, string destinationTokenAddress, BigInteger sellAmountWei)
+ public async Task Sell_Quote(
+ BigInteger originChainId,
+ string originTokenAddress,
+ BigInteger destinationChainId,
+ string destinationTokenAddress,
+ BigInteger sellAmountWei,
+ int maxSteps = 3
+ )
{
if (originChainId <= 0)
{
@@ -189,7 +212,8 @@ public async Task Sell_Quote(BigInteger originChainId, string ori
{ "originTokenAddress", originTokenAddress },
{ "destinationChainId", destinationChainId.ToString() },
{ "destinationTokenAddress", destinationTokenAddress },
- { "sellAmountWei", sellAmountWei.ToString() }
+ { "sellAmountWei", sellAmountWei.ToString() },
+ { "maxSteps", maxSteps.ToString() }
};
url = AppendQueryParams(url, queryParams);
@@ -210,6 +234,8 @@ public async Task Sell_Quote(BigInteger originChainId, string ori
/// The amount of tokens to sell in wei.
/// The address of the sender.
/// The address of the receiver.
+ /// The maximum number of steps in the returned route.
+ /// Arbitrary purchase data to be included with the payment and returned with all webhooks and status checks.
/// A object representing the prepare data.
/// Thrown when one of the parameters is invalid.
public async Task Sell_Prepare(
@@ -219,7 +245,9 @@ public async Task Sell_Prepare(
string destinationTokenAddress,
BigInteger sellAmountWei,
string sender,
- string receiver
+ string receiver,
+ int maxSteps = 3,
+ object purchaseData = null
)
{
if (originChainId <= 0)
@@ -261,7 +289,9 @@ string receiver
{ "destinationTokenAddress", destinationTokenAddress },
{ "sellAmountWei", sellAmountWei.ToString() },
{ "sender", sender },
- { "receiver", receiver }
+ { "receiver", receiver },
+ { "maxSteps", maxSteps.ToString() },
+ { "purchaseData", purchaseData != null ? JsonConvert.SerializeObject(purchaseData) : null }
};
url = AppendQueryParams(url, queryParams);
@@ -284,9 +314,19 @@ string receiver
/// The amount of tokens to transfer in wei.
/// The address of the sender.
/// The address of the receiver.
+ /// The fee payer (default is "sender").
+ /// Arbitrary purchase data to be included with the payment and returned with all webhooks and status checks.
/// A object representing the prepare data.
/// Thrown when one of the parameters is invalid.
- public async Task Transfer_Prepare(BigInteger chainId, string tokenAddress, BigInteger transferAmountWei, string sender, string receiver)
+ public async Task Transfer_Prepare(
+ BigInteger chainId,
+ string tokenAddress,
+ BigInteger transferAmountWei,
+ string sender,
+ string receiver,
+ string feePayer = "sender",
+ object purchaseData = null
+ )
{
if (chainId <= 0)
{
@@ -320,7 +360,9 @@ public async Task Transfer_Prepare(BigInteger chainId, stri
{ "tokenAddress", tokenAddress },
{ "transferAmountWei", transferAmountWei.ToString() },
{ "sender", sender },
- { "receiver", receiver }
+ { "receiver", receiver },
+ { "feePayer", feePayer },
+ { "purchaseData", purchaseData != null ? JsonConvert.SerializeObject(purchaseData) : null }
};
url = AppendQueryParams(url, queryParams);
@@ -333,6 +375,92 @@ public async Task Transfer_Prepare(BigInteger chainId, stri
#endregion
+ #region Onramp
+
+ public async Task Onramp_Prepare(
+ OnrampProvider onramp,
+ BigInteger chainId,
+ string tokenAddress,
+ string amount,
+ string receiver,
+ string onrampTokenAddress = null,
+ BigInteger? onrampChainId = null,
+ string currency = "USD",
+ int? maxSteps = 3,
+ List excludeChainIds = null,
+ object purchaseData = null
+ )
+ {
+ if (chainId <= 0)
+ {
+ throw new ArgumentException("chainId cannot be less than or equal to 0", nameof(chainId));
+ }
+
+ if (!Utils.IsValidAddress(tokenAddress))
+ {
+ throw new ArgumentException("tokenAddress is not a valid address", nameof(tokenAddress));
+ }
+
+ if (string.IsNullOrWhiteSpace(amount))
+ {
+ throw new ArgumentException("amount cannot be null or empty", nameof(amount));
+ }
+
+ if (!Utils.IsValidAddress(receiver))
+ {
+ throw new ArgumentException("receiver is not a valid address", nameof(receiver));
+ }
+
+ var url = $"{Constants.BRIDGE_API_URL}/v1/onramp/prepare";
+ var queryParams = new Dictionary
+ {
+ { "onramp", onramp.ToString().ToLower() },
+ { "chainId", chainId.ToString() },
+ { "tokenAddress", tokenAddress },
+ { "amount", amount },
+ { "receiver", receiver },
+ { "purchaseData", purchaseData != null ? JsonConvert.SerializeObject(purchaseData) : null },
+ { "onrampTokenAddress", onrampTokenAddress },
+ { "onrampChainId", onrampChainId?.ToString() },
+ { "currency", currency },
+ { "maxSteps", maxSteps?.ToString() }
+ };
+
+ if (excludeChainIds != null && excludeChainIds.Count > 0)
+ {
+ queryParams.Add("excludeChainIds", string.Join(",", excludeChainIds));
+ }
+
+ url = AppendQueryParams(url, queryParams);
+
+ var response = await this._httpClient.GetAsync(url).ConfigureAwait(false);
+ _ = response.EnsureSuccessStatusCode();
+
+ var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ var result = JsonConvert.DeserializeObject>(responseContent);
+ return result.Data;
+ }
+
+ public async Task Onramp_Status(string id)
+ {
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ throw new ArgumentException("id cannot be null or empty", nameof(id));
+ }
+
+ var url = $"{Constants.BRIDGE_API_URL}/v1/onramp/status";
+ var queryParams = new Dictionary { { "id", id } };
+ url = AppendQueryParams(url, queryParams);
+
+ var response = await this._httpClient.GetAsync(url).ConfigureAwait(false);
+ _ = response.EnsureSuccessStatusCode();
+ var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ var result = JsonConvert.DeserializeObject>(responseContent);
+ return result.Data;
+ }
+
+ #endregion
+
#region Status
///
@@ -372,6 +500,10 @@ private static string AppendQueryParams(string url, Dictionary q
var query = new List();
foreach (var param in queryParams)
{
+ if (string.IsNullOrEmpty(param.Value))
+ {
+ continue;
+ }
query.Add($"{param.Key}={param.Value}");
}
From 151f4744f9b032ef2758948b66d38a7143307544 Mon Sep 17 00:00:00 2001
From: 0xFirekeeper <0xFirekeeper@gmail.com>
Date: Sat, 10 May 2025 03:18:24 +0700
Subject: [PATCH 2/2] GET -> POST on /prepare
---
Thirdweb.Console/Program.cs | 294 ++++++++-------------
Thirdweb/Thirdweb.Bridge/ThirdwebBridge.cs | 125 +++++----
2 files changed, 185 insertions(+), 234 deletions(-)
diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs
index 0d23258..cf240f5 100644
--- a/Thirdweb.Console/Program.cs
+++ b/Thirdweb.Console/Program.cs
@@ -45,123 +45,121 @@
#region Bridge
-var myWallet = await PrivateKeyWallet.Generate(client);
-
-// Create a ThirdwebBridge instance
-var bridge = await ThirdwebBridge.Create(client);
-
-// Buy - Get a quote for buying a specific amount of tokens
-var buyQuote = await bridge.Buy_Quote(
- originChainId: 1,
- originTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
- destinationChainId: 324,
- destinationTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
- buyAmountWei: BigInteger.Parse("0.01".ToWei())
-);
-Console.WriteLine($"Buy quote: {JsonConvert.SerializeObject(buyQuote, Formatting.Indented)}");
-
-// Buy - Get an executable set of transactions (alongside a quote) for buying a specific amount of tokens
-var preparedBuy = await bridge.Buy_Prepare(
- originChainId: 1,
- originTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
- destinationChainId: 324,
- destinationTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
- buyAmountWei: BigInteger.Parse("0.01".ToWei()),
- sender: await Utils.GetAddressFromENS(client, "vitalik.eth"),
- receiver: await myWallet.GetAddress()
-);
-Console.WriteLine($"Prepared Buy contains {preparedBuy.Steps.Count} steps(s) with a total of {preparedBuy.Steps.Sum(step => step.Transactions.Count)} transactions!");
-
-// Sell - Get a quote for selling a specific amount of tokens
-var sellQuote = await bridge.Sell_Quote(
- originChainId: 324,
- originTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
- destinationChainId: 1,
- destinationTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
- sellAmountWei: BigInteger.Parse("0.01".ToWei())
-);
-Console.WriteLine($"Sell quote: {JsonConvert.SerializeObject(sellQuote, Formatting.Indented)}");
-
-// Sell - Get an executable set of transactions (alongside a quote) for selling a specific amount of tokens
-var preparedSell = await bridge.Sell_Prepare(
- originChainId: 324,
- originTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
- destinationChainId: 1,
- destinationTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
- sellAmountWei: BigInteger.Parse("0.01".ToWei()),
- sender: await Utils.GetAddressFromENS(client, "vitalik.eth"),
- receiver: await myWallet.GetAddress()
-);
-Console.WriteLine($"Prepared Sell contains {preparedBuy.Steps.Count} steps(s) with a total of {preparedBuy.Steps.Sum(step => step.Transactions.Count)} transactions!");
-
-// Transfer - Get an executable transaction for transferring a specific amount of tokens
-var preparedTransfer = await bridge.Transfer_Prepare(
- chainId: 137,
- tokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // POL on Polygon
- transferAmountWei: BigInteger.Parse("0.01".ToWei()),
- sender: await Utils.GetAddressFromENS(client, "vitalik.eth"),
- receiver: await myWallet.GetAddress()
-);
-Console.WriteLine($"Prepared Transfer: {JsonConvert.SerializeObject(preparedTransfer, Formatting.Indented)}");
-
-// You may use our extensions to execute yourself...
-var myTx = await preparedTransfer.Transactions[0].ToThirdwebTransaction(myWallet);
-var myHash = await ThirdwebTransaction.Send(myTx);
-
-// ...and poll for the status...
-var status = await bridge.Status(transactionHash: myHash, chainId: 1);
-var isComplete = status.StatusType == StatusType.COMPLETED;
-Console.WriteLine($"Status: {JsonConvert.SerializeObject(status, Formatting.Indented)}");
-
-// Or use our Execute extensions directly to handle everything for you!
-
-// Execute a prepared Buy
-var buyResult = await bridge.Execute(myWallet, preparedBuy);
-var buyHashes = buyResult.Select(receipt => receipt.TransactionHash).ToList();
-Console.WriteLine($"Buy hashes: {JsonConvert.SerializeObject(buyHashes, Formatting.Indented)}");
-
-// Execute a prepared Sell
-var sellResult = await bridge.Execute(myWallet, preparedSell);
-var sellHashes = sellResult.Select(receipt => receipt.TransactionHash).ToList();
-Console.WriteLine($"Sell hashes: {JsonConvert.SerializeObject(sellHashes, Formatting.Indented)}");
-
-// Execute a prepared Transfer
-var transferResult = await bridge.Execute(myWallet, preparedTransfer);
-var transferHashes = transferResult.Select(receipt => receipt.TransactionHash).ToList();
-Console.WriteLine($"Transfer hashes: {JsonConvert.SerializeObject(transferHashes, Formatting.Indented)}");
-
-// Onramp - Get a quote for buying crypto with Fiat
-var preparedOnramp = await bridge.Onramp_Prepare(
- onramp: OnrampProvider.Coinbase,
- chainId: 8453,
- tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
- amount: "10000000",
- receiver: await myWallet.GetAddress()
-);
-Console.WriteLine($"Onramp link: {preparedOnramp.Link}");
-Console.WriteLine($"Full onramp quote and steps data: {JsonConvert.SerializeObject(preparedOnramp, Formatting.Indented)}");
-
-while (true)
-{
- var onrampStatus = await bridge.Onramp_Status(id: preparedOnramp.Id);
- Console.WriteLine($"Full Onramp Status: {JsonConvert.SerializeObject(onrampStatus, Formatting.Indented)}");
- if (onrampStatus.StatusType is StatusType.COMPLETED or StatusType.FAILED)
- {
- break;
- }
- await ThirdwebTask.Delay(5000);
-}
-
-if (preparedOnramp.IsSwapRequiredPostOnramp())
-{
- // Execute additional steps that are required post-onramp to get to your token, manually or via the Execute extension
- var receipts = await bridge.Execute(myWallet, preparedOnramp);
- Console.WriteLine($"Onramp receipts: {JsonConvert.SerializeObject(receipts, Formatting.Indented)}");
-}
-else
-{
- Console.WriteLine("No additional steps required post-onramp, you can use the tokens directly!");
-}
+// // Create a ThirdwebBridge instance
+// var bridge = await ThirdwebBridge.Create(client);
+
+// // Buy - Get a quote for buying a specific amount of tokens
+// var buyQuote = await bridge.Buy_Quote(
+// originChainId: 1,
+// originTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
+// destinationChainId: 324,
+// destinationTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
+// buyAmountWei: BigInteger.Parse("0.01".ToWei())
+// );
+// Console.WriteLine($"Buy quote: {JsonConvert.SerializeObject(buyQuote, Formatting.Indented)}");
+
+// // Buy - Get an executable set of transactions (alongside a quote) for buying a specific amount of tokens
+// var preparedBuy = await bridge.Buy_Prepare(
+// originChainId: 1,
+// originTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
+// destinationChainId: 324,
+// destinationTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
+// buyAmountWei: BigInteger.Parse("0.01".ToWei()),
+// sender: await Utils.GetAddressFromENS(client, "vitalik.eth"),
+// receiver: await myWallet.GetAddress()
+// );
+// Console.WriteLine($"Prepared Buy contains {preparedBuy.Steps.Count} steps(s) with a total of {preparedBuy.Steps.Sum(step => step.Transactions.Count)} transactions!");
+
+// // Sell - Get a quote for selling a specific amount of tokens
+// var sellQuote = await bridge.Sell_Quote(
+// originChainId: 324,
+// originTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
+// destinationChainId: 1,
+// destinationTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
+// sellAmountWei: BigInteger.Parse("0.01".ToWei())
+// );
+// Console.WriteLine($"Sell quote: {JsonConvert.SerializeObject(sellQuote, Formatting.Indented)}");
+
+// // Sell - Get an executable set of transactions (alongside a quote) for selling a specific amount of tokens
+// var preparedSell = await bridge.Sell_Prepare(
+// originChainId: 324,
+// originTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync
+// destinationChainId: 1,
+// destinationTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
+// sellAmountWei: BigInteger.Parse("0.01".ToWei()),
+// sender: await Utils.GetAddressFromENS(client, "vitalik.eth"),
+// receiver: await myWallet.GetAddress()
+// );
+// Console.WriteLine($"Prepared Sell contains {preparedBuy.Steps.Count} steps(s) with a total of {preparedBuy.Steps.Sum(step => step.Transactions.Count)} transactions!");
+
+// // Transfer - Get an executable transaction for transferring a specific amount of tokens
+// var preparedTransfer = await bridge.Transfer_Prepare(
+// chainId: 137,
+// tokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // POL on Polygon
+// transferAmountWei: BigInteger.Parse("0.01".ToWei()),
+// sender: await Utils.GetAddressFromENS(client, "vitalik.eth"),
+// receiver: await myWallet.GetAddress()
+// );
+// Console.WriteLine($"Prepared Transfer: {JsonConvert.SerializeObject(preparedTransfer, Formatting.Indented)}");
+
+// // You may use our extensions to execute yourself...
+// var myTx = await preparedTransfer.Transactions[0].ToThirdwebTransaction(myWallet);
+// var myHash = await ThirdwebTransaction.Send(myTx);
+
+// // ...and poll for the status...
+// var status = await bridge.Status(transactionHash: myHash, chainId: 1);
+// var isComplete = status.StatusType == StatusType.COMPLETED;
+// Console.WriteLine($"Status: {JsonConvert.SerializeObject(status, Formatting.Indented)}");
+
+// // Or use our Execute extensions directly to handle everything for you!
+
+// // Execute a prepared Buy
+// var buyResult = await bridge.Execute(myWallet, preparedBuy);
+// var buyHashes = buyResult.Select(receipt => receipt.TransactionHash).ToList();
+// Console.WriteLine($"Buy hashes: {JsonConvert.SerializeObject(buyHashes, Formatting.Indented)}");
+
+// // Execute a prepared Sell
+// var sellResult = await bridge.Execute(myWallet, preparedSell);
+// var sellHashes = sellResult.Select(receipt => receipt.TransactionHash).ToList();
+// Console.WriteLine($"Sell hashes: {JsonConvert.SerializeObject(sellHashes, Formatting.Indented)}");
+
+// // Execute a prepared Transfer
+// var transferResult = await bridge.Execute(myWallet, preparedTransfer);
+// var transferHashes = transferResult.Select(receipt => receipt.TransactionHash).ToList();
+// Console.WriteLine($"Transfer hashes: {JsonConvert.SerializeObject(transferHashes, Formatting.Indented)}");
+
+// // Onramp - Get a quote for buying crypto with Fiat
+// var preparedOnramp = await bridge.Onramp_Prepare(
+// onramp: OnrampProvider.Coinbase,
+// chainId: 8453,
+// tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
+// amount: "10000000",
+// receiver: await myWallet.GetAddress()
+// );
+// Console.WriteLine($"Onramp link: {preparedOnramp.Link}");
+// Console.WriteLine($"Full onramp quote and steps data: {JsonConvert.SerializeObject(preparedOnramp, Formatting.Indented)}");
+
+// while (true)
+// {
+// var onrampStatus = await bridge.Onramp_Status(id: preparedOnramp.Id);
+// Console.WriteLine($"Full Onramp Status: {JsonConvert.SerializeObject(onrampStatus, Formatting.Indented)}");
+// if (onrampStatus.StatusType is StatusType.COMPLETED or StatusType.FAILED)
+// {
+// break;
+// }
+// await ThirdwebTask.Delay(5000);
+// }
+
+// if (preparedOnramp.IsSwapRequiredPostOnramp())
+// {
+// // Execute additional steps that are required post-onramp to get to your token, manually or via the Execute extension
+// var receipts = await bridge.Execute(myWallet, preparedOnramp);
+// Console.WriteLine($"Onramp receipts: {JsonConvert.SerializeObject(receipts, Formatting.Indented)}");
+// }
+// else
+// {
+// Console.WriteLine("No additional steps required post-onramp, you can use the tokens directly!");
+// }
#endregion
@@ -763,68 +761,6 @@
#endregion
-#region Buy with Fiat
-
-// // Supported currencies
-// var supportedCurrencies = await ThirdwebPay.GetBuyWithFiatCurrencies(client);
-// Console.WriteLine($"Supported currencies: {JsonConvert.SerializeObject(supportedCurrencies, Formatting.Indented)}");
-
-// // Get a Buy with Fiat quote
-// var fiatQuoteParamsWithProvider = new BuyWithFiatQuoteParams(fromCurrencySymbol: "USD", toAddress: walletAddress, toChainId: "137", toTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, toAmount: "20", preferredProvider: "STRIPE");
-// var fiatQuoteParams = new BuyWithFiatQuoteParams(fromCurrencySymbol: "USD", toAddress: walletAddress, toChainId: "137", toTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, toAmount: "20");
-// var fiatOnrampQuote = await ThirdwebPay.GetBuyWithFiatQuote(client, fiatQuoteParams);
-// Console.WriteLine($"Fiat onramp quote: {JsonConvert.SerializeObject(fiatOnrampQuote, Formatting.Indented)}");
-
-// // Get a Buy with Fiat link
-// var onRampLink = ThirdwebPay.BuyWithFiat(fiatOnrampQuote);
-// Console.WriteLine($"Fiat onramp link: {onRampLink}");
-
-// // Open onramp link to start the process (use your framework's version of this)
-// var psi = new ProcessStartInfo { FileName = onRampLink, UseShellExecute = true };
-// _ = Process.Start(psi);
-
-// // Poll for status
-// var currentOnRampStatus = OnRampStatus.NONE;
-// while (currentOnRampStatus is not OnRampStatus.ON_RAMP_TRANSFER_COMPLETED and not OnRampStatus.ON_RAMP_TRANSFER_FAILED)
-// {
-// var onRampStatus = await ThirdwebPay.GetBuyWithFiatStatus(client, fiatOnrampQuote.IntentId);
-// currentOnRampStatus = Enum.Parse(onRampStatus.Status);
-// Console.WriteLine($"Fiat onramp status: {JsonConvert.SerializeObject(onRampStatus, Formatting.Indented)}");
-// await Task.Delay(5000);
-// }
-
-#endregion
-
-#region Buy with Crypto
-
-// // Swap Polygon MATIC to Base ETH
-// var swapQuoteParams = new BuyWithCryptoQuoteParams(
-// fromAddress: walletAddress,
-// fromChainId: 137,
-// fromTokenAddress: Constants.NATIVE_TOKEN_ADDRESS,
-// toTokenAddress: Constants.NATIVE_TOKEN_ADDRESS,
-// toChainId: 8453,
-// toAmount: "0.1"
-// );
-// var swapQuote = await ThirdwebPay.GetBuyWithCryptoQuote(client, swapQuoteParams);
-// Console.WriteLine($"Swap quote: {JsonConvert.SerializeObject(swapQuote, Formatting.Indented)}");
-
-// // Initiate swap
-// var txHash3 = await ThirdwebPay.BuyWithCrypto(wallet: privateKeyWallet, buyWithCryptoQuote: swapQuote);
-// Console.WriteLine($"Swap transaction hash: {txHash3}");
-
-// // Poll for status
-// var currentSwapStatus = SwapStatus.NONE;
-// while (currentSwapStatus is not SwapStatus.COMPLETED and not SwapStatus.FAILED)
-// {
-// var swapStatus = await ThirdwebPay.GetBuyWithCryptoStatus(client, txHash3);
-// currentSwapStatus = Enum.Parse(swapStatus.Status);
-// Console.WriteLine($"Swap status: {JsonConvert.SerializeObject(swapStatus, Formatting.Indented)}");
-// await Task.Delay(5000);
-// }
-
-#endregion
-
#region Storage Actions
// // Will download from IPFS or normal urls
diff --git a/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.cs b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.cs
index 331a6f7..733cbc6 100644
--- a/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.cs
+++ b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.cs
@@ -1,4 +1,5 @@
using System.Numerics;
+using System.Text;
using Newtonsoft.Json;
namespace Thirdweb.Bridge;
@@ -139,25 +140,30 @@ public async Task Buy_Prepare(
throw new ArgumentException("receiver is not a valid address", nameof(receiver));
}
- var url = $"{Constants.BRIDGE_API_URL}/v1/buy/prepare";
- var queryParams = new Dictionary
+ var requestBody = new
{
- { "originChainId", originChainId.ToString() },
- { "originTokenAddress", originTokenAddress },
- { "destinationChainId", destinationChainId.ToString() },
- { "destinationTokenAddress", destinationTokenAddress },
- { "buyAmountWei", buyAmountWei.ToString() },
- { "sender", sender },
- { "receiver", receiver },
- { "maxSteps", maxSteps.ToString() },
- { "purchaseData", purchaseData != null ? JsonConvert.SerializeObject(purchaseData) : null }
+ originChainId = originChainId.ToString(),
+ originTokenAddress,
+ destinationChainId = destinationChainId.ToString(),
+ destinationTokenAddress,
+ buyAmountWei = buyAmountWei.ToString(),
+ sender,
+ receiver,
+ maxSteps,
+ purchaseData
};
- url = AppendQueryParams(url, queryParams);
- var response = await this._httpClient.GetAsync(url).ConfigureAwait(false);
+ var url = $"{Constants.BRIDGE_API_URL}/v1/buy/prepare";
+
+ var jsonBody = JsonConvert.SerializeObject(requestBody, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+
+ var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
+ var response = await this._httpClient.PostAsync(url, content).ConfigureAwait(false);
_ = response.EnsureSuccessStatusCode();
+
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject>(responseContent);
+
return result.Data;
}
@@ -280,25 +286,30 @@ public async Task Sell_Prepare(
throw new ArgumentException("receiver is not a valid address", nameof(receiver));
}
- var url = $"{Constants.BRIDGE_API_URL}/v1/sell/prepare";
- var queryParams = new Dictionary
+ var requestBody = new
{
- { "originChainId", originChainId.ToString() },
- { "originTokenAddress", originTokenAddress },
- { "destinationChainId", destinationChainId.ToString() },
- { "destinationTokenAddress", destinationTokenAddress },
- { "sellAmountWei", sellAmountWei.ToString() },
- { "sender", sender },
- { "receiver", receiver },
- { "maxSteps", maxSteps.ToString() },
- { "purchaseData", purchaseData != null ? JsonConvert.SerializeObject(purchaseData) : null }
+ originChainId = originChainId.ToString(),
+ originTokenAddress,
+ destinationChainId = destinationChainId.ToString(),
+ destinationTokenAddress,
+ sellAmountWei = sellAmountWei.ToString(),
+ sender,
+ receiver,
+ maxSteps,
+ purchaseData
};
- url = AppendQueryParams(url, queryParams);
- var response = await this._httpClient.GetAsync(url).ConfigureAwait(false);
+ var url = $"{Constants.BRIDGE_API_URL}/v1/sell/prepare";
+
+ var jsonBody = JsonConvert.SerializeObject(requestBody, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+
+ var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
+ var response = await this._httpClient.PostAsync(url, content).ConfigureAwait(false);
_ = response.EnsureSuccessStatusCode();
+
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject>(responseContent);
+
return result.Data;
}
@@ -353,23 +364,28 @@ public async Task Transfer_Prepare(
throw new ArgumentException("receiver is not a valid address", nameof(receiver));
}
- var url = $"{Constants.BRIDGE_API_URL}/v1/transfer/prepare";
- var queryParams = new Dictionary
+ var requestBody = new
{
- { "chainId", chainId.ToString() },
- { "tokenAddress", tokenAddress },
- { "transferAmountWei", transferAmountWei.ToString() },
- { "sender", sender },
- { "receiver", receiver },
- { "feePayer", feePayer },
- { "purchaseData", purchaseData != null ? JsonConvert.SerializeObject(purchaseData) : null }
+ chainId = chainId.ToString(),
+ tokenAddress,
+ transferAmountWei = transferAmountWei.ToString(),
+ sender,
+ receiver,
+ feePayer,
+ purchaseData
};
- url = AppendQueryParams(url, queryParams);
- var response = await this._httpClient.GetAsync(url).ConfigureAwait(false);
+ var url = $"{Constants.BRIDGE_API_URL}/v1/transfer/prepare";
+
+ var jsonBody = JsonConvert.SerializeObject(requestBody, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+
+ var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
+ var response = await this._httpClient.PostAsync(url, content).ConfigureAwait(false);
_ = response.EnsureSuccessStatusCode();
+
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject>(responseContent);
+
return result.Data;
}
@@ -411,33 +427,32 @@ public async Task Onramp_Prepare(
throw new ArgumentException("receiver is not a valid address", nameof(receiver));
}
- var url = $"{Constants.BRIDGE_API_URL}/v1/onramp/prepare";
- var queryParams = new Dictionary
+ var requestBody = new
{
- { "onramp", onramp.ToString().ToLower() },
- { "chainId", chainId.ToString() },
- { "tokenAddress", tokenAddress },
- { "amount", amount },
- { "receiver", receiver },
- { "purchaseData", purchaseData != null ? JsonConvert.SerializeObject(purchaseData) : null },
- { "onrampTokenAddress", onrampTokenAddress },
- { "onrampChainId", onrampChainId?.ToString() },
- { "currency", currency },
- { "maxSteps", maxSteps?.ToString() }
+ onramp = onramp.ToString().ToLower(),
+ chainId = chainId.ToString(),
+ tokenAddress,
+ amount,
+ receiver,
+ onrampTokenAddress,
+ onrampChainId = onrampChainId?.ToString(),
+ currency,
+ maxSteps,
+ excludeChainIds = excludeChainIds != null && excludeChainIds.Count > 0 ? excludeChainIds.Select(id => id.ToString()).ToList() : null,
+ purchaseData
};
- if (excludeChainIds != null && excludeChainIds.Count > 0)
- {
- queryParams.Add("excludeChainIds", string.Join(",", excludeChainIds));
- }
+ var url = $"{Constants.BRIDGE_API_URL}/v1/onramp/prepare";
- url = AppendQueryParams(url, queryParams);
+ var jsonBody = JsonConvert.SerializeObject(requestBody, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
- var response = await this._httpClient.GetAsync(url).ConfigureAwait(false);
+ var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
+ var response = await this._httpClient.PostAsync(url, content).ConfigureAwait(false);
_ = response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject>(responseContent);
+
return result.Data;
}