SageTrader.psm1


Class ChiaAsset {
    [string]$name
    [string]$id
    [Uint64]$denom
    [string]$tibet_liquidity_asset_id
    [string]$tibet_pair_id
    [string]$code
    [UInt64]$amount
    [decimal]$formatted_amount
    [Uint16]$fee_tenthousandths
    [UInt64]$fee_collected

    
    ChiaAsset(){}

    ChiaAsset([PSCustomobject]$props) {
        $this.Init([PSCustomObject]$props)
    }


    [ordered]makeRequestJson(){
        $amt = $this.amount + $this.fee_collected
        if($this.id -eq "xch"){
            $json = [ordered]@{
                xch = $amt
                cats= @()
                nfts = @()
            }    
        } else {
            $json = [ordered]@{
                xch = 0
                cats = @(
                    [ordered]@{
                        asset_id = ($this.id)
                        amount = $amt
                    }
                )
                nfts = @()
            }
        }
        
        return $json
    }

    [PSCustomObject]makeOfferJson(){
        $amt = $this.amount - $this.fee_collected
        if($this.id -eq "xch"){
            $json = [ordered]@{
                xch = $amt
                cats= @()
                nfts = @()
            }    
        } else {
            $json = [ordered]@{
                xch = 0
                cats = @(
                    [ordered]@{
                        asset_id = ($this.id)
                        amount = $amt
                    }
                )
                nfts = @()
            }
        }
        
        return $json
    }
    

    [ChiaAsset] Clone(){
        $clone = Get-ChiaAsset -id $this.id
        $clone.amount = $this.amount
        $clone.fee_tenthousandths = $this.fee_tenthousandths
        $clone.fee_collected = $this.fee_collected
        
        return $clone
    }



    [Quote] Quote(){
        if(
        ($null -eq $this.amount) -OR 
        ($this.amount -eq 0) -OR 
        ($null -eq $this.tibet_pair_id) -OR
        ($this.id -eq "XCH"))
        {
            return $null
        }

        return [Quote]::new((Get-DexieQuote -from ($this.id) -to xch -from_amount ($this.amount)).quote)
    }

    [void] Init([PSCustomobject]$props) {
        if(-not $null -eq $props.name){
            $this.name = $props.name
        }

        $this.id = $props.id
        $this.denom = [UInt64]$props.denom
        if(-not $null -eq $props.tibet_liquidity_asset_id){
            $this.tibet_liquidity_asset_id = $props.tibet_liquidity_asset_id
        }
        if(-not $null -eq $props.tibet_pair_id){
            $this.tibet_pair_id = $props.tibet_pair_id
        }
        $this.code = $props.code
        $this.fee_tenthousandths = $props.fee_tenthousandths
        $this.fee_collected = $props.fee_collected
        $this.formatted_amount = $props.formatted_amount
        $this.amount = $props.amount
        
    }

    [void] setFee([decimal]$fee){
        if($this.amount -ge 1){
            $this.fee_tenthousandths = $fee * 10000
            $this.fee_collected = [UInt64]($this.amount * $fee)
        } else {
            Write-SpectreHost -Message "[red]Cannot set fee for asset with amount less than 1.[/]"
        }
    }

    [void] addFeeToAmount(){
        $this.amount += $this.fee_collected
    }
    [void] removeFeeFromAmount(){
        $this.amount -= $this.fee_collected
    }

    [PSCustomObject] getDexieDetails(){
        $uri = "https://dexie.space/v1/assets?type=all&filter=$($this.id)"
        try{
            $response = Invoke-RestMethod -Uri $uri -Method Get 
            if($response -and $response.assets){
                return $response.assets | Where-Object { ($_.id -eq $this.id) -or ($_.code -eq $this.code) 
                }
            } else {
                Write-SpectreHost -Message "[red]No asset details found for ID: $($this.id)[/]"
                return $null
            }
        }
        catch {
            Write-SpectreHost -Message "[red]Failed to fetch asset details from Dexie: $($_.Exception.Message)[/]"
            return $null
        }
    }

    [void] setAmount([decimal]$amt){
        $this.amount = $amt * $this.denom
    }

    [void] setAmountFromMojo([UInt64]$mojo){
        $this.amount = $mojo
    }

    [PSCustomObject] getSimpleQuote() {
        
        if($this.id -eq "XCH"){
            return $null
        }
        $buy = Get-DexieQuote -from ($this.id) -to xch -to_amount 1000000000000
        $sell = Get-DexieQuote -from xch -to ($this.id) -from_amount 1000000000000

        $qbuy = [Quote]::new($buy.quote)
        $qsell = [Quote]::new($sell.quote)
        $avg_price = [Math]::Round(($qbuy.price + $qsell.price) / 2, 3)
        return [PSCustomObject]@{
            buy_quote = $qbuy
            sell_quote = $qsell
            avg_price = $avg_price
        }
    }

    [decimal] getFormattedAmount() {
        if($this.amount -eq 0){
            return 0
        }
        $this.formatted_amount = $this.amount / $this.denom
        return $this.formatted_amount
    }

    [UInt64] getBalance() {
        if($this.id -eq "XCH"){
            $xch = Get-SageSyncStatus
            return $xch.balance
        }
        $cat = Get-SageCat -asset_id $this.id
        return $cat.balance
    }

    [decimal] getFormattedBalance() { 
        return ($this.getBalance()/$this.denom)
    }

    [bool] canCoverAmount() {
        $balance = $this.getBalance()
        if($null -eq $balance) {
            Write-SpectreHost -Message "[red]Failed to retrieve balance for asset ID: $($this.id)[/]"
            return $false
        }
        if($this.amount -gt $balance) {
            Write-SpectreHost -Message "[red]Insufficient balance for asset ID: $($this.id). Required: $($this.getFormattedAmount()), Available: $([decimal]$balance / $this.denom)[/]"
            return $false
        }
        return $true
    }

    [void]setAmountInteractive(){
        $max = $this.getFormattedBalance()
        [decimal]$amt = Read-SpectreText -Message "Enter the amount of $($this.code) you want use? (max: $($max))"
        if($this.code -eq "XCH"){
            $match = '^\d+(\.\d{1,12})?$'
            $message = "[red]Invalid amount. Please enter a valid number with up to 12 decimal places.[/]"
        } else {
            $match = '^\d+(\.\d{1,3})?$'
            $message = "[red]Invalid amount. Please enter a valid number with up to 3 decimal places.[/]"
        }
        if($amt -gt $max){
                $message = "
[red] Insufficient funds available Please enter an amount equal to or lower than $($max).[/]"

            }
        
        if($amt -match $match -and $amt -le $max){
            
            $this.setAmount([decimal]$amt)
            Write-SpectreHost -Message "[green]Amount set to $($this.getFormattedAmount()) $($this.name)[/]"
        } else {
            Write-SpectreHost -Message $message
            $this.setAmountInteractive()
        }
    }
    
    [Quote] getQuote(){
        $tmp_asset = Get-ChiaSwapAssets -asset_id $this.id
        if($null -eq $tmp_asset){
            Write-SpectreHost -Message "[red]Asset cannot be swapped at dexie[/]"
            return $null
        }
        return [Quote]::new((Get-DexieQuote -from $this.id -to xch -from_amount $this.amount))
    }
}


Class Quote {
    [ChiaAsset]$from
    [ChiaAsset]$to
    [UInt64]$suggested_tx_fee
    [UInt64]$combination_fee
    [decimal]$price
    [PSObject]$sageoffer
    [UInt64]$transaction_fee

    Quote(){}

    Quote([PSCustomObject]$Props){
        $this.Init([PSCustomObject]$Props)
    }


    Build(){
        $offer = Build-SageOffer
        if($this.from.id -eq "xch"){
            $offer.offerXch($this.from.amount)
            $offer.requestCat($this.to.id, $this.to.amount)
        } else {
            $offer.offerCat($this.from.id, $this.from.amount)
            $offer.requestXch($this.to.amount)
        }
        $this.sageoffer = $offer
    }

    [void] summary(){
        Write-SpectreHost -Message "
        [green]Quote Summary[/]
        From: [red]$($this.from.getFormattedAmount())[/] - $($this.from.name)
        To: [green]$($this.to.getFormattedAmount())[/] - $($this.to.name)
         
 
        Price: $($this.price)
         
        "

    }



    [void] Init([PSCustomobject]$props) {
    
        $this.from = (Get-ChiaAsset -id ($props.from))
        $this.from.setAmountFromMojo([UInt64]$props.from_amount)
        $this.to = (Get-ChiaAsset -id ($props.to))
        $this.to.setAmountFromMojo([UInt64]$props.to_amount)
        
        if($props.from -eq "XCH"){
            $this.price = [Math]::Round($this.to.getFormattedAmount() / $this.from.getFormattedAmount(),3)
        } else {
            $this.price = [Math]::Round($this.from.getFormattedAmount() / $this.to.getFormattedAmount(),3)
        }

        $this.suggested_tx_fee = [UInt64]$props.suggested_tx_fee
        $this.combination_fee = [UInt64]$props.combination_fee
        
    }
    
}



Class ChiaDCABot{
    [string]$id
    [string]$name
    [ChiaAsset]$offered_asset
    [ChiaAsset]$requested_asset
    [UInt64]$minutes_between_trades
    [decimal]$minimum_price
    [decimal]$maximum_price
    [UInt64]$max_token_spend
    [UInt64]$current_token_spend
    [bool]$active
    [datetime]$last_trade_time
    [datetime]$last_attemted_trade_time
    [datetime]$next_trade_time
    [array]$trade_history
    [uint64]$default_fee
    [uint64]$fingerprint
    

    ChiaDCABot(){
        $this.id = [Guid]::NewGuid().ToString()
        $this.last_attemted_trade_time = Get-Date
        $this.last_trade_time = Get-Date
        $this.next_trade_time = Get-Date
        $this.active = $false
        $this.trade_history = @()   
        $this.default_fee = 0    
        $this.current_token_spend = 0 
    }



    ChiaDCABot([PSCustomobject]$props) {$this.Init([PSCustomObject]$props)}

    [void] destroy(){
        $path = Get-SageTraderPath("DCABots")
        $path = Join-Path -Path $path -ChildPath "$($this.id).json"
        
        $check = Read-SpectreConfirm -Message "Are you sure you want to delete this bot?" -DefaultAnswer "n"
        if($check -eq $true){
            if(Test-Path -Path $path){
                Remove-Item -Path $path -Force
                Write-SpectreHost -Message "[green]Bot deleted successfully.[/]"
            } else {
                Write-SpectreHost -Message "[red]Bot not found.[/]"
            }
        } else {
            Write-SpectreHost -Message "[yellow]Bot deletion cancelled.[/]"
        }
    }

    [void] Init([PSCustomobject]$props)  {
        $this.id = $props.id
        $this.name = $props.name
        $this.offered_asset = [ChiaAsset]::new($props.offered_asset)
        $this.requested_asset = [ChiaAsset]::new($props.requested_asset)
        $this.minutes_between_trades = [UInt64]$props.minutes_between_trades
        $this.minimum_price = [decimal]$props.minimum_price
        $this.maximum_price = [decimal]$props.maximum_price
        $this.active = $props.active
        $this.last_trade_time = [datetime]::Parse($props.last_trade_time)
        $this.last_attemted_trade_time = [datetime]::Parse($props.last_attemted_trade_time)
        $this.next_trade_time = [datetime]::Parse($props.next_trade_time)
        $this.default_fee = [UInt64]$props.default_fee
        $this.fingerprint = [UInt64]$props.fingerprint
        $this.max_token_spend = [UInt64]$props.max_token_spend
        $this.current_token_spend = [UInt64]$props.current_token_spend
    }



    [Quote] GetQuote(){
        $quote = Get-DexieQuote -from $this.offered_asset.id -to $this.requested_asset.id -from_amount $this.offered_asset.amount
        if ($null -eq $quote) {
            Write-SpectreHost -Message "[red]Failed to get a quote. Please check your assets and try again.[/]"
            return $null
        }
        return [Quote]::new($($quote.quote))
    }

    [bool] hasValidBalance(){
        if($this.max_token_spend -gt 0){
            Write-SpectreHost -message "[green]Bot [/][blue]$($this.name)[/][green] has spent [/][Magenta2_1]$($this.current_token_spend) / $($this.max_token_spend)[/]."
        } else {
            Write-SpectreHost -message "[green]Bot [/][blue]$($this.name)[/][green] has spent [/][Magenta2_1]$($this.current_token_spend)[/]."
        }
        
        $ballance = $this.offered_asset.getBalance()
        if($ballance -lt $this.offered_asset.amount){
            Write-SpectreHost -Message "[red]Insufficient balance for bot[/][blue] $($this.name)[/][red]. You need at least [/][green]$($this.offered_asset.getFormattedAmount()) $($this.offered_asset.name)[/][red] to run this bot.[/]"
            return $false
        }

        if(($this.max_token_spend -ne 0) -and (($this.current_token_spend + $this.offered_asset.amount) -gt $this.max_token_spend)){
            Write-SpectreHost -Message "
            [red]Bot [/][blue]$($this.name)[/][red] has reached the maximum token spend of [/][green]$($this.max_token_spend)[/].
            [red]Disabling the bot.[/]"

            $this.deactivate()
            return $false
        }

        return $true
    }

    [bool] isLoggedIn(){
        $fp = (Invoke-SageRPC -endpoint get_key -json @{})
        if($null -eq $fp){
            Write-SpectreHost -Message "[red]Bot [/][blue]$($this.name)[/][red] does not have access to this wallet.
            Please log in with the fingerprint: [/][blue]$($this.fingerprint)[/]"

            return $false
        }
        if($fp.key.fingerprint -eq $this.fingerprint){
            return $true
        }
        Write-SpectreHost -Message "
        [red]Bot [/][blue]$($this.name)[/][red] does not have access to this wallet.
        Please log in with the fingerprint: [/][blue]$($this.fingerprint)[/]"

        return $false
    }

    [void] activate(){
        $this.active = $true
        $this.save()
    }

    [void] deactivate(){
        $this.active = $false
        $this.save()
    }

    [void] runNow(){
        $this.next_trade_time = Get-Date
        $this.Handle()
    }

    [void] login(){
        if(-not $this.isLoggedIn()){
            try{
                    Connect-SageFingerprint -fingerprint ($this.fingerprint)    
                
            }
            catch {
                Write-SpectreHost -Message "[red]Failed to connect to Sage with fingerprint $($this.fingerprint). Please check your Sage configuration.[/]"
                return
            }
        } else {
            Write-SpectreHost -Message "[green]Already logged in with fingerprint $($this.fingerprint).[/]"
        }
    }

    [bool] hasValidTradeTime(){
        $now = Get-Date
        
        if($this.next_trade_time -gt $now){
            Write-SpectreHost -Message "[yellow]Bot [/][blue]$($this.name)[/][yellow] is not ready to trade yet. Next trade time is $($this.next_trade_time).[/]"
            return $false
        }
        return $true
    }

        [void] showMenu(){
        $choice = 0
        do{
                Write-SpectreHost -message ($this.summary())

        Write-SpectreHost -Message "
[cyan]BOT MENU
---------------------------------
1. $($this.active ? "[red]Deactivate Bot[/]" : "[green]Activate Bot[/]")
2. Destroy Bot
 
9. Back to main menu
[/]
 
Choose an option
        "

$choices = @(1,2,9)
$choice = Read-ValidMenu -choices $choices -message "Select an option:"

    switch ($choice) {
        1 {
            if ($this.active) {
                $this.deactivate()
                Write-SpectreHost -Message "[red]Bot [/][blue]$($this.name)[/] [red]is now deactivated.[/]"
                
            } else {
                $this.activate()
                Write-SpectreHost -Message "[green]Bot [/][blue]$($this.name)[/] [green]is now active.[/]"
                
            }
        }
        2 {
            $this.destroy()
        }
    }
    } until ($choice -eq "9")


    Write-SpectreHost -Message "[green]Returning to main menu...[/]"
}


    [void] Save(){
        $path = Get-SageTraderPath("DCABots")
        $file = Join-Path -Path $path -ChildPath "$($this.id).json"
        if(-not (Test-Path -Path $path)){
            New-Item -Path $path -ItemType Directory | Out-Null
        }
        
        $this | ConvertTo-Json -Depth 10 | Out-File -FilePath $file -Encoding utf8
    }

[string] summary(){
    $summary = @"
[green]Grid Bot Summary[/]
Name: $($this.name)
ID: $($this.id)
Requested Asset: $($this.requested_asset.code) - $($this.requested_asset.getFormattedAmount())
Offered Asset: $($this.offered_asset.code) - $($this.offered_asset.getFormattedAmount())
Minimum Price: $($this.minimum_price)
Maximum Price: $($this.maximum_price)
Minutes Between Trades: $($this.minutes_between_trades)
Last Trade Time: $($this.last_trade_time)
Last Attempted Trade Time: $($this.last_attempted_trade_time)
Next Trade Time: $($this.next_trade_time)
Max Token Spend: $($this.max_token_spend)
Current Token Spend: $($this.current_token_spend)
 
Active: $($this.active ? "[green]Yes[/]" : "[red]No[/]")
"@

        return $summary
    }


    [void] minisummary(){
        write-SpectreHost -Message "
        This BOT spend $($this.offered_asset.getFormattedAmount()) $($this.offered_asset.name) to buy $($this.requested_asset.name) every $($this.minutes_between_trades) minutes.
 
        "

        if($this.minimum_price -ne 0){
            Write-SpectreHost -Message "This BOT will only trade if the price is above [green]$($this.minimum_price)[/]."
        }
        if($this.maximum_price -ne 0){
            Write-SpectreHost -Message "This BOT will only trade if the price is below [red]$($this.maximum_price)[/]."
        }
    }

    [void] InitialSave(){
        $check = Read-Spectreconfirm -Message "Do you want to save this bot?" -DefaultAnswer "y"
        if($check -eq $true){
            $this.Save()
            Write-SpectreHost -Message "[green]Bot saved successfully.[/]"
        } else {
            Write-SpectreHost -Message "[yellow]Bot not saved.[/]"
        }
    }

    [bool] quoteIsValid([Quote]$quote){
        if($null-eq $quote){
            Write-SpectreHost -Message "[red]Quote is null. Cannot validate.[/]"
            return $false
        }
        if($quote.price -lt $this.minimum_price -and $this.minimum_price -ne 0){
            Write-SpectreHost -Message "[red]Quote price is below the minimum price of $($this.minimum_price).[/]"
            return $false
        }
        if($quote.price -gt $this.maximum_price -and $this.maximum_price -ne 0){
            Write-SpectreHost -Message "[red]Quote price is above the maximum price of $($this.maximum_price).[/]"
            return $false
        }
        return $true
    }

   
    [array] getLog(){
        $path = Get-SageTraderPath("offerlogs")
        $file = Join-Path -Path $path -ChildPath "$($this.id).csv"
        
        if(-not (Test-Path -Path $file)){
            Write-SpectreHost -Message "[red]No logs found for this bot.[/]"
            return @()
        }
        $log = Import-Csv -Path $file
        if($null -eq $log){
            Write-SpectreHost -Message "[red]No logs found for this bot.[/]"
            return @()
        }
        if($log.count -eq 0){
            Write-SpectreHost -Message "[red]No logs found for this bot.[/]"
            return @()
        }
        return $log
    }

    [bool] isActive(){
        if($this.active -eq $true){
            Write-SpectreHost -Message "[green]Bot [/][blue]$($this.name)[/][green] is active.[/]"
            return $true
        } else {
            Write-SpectreHost -Message "[red]Bot [/][blue]$($this.name)[/][red] is not active.[/]"
            return $false    
        }
        
    }
    

    [void] Handle(){
        
        
        if($this.isActive() -and $this.isLoggedIn() -and $this.hasValidBalance() -and $this.hasValidTradeTime()){
            Write-SpectreHost -Message "[green]Retrieving Quote for Bot [/][blue]$($this.name)[/][green][/]"
            $quote = $this.GetQuote()
            if($null -eq $quote){
                return
            }
            if(-not $this.quoteIsValid($quote)){
                Write-SpectreHost -Message "[red]Quote is not valid for this bot. Skipping trade.[/]"
                return
            }
            Write-SpectreHost -Message "[gray]
            Offered: [/][green] $($quote.from.getFormattedAmount()) [/][blue]$($quote.from.name)[/]
            [gray]Requested: [/][green] $($quote.to.getFormattedAmount()) [/][blue]$($quote.to.name)[/]
            [gray]Price: [/][green]$($quote.price) [/]
            "

            $quote.Build()
            if($null -eq $quote.sageoffer){
                Write-SpectreHost -Message "[red]Failed to build the offer. Please check your assets and try again.[/]"
                return
            }
            # Adding Default Fee
            $quote.sageoffer.fee = $this.default_fee
            # Create the offer in sage.
            $quote.sageoffer.createoffer()
            write-spectrehost -Message "[green]Offer created successfully.[/]"
            $dexie = Submit-DexieSwap -offer $quote.sageoffer.offer_data.offer
            if(-not $null -eq $dexie){
                Write-SpectreHost -Message "[green]Offer [/][blue] - $($dexie.id) - [/][green] submitted to Dexie successfully.[/]"
                $this.current_token_spend += $quote.from.amount
                $this.last_trade_time = Get-Date
                $this.next_trade_time = $this.last_trade_time.AddMinutes($this.minutes_between_trades)
                $this.last_attemted_trade_time = Get-Date
                $this.save()
            }

            $log = [PSCustomObject]@{
                offer_id = $quote.sageoffer.offer_data.offer_id    
                bot_type = $this.GetType().Name
                bot_id = $this.id
                offered_asset_id = $quote.from.code
                offered_asset_amount = [decimal]($quote.from.getFormattedAmount() * -1)
                requested_asset_id =  $quote.to.code
                requested_asset_amount = [decimal]($quote.to.getFormattedAmount())
                status = "pending"
                created_at = (Get-Date)
                updated_at = (Get-Date)
                fingerprint = $this.fingerprint
                dexie_id = ($dexie.id)
                }
                $this.logOffer($log)

        } 
    }

    [array] showLog(){
        $path = Get-SageTraderPath("offerlogs")
        $file = Join-Path -Path $path -ChildPath "$($this.id).csv"

        if(-not (Test-Path -Path $file)){
            Write-SpectreHost -Message "[red]No logs found for this bot.[/]"
            return @()
        }
        $log = Import-Csv -Path $file
        if($log.count -eq 0){
            Write-SpectreHost -Message "[red]No logs found for this bot.[/]"
            return @()
        }
        return $log
    }

    [void] logOffer($log){
        $path = Get-SageTraderPath("offerlogs")
        $file = Join-Path -Path $path -ChildPath "$($this.id).csv"
        
        if(-not (Test-Path -Path $path)){
            New-Item -Path $path -ItemType Directory | Out-Null
        }
        if(-not (Test-Path -Path $file)){
            $log | Export-Csv -Path $file -NoTypeInformation
        } else {
            $log | Export-Csv -Path $file -NoTypeInformation -Append
        }

    }
}


function Get-SageTraderPath() {
    [CmdletBinding()]
    param(
        [string]$subfolder = $null
    )
    <#
    .SYNOPSIS
    Get the path to the SageTrader folder.
     
    .DESCRIPTION
    Returns the path to the SageTrader folder, optionally including a subfolder.
     
    .PARAMETER subfolder
    The subfolder to include in the path. If not specified, returns the main SageTrader folder.
     
    .EXAMPLE
    Get-SageTraderPath -subfolder "DCABots"
     
    Returns the path to the DCABots subfolder within the SageTrader folder.
     
    #>

    
    if($isWindows){
        if(-not (Test-Path -Path "$env:LOCALAPPDATA\SageTrader")){
            New-Item -Path "$env:LOCALAPPDATA\SageTrader" -ItemType Directory | Out-Null
        }
        if($null -eq $subfolder){
            return "$env:LOCALAPPDATA\SageTrader"
        }
        if(-not (Test-Path -Path "$env:LOCALAPPDATA\SageTrader\$subfolder")){
            New-Item -Path "$env:LOCALAPPDATA\SageTrader\$subfolder" -ItemType Directory | Out-Null
        }
        return "$env:LOCALAPPDATA\SageTrader\$subfolder"
    } 

    if($IsLinux){
        if(-not (Test-Path -Path "$HOME/.local/share/SageTrader")){
            New-Item -Path "$HOME/.local/share/SageTrader" -ItemType Directory | Out-Null
        }
        if($null -eq $subfolder){
            return "$HOME/.local/share/SageTrader"
        }
        if(-not (Test-Path -Path "$HOME/.local/share/SageTrader/$subfolder")){
            New-Item -Path "$HOME/.local/share/SageTrader/$subfolder" -ItemType Directory | Out-Null
        }
        return "$HOME/.local/share/SageTrader/$subfolder"
    }
    if($IsMacOS){
        if(-not (Test-Path -Path "$HOME/Library/Application Support/SageTrader")){
            New-Item -Path "$HOME/Library/Application Support/SageTrader" -ItemType Directory | Out-Null
        }
        if($null -eq $subfolder){
            return "$HOME/Library/Application Support/SageTrader"
        }
        if(-not (Test-Path -Path "$HOME/Library/Application Support/SageTrader/$subfolder")){
            New-Item -Path "$HOME/Library/Application Support/SageTrader/$subfolder" -ItemType Directory | Out-Null
        }
        return "$HOME/Library/Application Support/SageTrader/$subfolder"
    }

    
}

function Get-ChiaAsset {
    <#
    .SYNOPSIS
    Get a specific Chia Asset by code or id.
    .DESCRIPTION
    Retrieves a specific Chia Asset by its code or id.
    .PARAMETER id
    This can be either the code or the id of the asset.
    .EXAMPLE
    Get-ChiaAsset -Code "XCH"
    Retrieves the Chia Asset with the code "XCH".
 
    .EXAMPLE
    Get-ChiaAsset -Id "fa4a180ac326e67ea289b869e3448256f6af05721f7cf934cb9901baa6b7a99d"
 
    Retrieves the Chia Asset with the specified id ().
    .NOTES
    This function retrieves a Chia Asset from the local assets.json file.
     
 
 
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]$id
    )

    $assets = Get-ChiaAssets
    return $assets | Where-Object { $_.id -eq $id -or $_.code -eq $id }
}

function Sync-ChiaAssets{
    
    $path = Get-SageTraderPath
    $file = Join-Path -Path $path -ChildPath "assets.json"


    $page = 1
    $assets = Get-DexieAssets -page_size 100 -page $page -cats
    $tokens = @()
    $pairs = Invoke-RestMethod -uri "https://api.v2.tibetswap.io/pairs?skip=0&limit=10000" -Method Get
    $xch = @{}
    $xch.name = "XCH"
    $xch.code = "XCH"
    $xch.id = "xch"
    $xch.denom = 1000000000000
    
    $assetarray = @()
    $assetarray += @($xch)
    while ($tokens.count -lt $assets.count){
        foreach ($asset in $assets.assets){
            $token= @{}
            $token.name = $asset.name
            $token.code = $asset.code
            $token.id = $asset.id
            $token.denom = $asset.denom
            $pair = $pairs | Where-Object { $_.asset_id -eq $asset.id}
            if($pair){
                $token.tibet_pair_id = $pair.launcher_id
                $token.tibet_liquidity_asset_id = $pair.liquidity_asset_id
            }
            $assetarray += @($token)
        }
        
        $page++
        $assets = Get-DexieAssets -page_size 100 -page $page -cats
    }
    
    $assetarray | ConvertTo-Json -Depth 10 | Out-File -FilePath $file -Encoding utf8

}

function Get-ChiaAssets {
    <#
    .SYNOPSIS
    Get a list of all Chia Assets.
     
    .DESCRIPTION
    Gets an array of all Chia Assets.
    .EXAMPLE
    Get-ChiaAssets
     
    Retrieves and displays the list of Chia assets.
     
     
    #>

    $path = Get-SageTraderPath
    $file = Join-Path -Path $path -ChildPath "assets.json"
    
    if(-not (Test-Path -Path $file)){
        Sync-ChiaAssets
    }
    
    
    $assets = Get-Content -Path $file | ConvertFrom-Json
    $assetList = @()
    Foreach ($asset in $assets){
        $asset = [ChiaAsset]::new($asset)
        $assetList += $asset
    }

    return $assetList
}

function Sync-ChiaSwapAssets {
    <#
    .SYNOPSIS
    Sync Chia Swap Assets.
     
    .DESCRIPTION
    This function syncs the Chia Swap assets by fetching them from the API and saving them to a local file.
     
    .EXAMPLE
    Sync-ChiaSwapAssets
     
    Syncs the Chia Swap assets and saves them to a local file.
     
    #>

    $path = Get-SageTraderPath
    $file = Join-Path -Path $path -ChildPath "swapassets.json"
   
    Write-SpectreHost -Message "[green]Syncing Chia Swap Assets...[/]"

    
    $uri = 'https://api.dexie.space/v1/swap/tokens'

    $response = Invoke-RestMethod -Uri $uri -Method Get
    if ($response -and $response.tokens) {
        $tokens = $response.tokens 
        $tokens | ConvertTo-Json -Depth 10 | Out-File -FilePath $file -Encoding utf8
    } else {
        Write-Host "Failed to retrieve Chia Swap assets."
    } 
}

function Get-ChiaSwapAssets {
    <#
    .SYNOPSIS
    Get a list of all Chia Swap Assets.
     
    .DESCRIPTION
    Gets an array of all Chia Swap Assets.
    .EXAMPLE
    Get-ChiaSwapAssets
     
    Retrieves and displays the list of Chia Swap assets.
     
     
    #>

    $path = Get-SageTraderPath
    $file = Join-Path -Path $path -ChildPath "swapassets.json"
    if(-not (Test-Path -Path $file)){
        Sync-ChiaSwapAssets
    }

    
    $assets = Get-Content -Path $file | ConvertFrom-Json
    $assetList = @()
    Foreach ($asset in $assets){
        $assetObj = [ChiaAsset]::new($asset)
        $assetList += $assetObj
    }

    return $assetList
}

function New-ChiaBot {
    do{
    Clear-Host
    Write-SpectreFigletText -Text "Create a New Chia Bot" -Color green Center
    
    Write-SpectreHost -message "
What type of bot do you want to create?
 
1. Dollar Cost Averaging
2. Grid Trading
 
9. Back to main menu
    "


$choice = Read-ValidMenu -choices @(1,2,9) -message "Select a bot type:"


    switch ($choice) {
        1 {
            New-ChiaDCABot
            $choice = 9 # Exit the loop after creating a DCA bot
        }
        2 {
            New-ChiaGridBot
            $choice = 9 # Exit the loop after creating a Grid Trading bot
        }
        
    }
} while ($choice -ne 9)
    Clear-Host
    return
    
}

function Select-ChiaBotAnswer{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]$bot
    )
    switch ($bot) {
        "Dollar Cost Averaging" {
            New-ChiaDCABot
        }
        "Grid Trading" {
            New-ChiaGridBot
        }
        default {
            Write-Host "Invalid selection. Please choose a valid bot type."
        }
    }
    pause
}



function New-ChiaDCABot{
    $bot = [ChiaDCABot]::new()
    $direction_choice = ''
    Clear-Host
    Write-SpectreFigletText -Text "New DCA Bot" -Color green Center
    Write-SpectreHost -Message "Select enter the asset you want to Dollar Cost Average.
     
    "

    $asset = Select-ChiaSwapAsset
    $direction = Read-SpectreSelection -Message "Do you want to [green]Buy (XCH->$($asset.code))[/] or [red]Sell ($($asset.code)->XCH)[/] this $($asset.name)?" -Choices @("[green]Buy[/]", "[red]Sell[/]") 
    if ($direction -eq "[green]Buy[/]") {
        $direction_choice = 'buy'
        $requested_asset = $asset
        $offered_asset = (Get-ChiaAsset -id "xch")
    } else {
        $direction_choice = 'sell'
        $requested_asset = (Get-ChiaAsset -id "xch")
        $offered_asset = $asset
    }
    

    $amount = Read-SpectreText -Message "How much $($($offered_asset).name) do you want to spend each time?" -DefaultAnswer "0.1"
    $offered_asset.setAmount([decimal]$amount)
    
    $bot.offered_asset = $offered_asset
    $bot.requested_asset = $requested_asset



    $quote = Get-DexieQuote -from $offered_asset.id -to $requested_asset.id -from_amount $offered_asset.amount
    if ($null -eq $quote) {
        Write-SpectreHost -Message "[red]Failed to get a quote. Please check your assets and try again.[/]"
        return
    }
    $dexie_quote = [Quote]::new($($quote.quote))
    Clear-Host
    Write-SpectreHost -Message "The current price for [blue]$($offered_asset.name)[/] to [blue]$($requested_asset.name)[/] is [green]$($dexie_quote.price)[/].
    "

    $restrict = Read-SpectreConfirm -Message "Do you want to restrict this bot to a specific price range?" -DefaultAnswer "n"
    if($restrict -eq $true){
        Write-SpectreHost -Message "
        You are $($direction_choice)ing [blue]$($offered_asset.getFormattedAmount()) $($offered_asset.code)[/] for [blue]$($requested_asset.code)[/].
 
        If buying (receive CAT), you want to set a [red]minimum price[/].
        [gray]Example: If the price is 10 wUSDC.b/XCH, you want to sent a minimum of 10.0 so you don't send XCH when you don't receive as much CAT.[/]
         
        If selling (sending CAT), you want to set a [green]maximum price[/]
        [gray]Example: If the price is 10 wUSDC.b/XCH, you want to set a maximum of 10.0 so you don't send more CAT when you don't receive as much XCH.[/]
 
        THE CURRENT PRICE IS [green]$($dexie_quote.price)[/].
        "

        if($direction_choice -eq 'buy'){
            $bot.minimum_price = Get-MinPrice
            $bot.maximum_price = 0
        } else {
            $bot.minimum_price = 0
            $bot.maximum_price = Get-MaxPrice
        }
        
    } 

    $max_spend = Get-MaxTokenSpend -asset $offered_asset
    if ($max_spend -gt 0) {
        $bot.max_token_spend = $max_spend
        Write-SpectreHost -Message "This bot will not spend more than [purple]$($max_spend / $offered_asset.denom)[/] $($offered_asset.name) in total."
    } else {
        $bot.max_token_spend = 0
        Write-SpectreHost -Message "This bot will spend [purple]unlimited[/] $($offered_asset.name)."
    }

    $bot.minutes_between_trades = Get-MinutesBetweenTrades
    Clear-Host
    $bot.minisummary()
    $bot.name = Read-SpectreText -Message "What do you want to name this bot?" -DefaultAnswer "My DCA Bot"
    $bot.default_fee = Get-ChiaDefaultFee
    $bot.fingerprint = Get-ChiaFingerprint
    $bot.InitialSave()
    $act = Read-SpectreConfirm -Message "Do you want to activate this bot now?" -DefaultAnswer "y"
    if($act -eq $true){
        $bot.activate()
        Write-SpectreHost -Message "[green]Bot [/][blue]$($bot.name)[/] [green]is now active.[/]"
    } else {
        Write-SpectreHost -Message "[yellow]Bot [/][blue]$($bot.name)[/] [yellow]is not active. You can activate it later.[/]"
    }
    
}


function Get-ChiaFingerprint {
    $fingerprints = Get-SageKeys

    $fingerprint = Read-SpectreSelection -Message "Authorize Bot to access specific fingerprint." -Choices ($fingerprints.name) -EnableSearch -SearchHighlightColor purple
    return ($fingerprints | Where-Object { $_.name -eq $fingerprint }).fingerprint
}



function Connect-ChiaFingerprint {
    $fingerprints = Get-SageKeys

    $fingerprint = Read-SpectreSelection -Message "Select which wallet to log into." -Choices ($fingerprints.name) -EnableSearch -SearchHighlightColor purple
    $selected_fingerprint = ($fingerprints | Where-Object { $_.name -eq $fingerprint }).fingerprint
    if ($null -eq $selected_fingerprint) {
        Write-SpectreHost -Message "[red]No fingerprint selected. Please try again.[/]"
        return Connect-ChiaFingerprint
    }
    try {
        Connect-SageFingerprint -fingerprint $selected_fingerprint
        Write-SpectreHost -Message "[green]Successfully connected to fingerprint $selected_fingerprint.[/]"
    } catch {
        Write-SpectreHost -Message "[red]Failed to connect to fingerprint $selected_fingerprint. Please check your Sage configuration.[/]"
    }
}

function Format-ChiaAssetBalance {
    Get-SageBalances
}

function Get-SageBalances{
    param(
        [switch]$cats_only
    )
    $data = @()
    if(-not $cats_only.IsPresent){
         $xch = Get-SageSyncStatus
        if ($xch -and $xch.balance) {
            $xch_balance = [decimal]($xch.balance / 1000000000000)
            $data += [pscustomobject]@{
                Image = "https://icons.dexie.space/xch.webp"
                Asset = "XCH"
                Balance = $xch_balance
            }
        } 
    }
   
    $cats = Get-SageCats | Sort-Object -Property balance -Descending
    if ($cats -and $cats.Count -gt 0) {
        foreach ($cat in $cats) {
            if($cat.balance -gt 0) {
                $balance = [decimal]($cat.balance / 1000)
                $data += [pscustomobject]@{
                    Image = ($cat.icon_url)
                    Asset = $cat.ticker
                    Balance = $balance
                }
            } 
            
        }
    }
    return $data
}

function Show-SageBalanceTable{
    $fp = Get-SageKey
    Get-SageBalances | Select-Object -Property Asset, Balance | Out-ConsoleGridView -Title "Sage Assets for fingerprint: $($fp.fingerprint)" -OutputMode Single
    
}

function Get-ChiaDefaultFee{
    $asset = Get-ChiaAsset -id "xch"
    [decimal]$fee = Get-SpectreNumber -Message "What is the default fee for this bot? (0 for no fee)" -DefaultAnswer "0.00005" -numberOfDecimals 12
    
    if ($fee -match '^\d+(\.\d{1,12})?$') {
        if($fee -gt 0.1){
            $confirm = Read-SpectreConfirm -Message "You are setting a high fee of $fee XCH. Are you sure you want to continue?" -DefaultAnswer "n"
            if($confirm -eq $false){
                return Get-ChiaDefaultFee
            }
        
        }
        
    } else {
        Write-SpectreHost -Message "[red]Invalid input. Please enter a valid number.[/]"
        return Get-ChiaDefaultFee 
    }
    return $asset.denom * $fee
}

function Get-MinutesBetweenTrades {

    $minutes = Read-SpectreText -Message "How many [blue]minutes[/] between trades?" 
    if ($minutes -match '^\d+$') {
        return [int]$minutes
    } else {
        Write-SpectreHost -Message "[red]Invalid input. Please enter a valid number.[/]"
        return Get-MinutesBetweenTrades 
    }
}

function Get-MaxTokenSpend {
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [ChiaAsset]$asset
    )
    $max_spend = Read-SpectreText -Message "What is the maximum [blue]$($asset.code)[/] this bot can spend in total? (0 for no limit)" -DefaultAnswer "0"
    if ($max_spend -match '^\d+(\.\d{1,12})?$') {
        return ([UInt64]$max_spend * $asset.denom)
    } else {
        Write-SpectreHost -Message "[red]Invalid input. Please enter a valid number.[/]"
        return Get-MaxTokenSpend 
    }
}

function Get-MinPrice {
    $min_price = Read-SpectreText -Message "What is the [red]minimum price[/] you are willing to accept for this trade?" -AllowEmpty 
    if ($min_price -eq '') {
        return 0
    }
    if ($min_price -match '^\d+(\.\d{1,3})?$') {
        return [decimal]$min_price
    } else {
        Write-SpectreHost -Message "[red]Invalid input. Please enter a valid number.[/]"
        return Get-MinPrice 
    }
}

function Get-MaxPrice {
    $max_price = Read-SpectreText -Message "What is the [green]maximum price[/] you are willing to pay for this trade?" -AllowEmpty 
    if ($max_price -eq '') {
        return 0
    }
    if ($max_price -match '^\d+(\.\d{1,3})?$') {
        return [decimal]$max_price
    } else {
        Write-SpectreHost -Message "[red]Invalid input. Please enter a valid number.[/]"
        return Get-MaxPrice 
    }
}

function Get-XCHInput {
    $xch = Read-SpectreText -Message "How much [purple]XCH[/] do you want to spend per trade?" -DefaultAnswer "0.1"
    if ($xch -match '^\d+(\.\d{1,12})?$') {
        return [decimal]$xch
    } else {
        Write-SpectreHost -Message "[red]Invalid input. Please enter a valid number.[/]"
        return Get-XCHInput
    }
}

function Select-ChiaSwapAsset {
    $assets = Get-ChiaSwapAssets
    
    $result = Read-SpectreSelection -Message "Select a [purple]Chia Asset[/]" -Choices ($assets.code ) -EnableSearch -SearchHighlightColor purple
    $asset = Get-ChiaAsset -id $result
    return $asset
}

function Get-ChiaBots {
    $bots = @()
    $dcabots = Get-ChiaDCABots
    $gridbots = Get-ChiaGridbots
    
    $dcabots | ForEach-Object {$bots += $_}
    $gridbots | ForEach-Object {$bots += $_}

    

    return $bots
}
   
function Get-ChiaDCABots {
    $bots = @()
    
    $path = Get-SageTraderPath("DCABots")
    if(-not (Test-Path -Path $path)){
        
        return
    }
    $files = Get-ChildItem -Path $path -Filter "*.json"
    if($files.Count -eq 0){
        
        return
    }
    foreach ($file in $files) {
        $bot = Get-Content -Path $file.FullName | ConvertFrom-Json
        $bots += [ChiaDCABot]::new($bot)
    }
    return $bots
}

function Get-ChiaGridbots(){
    $bots = @()
    $path = Get-SageTraderPath("GridBots")
    if(-not (Test-Path -Path $path)){
        
        return
    }
    $files = Get-ChildItem -Path $path -Filter "*.json"
    if($files.Count -eq 0){
        return
    }
    foreach ($file in $files) {
        $bot = Get-Content -Path $file.FullName | ConvertFrom-Json
        $bots += [GridBot]::new($bot)
    }
    return $bots

}

function Get-ChiaOfferLog {
    <#
    .SYNOPSIS
    Get the Chia Offer Log.
 
    .DESCRIPTION
    Retrieves the Chia Offer Log from the local file.
 
    .EXAMPLE
    Get-ChiaOfferLog
 
    Retrieves and displays the Chia Offer Log.
    #>

    $path = Get-SageTraderPath("offerlogs")
    $file = Join-Path -Path $path -ChildPath "offers.csv"


    if(-not (Test-Path -Path $file)){
        Write-SpectreHost -Message "[red]No offer logs found.[/]"
        return @()
    }
    
    return Import-Csv -Path $file
}

function Update-ChiaOfferLog {
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [array]$logs
    )

}

function New-ChiaOfferLog{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]$bot_type,
        [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true)]
        [string]$bot_id,
        [Parameter(Mandatory = $true, Position = 2, ValueFromPipeline = $true)]
        [string]$offered_asset_id,
        [Parameter(Mandatory = $true, Position = 3, ValueFromPipeline = $true)]
        [Int128]$offered_asset_amount,
        [Parameter(Mandatory = $true, Position = 4, ValueFromPipeline = $true)]
        [string]$requested_asset_id,   
        [Parameter(Mandatory = $true, Position = 5, ValueFromPipeline = $true)]
        [Int128]$requested_asset_amount,
        [Parameter(Mandatory = $true, Position = 6, ValueFromPipeline = $true)]
        [string]$status,
        [Parameter(Mandatory = $true, Position = 7, ValueFromPipeline = $true)]
        [datetime]$created_at,
        [Parameter(Mandatory = $true, Position = 8, ValueFromPipeline = $true)]
        [datetime]$updated_at,
        [Parameter(Mandatory = $true, Position = 9, ValueFromPipeline = $true)]
        [string]$offer_id,
        [Parameter(Mandatory = $true, Position = 10, ValueFromPipeline = $true)]
        [string]$fingerprint,
        [Parameter(Mandatory = $true, Position = 11, ValueFromPipeline = $true)]
        [string]$dexie_id
    )

    $log = [PSCustomObject]@{
        bot_type = $bot_type
        bot_id = $bot_id
        offered_asset_id = $offered_asset_id
        offered_asset_amount = $offered_asset_amount
        requested_asset_id = $requested_asset_id
        requested_asset_amount = $requested_asset_amount
        status = $status
        created_at = $created_at
        updated_at = $updated_at
        offer_id = $offer_id
        fingerprint = $fingerprint
        dexie_id = $dexie_id
    }
    $path = Get-SageTraderPath("offerlogs")
    $file = Join-Path -Path $path -ChildPath "offers.csv"
    
    if(-not (Test-Path -Path $file)){
        $log | Export-Csv -Path $file -NoTypeInformation
    } else {
        $log | Export-Csv -Path $file -NoTypeInformation -Append
    }

}

function Start-Bots {
    $choice = 0
    do{
        Write-SpectreHost -Message "[purple] $(Get-Date) [/]"
        Write-SpectreRule -Color purple
        $bots = Get-ChiaBots
        if($null -eq $bots){
            Write-SpectreHost -Message "[red]No bots found.[/]"
            pause
            return
        }
        foreach ($bot in $bots) {
            Write-Information "Starting bot: $($bot.name)"
            $bot.Handle()
        }
        Write-SpectreHost -Message "[green]All bots have been processed. Waiting for the next cycle...[/]"
        Write-SpectreRule -Color purple
        $choice = Read-SpectreText -Message "To exit, press [red]Q ↲ [/]" -TimeoutSeconds 60
    } until ($choice -eq 'Q' -or $choice -eq 'q')
    Start-SageTrader
}


function Get-SageTraderConfig{
    $path = Get-SageTraderPath -subfolder config
    $file = Join-Path -Path $path -ChildPath "config.json"
    if(-not (Test-Path -Path $file)){
        $config = [PSCustomObject]@{
             colors = @{
                default = "cornsilk1"
                info = "aqua"
                warning = "yellow2"
                danger = "maroon"
                primary = "dodgerblue3"
             }
        } | ConvertTo-Json -Depth 20 | Out-File -FilePath $file
    } 
    $config = Get-Content -Path $file | ConvertFrom-Json 
    return $config
}






function Read-ValidMenu{
    param(
        [Int16[]]$choices,
        [string]$message
    )
    $choice = Read-SpectreText -Message $message
    if($null -ne ($choice -as [int16])){
        if([Int16]$choice -in $choices){
            return [Int16]$choice
        }
    }
    
    Read-ValidMenu -choices $choices -message $message
}



function Get-ChiaBot{
    param($name)
    $bots = Get-ChiaBots
    if ($null -eq $bots) {
        Write-SpectreHost -Message "[red]No Chia bots found.[/]"
        return $null
    }
    $bot = $bots | Where-Object { $_.name -eq $name -or $_.id -eq $name }
    if ($null -eq $bot) {
        Write-SpectreHost -Message "[red]Bot with name '$name' not found.[/]"
        return $null
    }
    return $bot
}

function Test-SageRunning(){
    try{
        Test-SageRPC
    } catch {
        Write-SpectreHost -Message "[red]Sage RPC is not running. Please start Sage first.[/]"
        return $false
    }
}

function Start-SageTrader{
    if(-not (Get-SagePfxCertificate)){
        Write-SpectreHost -Message "
[red]No Sage PFX certificate found. Please create one first.[/]
 
[yellow]You can create a PFX certificate by running the command: New-SagePfxCertificate[/]
"

    }
    do{
        Show-STHeader

        Write-SpectreHost -Message "
    1. Market Order
    2. Create Bot
    3. Show Bots
    4. Run Bots
     
    [grey0]5. (Coming Soon: Dexie Search)[/]
 
    9. Exit
 
    "

        $choices = @(1,2,3,4,9)
        $choice = Read-ValidMenu -choices $choices -message "Select an option:"
        
        switch ($choice) {
            1 { Start-MarketOrder}
            2 { New-ChiaBot }
            3 { Show-Bots }
            4 { Start-Bots }

        }
    } until ($choice -eq 9)
}

function Start-MarketOrder {
        do{
        Clear-Host
        $fp = (Get-SageKey).fingerprint
        Write-SpectreFigletText -Text "Market Orders" -Color "darkseagreen" 
        Write-SpectreRule -LineColor green -Title "[green]Fingerprint: [/]$($fp)" -Alignment Center

    Write-SpectreHost -Message "
 
1. Sell CAT for XCH
2. Buy CAT with XCH
 
9. Back to main menu
 
        "

        $choices = @(1,2,9)
        $choice = Read-ValidMenu -choices $choices -message "Select an option:"
        
        switch ($choice) {
            1 { Start-CatSellForXCH 
                $choice = 9 # Exit the loop after selling CAT for XCH}

            }
            2 { Start-CatBuyWithXCH
                $choice = 9 # Exit the loop after buying CAT with XCH
            }
            9 { return }
            default { Write-SpectreHost -Message "[red]Invalid choice. Please try again.[/]" }
        }
    } while ($choice -ne 9)
    Clear-Host
}

function Start-CatBuyWithXCH {
    Clear-Host
    $myCats = Get-ChiaSwapAssets | Select-Object -Property @{Name="Asset";Expression={$_.code}}, name | Out-ConsoleGridView -Title "Select an Asset to Buy" -OutputMode Single
    if ($null -eq $myCats) {
        Write-SpectreHost -Message "[red]No assets found. Please create a CAT first.[/]"
        Pause
        return
    }
    $method = @(
        [pscustomobject]@{Name="Spend a fixed amount of XCH"; Value="fixed"},
        [pscustomobject]@{Name="Acquire a specific amount of CAT"; Value="specific"}
    ) | Out-ConsoleGridView -Title "Select a Method" -OutputMode Single

    if($method.Value -eq "fixed"){
        $xch = Get-ChiaAsset -id "xch"
        $amount = Get-SpectreNumber -message "How much [green]XCH[/] do you want to [red]Spend[/] to buy $($myCats.name)? (max: $($xch.getFormattedBalance()))" -numberOfDecimals 3
        if ($amount -le 0) {
            Write-SpectreHost -Message "[yellow]Cancelling the Buy.[/]"
            Pause
            return
        }
        $asset = Get-ChiaAsset -id $myCats.Asset
        
        $quote = Get-DexieQuote -from "xch" -to ($asset.id) -from_amount ($xch.denom * $amount)
    } else {
        $amount = Get-SpectreNumber -message "How much [purple]$($myCats.Asset)[/] do you want to buy?" -numberOfDecimals 3
        if ($amount -le 0) {
            Write-SpectreHost -Message "[yellow]Cancelling the Buy.[/]"
            Pause
            return
        }
        $asset = Get-ChiaAsset -id $myCats.Asset
        $quote = Get-DexieQuote -from "xch" -to $asset.id -to_amount ($asset.denom * $amount)
    }
    

    if ($null -eq $quote) {
        Write-SpectreHost -Message "[red]Failed to get a quote. Please check your assets and try again.[/]"
        return
    }
    $dexie_quote = [Quote]::new($($quote.quote))
    $dexie_quote.summary()
    $confirm = Read-SpectreConfirm -Message "Do you want to spend [purple]$($dexie_quote.from.getFormattedAmount())[/] [purple]$($dexie_quote.from.code)[/] for [green]$($dexie_quote.to.getFormattedAmount()) $($dexie_quote.to.code)[/] ?" -DefaultAnswer "n"

    if ($confirm -eq $true){
        Write-SpectreHost -message "Building Offer..."
        $dexie_quote.Build()
        Write-SpectreHost -Message "Submitting to Dexie..."
         $dexie_quote.sageoffer.createoffer()

        $submit = Submit-DexieSwap -offer $dexie_quote.sageoffer.offer_data.offer
        if($submit){
            Write-SpectreHost -Message "[green]Offer submitted successfully![/]"
            Write-SpectreHost -Message "[green]Offer ID: https://dexie.space/offers/$($submit.id)[/]"
            Pause
        }
    } else {
        Write-SpectreHost -Message "[yellow]Cancelled the Buy.[/]"
        Pause
        return
    }
}

function Start-CatSellForXCH {
    Clear-Host
    $myCats = Get-SageBalances -cats_only | Select-Object -Property Asset, Balance | Out-ConsoleGridView -Title "Select an Asset to Sell" -OutputMode Single
    if ($null -eq $myCats) {
        Write-SpectreHost -Message "[red]No assets found. Please create a CAT first.[/]"
        Pause
        return
    }
    $amount = Get-SpectreNumber -message "How much [purple]$($myCats.Asset)[/] do you want to sell? (max: $($myCats.Balance))" -numberOfDecimals 3
 
    if ($amount -le 0) {
        Write-SpectreHost -Message "[yellow]Cancelling the Sell.[/]"
        Pause
        return
    }
    $asset = Get-ChiaAsset -id $myCats.Asset
    $quote = Get-DexieQuote -from $asset.id -to "xch" -from_amount ($asset.denom * $amount)
    if ($null -eq $quote) {
        Write-SpectreHost -Message "[red]Failed to get a quote. Please check your assets and try again.[/]"
        return
    }
    $dexie_quote = [Quote]::new($($quote.quote))
    $dexie_quote.summary()
    $confirm = Read-SpectreConfirm -Message "Do you want to sell [purple]$($amount)[/] [purple]$($asset.code)[/] for [green]$($dexie_quote.to.getFormattedAmount())[/] XCH?" -DefaultAnswer "n"
    if ($confirm -eq $true){
        Write-SpectreHost -message "Building Offer..."
        $dexie_quote.Build()
        Write-SpectreHost -Message "Submitting to Dexie..."
        $dexie_quote.sageoffer.createoffer()

        $submit = Submit-DexieSwap -offer $dexie_quote.sageoffer.offer_data.offer
        if($submit){
            Write-SpectreHost -Message "[green]Offer submitted successfully![/]"
            Write-SpectreHost -Message "[green]Offer ID: https://dexie.space/offers/$($submit.id)[/]"
            Pause
        }
    } else {
        Write-SpectreHost -Message "[yellow]Cancelled the Sell.[/]"
        Pause
        return
    }
}



function Show-Bots{
    
    $bots = Get-ChiaBots
    if ($null -eq $bots) {
        Write-SpectreHost -Message "[yellow]No bots found. Please Create a bot first.[/]"
        Pause
        return
    }
    $display = @()
    $bots | ForEach-Object {
        $disp = [PSCustomObject]@{
            type = ($_.GetType().Name)
            name = ($_.name)
            active = ($_.active)
            id = ($_.id)
        }
        $display += $disp
    }
    $display | Out-ConsoleGridView -Title Bots -OutputMode Single | Show-BotMenu
}

function Show-BotMenu {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        $selection
    )
    Clear-Host
    $bot = Get-ChiaBot -name ($selection.id)
    if ($null -eq $bot) {
        Write-SpectreHost -Message "[red]Bot not found.[/]"
        return
    }
    $bot.showMenu()
    
}

function New-ChiaGridBot{

    clear-host
    Write-SpectreFigletText -Text "Grid Bot: Wizard" -Color "darkseagreen" 
    Write-SpectreHost -Message "
[darkturquoise]
A grid bot is a trading method where you create a spread between a two assets,
and trade those assets between two price ranges. This bot is designed to
be asymetrical. Meaning you can trade uneven amounts of each token.
 
 
Trading is done as an X/Y pair. This bot is opinionated in what the price for
a trading pair is. The bot will always use the following formulas for price:
 
 
[seagreen2]Y: Token Y[/]
[deepskyblue1]X: Token X[/]
[yellow]P: Price[/]
 
 
[seagreen2]Y[/] [white]/[/] [deepskyblue1]X[/] [white]=[/] [yellow]P[/]
[yellow]P[/] [white]*[/] [deepskyblue1]X[/] [white]=[/] [seagreen2]Y[/]
[yellow]P[/] [white]/[/] [seagreen2]Y[/] [white]=[/] [deepskyblue1]X[/]
[/]
    "


    $type = Read-SpectreSelection -Message "
[darkturquoise]What trading pair type will you create?[/]"
 -Choices @("XCH-CAT","CAT-CAT") 
    
    if($type -eq "XCH-CAT"){

        $token_x = Get-ChiaAsset -id "xch"
        $token_y = Select-ChiaAsset -cats_only -title "SELECT A TOKEN TO TRADE"

    } else {
        $token_y = Select-ChiaAsset -cats_only -title "SELECT A TOKEN Y TO TRADE"
        $token_y = Select-ChiaAsset -cats_only -title "SELECT A TOKEN X TO TRADE"
        if($token_x.id -eq $token_y.id){
            Write-SpectreHost -Message "
[yellow]You cannot create a bot with the same token for both sides. Please try again.
[/]"

            return New-ChiaGridBot
        }
    }
    $token_y.setAmountInteractive()
    $token_x.setAmountInteractive()
    
    
    if($token_x.code -eq 'xch'){
        $current_price = $token_y.getSimpleQuote()
        
        if($null -eq $current_price){
            Write-SpectreHost "[red]Failed to fetch current price."
            $starting_price = Get-SpectreNumber -message "
            [green]
Price[/] = [yellow]$($token_y.code)[/] / [blue]$($token_x.code)[/]
Enter the current price of the pair:
"
 -numberOfDecimals 3     
            
            } else {
                Write-SpectreHost "Current price is for $($token_y.code) / $($token_x.code) is [green]$($current_price.avg_price)[/]"
                $starting_price = Get-SpectreNumber -message "
[green]
Price[/] = [yellow]$($token_y.code)[/] / [blue]$($token_x.code)[/]
Enter the current price of the pair:"
 -numberOfDecimals 3 -DefaultAnswer $current_price.avg_price
            }
    } else {
        $x_price = $token_x.getSimpleQuote()
        $y_price = $token_y.getSimpleQuote()
        if($null -eq $x_price -or $null -eq $y_price){
            Write-SpectreHost "[red]Failed to fetch current price."
            $starting_price = Get-SpectreNumber -message "
[green]
Price[/] = [yellow]$($token_y.code)[/] / [blue]$($token_x.code)[/]
Enter the current price of the pair:"
 -numberOfDecimals 3    
        } else {
            $avg_price = [Math]::Round(($y_price.avg_price / $x_price.avg_price),3)
            
            
            Write-SpectreHost -Message "
$($token_y.code): $($y_price.avg_price) per XCH
$($token_x.code): $($x_price.avg_price) per XCH
---------------------------------
price: $($avg_price)
 
"


$starting_price = Get-SpectreNumber -message "
[green]
Price[/] = [yellow]$($token_y.code)[/] / [blue]$($token_x.code)[/]
Enter the current price of the pair:"
 -numberOfDecimals 3 -DefaultAnswer $avg_price
            
        }
    }
    
    
    

    $min_price = Get-SpectreNumber -message "Enter the low price of range:" -numberOfDecimals 3 -DefaultAnswer $([Math]::round($starting_price *.9,3))
    $max_price = Get-SpectreNumber -message "Enter the high price of range:" -numberOfDecimals 3 -DefaultAnswer $([Math]::round($starting_price * 1.1,3))
    $step = Get-SpectreNumber -message "
[gray]
The more steps you have the more opportunities to trade
[/]
 
Enter the number of steps you want to create:"
 -numberOfDecimals 0
# Calculate amount of X needed.

    
    
$fee_percentage = Get-SpectreNumber -message "
[gray]
This is the fee's you'll collect for providing liquidity.
The fee is applied to each side of the spread.
[/]
Enter the spread percentage: (#.###)"
 -numberOfDecimals 3 -DefaultAnswer 0.003

    $fee_percentage = $fee_percentage / 2


    Write-SpectreHost -Message "
Token X: [blue]$($token_x.code)[/]
Token Y: [blue]$($token_y.code)[/]
"


$fee_token = Read-SpectreSelection -Message "What token will you pay the fee in?" -Choices @("token_x","token_y") 
    if($fee_token -eq "token_x"){
        $fee_id = $token_x.id
    } else {
        $fee_id = $token_y.id
    }

       

    $confirm = Read-SpectreConfirm -Message "
[green]Confirm Bot Details
 
Token X: [blue]$($token_x.getFormattedAmount()) $($token_x.code)[/]
Token Y: [blue]$($token_y.getFormattedAmount()) $($token_y.code)[/]
Steps: [cyan1]$step[/]
Min Price: [lightcoral]$min_price[/]
Current Price: [darkorange3]$starting_price[/]
Max Price: [maroon]$max_price[/]
[/]
    "

    
    if(-NOT $confirm){
        Write-Host "Bot creation cancelled."
        Start-SageTrader
    }
    [decimal]$transaction_fee = Get-SpectreNumber -Message "Blockchain transaction fee? No fee is suggested as it complicates coin management." -DefaultAnswer 0 -numberOfDecimals 12
    $name = Read-SpectreText -Message "What name do you want to use for this bot?" -DefaultAnswer "$($token_x.code)->$($token_y.code)"
    $fingerprint = Get-ChiaFingerprint

    $bot = [GridBot]::new()
    $bot.name = $name
    $bot.token_x = $token_x
    $bot.token_y = $token_y
    $bot.starting_price = $starting_price
    $bot.min_price = $min_price
    $bot.max_price = $max_price
    $bot.steps = $step
    $bot.fee_percentage = $fee_percentage
    $bot.fee_token_id = $fee_id
    $bot.fingerprint = $fingerprint
    $bot.transaction_fee = $transaction_fee
    $bot.BuildYGrid()
    $bot.BuildXGrid()
    $bot.save()
    
    Write-SpectreHost -Message "
[green]Created bot with ID: $($bot.id)
 [/] "

 return
}

function Select-ChiaAsset{
    param(
        [string]$title = "Choose an asset",
        [switch]$cats_only
    )
    if($cats_only.IsPresent){
        $assets = Get-ChiaAssets | Where-Object {$_.id -ne 'xch'}
    } else {
        $assets = Get-ChiaAssets 
    }

    $choice = $assets | Select-Object -Property code,name,id | Out-ConsoleGridView -Title $title -OutputMode Single

    if($choice){
        return Get-ChiaAsset -id ($choice.id)
    } else {
        Select-ChiaAsset
    }

}

function Get-ValidChiaToken{
    param(

        [string]$message,
        [string]$DefaultAnswer
    )

    $token = Read-SpectreText -Message $message -DefaultAnswer $DefaultAnswer
    $asset = Get-ChiaAsset -id $token
    if($null -eq $asset){
        Write-Host "Invalid token ID. Please try again."
        return Get-ValidChiaToken
    }
    if($asset.count -gt 1){
        Write-Host "Multiple assets found with the same code. Please copy the ID of the asset you want and paste it below"
        Write-Host "----------------------"
        $asset | ForEach-Object {
            $_
            Write-Host "----------------------"
         }
        return Get-ValidChiaToken
    }
    return $asset
}

function Show-STHeader{
    param(
        [string]$title="Sage-Trader"
    )
    try{
        $fp = (Get-SageKey).fingerprint
        Clear-Host
        Write-SpectreFigletText -Text $title -Alignment Center -Color green
        Write-SpectreRule -LineColor green -Title "[green]Fingerprint: [/]$($fp)" -Alignment Center
        Write-SpectreHost -Message "
         
        "

    
    } 
    catch {
        Write-SpectreHost -Message "
[red]Could not retrieve Sage Fingerprint. [/]
 
[yellow]Make sure you have Sage Wallet Installed and the RPC is running.[/]
Visit: [blue]https://themayor.gitbook.io/xchplayground/[/] for more information.
        "

        break;
    }
   
    
}

function Get-SpectreNumber{
    param(
        [Parameter(Mandatory=$true)]
        [string]$message,
        
        [Parameter(Mandatory=$true)]
        [Int16]$numberOfDecimals,
        $DefaultAnswer
    )
    if($null -eq $DefaultAnswer){
        $dinput = Read-SpectreText -Message $message
    } else {
        $dinput = Read-SpectreText -Message $message -DefaultAnswer $DefaultAnswer
    }
    
    if($numberOfDecimals -lt 1){
        $match = '^\d+$'
    } else {
        $match = '^\d+(\.\d{1,'+"$($numberOfDecimals)"+'})?$'
    }
    
    if($dinput -match $match){
        return [decimal]$dinput
    } else {
        
        Write-Host "Invalid input. Please enter a valid number with up to $numberOfDecimals decimal places."
        return Get-SpectreNumber -message $message  -numberOfDecimals $numberOfDecimals
    }
}


class GridBot{
    [string]$id
    [string]$name
    [ChiaAsset]$token_x
    [ChiaAsset]$token_y
    [UInt64]$starting_x_amount
    [UInt64]$starting_y_amount
    [UInt64]$current_x_amount
    [UInt64]$current_y_amount
    [decimal]$starting_price
    [decimal]$min_price
    [decimal]$max_price
    [int]$steps
    [array]$grid
    [array]$active_offers
    [array]$completed_offers    
    [UInt64]$transaction_fee    
    [string]$fingerprint
    [array]$cancelled_offers
    [decimal]$fee_percentage
    [UInt64]$x_fee_collected
    [UInt64]$y_fee_collected
    [string]$fee_token_id
    [bool]$isPrepped
    [bool]$active
    
    
    

    GridBot(){
        $this.id = (New-Guid).Guid
        $this.active = $false
        $this.isPrepped = $false
        $this.grid = @()
        $this.starting_x_amount = 0
        $this.starting_y_amount = 0
        $this.current_x_amount = 0
        $this.current_y_amount = 0
        $this.x_fee_collected = 0
        $this.y_fee_collected = 0
    }

    GridBot([PSCustomobject]$props){
        $this.Init([PSCustomObject]$props)
        
    }

    [bool] isLoggedIn(){
        $fp = (Invoke-SageRPC -endpoint get_key -json @{})
        if($null -eq $fp){
            Write-SpectreHost -Message "[red]Bot [/][blue]$($this.name)[/][red] does not have access to this wallet.
            Please log in with the fingerprint: [/][blue]$($this.fingerprint)[/]"

            return $false
        }
        if($fp.key.fingerprint -eq $this.fingerprint){
            return $true
        }
        Write-SpectreHost -Message "
        [red]Bot [/][blue]$($this.name)[/][red] does not have access to this wallet.
        Please log in with the fingerprint: [/][blue]$($this.fingerprint)[/]"

        return $false
    }

    [bool] isActive(){
        if($this.active -eq $true){
            Write-SpectreHost -Message "[green]Bot [/][blue]$($this.name)[/][green] is active.[/]"
            return $true
        } else {
            Write-SpectreHost -Message "[red]Bot [/][blue]$($this.name)[/][red] is not active.[/]"
            return $false    
        }
        
    }

    [void] showMenu(){
    $choice=0
    do{
        Clear-Host
        
        Write-SpectreHost -message ($this.summary())

        Write-SpectreHost -Message "
[cyan]BOT MENU
---------------------------------
1. $($this.active ? "[red]Deactivate Bot[/]" : "[green]Activate Bot[/]")
2. $($this.isPrepped ? "[green]Coins are prepped[/]" : "[yellow]Prepare Coins[/]")
3. Make Initial Offers
4. Cancel All Offers
5. Destroy Bot
9. Back to main menu
[/]
 
"


$choices = @(1,2,3,4,5,9)
$choice = Read-ValidMenu -choices $choices -message "Select an option:"

    switch ($choice) {
        1 {
            if ($this.active) {
                $this.deactivate()
                Write-SpectreHost -Message "[red]Bot [/][blue]$($this.name)[/] [red]is now deactivated.[/]"
                
            } else {
                $this.activate()
                Write-SpectreHost -Message "[green]Bot [/][blue]$($this.name)[/] [green]is now active.[/]"
                
            }
        }
        2 {
            try{
                $coins = $this._splitCoins()
                if($coins){
                    Write-SpectreHost -Message "[green]Coins split successfully. It may take up to 5 minutes before you can create the initial offer.[/]"
                } else {
                    Write-SpectreHost -Message "[red]Failed to split coins.[/]"
                }
                pause
            } catch {
                Write-SpectreHost -Message "[red]An error occurred while splitting coins: $($_.Exception.Message)[/]"
                pause
            }
        }
        3 {
            if($this.isPrepped){
                try{
                    $this.makeInitialOffers()
                } catch {
                    Write-SpectreHost -Message "[red]An error occurred while making initial offers: $($_.Exception.Message)[/]"
                    pause
                }
                
            } else {
                Write-SpectreHost -Message "[red]Coins are not prepped for this bot. Please prep them first.[/]"
                pause
            }
            
        }
        4 {
            if($this.active_offers.Count -gt 0){
                $this.CancelOffers()
                $choice = 9
            }
            
        }

        5 {$this.destroy()
            $choice = 9
        }
        }}until ($choice -eq 9)
        Write-SpectreHost -Message "[green]Returning to main menu...[/]"
    }


    [string] summary(){
        $summary = @"
[green]Grid Bot Summary[/]
Name: $($this.name)
ID: $($this.id)
Fingerprint: $($this.fingerprint)
Token X: $($this.token_x.code) - $($this.token_x.getFormattedAmount())
Token Y: $($this.token_y.code) - $($this.token_y.getFormattedAmount())
Starting Price: $($this.starting_price)
Min Price: $($this.min_price)
Max Price: $($this.max_price)
X Fee Collected: $($this.x_fee_collected) $($this.token_x.code)
Y Fee Collected: $($this.y_fee_collected) $($this.token_y.code)
Steps: $($this.steps)
Completed Trades: $($this.completed_offers.Count)
Active Offers: $($this.active_offers.Count)
 
Active: $($this.active ? "[green]Yes[/]" : "[red]No[/]")
"@

        return $summary
    }

    [void] activate(){
        if(-not $this.isPrepped){
            $pre = Read-SpectreConfirm -Message "[yellow]Coins are not prepped for this bot. Do you want to prep them now?[/]" -DefaultAnswer "y"
            if ($pre) {
                $this.forcePrep() 
                Write-SpectreHost -Message "[green]Coins prepared for bot.[/]"
                
            }
        }
        $this.active = $true
        $this.save()
    }

    [void] deactivate(){
        $this.active = $false
        $this.save()
    }

    [void] Handle(){
        $this.checkOffers()
    }

    [void] checkOffers(){
        
        if($this.isActive() -and $this.isLoggedIn()){
            $actives = $this.active_offers
            foreach($active in $actives) {
                $offer = Get-SageOffer -offer_id $active.offer_id
                if($offer.status -eq "completed"){
                    $this.updateLogOffer($active.offer_id,"completed")
                    
                    #remove this offer
                    $completed = @{
                        grid = $this.grid[($active.index)].($active.side)
                        offer_id = ($active.offer_id)
                    }
                    $this.x_fee_collected += $this.grid[($active.index)].x_fee_amount
                    $this.y_fee_collected += $this.grid[($active.index)].y_fee_amount
                    
                    $this.completed_offers += $completed
                    $this.active_offers = $this.active_offers | Where-Object {$_.offer_id -ne $active.offer_id}
                    $index = $active.index
                    $isAsk = ($active.side -eq "ask") ? $true : $false
                    $this.CreateOfferFromGridIndex($index,(-not $isAsk))
                }
                
            }
        }
    }

    [void] Init([PSCustomobject]$props)  {
        $this.id = $props.id
        if($props.token_x){
            $this.token_x = [ChiaAsset]::new($props.token_x)
        }
        if($props.token_y){
            $this.token_y = [ChiaAsset]::new($props.token_y)
        }
        $this.name = $props.name
        $this.starting_price = $props.starting_price
        $this.min_price = $props.min_price
        $this.max_price = $props.max_price
        $this.steps = $props.steps
        $this.transaction_fee = $props.transaction_fee
        $this.fingerprint = $props.fingerprint
        $this.fee_percentage = $props.fee_percentage
        $this.fee_token_id = $props.fee_token_id
        $this.active_offers = $props.active_offers
        $this.completed_offers = $props.completed_offers
        $this.grid = $props.grid
        $this.active = $props.active
        $this.starting_x_amount = $props.starting_x_amount
        $this.starting_y_amount = $props.starting_y_amount
        $this.current_x_amount = $props.current_x_amount
        $this.current_y_amount = $props.current_y_amount
        $this.x_fee_collected = $props.x_fee_collected
        $this.y_fee_collected = $props.y_fee_collected
        $this.isPrepped = $props.isPrepped

    }


    [array] forcePrep(){
        if($this.isPrepped){
            Write-SpectreHost -Message "[yellow]Coins are already prepped for this bot.[/]"
            return $null
        }
        return $this._splitCoins()
    }

    [array] _splitCoins(){
        if($this.isPrepped){
            Write-SpectreHost -Message "[yellow]Coins are already prepped for this bot.[/]"
            pause
            return $null
        }
        if(-not $this.isLoggedIn()){
            
            pause
            return $null
        }
        $array = @()
    
        
        $addresses = Get-SageDerivations -offset 0 -limit ($this.steps*2)
        if($this.token_x.id -eq 'xch' -and $this.token_x.amount -gt 0){
            
            $payments = Build-SageBulkPayments
            1..($this.steps) | ForEach-Object {
                $payments.addXchPayment($addresses[$_].address,($this.token_x.amount/$this.steps))
                }
            $payments.submit()
            $array += ($payments.response )
        } elseif($this.token_x.id -ne 'xch' -and $this.token_x.amount -gt 0){
            $payments = Build-SageBulkPayments
            1..($this.steps) | ForEach-Object {
            $payments.addCatPayment($this.token_x.id,$addresses[$_].address,($this.token_x.amount/$this.steps))
            }
            $payments.submit()
            $array += ($payments.response )
        }
            if($this.token_y.id -eq 'xch' -and $this.token_y.amount -gt 0){
            $payments = Build-SageBulkPayments
            1..($this.steps) | ForEach-Object {
            $payments.addXchPayment($addresses[$_].address,($this.token_y.amount/$this.steps))
            }
            $payments.submit()
            $array += ($payments.response )
        } elseif($this.token_y.id -ne 'xch' -and $this.token_y.amount -gt 0) {
            $payments = Build-SageBulkPayments
            1..($this.steps) | ForEach-Object {
            $payments.addCatPayment($this.token_y.id,$addresses[$_].address,($this.token_y.amount/$this.steps))
            }
            $payments.submit()
            $array += ($payments.response )
        }
        if($array.count -gt 0){
            $this.isPrepped = $true
            $this.save()
        }
        return $array

    }

    [array] prepCoins(){
        if($this.isPrepped){
            Write-SpectreHost -Message "[yellow]Coins are already prepped for this bot.[/]"
            return $null
        }
        $confirm = Read-SpectreConfirm "Do you want to split your coins to run the bot?"
        if(-not $confirm){
            Write-SpectreHost -Message "[yellow]Coins not split.[/]"
            return $null
        }
        return $this._splitCoins()
        
    }

    [void] destroy(){
        $path = Get-SageTraderPath("GridBots")
        $path = Join-Path -Path $path -ChildPath "$($this.id).json"
        
        $check = Read-SpectreConfirm -Message "Are you sure you want to delete this bot?" -DefaultAnswer "n"
        
        if($check -eq $true){
            if($this.isLoggedIn()){
                if(Test-Path -Path $path){
                    $this.CancelOffers()
                    Remove-Item -Path $path -Force
                    Write-SpectreHost -Message "[green]Bot deleted successfully.[/]"
                    
                } else {
                    Write-SpectreHost -Message "[red]Bot not found.[/]"
                }
            } else {
                Write-SpectreHost -Message "[red]Bot [/][blue]$($this.name)[/][red] does not have access to this wallet.
                Please log in with the fingerprint: [/][blue]$($this.fingerprint)[/]"

            }
        } else {
            Write-SpectreHost -Message "[yellow]Bot deletion cancelled.[/]"
        
        }
        
    }

    [void] logOffer($log){
        $path = Get-SageTraderPath("offerlogs")
        $file = Join-Path -Path $path -ChildPath "$($this.id).csv"
        
        if(-not (Test-Path -Path $path)){
            New-Item -Path $path -ItemType Directory | Out-Null
        }
        if(-not (Test-Path -Path $file)){
            $log | Export-Csv -Path $file -NoTypeInformation
        } else {
            $log | Export-Csv -Path $file -NoTypeInformation -Append
        }

    }

    [void] updateLogOffer($offer_id,$status){
        $path = Get-SageTraderPath("offerlogs")
        $file = Join-Path -Path $path -ChildPath "$($this.id).csv"
        $offers = Import-Csv -Path $file
        $offer = $offers | Where-Object {$_.offer_id -eq $offer_id}
        if($offer){
            $offer.status = $status
            $offers | Export-Csv -Path $file -NoTypeInformation
        }
    }

    [void]makeInitialOffers(){
        if($this.isLoggedIn() -and $this.active_offers.Count -eq 0){
            $this.grid | ForEach-Object {
                if($_.index -lt $this.steps){
                    $this.CreateOfferFromGridIndex($_.index,$true)
                } else {
                    $this.CreateOfferFromGridIndex($_.index,$false)
                }
            }
        }
    }

    [void]CreateOfferFromGridIndex([UInt32]$index,[bool]$isAsk){
        
        if($isAsk -eq $true){
            $side = "ask"
        } else {
            $side = "bid"
        }
        
        $addresses = Get-SageDerivations -offset 0 -limit ($this.steps * 2)
        $row = $this.grid | Where-Object {$_.index -eq $index}
        $buildData = $row.$side
        if($null -eq $buildData){
            Write-SpectreHost "[red]Failed to find data for bot[/]"
            return
        }
        $offer = Build-SageOffer
        ($buildData.requested_asset_id -eq "xch") ? $offer.requestXch($buildData.requested_asset_amount) : $offer.requestCat($buildData.requested_asset_id,$buildData.requested_asset_amount)
        ($buildData.offered_asset_id -eq "xch") ? $offer.offerXch($buildData.offered_asset_amount) : $offer.offerCat($buildData.offered_asset_id,$buildData.offered_asset_amount)
        ($this.transaction_fee -gt 0) ? $offer.setFee($this.transaction_fee) : $offer.setFee(0)
        $offer.setReceiveAddress($addresses[$index].address)
        Write-SpectreHost -Message "
        GridBot with ID: [green]$($this.id)[/] is ATTEMPTING to create a(n) [green]$($side)[/] offer from Index: [green]$($index) [/]
        "

        $offer.createoffer()
        
        
        if($offer.offer_data){
            Write-SpectreHost -Message "
        Offer Created Successfully.
            "

            $active_offer = [PSCustomObject]@{
                offer_id = $offer.offer_data.offer_id
                index = $index
                side = $side                
            }
            $this.active_offers += $active_offer
            $this.save()
            $dexie = Submit-DexieOffer -offer $offer.offer_data.offer -claim_rewards

            if(-not $null -eq $dexie){
                Write-SpectreHost -Message "[green]Offer [/][blue] - $($dexie.id) - [/][green] submitted to Dexie successfully.[/]"                
                
            }
            $log = [PSCustomObject]@{
                offer_id = $offer.offer_data.offer_id    
                bot_type = $this.GetType().Name
                bot_id = $this.id
                offered_asset_id = $buildData.offered_asset_id
                offered_asset_amount = $buildData.offered_asset_amount
                requested_asset_id =  $buildData.requested_asset_id
                requested_asset_amount = $buildData.requested_asset_amount
                fee_token_id = $this.fee_token_id
                status = "pending"
                created_at = (Get-Date)
                updated_at = (Get-Date)
                fingerprint = $this.fingerprint
                dexie_id = ($dexie.id)
            }

            $this.logOffer($log)


        }

    }
    
    [pscustomobject] MakeOfferFromGrid($index, $side,[boolean]$submit=$false,[boolean]$add_to_active = $false){


        if($index -lt 0 -or $index -ge $this.grid.count){
            write-host "Index out of range. Please provide a valid index."
            return $null
        }
        if($side -ne "bid" -and $side -ne "ask"){
            write-host "Invalid side specified. Use 'bid' or 'ask'."
            return $null
        }
        $addresses = Get-Sagederivations -limit 100 -offset 0 
        $send_to = $addresses[$index].address
        
        $json = $this.grid[$index].$side
        $json | Add-Member -MemberType NoteProperty -Name "receive_address" -Value $send_to
        
            $offer = Invoke-SageRPC -endpoint make_offer -json $json
            $details = @{
                offer_id = $offer.offer_id
                side = $side
                price = $this.grid[$index].price
                index = $index
            }
            if($submit){
                $this.SubmitOffer($offer.offer_id)
            }
            if($add_to_active){
                $this.active_offers += [pscustomobject]$details
                $this.save()
            }
           
            
            
        return [pscustomobject]$details
    }

    
    CancelOffers(){
        try {
            if($this.isLoggedIn()){
            $this.active_offers | ForEach-Object {
            
                $offer_id = $_.offer_id
                $this.updateLogOffer($offer_id,"cancelled")
                $response = Revoke-SageOffer -offer_id $offer_id
                if($response){
                    write-host "Offer $offer_id cancelled successfully."
                    $this.cancelled_offers += $_
                } else {
                    write-host "Failed to cancel offer $offer_id."
                }
            
                }
            
            $this.save()
            }
        }
        catch {
            Write-SpectreHost -Message "[red]Failed to cancel offers. Please check your connection and try again.[/]"
            Write-SpectreHost -Message "[red]Error: $($_.Exception.Message)[/]"
        }
        
        pause
    }

    BuildXGrid(){
        $step_amount = $this.token_x.getFormattedAmount() / $this.steps
        $step_size = ($this.max_price - $this.starting_price) / ($this.steps-1)
        if($step_amount -eq 0 -OR $step_size -eq 0){
            return
        }
        for ($i = 0; $i -lt $this.steps; $i++){
            $tPrice = [System.Math]::Round($this.starting_price + ($step_size * $i),3)
            [UInt64]$x_amount = (($step_amount * $this.token_x.denom))
            [UInt64]$y_amount = ($tPrice * $step_amount * $this.token_y.denom)
            $x_fee_percentage = (($this.fee_token_id -eq $this.token_x.id) ? ($this.fee_percentage) : 0)
            [UInt64]$x_fee_amount = ($x_fee_percentage * $x_amount)
            $y_fee_percentage = (($this.fee_token_id -eq $this.token_y.id) ? ($this.fee_percentage) : 0)
            [UInt64]$y_fee_amount = ($y_fee_percentage * $y_amount)
            
            $row = [pscustomobject]@{
                x_fee_percentage = $x_fee_percentage
                y_fee_percentage = $y_fee_percentage
                x_fee_amount = $x_fee_amount
                y_fee_amount = $y_fee_amount
                index = ($i+$this.steps)
                price = [decimal]$tPrice
                x_code = $this.token_x.code
                x_amount = $x_amount
                y_code = $this.token_y.code
                y_amount = $y_amount
                ask = [ordered]@{
                    requested_asset_id = $this.token_x.id
                    requested_asset_amount = ($x_amount + $x_fee_amount)
                    offered_asset_id = $this.token_y.id
                    offered_asset_amount = ($y_amount - $y_fee_amount)
                }
                bid = [ordered]@{
                    requested_asset_id = $this.token_y.id
                    requested_asset_amount = ($y_amount + $y_fee_amount)
                    offered_asset_id = $this.token_x.id
                    offered_asset_amount =  ($x_amount - $x_fee_amount)
                }
            }
            $this.grid += $row
        }
    }

    BuildYGrid(){
        $this.grid = @()
        $step_amount = $this.token_y.getFormattedAmount() / $this.steps
        $step_size = ($this.starting_price - $this.min_price) / ($this.steps-1)
        if($step_amount -eq 0 -OR $step_size -eq 0){
            return
        }
        for ($i = 0; $i -lt $this.steps; $i++){
            $x_fee_percentage = (($this.fee_token_id -eq $this.token_x.id) ? ($this.fee_percentage) : 0)
            $y_fee_percentage = (($this.fee_token_id -eq $this.token_y.id) ? ($this.fee_percentage) : 0)
            
            $tPrice = [System.Math]::Round($this.min_price + ($step_size * $i),3)
            [UInt64]$x_amount = (($step_amount / $tPrice)*$this.token_x.denom)
            [UInt64]$y_amount = ($step_amount*$this.token_y.denom)
            [UInt64]$x_fee_amount = ($x_fee_percentage * $x_amount)
            [UInt64]$y_fee_amount = ($y_fee_percentage * $y_amount)
            $row = [pscustomobject]@{
                x_fee_percentage = $x_fee_percentage
                y_fee_percentage = $y_fee_percentage
                x_fee_amount = $x_fee_amount
                y_fee_amount = $y_fee_amount
                x_code = $this.token_x.code
                x_amount = $x_amount
                y_code = $this.token_y.code
                y_amount = $y_amount
                index = $i
                price = [decimal]$tPrice
                ask = [ordered]@{      
                    requested_asset_id = $this.token_x.id
                    requested_asset_amount = ($x_amount + $x_fee_amount)
                    offered_asset_id = $this.token_y.id
                    offered_asset_amount = ($y_amount - $y_fee_amount)
                }
                bid = [ordered]@{
                    requested_asset_id = $this.token_y.id
                    requested_asset_amount = ($y_amount + $y_fee_amount)
                    offered_asset_id = $this.token_x.id
                    offered_asset_amount = ($x_amount - $x_fee_amount)
                }
            }
            $this.grid += $row
        }
        
    }

    
    save(){
        $path = Get-SageTraderPath("GridBots")
        $file = Join-Path -Path $path -ChildPath "$($this.id).json"
        $this | ConvertTo-Json -Depth 20 | Out-File -FilePath $file -Encoding utf8
    }

}




Export-ModuleMember -Function *