diff --git a/codebase.md b/codebase.md index 120286e58..f6acacdb8 100644 --- a/codebase.md +++ b/codebase.md @@ -277,7 +277,7 @@ package event import ( "context" "strings" "time" "github.com/dwarvesf/fortress- # pkg/controller/icy/icy.go ```go -package icy import ( "math/big" "strings" "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/dwarvesf/fortress-api/pkg/config" "github.com/dwarvesf/fortress-api/pkg/logger" "github.com/dwarvesf/fortress-api/pkg/model" "github.com/dwarvesf/fortress-api/pkg/service" "github.com/dwarvesf/fortress-api/pkg/service/icyswap" "github.com/dwarvesf/fortress-api/pkg/service/mochipay" "github.com/dwarvesf/fortress-api/pkg/service/mochiprofile" ) type IController interface { Accounting() (*model.IcyAccounting, error) } type controller struct { service *service.Service logger logger.Logger config *config.Config } func New(service *service.Service, logger logger.Logger, cfg *config.Config) IController { return &controller{ service: service, logger: logger, config: cfg, } } func (c *controller) Accounting() (*model.IcyAccounting, error) { l := c.logger.Fields(logger.Fields{ "controller": "icy", "method": "Accounting", }) // 0.Prepare token and swap contract data icy := c.icy() usdt := c.usdc() icySwap := c.icySwap() // 1.Get current conversion rate from icyswap contract conversionRate, err := c.service.IcySwap.ConversionRate() if err != nil { l.Error(err, "failed to get icy conversion rate") return nil, err } usdtDecimals := new(big.Float).SetInt(math.BigPow(10, int64(usdt.Decimals))) conversionRateFloat, _ := new(big.Float).Quo(new(big.Float).SetInt(conversionRate), usdtDecimals).Float32() // 2. Get current usdt fund in icyswap contract icyswapUsdtBal, err := c.service.IcySwap.UsdcFund() if err != nil { l.Error(err, "failed to get usdt fund in icyswap contract") return nil, err } // 3. Get Circulating Icy // circulating icy = total supply - icy in contract - icy of team - icy in mochi app - icy in vault // 3.1 Get total icy supply icyTotalSupply, _ := new(big.Int).SetString(icy.TotalSupply, 10) // 3.2 Get total locked icy amount lockedIcyAmount, err := c.lockedIcyAmount() if err != nil { c.logger.Error(err, "failed to get locked icy amount") return nil, err } // 3.3 Calculate circulating icy amount circulatingIcy := new(big.Int).Sub(icyTotalSupply, lockedIcyAmount) // 4.Get offset usdt // offset usd: circulating icy in usdt - usd fund in contract -> get how many usd left to redeem circulatingIcyInUsdt := new(big.Int).Mul(circulatingIcy, conversionRate) // continue to divide to 10^18 for get the amount in usdt decimals circulatingIcyInUsdt = new(big.Int).Div(circulatingIcyInUsdt, math.BigPow(10, 18)) offsetUsdt := new(big.Int).Sub(circulatingIcyInUsdt, icyswapUsdtBal) // 5. Return accounting result return &model.IcyAccounting{ ICY: &icy, USDT: &usdt, IcySwap: &icySwap, ConversionRate: conversionRateFloat, ContractFundInUSDT: icyswapUsdtBal.String(), CirculatingICY: circulatingIcy.String(), OffsetUSDT: offsetUsdt.String(), }, nil } func (c *controller) lockedIcyAmount() (*big.Int, error) { lockedIcyAmount := big.NewInt(0) // 0. fetch onchain locked icy amount onchainLockedAmount, err := c.onchainLockedIcyAmount() if err != nil { c.logger.Error(err, "failed to get onchain locked icy amount") return nil, err } lockedIcyAmount = new(big.Int).Add(lockedIcyAmount, onchainLockedAmount) // 1. fetch offchain locked icy amount offchainLockedAmount, err := c.offchainLockedIcyAmount() if err != nil { c.logger.Error(err, "failed to get offchain locked icy amount") return nil, err } lockedIcyAmount = new(big.Int).Add(lockedIcyAmount, offchainLockedAmount) // 2. return result return lockedIcyAmount, nil } func (c *controller) onchainLockedIcyAmount() (*big.Int, error) { icyAddress := common.HexToAddress(c.icy().Address) oldIcySwapContractAddr := common.HexToAddress("0xd327b6d878bcd9d5ec6a5bc99445985d75f0d6e5") icyswapAddr := common.HexToAddress(icyswap.ICYSwapAddress) teamAddr := common.HexToAddress("0x0762c4b40c9cb21Af95192a3Dc3EDd3043CF3d41") icyLockedAddrs := []common.Address{oldIcySwapContractAddr, icyswapAddr, teamAddr} type FetchResult struct { amount *big.Int err error } fetchIcyResults := make(chan FetchResult) wg := sync.WaitGroup{} wg.Add(len(icyLockedAddrs)) go func() { wg.Wait() close(fetchIcyResults) }() for _, lockedAddr := range icyLockedAddrs { go func(ownerAddr common.Address) { amount, err := c.service.BaseClient.ERC20Balance(icyAddress, ownerAddr) fetchIcyResults <- FetchResult{ amount: amount, err: err, } wg.Done() }(lockedAddr) } lockedIcyAmount := big.NewInt(0) for res := range fetchIcyResults { if res.err != nil { return nil, res.err } lockedIcyAmount = new(big.Int).Add(lockedIcyAmount, res.amount) } return lockedIcyAmount, nil } func (c *controller) offchainLockedIcyAmount() (*big.Int, error) { // 0. get all profile, which type is vault or app profileIds := make([]string, 0) const pageSize int64 = 50 var page int64 = 0 for { res, err := c.service.MochiProfile.GetListProfiles(mochiprofile.ListProfilesRequest{ Types: []mochiprofile.ProfileType{ mochiprofile.ProfileTypeApplication, mochiprofile.ProfileTypeVault, }, Page: page, Size: pageSize, }) if err != nil { return nil, err } for _, p := range res.Data { profileIds = append(profileIds, p.ID) } hasNext := res.Pagination.Total/pageSize-page > 0 if !hasNext { break } page += 1 } // 1. get balance of all profiles balRes, err := c.service.MochiPay.GetBatchBalances(profileIds) if err != nil { return nil, err } total := big.NewInt(0) icy := c.icy() for _, b := range balRes.Data { // filter token icy if strings.EqualFold(b.Token.Address, icy.Address) && strings.EqualFold(b.Token.ChainId, icy.ChainID) { amount, _ := new(big.Int).SetString(b.Amount, 10) total = new(big.Int).Add(total, amount) } } return total, nil } func (c *controller) icy() model.TokenInfo { return model.TokenInfo{ Name: "Icy", Symbol: "ICY", Address: mochipay.ICYAddress, Decimals: 18, Chain: mochipay.BASEChainID, ChainID: mochipay.BASEChainID, TotalSupply: "100000000000000000000000", } } func (c *controller) usdc() model.TokenInfo { return model.TokenInfo{ Name: "USD Base Coin", Symbol: "USDbC", Address: icyswap.USDCAddress, Decimals: 6, Chain: mochipay.BaseChainName, ChainID: mochipay.BASEChainID, } } func (c *controller) icySwap() model.ContractInfo { return model.ContractInfo{ Name: "IcySwap", Address: icyswap.ICYSwapAddress, Chain: mochipay.BaseChainName, } } +package icy import ( "math/big" "strings" "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/dwarvesf/fortress-api/pkg/config" "github.com/dwarvesf/fortress-api/pkg/logger" "github.com/dwarvesf/fortress-api/pkg/model" "github.com/dwarvesf/fortress-api/pkg/service" "github.com/dwarvesf/fortress-api/pkg/service/icyswap" "github.com/dwarvesf/fortress-api/pkg/service/mochipay" "github.com/dwarvesf/fortress-api/pkg/service/mochiprofile" ) type IController interface { Accounting() (*model.IcyAccounting, error) } type controller struct { service *service.Service logger logger.Logger config *config.Config } func New(service *service.Service, logger logger.Logger, cfg *config.Config) IController { return &controller{ service: service, logger: logger, config: cfg, } } func (c *controller) Accounting() (*model.IcyAccounting, error) { l := c.logger.Fields(logger.Fields{ "controller": "icy", "method": "Accounting", }) // 0.Prepare token and icyswapbtc contract data icy := c.icy() usdt := c.usdc() icySwap := c.icySwap() // 1.Get current conversion rate from icyswap contract conversionRate, err := c.service.IcySwap.ConversionRate() if err != nil { l.Error(err, "failed to get icy conversion rate") return nil, err } usdtDecimals := new(big.Float).SetInt(math.BigPow(10, int64(usdt.Decimals))) conversionRateFloat, _ := new(big.Float).Quo(new(big.Float).SetInt(conversionRate), usdtDecimals).Float32() // 2. Get current usdt fund in icyswap contract icyswapUsdtBal, err := c.service.IcySwap.UsdcFund() if err != nil { l.Error(err, "failed to get usdt fund in icyswap contract") return nil, err } // 3. Get Circulating Icy // circulating icy = total supply - icy in contract - icy of team - icy in mochi app - icy in vault // 3.1 Get total icy supply icyTotalSupply, _ := new(big.Int).SetString(icy.TotalSupply, 10) // 3.2 Get total locked icy amount lockedIcyAmount, err := c.lockedIcyAmount() if err != nil { c.logger.Error(err, "failed to get locked icy amount") return nil, err } // 3.3 Calculate circulating icy amount circulatingIcy := new(big.Int).Sub(icyTotalSupply, lockedIcyAmount) // 4.Get offset usdt // offset usd: circulating icy in usdt - usd fund in contract -> get how many usd left to redeem circulatingIcyInUsdt := new(big.Int).Mul(circulatingIcy, conversionRate) // continue to divide to 10^18 for get the amount in usdt decimals circulatingIcyInUsdt = new(big.Int).Div(circulatingIcyInUsdt, math.BigPow(10, 18)) offsetUsdt := new(big.Int).Sub(circulatingIcyInUsdt, icyswapUsdtBal) // 5. Return accounting result return &model.IcyAccounting{ ICY: &icy, USDT: &usdt, IcySwap: &icySwap, ConversionRate: conversionRateFloat, ContractFundInUSDT: icyswapUsdtBal.String(), CirculatingICY: circulatingIcy.String(), OffsetUSDT: offsetUsdt.String(), }, nil } func (c *controller) lockedIcyAmount() (*big.Int, error) { lockedIcyAmount := big.NewInt(0) // 0. fetch onchain locked icy amount onchainLockedAmount, err := c.onchainLockedIcyAmount() if err != nil { c.logger.Error(err, "failed to get onchain locked icy amount") return nil, err } lockedIcyAmount = new(big.Int).Add(lockedIcyAmount, onchainLockedAmount) // 1. fetch offchain locked icy amount offchainLockedAmount, err := c.offchainLockedIcyAmount() if err != nil { c.logger.Error(err, "failed to get offchain locked icy amount") return nil, err } lockedIcyAmount = new(big.Int).Add(lockedIcyAmount, offchainLockedAmount) // 2. return result return lockedIcyAmount, nil } func (c *controller) onchainLockedIcyAmount() (*big.Int, error) { icyAddress := common.HexToAddress(c.icy().Address) oldIcySwapContractAddr := common.HexToAddress("0xd327b6d878bcd9d5ec6a5bc99445985d75f0d6e5") icyswapAddr := common.HexToAddress(icyswap.ICYSwapAddress) teamAddr := common.HexToAddress("0x0762c4b40c9cb21Af95192a3Dc3EDd3043CF3d41") icyLockedAddrs := []common.Address{oldIcySwapContractAddr, icyswapAddr, teamAddr} type FetchResult struct { amount *big.Int err error } fetchIcyResults := make(chan FetchResult) wg := sync.WaitGroup{} wg.Add(len(icyLockedAddrs)) go func() { wg.Wait() close(fetchIcyResults) }() for _, lockedAddr := range icyLockedAddrs { go func(ownerAddr common.Address) { amount, err := c.service.BaseClient.ERC20Balance(icyAddress, ownerAddr) fetchIcyResults <- FetchResult{ amount: amount, err: err, } wg.Done() }(lockedAddr) } lockedIcyAmount := big.NewInt(0) for res := range fetchIcyResults { if res.err != nil { return nil, res.err } lockedIcyAmount = new(big.Int).Add(lockedIcyAmount, res.amount) } return lockedIcyAmount, nil } func (c *controller) offchainLockedIcyAmount() (*big.Int, error) { // 0. get all profile, which type is vault or app profileIds := make([]string, 0) const pageSize int64 = 50 var page int64 = 0 for { res, err := c.service.MochiProfile.GetListProfiles(mochiprofile.ListProfilesRequest{ Types: []mochiprofile.ProfileType{ mochiprofile.ProfileTypeApplication, mochiprofile.ProfileTypeVault, }, Page: page, Size: pageSize, }) if err != nil { return nil, err } for _, p := range res.Data { profileIds = append(profileIds, p.ID) } hasNext := res.Pagination.Total/pageSize-page > 0 if !hasNext { break } page += 1 } // 1. get balance of all profiles balRes, err := c.service.MochiPay.GetBatchBalances(profileIds) if err != nil { return nil, err } total := big.NewInt(0) icy := c.icy() for _, b := range balRes.Data { // filter token icy if strings.EqualFold(b.Token.Address, icy.Address) && strings.EqualFold(b.Token.ChainId, icy.ChainID) { amount, _ := new(big.Int).SetString(b.Amount, 10) total = new(big.Int).Add(total, amount) } } return total, nil } func (c *controller) icy() model.TokenInfo { return model.TokenInfo{ Name: "Icy", Symbol: "ICY", Address: mochipay.ICYAddress, Decimals: 18, Chain: mochipay.BASEChainID, ChainID: mochipay.BASEChainID, TotalSupply: "100000000000000000000000", } } func (c *controller) usdc() model.TokenInfo { return model.TokenInfo{ Name: "USD Base Coin", Symbol: "USDbC", Address: icyswap.USDCAddress, Decimals: 6, Chain: mochipay.BaseChainName, ChainID: mochipay.BASEChainID, } } func (c *controller) icySwap() model.ContractInfo { return model.ContractInfo{ Name: "IcySwap", Address: icyswap.ICYSwapAddress, Chain: mochipay.BaseChainName, } } ``` # pkg/controller/invoice/commission.go @@ -2203,7 +2203,7 @@ package mochipay import ( "bytes" "encoding/json" "fmt" "net/http" "net/url" "st # pkg/service/mochipay/request.go ```go -package mochipay import ( "time" ) type TransactionType string const ( TransactionTypeSend TransactionType = "out" TransactionTypeReceive TransactionType = "in" ) type TransactionStatus string const ( TransactionStatusPending TransactionStatus = "pending" TransactionStatusSubmitted TransactionStatus = "submitted" TransactionStatusSuccess TransactionStatus = "success" TransactionStatusFailed TransactionStatus = "failed" TransactionStatusExpired TransactionStatus = "expired" TransactionStatusCancelled TransactionStatus = "cancelled" //nolint:all ) type TransactionAction string const ( TransactionActionTransfer TransactionAction = "transfer" TransactionActionAirdrop TransactionAction = "airdrop" TransactionActionDeposit TransactionAction = "deposit" TransactionActionWithdraw TransactionAction = "withdraw" TransactionActionSwap TransactionAction = "swap" TransactionActionVaultTransfer TransactionAction = "vault_transfer" ) type TransactionPlatform string const ( TransactionPlatformDiscord TransactionPlatform = "discord" ) type ListTransactionsRequest struct { Type TransactionType `json:"type"` Status TransactionStatus `json:"status"` ActionList []TransactionAction `json:"action_list"` TokenAddress string `json:"token_address"` ProfileID string `json:"profile_id"` Platforms []TransactionPlatform `json:"platforms"` ChainIDs []string `json:"chain_ids"` Page int64 `json:"page"` Size int64 `json:"size"` IsSender *bool `json:"is_sender"` SortBy string `json:"sort_by"` } type ListTransactionsResponse struct { Data []TransactionData `json:"data"` Pagination Pagination `json:"pagination"` } type Pagination struct { Total int64 `json:"total"` Page int64 `json:"page"` Size int64 `json:"size"` } type TransactionData struct { Id string `json:"id"` FromProfileId string `json:"from_profile_id"` OtherProfileId string `json:"other_profile_id"` FromProfileSource string `json:"from_profile_source"` OtherProfileSource string `json:"other_profile_source"` SourcePlatform string `json:"source_platform"` Amount string `json:"amount"` TokenId string `json:"token_id"` ChainId string `json:"chain_id"` InternalId int64 `json:"internal_id"` ExternalId string `json:"external_id"` OnchainTxHash string `json:"onchain_tx_hash"` Type TransactionType `json:"type"` Action TransactionAction `json:"action"` Status TransactionStatus `json:"status"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` ExpiredAt *time.Time `json:"expired_at"` SettledAt *time.Time `json:"settled_at"` Token *Token `json:"token"` OriginalTxId string `json:"original_tx_id"` OtherProfile *MochiProfile `json:"other_profile"` FromProfile *MochiProfile `json:"from_profile"` OtherProfiles []MochiProfile `json:"other_profiles"` AmountEachProfiles []AmountEachProfiles `json:"amount_each_profiles"` UsdAmount float64 `json:"usd_amount"` Metadata map[string]interface{} `json:"metadata"` // used in airdrop response OtherProfileIds []string `json:"other_profile_ids"` TotalAmount string `json:"total_amount"` // used in swap response FromTokenId string `json:"from_token_id"` ToTokenId string `json:"to_token_id"` FromToken *Token `json:"from_token,omitempty"` ToToken *Token `json:"to_token,omitempty"` FromAmount string `json:"from_amount"` ToAmount string `json:"to_amount"` } type Token struct { Id string `json:"id"` Name string `json:"name"` Symbol string `json:"symbol"` Decimal int64 `json:"decimal"` ChainId string `json:"chain_id"` Native bool `json:"native"` Address string `json:"address"` Icon string `json:"icon"` CoinGeckoId string `json:"coin_gecko_id"` Price float64 `json:"price"` Chain *Chain `json:"chain"` } type Chain struct { Id string `json:"id"` ChainId string `json:"chain_id"` Name string `json:"name"` Symbol string `json:"symbol"` Rpc string `json:"rpc"` Explorer string `json:"explorer"` Icon string `json:"icon"` Type string `json:"type"` } type MochiProfile struct { Id string `json:"id"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` ProfileName string `json:"profile_name"` Avatar string `json:"avatar"` AssociatedAccounts []AssociatedAccounts `json:"associated_accounts"` Type string `json:"type"` Application *Application `json:"application"` } type Application struct { Id int `json:"id"` Name string `json:"name"` OwnerProfileId string `json:"owner_profile_id"` ServiceFee float64 `json:"service_fee"` ApplicationProfileId string `json:"application_profile_id"` Active bool `json:"active"` } type AssociatedAccounts struct { Id string `json:"id"` ProfileId string `json:"profile_id"` Platform string `json:"platform"` PlatformIdentifier string `json:"platform_identifier"` PlatformMetadata interface{} `json:"platform_metadata"` IsGuildMember bool `json:"is_guild_member"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } type AmountEachProfiles struct { ProfileId string `json:"profile_id"` Amount string `json:"amount"` UsdAmount float64 `json:"usd_amount"` } type TokenInfo struct { Address string `json:"address"` Chain *Chain `json:"chain"` ChainID string `json:"chain_id"` CoinGeckoID string `json:"coin_gecko_id"` Decimal int `json:"decimal"` Icon string `json:"icon"` ID string `json:"id"` Name string `json:"name"` Native bool `json:"native"` Price int `json:"price"` Symbol string `json:"symbol"` } type VaultRequest struct { Amount string `json:"amount"` Chain string `json:"chain"` ListNotify []string `json:"list_notify"` Message string `json:"message"` Name string `json:"name"` Platform string `json:"platform"` PrivateKey string `json:"private_key"` ProfileID string `json:"profile_id"` Receiver string `json:"receiver"` RequestID int `json:"request_id"` To string `json:"to"` Token string `json:"token"` TokenID string `json:"token_id"` TokenInfo *TokenInfo `json:"token_info"` VaultID int `json:"vault_id"` } type TransactionMetadata struct { Message string `json:"message"` RecipientProfileType string `json:"recipient_profile_type"` RequestID int `json:"request_id"` SenderProfileType string `json:"sender_profile_type"` TransferType string `json:"transfer_type"` TxHash string `json:"tx_hash"` VaultRequest *VaultRequest `json:"vault_request"` } type BatchBalancesResponse struct { Data []BatchBalancesData `json:"data"` } type BatchBalancesData struct { Id string `json:"id"` ProfileID string `json:"profile_id"` TokenID string `json:"token_id"` Amount string `json:"amount"` Token Token `json:"token"` } +package mochipay import ( "time" ) type TransactionType string const ( TransactionTypeSend TransactionType = "out" TransactionTypeReceive TransactionType = "in" ) type TransactionStatus string const ( TransactionStatusPending TransactionStatus = "pending" TransactionStatusSubmitted TransactionStatus = "submitted" TransactionStatusSuccess TransactionStatus = "success" TransactionStatusFailed TransactionStatus = "failed" TransactionStatusExpired TransactionStatus = "expired" TransactionStatusCancelled TransactionStatus = "cancelled" //nolint:all ) type TransactionAction string const ( TransactionActionTransfer TransactionAction = "transfer" TransactionActionAirdrop TransactionAction = "airdrop" TransactionActionDeposit TransactionAction = "deposit" TransactionActionWithdraw TransactionAction = "withdraw" TransactionActionSwap TransactionAction = "icyswapbtc" TransactionActionVaultTransfer TransactionAction = "vault_transfer" ) type TransactionPlatform string const ( TransactionPlatformDiscord TransactionPlatform = "discord" ) type ListTransactionsRequest struct { Type TransactionType `json:"type"` Status TransactionStatus `json:"status"` ActionList []TransactionAction `json:"action_list"` TokenAddress string `json:"token_address"` ProfileID string `json:"profile_id"` Platforms []TransactionPlatform `json:"platforms"` ChainIDs []string `json:"chain_ids"` Page int64 `json:"page"` Size int64 `json:"size"` IsSender *bool `json:"is_sender"` SortBy string `json:"sort_by"` } type ListTransactionsResponse struct { Data []TransactionData `json:"data"` Pagination Pagination `json:"pagination"` } type Pagination struct { Total int64 `json:"total"` Page int64 `json:"page"` Size int64 `json:"size"` } type TransactionData struct { Id string `json:"id"` FromProfileId string `json:"from_profile_id"` OtherProfileId string `json:"other_profile_id"` FromProfileSource string `json:"from_profile_source"` OtherProfileSource string `json:"other_profile_source"` SourcePlatform string `json:"source_platform"` Amount string `json:"amount"` TokenId string `json:"token_id"` ChainId string `json:"chain_id"` InternalId int64 `json:"internal_id"` ExternalId string `json:"external_id"` OnchainTxHash string `json:"onchain_tx_hash"` Type TransactionType `json:"type"` Action TransactionAction `json:"action"` Status TransactionStatus `json:"status"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` ExpiredAt *time.Time `json:"expired_at"` SettledAt *time.Time `json:"settled_at"` Token *Token `json:"token"` OriginalTxId string `json:"original_tx_id"` OtherProfile *MochiProfile `json:"other_profile"` FromProfile *MochiProfile `json:"from_profile"` OtherProfiles []MochiProfile `json:"other_profiles"` AmountEachProfiles []AmountEachProfiles `json:"amount_each_profiles"` UsdAmount float64 `json:"usd_amount"` Metadata map[string]interface{} `json:"metadata"` // used in airdrop response OtherProfileIds []string `json:"other_profile_ids"` TotalAmount string `json:"total_amount"` // used in icyswapbtc response FromTokenId string `json:"from_token_id"` ToTokenId string `json:"to_token_id"` FromToken *Token `json:"from_token,omitempty"` ToToken *Token `json:"to_token,omitempty"` FromAmount string `json:"from_amount"` ToAmount string `json:"to_amount"` } type Token struct { Id string `json:"id"` Name string `json:"name"` Symbol string `json:"symbol"` Decimal int64 `json:"decimal"` ChainId string `json:"chain_id"` Native bool `json:"native"` Address string `json:"address"` Icon string `json:"icon"` CoinGeckoId string `json:"coin_gecko_id"` Price float64 `json:"price"` Chain *Chain `json:"chain"` } type Chain struct { Id string `json:"id"` ChainId string `json:"chain_id"` Name string `json:"name"` Symbol string `json:"symbol"` Rpc string `json:"rpc"` Explorer string `json:"explorer"` Icon string `json:"icon"` Type string `json:"type"` } type MochiProfile struct { Id string `json:"id"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` ProfileName string `json:"profile_name"` Avatar string `json:"avatar"` AssociatedAccounts []AssociatedAccounts `json:"associated_accounts"` Type string `json:"type"` Application *Application `json:"application"` } type Application struct { Id int `json:"id"` Name string `json:"name"` OwnerProfileId string `json:"owner_profile_id"` ServiceFee float64 `json:"service_fee"` ApplicationProfileId string `json:"application_profile_id"` Active bool `json:"active"` } type AssociatedAccounts struct { Id string `json:"id"` ProfileId string `json:"profile_id"` Platform string `json:"platform"` PlatformIdentifier string `json:"platform_identifier"` PlatformMetadata interface{} `json:"platform_metadata"` IsGuildMember bool `json:"is_guild_member"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } type AmountEachProfiles struct { ProfileId string `json:"profile_id"` Amount string `json:"amount"` UsdAmount float64 `json:"usd_amount"` } type TokenInfo struct { Address string `json:"address"` Chain *Chain `json:"chain"` ChainID string `json:"chain_id"` CoinGeckoID string `json:"coin_gecko_id"` Decimal int `json:"decimal"` Icon string `json:"icon"` ID string `json:"id"` Name string `json:"name"` Native bool `json:"native"` Price int `json:"price"` Symbol string `json:"symbol"` } type VaultRequest struct { Amount string `json:"amount"` Chain string `json:"chain"` ListNotify []string `json:"list_notify"` Message string `json:"message"` Name string `json:"name"` Platform string `json:"platform"` PrivateKey string `json:"private_key"` ProfileID string `json:"profile_id"` Receiver string `json:"receiver"` RequestID int `json:"request_id"` To string `json:"to"` Token string `json:"token"` TokenID string `json:"token_id"` TokenInfo *TokenInfo `json:"token_info"` VaultID int `json:"vault_id"` } type TransactionMetadata struct { Message string `json:"message"` RecipientProfileType string `json:"recipient_profile_type"` RequestID int `json:"request_id"` SenderProfileType string `json:"sender_profile_type"` TransferType string `json:"transfer_type"` TxHash string `json:"tx_hash"` VaultRequest *VaultRequest `json:"vault_request"` } type BatchBalancesResponse struct { Data []BatchBalancesData `json:"data"` } type BatchBalancesData struct { Id string `json:"id"` ProfileID string `json:"profile_id"` TokenID string `json:"token_id"` Amount string `json:"amount"` Token Token `json:"token"` } ``` # pkg/service/mochiprofile/mochiprofile.go diff --git a/go.mod b/go.mod index 2f0a153fc..e7f062a4b 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,6 @@ require ( github.com/go-playground/validator/v10 v10.14.1 github.com/go-testfixtures/testfixtures/v3 v3.9.0 github.com/goccy/go-json v0.10.2 - github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 diff --git a/go.sum b/go.sum index 4fa46b5ab..0b8550631 100644 --- a/go.sum +++ b/go.sum @@ -276,8 +276,6 @@ github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14j github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= diff --git a/migrations/schemas/20250320153030-create-icy_btc_swap_request.sql b/migrations/schemas/20250320153030-create-icy_btc_swap_request.sql new file mode 100644 index 000000000..42baa4a36 --- /dev/null +++ b/migrations/schemas/20250320153030-create-icy_btc_swap_request.sql @@ -0,0 +1,29 @@ +-- +migrate Up +CREATE TABLE icy_swap_btc_requests ( + id uuid PRIMARY KEY DEFAULT (uuid()), + profile_id varchar(255) NOT NULL, + request_code varchar(255) NOT NULL, + tx_status varchar(255), + tx_id int, + btc_address varchar(255), + timestamp integer, + amount varchar(255), + token_name varchar(255), + token_id varchar(255), + swap_request_status varchar(255), + swap_request_error text, + revert_status varchar(255), + revert_error text, + tx_swap text, + tx_deposit text, + created_at TIMESTAMP(6) DEFAULT NOW(), + updated_at TIMESTAMP(6) DEFAULT NOW() +); + +CREATE INDEX idx_icy_swap_btc_profile_id ON icy_swap_btc_requests(profile_id); +CREATE INDEX idx_icy_swap_btc_request_code ON icy_swap_btc_requests(request_code); + +-- +migrate Down +DROP INDEX IF EXISTS idx_icy_swap_btc_profile_id; +DROP INDEX IF EXISTS idx_icy_swap_btc_request_code; +DROP TABLE icy_swap_btc_requests; \ No newline at end of file diff --git a/pkg/config/config.go b/pkg/config/config.go index 48d93beae..a3d539f1d 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -29,6 +29,7 @@ type Config struct { Mochi Mochi MochiPay MochiPay MochiProfile MochiProfile + IcyBackend IcyBackend Tono Tono ImprovMX ImprovMX CommunityNft CommunityNft @@ -118,13 +119,24 @@ type Mochi struct { } type MochiPay struct { - BaseURL string + BaseURL string + ApplicationName string + ApplicationPrivateKey string + ApplicationId string + ApplicationVaultId string + ApplicationOwnerId string + IcyPoolPrivateKey string + IcyPoolPublicKey string } type MochiProfile struct { BaseURL string } +type IcyBackend struct { + BaseURL string +} + type Tono struct { BaseURL string } @@ -325,11 +337,21 @@ func Generate(v ENV) *Config { APIKey: v.GetString("MOCHI_API_KEY"), }, MochiPay: MochiPay{ - BaseURL: v.GetString("MOCHI_PAY_BASE_URL"), + BaseURL: v.GetString("MOCHI_PAY_BASE_URL"), + ApplicationName: v.GetString("MOCHI_PAY_APPLICATION_NAME"), + ApplicationPrivateKey: v.GetString("MOCHI_PAY_APPLICATION_PRIVATE_KEY"), + ApplicationId: v.GetString("MOCHI_PAY_APPLICATION_ID"), + ApplicationVaultId: v.GetString("MOCHI_PAY_APPLICATION_VAULT_ID"), + ApplicationOwnerId: v.GetString("MOCHI_PAY_APPLICATION_OWNER_ID"), + IcyPoolPrivateKey: v.GetString("MOCHI_PAY_ICY_POOL_PRIVATE_KEY"), + IcyPoolPublicKey: v.GetString("MOCHI_PAY_ICY_POOL_PUBLIC_KEY"), }, MochiProfile: MochiProfile{ BaseURL: v.GetString("MOCHI_PROFILE_BASE_URL"), }, + IcyBackend: IcyBackend{ + BaseURL: v.GetString("ICY_BACKEND_BASE_URL"), + }, Tono: Tono{ BaseURL: v.GetString("TONO_BASE_URL"), }, diff --git a/pkg/contracts/erc20/erc20.go b/pkg/contracts/erc20/erc20.go index dac5be931..da8516d06 100644 --- a/pkg/contracts/erc20/erc20.go +++ b/pkg/contracts/erc20/erc20.go @@ -785,4 +785,4 @@ func (_Erc20 *Erc20Filterer) ParseTransfer(log types.Log) (*Erc20Transfer, error } event.Raw = log return event, nil -} \ No newline at end of file +} diff --git a/pkg/contracts/erc721/erc721.go b/pkg/contracts/erc721/erc721.go index 8ae663c52..30640aa2b 100644 --- a/pkg/contracts/erc721/erc721.go +++ b/pkg/contracts/erc721/erc721.go @@ -62,7 +62,7 @@ type ERC721Filterer struct { // ERC721Session is an auto generated Go binding around an Ethereum contract, // with pre-set call and transact options. type ERC721Session struct { - Contract *ERC721 // Generic contract binding to set the session for + Contract *ERC721 // Generic contract binding to set the session for CallOpts bind.CallOpts // Call options to use throughout this session TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session } @@ -70,14 +70,14 @@ type ERC721Session struct { // ERC721CallerSession is an auto generated read-only Go binding around an Ethereum contract, // with pre-set call options. type ERC721CallerSession struct { - Contract *ERC721Caller // Generic contract caller binding to set the session for + Contract *ERC721Caller // Generic contract caller binding to set the session for CallOpts bind.CallOpts // Call options to use throughout this session } // ERC721TransactorSession is an auto generated write-only Go binding around an Ethereum contract, // with pre-set transact options. type ERC721TransactorSession struct { - Contract *ERC721Transactor // Generic contract transactor binding to set the session for + Contract *ERC721Transactor // Generic contract transactor binding to set the session for TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session } diff --git a/pkg/contracts/icyswapbtc/icyswapbtc.abi b/pkg/contracts/icyswapbtc/icyswapbtc.abi new file mode 100644 index 000000000..a507ee557 --- /dev/null +++ b/pkg/contracts/icyswapbtc/icyswapbtc.abi @@ -0,0 +1,249 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_icy", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "icyAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "btcAddress", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "btcAmount", + "type": "uint256" + } + ], + "name": "RevertIcy", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "signerAddress", + "type": "address" + } + ], + "name": "SetSigner", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "icyAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "btcAddress", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "btcAmount", + "type": "uint256" + } + ], + "name": "Swap", + "type": "event" + }, + { "stateMutability": "nonpayable", "type": "fallback" }, + { + "inputs": [], + "name": "REVERT_ICY_HASH", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SWAP_HASH", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { "internalType": "bytes1", "name": "fields", "type": "bytes1" }, + { "internalType": "string", "name": "name", "type": "string" }, + { "internalType": "string", "name": "version", "type": "string" }, + { "internalType": "uint256", "name": "chainId", "type": "uint256" }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { "internalType": "bytes32", "name": "salt", "type": "bytes32" }, + { "internalType": "uint256[]", "name": "extensions", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "icyAmount", "type": "uint256" }, + { "internalType": "string", "name": "btcAddress", "type": "string" }, + { "internalType": "uint256", "name": "btcAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "name": "getRevertIcyHash", + "outputs": [ + { "internalType": "bytes32", "name": "hash", "type": "bytes32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "_digest", "type": "bytes32" }, + { "internalType": "bytes", "name": "_signature", "type": "bytes" } + ], + "name": "getSigner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "icyAmount", "type": "uint256" }, + { "internalType": "string", "name": "btcAddress", "type": "string" }, + { "internalType": "uint256", "name": "btcAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "name": "getSwapHash", + "outputs": [ + { "internalType": "bytes32", "name": "hash", "type": "bytes32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "icy", + "outputs": [ + { "internalType": "contract ERC20", "name": "", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "icyAmount", "type": "uint256" }, + { "internalType": "string", "name": "btcAddress", "type": "string" }, + { "internalType": "uint256", "name": "btcAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "bytes", "name": "_signature", "type": "bytes" } + ], + "name": "revertIcy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "name": "revertedIcyHashes", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_signerAddress", "type": "address" } + ], + "name": "setSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "signerAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "icyAmount", "type": "uint256" }, + { "internalType": "string", "name": "btcAddress", "type": "string" }, + { "internalType": "uint256", "name": "btcAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "bytes", "name": "_signature", "type": "bytes" } + ], + "name": "swap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "name": "swappedHashes", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } +] \ No newline at end of file diff --git a/pkg/contracts/icyswapbtc/icyswapbtc.go b/pkg/contracts/icyswapbtc/icyswapbtc.go new file mode 100644 index 000000000..d0b024254 --- /dev/null +++ b/pkg/contracts/icyswapbtc/icyswapbtc.go @@ -0,0 +1,1245 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package icyswapbtc + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// IcyswapbtcMetaData contains all meta data concerning the Icyswapbtc contract. +var IcyswapbtcMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_icy\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"icyAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"btcAddress\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"btcAmount\",\"type\":\"uint256\"}],\"name\":\"RevertIcy\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"signerAddress\",\"type\":\"address\"}],\"name\":\"SetSigner\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"icyAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"btcAddress\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"btcAmount\",\"type\":\"uint256\"}],\"name\":\"Swap\",\"type\":\"event\"},{\"stateMutability\":\"nonpayable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"REVERT_ICY_HASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SWAP_HASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eip712Domain\",\"outputs\":[{\"internalType\":\"bytes1\",\"name\":\"fields\",\"type\":\"bytes1\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"verifyingContract\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"salt\",\"type\":\"bytes32\"},{\"internalType\":\"uint256[]\",\"name\":\"extensions\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"icyAmount\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"btcAddress\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"btcAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"getRevertIcyHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"hash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_digest\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"_signature\",\"type\":\"bytes\"}],\"name\":\"getSigner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"icyAmount\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"btcAddress\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"btcAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"getSwapHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"hash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"icy\",\"outputs\":[{\"internalType\":\"contractERC20\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"icyAmount\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"btcAddress\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"btcAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"_signature\",\"type\":\"bytes\"}],\"name\":\"revertIcy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"revertedIcyHashes\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_signerAddress\",\"type\":\"address\"}],\"name\":\"setSigner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"signerAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"icyAmount\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"btcAddress\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"btcAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"_signature\",\"type\":\"bytes\"}],\"name\":\"swap\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"swappedHashes\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]", +} + +// IcyswapbtcABI is the input ABI used to generate the binding from. +// Deprecated: Use IcyswapbtcMetaData.ABI instead. +var IcyswapbtcABI = IcyswapbtcMetaData.ABI + +// Icyswapbtc is an auto generated Go binding around an Ethereum contract. +type Icyswapbtc struct { + IcyswapbtcCaller // Read-only binding to the contract + IcyswapbtcTransactor // Write-only binding to the contract + IcyswapbtcFilterer // Log filterer for contract events +} + +// IcyswapbtcCaller is an auto generated read-only Go binding around an Ethereum contract. +type IcyswapbtcCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// IcyswapbtcTransactor is an auto generated write-only Go binding around an Ethereum contract. +type IcyswapbtcTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// IcyswapbtcFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type IcyswapbtcFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// IcyswapbtcSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type IcyswapbtcSession struct { + Contract *Icyswapbtc // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// IcyswapbtcCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type IcyswapbtcCallerSession struct { + Contract *IcyswapbtcCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// IcyswapbtcTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type IcyswapbtcTransactorSession struct { + Contract *IcyswapbtcTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// IcyswapbtcRaw is an auto generated low-level Go binding around an Ethereum contract. +type IcyswapbtcRaw struct { + Contract *Icyswapbtc // Generic contract binding to access the raw methods on +} + +// IcyswapbtcCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type IcyswapbtcCallerRaw struct { + Contract *IcyswapbtcCaller // Generic read-only contract binding to access the raw methods on +} + +// IcyswapbtcTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type IcyswapbtcTransactorRaw struct { + Contract *IcyswapbtcTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewIcyswapbtc creates a new instance of Icyswapbtc, bound to a specific deployed contract. +func NewIcyswapbtc(address common.Address, backend bind.ContractBackend) (*Icyswapbtc, error) { + contract, err := bindIcyswapbtc(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Icyswapbtc{IcyswapbtcCaller: IcyswapbtcCaller{contract: contract}, IcyswapbtcTransactor: IcyswapbtcTransactor{contract: contract}, IcyswapbtcFilterer: IcyswapbtcFilterer{contract: contract}}, nil +} + +// NewIcyswapbtcCaller creates a new read-only instance of Icyswapbtc, bound to a specific deployed contract. +func NewIcyswapbtcCaller(address common.Address, caller bind.ContractCaller) (*IcyswapbtcCaller, error) { + contract, err := bindIcyswapbtc(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &IcyswapbtcCaller{contract: contract}, nil +} + +// NewIcyswapbtcTransactor creates a new write-only instance of Icyswapbtc, bound to a specific deployed contract. +func NewIcyswapbtcTransactor(address common.Address, transactor bind.ContractTransactor) (*IcyswapbtcTransactor, error) { + contract, err := bindIcyswapbtc(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &IcyswapbtcTransactor{contract: contract}, nil +} + +// NewIcyswapbtcFilterer creates a new log filterer instance of Icyswapbtc, bound to a specific deployed contract. +func NewIcyswapbtcFilterer(address common.Address, filterer bind.ContractFilterer) (*IcyswapbtcFilterer, error) { + contract, err := bindIcyswapbtc(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &IcyswapbtcFilterer{contract: contract}, nil +} + +// bindIcyswapbtc binds a generic wrapper to an already deployed contract. +func bindIcyswapbtc(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(IcyswapbtcABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Icyswapbtc *IcyswapbtcRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Icyswapbtc.Contract.IcyswapbtcCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Icyswapbtc *IcyswapbtcRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Icyswapbtc.Contract.IcyswapbtcTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Icyswapbtc *IcyswapbtcRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Icyswapbtc.Contract.IcyswapbtcTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Icyswapbtc *IcyswapbtcCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Icyswapbtc.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Icyswapbtc *IcyswapbtcTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Icyswapbtc.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Icyswapbtc *IcyswapbtcTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Icyswapbtc.Contract.contract.Transact(opts, method, params...) +} + +// REVERTICYHASH is a free data retrieval call binding the contract method 0x2d6d3d01. +// +// Solidity: function REVERT_ICY_HASH() view returns(bytes32) +func (_Icyswapbtc *IcyswapbtcCaller) REVERTICYHASH(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _Icyswapbtc.contract.Call(opts, &out, "REVERT_ICY_HASH") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// REVERTICYHASH is a free data retrieval call binding the contract method 0x2d6d3d01. +// +// Solidity: function REVERT_ICY_HASH() view returns(bytes32) +func (_Icyswapbtc *IcyswapbtcSession) REVERTICYHASH() ([32]byte, error) { + return _Icyswapbtc.Contract.REVERTICYHASH(&_Icyswapbtc.CallOpts) +} + +// REVERTICYHASH is a free data retrieval call binding the contract method 0x2d6d3d01. +// +// Solidity: function REVERT_ICY_HASH() view returns(bytes32) +func (_Icyswapbtc *IcyswapbtcCallerSession) REVERTICYHASH() ([32]byte, error) { + return _Icyswapbtc.Contract.REVERTICYHASH(&_Icyswapbtc.CallOpts) +} + +// SWAPHASH is a free data retrieval call binding the contract method 0x30c8b3da. +// +// Solidity: function SWAP_HASH() view returns(bytes32) +func (_Icyswapbtc *IcyswapbtcCaller) SWAPHASH(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _Icyswapbtc.contract.Call(opts, &out, "SWAP_HASH") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// SWAPHASH is a free data retrieval call binding the contract method 0x30c8b3da. +// +// Solidity: function SWAP_HASH() view returns(bytes32) +func (_Icyswapbtc *IcyswapbtcSession) SWAPHASH() ([32]byte, error) { + return _Icyswapbtc.Contract.SWAPHASH(&_Icyswapbtc.CallOpts) +} + +// SWAPHASH is a free data retrieval call binding the contract method 0x30c8b3da. +// +// Solidity: function SWAP_HASH() view returns(bytes32) +func (_Icyswapbtc *IcyswapbtcCallerSession) SWAPHASH() ([32]byte, error) { + return _Icyswapbtc.Contract.SWAPHASH(&_Icyswapbtc.CallOpts) +} + +// Eip712Domain is a free data retrieval call binding the contract method 0x84b0196e. +// +// Solidity: function eip712Domain() view returns(bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions) +func (_Icyswapbtc *IcyswapbtcCaller) Eip712Domain(opts *bind.CallOpts) (struct { + Fields [1]byte + Name string + Version string + ChainId *big.Int + VerifyingContract common.Address + Salt [32]byte + Extensions []*big.Int +}, error) { + var out []interface{} + err := _Icyswapbtc.contract.Call(opts, &out, "eip712Domain") + + outstruct := new(struct { + Fields [1]byte + Name string + Version string + ChainId *big.Int + VerifyingContract common.Address + Salt [32]byte + Extensions []*big.Int + }) + if err != nil { + return *outstruct, err + } + + outstruct.Fields = *abi.ConvertType(out[0], new([1]byte)).(*[1]byte) + outstruct.Name = *abi.ConvertType(out[1], new(string)).(*string) + outstruct.Version = *abi.ConvertType(out[2], new(string)).(*string) + outstruct.ChainId = *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + outstruct.VerifyingContract = *abi.ConvertType(out[4], new(common.Address)).(*common.Address) + outstruct.Salt = *abi.ConvertType(out[5], new([32]byte)).(*[32]byte) + outstruct.Extensions = *abi.ConvertType(out[6], new([]*big.Int)).(*[]*big.Int) + + return *outstruct, err + +} + +// Eip712Domain is a free data retrieval call binding the contract method 0x84b0196e. +// +// Solidity: function eip712Domain() view returns(bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions) +func (_Icyswapbtc *IcyswapbtcSession) Eip712Domain() (struct { + Fields [1]byte + Name string + Version string + ChainId *big.Int + VerifyingContract common.Address + Salt [32]byte + Extensions []*big.Int +}, error) { + return _Icyswapbtc.Contract.Eip712Domain(&_Icyswapbtc.CallOpts) +} + +// Eip712Domain is a free data retrieval call binding the contract method 0x84b0196e. +// +// Solidity: function eip712Domain() view returns(bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions) +func (_Icyswapbtc *IcyswapbtcCallerSession) Eip712Domain() (struct { + Fields [1]byte + Name string + Version string + ChainId *big.Int + VerifyingContract common.Address + Salt [32]byte + Extensions []*big.Int +}, error) { + return _Icyswapbtc.Contract.Eip712Domain(&_Icyswapbtc.CallOpts) +} + +// GetRevertIcyHash is a free data retrieval call binding the contract method 0x32ce558d. +// +// Solidity: function getRevertIcyHash(uint256 icyAmount, string btcAddress, uint256 btcAmount, uint256 nonce, uint256 deadline) view returns(bytes32 hash) +func (_Icyswapbtc *IcyswapbtcCaller) GetRevertIcyHash(opts *bind.CallOpts, icyAmount *big.Int, btcAddress string, btcAmount *big.Int, nonce *big.Int, deadline *big.Int) ([32]byte, error) { + var out []interface{} + err := _Icyswapbtc.contract.Call(opts, &out, "getRevertIcyHash", icyAmount, btcAddress, btcAmount, nonce, deadline) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// GetRevertIcyHash is a free data retrieval call binding the contract method 0x32ce558d. +// +// Solidity: function getRevertIcyHash(uint256 icyAmount, string btcAddress, uint256 btcAmount, uint256 nonce, uint256 deadline) view returns(bytes32 hash) +func (_Icyswapbtc *IcyswapbtcSession) GetRevertIcyHash(icyAmount *big.Int, btcAddress string, btcAmount *big.Int, nonce *big.Int, deadline *big.Int) ([32]byte, error) { + return _Icyswapbtc.Contract.GetRevertIcyHash(&_Icyswapbtc.CallOpts, icyAmount, btcAddress, btcAmount, nonce, deadline) +} + +// GetRevertIcyHash is a free data retrieval call binding the contract method 0x32ce558d. +// +// Solidity: function getRevertIcyHash(uint256 icyAmount, string btcAddress, uint256 btcAmount, uint256 nonce, uint256 deadline) view returns(bytes32 hash) +func (_Icyswapbtc *IcyswapbtcCallerSession) GetRevertIcyHash(icyAmount *big.Int, btcAddress string, btcAmount *big.Int, nonce *big.Int, deadline *big.Int) ([32]byte, error) { + return _Icyswapbtc.Contract.GetRevertIcyHash(&_Icyswapbtc.CallOpts, icyAmount, btcAddress, btcAmount, nonce, deadline) +} + +// GetSigner is a free data retrieval call binding the contract method 0xf7b2ec0d. +// +// Solidity: function getSigner(bytes32 _digest, bytes _signature) view returns(address) +func (_Icyswapbtc *IcyswapbtcCaller) GetSigner(opts *bind.CallOpts, _digest [32]byte, _signature []byte) (common.Address, error) { + var out []interface{} + err := _Icyswapbtc.contract.Call(opts, &out, "getSigner", _digest, _signature) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// GetSigner is a free data retrieval call binding the contract method 0xf7b2ec0d. +// +// Solidity: function getSigner(bytes32 _digest, bytes _signature) view returns(address) +func (_Icyswapbtc *IcyswapbtcSession) GetSigner(_digest [32]byte, _signature []byte) (common.Address, error) { + return _Icyswapbtc.Contract.GetSigner(&_Icyswapbtc.CallOpts, _digest, _signature) +} + +// GetSigner is a free data retrieval call binding the contract method 0xf7b2ec0d. +// +// Solidity: function getSigner(bytes32 _digest, bytes _signature) view returns(address) +func (_Icyswapbtc *IcyswapbtcCallerSession) GetSigner(_digest [32]byte, _signature []byte) (common.Address, error) { + return _Icyswapbtc.Contract.GetSigner(&_Icyswapbtc.CallOpts, _digest, _signature) +} + +// GetSwapHash is a free data retrieval call binding the contract method 0x6327a9d0. +// +// Solidity: function getSwapHash(uint256 icyAmount, string btcAddress, uint256 btcAmount, uint256 nonce, uint256 deadline) view returns(bytes32 hash) +func (_Icyswapbtc *IcyswapbtcCaller) GetSwapHash(opts *bind.CallOpts, icyAmount *big.Int, btcAddress string, btcAmount *big.Int, nonce *big.Int, deadline *big.Int) ([32]byte, error) { + var out []interface{} + err := _Icyswapbtc.contract.Call(opts, &out, "getSwapHash", icyAmount, btcAddress, btcAmount, nonce, deadline) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// GetSwapHash is a free data retrieval call binding the contract method 0x6327a9d0. +// +// Solidity: function getSwapHash(uint256 icyAmount, string btcAddress, uint256 btcAmount, uint256 nonce, uint256 deadline) view returns(bytes32 hash) +func (_Icyswapbtc *IcyswapbtcSession) GetSwapHash(icyAmount *big.Int, btcAddress string, btcAmount *big.Int, nonce *big.Int, deadline *big.Int) ([32]byte, error) { + return _Icyswapbtc.Contract.GetSwapHash(&_Icyswapbtc.CallOpts, icyAmount, btcAddress, btcAmount, nonce, deadline) +} + +// GetSwapHash is a free data retrieval call binding the contract method 0x6327a9d0. +// +// Solidity: function getSwapHash(uint256 icyAmount, string btcAddress, uint256 btcAmount, uint256 nonce, uint256 deadline) view returns(bytes32 hash) +func (_Icyswapbtc *IcyswapbtcCallerSession) GetSwapHash(icyAmount *big.Int, btcAddress string, btcAmount *big.Int, nonce *big.Int, deadline *big.Int) ([32]byte, error) { + return _Icyswapbtc.Contract.GetSwapHash(&_Icyswapbtc.CallOpts, icyAmount, btcAddress, btcAmount, nonce, deadline) +} + +// Icy is a free data retrieval call binding the contract method 0x7f245ab1. +// +// Solidity: function icy() view returns(address) +func (_Icyswapbtc *IcyswapbtcCaller) Icy(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _Icyswapbtc.contract.Call(opts, &out, "icy") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Icy is a free data retrieval call binding the contract method 0x7f245ab1. +// +// Solidity: function icy() view returns(address) +func (_Icyswapbtc *IcyswapbtcSession) Icy() (common.Address, error) { + return _Icyswapbtc.Contract.Icy(&_Icyswapbtc.CallOpts) +} + +// Icy is a free data retrieval call binding the contract method 0x7f245ab1. +// +// Solidity: function icy() view returns(address) +func (_Icyswapbtc *IcyswapbtcCallerSession) Icy() (common.Address, error) { + return _Icyswapbtc.Contract.Icy(&_Icyswapbtc.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_Icyswapbtc *IcyswapbtcCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _Icyswapbtc.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_Icyswapbtc *IcyswapbtcSession) Owner() (common.Address, error) { + return _Icyswapbtc.Contract.Owner(&_Icyswapbtc.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_Icyswapbtc *IcyswapbtcCallerSession) Owner() (common.Address, error) { + return _Icyswapbtc.Contract.Owner(&_Icyswapbtc.CallOpts) +} + +// RevertedIcyHashes is a free data retrieval call binding the contract method 0x3d2b52db. +// +// Solidity: function revertedIcyHashes(bytes32 ) view returns(bool) +func (_Icyswapbtc *IcyswapbtcCaller) RevertedIcyHashes(opts *bind.CallOpts, arg0 [32]byte) (bool, error) { + var out []interface{} + err := _Icyswapbtc.contract.Call(opts, &out, "revertedIcyHashes", arg0) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// RevertedIcyHashes is a free data retrieval call binding the contract method 0x3d2b52db. +// +// Solidity: function revertedIcyHashes(bytes32 ) view returns(bool) +func (_Icyswapbtc *IcyswapbtcSession) RevertedIcyHashes(arg0 [32]byte) (bool, error) { + return _Icyswapbtc.Contract.RevertedIcyHashes(&_Icyswapbtc.CallOpts, arg0) +} + +// RevertedIcyHashes is a free data retrieval call binding the contract method 0x3d2b52db. +// +// Solidity: function revertedIcyHashes(bytes32 ) view returns(bool) +func (_Icyswapbtc *IcyswapbtcCallerSession) RevertedIcyHashes(arg0 [32]byte) (bool, error) { + return _Icyswapbtc.Contract.RevertedIcyHashes(&_Icyswapbtc.CallOpts, arg0) +} + +// SignerAddress is a free data retrieval call binding the contract method 0x5b7633d0. +// +// Solidity: function signerAddress() view returns(address) +func (_Icyswapbtc *IcyswapbtcCaller) SignerAddress(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _Icyswapbtc.contract.Call(opts, &out, "signerAddress") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// SignerAddress is a free data retrieval call binding the contract method 0x5b7633d0. +// +// Solidity: function signerAddress() view returns(address) +func (_Icyswapbtc *IcyswapbtcSession) SignerAddress() (common.Address, error) { + return _Icyswapbtc.Contract.SignerAddress(&_Icyswapbtc.CallOpts) +} + +// SignerAddress is a free data retrieval call binding the contract method 0x5b7633d0. +// +// Solidity: function signerAddress() view returns(address) +func (_Icyswapbtc *IcyswapbtcCallerSession) SignerAddress() (common.Address, error) { + return _Icyswapbtc.Contract.SignerAddress(&_Icyswapbtc.CallOpts) +} + +// SwappedHashes is a free data retrieval call binding the contract method 0x6072e236. +// +// Solidity: function swappedHashes(bytes32 ) view returns(bool) +func (_Icyswapbtc *IcyswapbtcCaller) SwappedHashes(opts *bind.CallOpts, arg0 [32]byte) (bool, error) { + var out []interface{} + err := _Icyswapbtc.contract.Call(opts, &out, "swappedHashes", arg0) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// SwappedHashes is a free data retrieval call binding the contract method 0x6072e236. +// +// Solidity: function swappedHashes(bytes32 ) view returns(bool) +func (_Icyswapbtc *IcyswapbtcSession) SwappedHashes(arg0 [32]byte) (bool, error) { + return _Icyswapbtc.Contract.SwappedHashes(&_Icyswapbtc.CallOpts, arg0) +} + +// SwappedHashes is a free data retrieval call binding the contract method 0x6072e236. +// +// Solidity: function swappedHashes(bytes32 ) view returns(bool) +func (_Icyswapbtc *IcyswapbtcCallerSession) SwappedHashes(arg0 [32]byte) (bool, error) { + return _Icyswapbtc.Contract.SwappedHashes(&_Icyswapbtc.CallOpts, arg0) +} + +// RevertIcy is a paid mutator transaction binding the contract method 0x666f5a65. +// +// Solidity: function revertIcy(uint256 icyAmount, string btcAddress, uint256 btcAmount, uint256 nonce, uint256 deadline, bytes _signature) returns() +func (_Icyswapbtc *IcyswapbtcTransactor) RevertIcy(opts *bind.TransactOpts, icyAmount *big.Int, btcAddress string, btcAmount *big.Int, nonce *big.Int, deadline *big.Int, _signature []byte) (*types.Transaction, error) { + return _Icyswapbtc.contract.Transact(opts, "revertIcy", icyAmount, btcAddress, btcAmount, nonce, deadline, _signature) +} + +// RevertIcy is a paid mutator transaction binding the contract method 0x666f5a65. +// +// Solidity: function revertIcy(uint256 icyAmount, string btcAddress, uint256 btcAmount, uint256 nonce, uint256 deadline, bytes _signature) returns() +func (_Icyswapbtc *IcyswapbtcSession) RevertIcy(icyAmount *big.Int, btcAddress string, btcAmount *big.Int, nonce *big.Int, deadline *big.Int, _signature []byte) (*types.Transaction, error) { + return _Icyswapbtc.Contract.RevertIcy(&_Icyswapbtc.TransactOpts, icyAmount, btcAddress, btcAmount, nonce, deadline, _signature) +} + +// RevertIcy is a paid mutator transaction binding the contract method 0x666f5a65. +// +// Solidity: function revertIcy(uint256 icyAmount, string btcAddress, uint256 btcAmount, uint256 nonce, uint256 deadline, bytes _signature) returns() +func (_Icyswapbtc *IcyswapbtcTransactorSession) RevertIcy(icyAmount *big.Int, btcAddress string, btcAmount *big.Int, nonce *big.Int, deadline *big.Int, _signature []byte) (*types.Transaction, error) { + return _Icyswapbtc.Contract.RevertIcy(&_Icyswapbtc.TransactOpts, icyAmount, btcAddress, btcAmount, nonce, deadline, _signature) +} + +// SetSigner is a paid mutator transaction binding the contract method 0x6c19e783. +// +// Solidity: function setSigner(address _signerAddress) returns() +func (_Icyswapbtc *IcyswapbtcTransactor) SetSigner(opts *bind.TransactOpts, _signerAddress common.Address) (*types.Transaction, error) { + return _Icyswapbtc.contract.Transact(opts, "setSigner", _signerAddress) +} + +// SetSigner is a paid mutator transaction binding the contract method 0x6c19e783. +// +// Solidity: function setSigner(address _signerAddress) returns() +func (_Icyswapbtc *IcyswapbtcSession) SetSigner(_signerAddress common.Address) (*types.Transaction, error) { + return _Icyswapbtc.Contract.SetSigner(&_Icyswapbtc.TransactOpts, _signerAddress) +} + +// SetSigner is a paid mutator transaction binding the contract method 0x6c19e783. +// +// Solidity: function setSigner(address _signerAddress) returns() +func (_Icyswapbtc *IcyswapbtcTransactorSession) SetSigner(_signerAddress common.Address) (*types.Transaction, error) { + return _Icyswapbtc.Contract.SetSigner(&_Icyswapbtc.TransactOpts, _signerAddress) +} + +// Swap is a paid mutator transaction binding the contract method 0xade44138. +// +// Solidity: function swap(uint256 icyAmount, string btcAddress, uint256 btcAmount, uint256 nonce, uint256 deadline, bytes _signature) returns() +func (_Icyswapbtc *IcyswapbtcTransactor) Swap(opts *bind.TransactOpts, icyAmount *big.Int, btcAddress string, btcAmount *big.Int, nonce *big.Int, deadline *big.Int, _signature []byte) (*types.Transaction, error) { + return _Icyswapbtc.contract.Transact(opts, "swap", icyAmount, btcAddress, btcAmount, nonce, deadline, _signature) +} + +// Swap is a paid mutator transaction binding the contract method 0xade44138. +// +// Solidity: function swap(uint256 icyAmount, string btcAddress, uint256 btcAmount, uint256 nonce, uint256 deadline, bytes _signature) returns() +func (_Icyswapbtc *IcyswapbtcSession) Swap(icyAmount *big.Int, btcAddress string, btcAmount *big.Int, nonce *big.Int, deadline *big.Int, _signature []byte) (*types.Transaction, error) { + return _Icyswapbtc.Contract.Swap(&_Icyswapbtc.TransactOpts, icyAmount, btcAddress, btcAmount, nonce, deadline, _signature) +} + +// Swap is a paid mutator transaction binding the contract method 0xade44138. +// +// Solidity: function swap(uint256 icyAmount, string btcAddress, uint256 btcAmount, uint256 nonce, uint256 deadline, bytes _signature) returns() +func (_Icyswapbtc *IcyswapbtcTransactorSession) Swap(icyAmount *big.Int, btcAddress string, btcAmount *big.Int, nonce *big.Int, deadline *big.Int, _signature []byte) (*types.Transaction, error) { + return _Icyswapbtc.Contract.Swap(&_Icyswapbtc.TransactOpts, icyAmount, btcAddress, btcAmount, nonce, deadline, _signature) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_Icyswapbtc *IcyswapbtcTransactor) TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*types.Transaction, error) { + return _Icyswapbtc.contract.Transact(opts, "transferOwnership", newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_Icyswapbtc *IcyswapbtcSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _Icyswapbtc.Contract.TransferOwnership(&_Icyswapbtc.TransactOpts, newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_Icyswapbtc *IcyswapbtcTransactorSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _Icyswapbtc.Contract.TransferOwnership(&_Icyswapbtc.TransactOpts, newOwner) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() returns() +func (_Icyswapbtc *IcyswapbtcTransactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { + return _Icyswapbtc.contract.RawTransact(opts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() returns() +func (_Icyswapbtc *IcyswapbtcSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _Icyswapbtc.Contract.Fallback(&_Icyswapbtc.TransactOpts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() returns() +func (_Icyswapbtc *IcyswapbtcTransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _Icyswapbtc.Contract.Fallback(&_Icyswapbtc.TransactOpts, calldata) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_Icyswapbtc *IcyswapbtcTransactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Icyswapbtc.contract.RawTransact(opts, nil) // calldata is disallowed for receive function +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_Icyswapbtc *IcyswapbtcSession) Receive() (*types.Transaction, error) { + return _Icyswapbtc.Contract.Receive(&_Icyswapbtc.TransactOpts) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_Icyswapbtc *IcyswapbtcTransactorSession) Receive() (*types.Transaction, error) { + return _Icyswapbtc.Contract.Receive(&_Icyswapbtc.TransactOpts) +} + +// IcyswapbtcOwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the Icyswapbtc contract. +type IcyswapbtcOwnershipTransferredIterator struct { + Event *IcyswapbtcOwnershipTransferred // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *IcyswapbtcOwnershipTransferredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(IcyswapbtcOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(IcyswapbtcOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *IcyswapbtcOwnershipTransferredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *IcyswapbtcOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// IcyswapbtcOwnershipTransferred represents a OwnershipTransferred event raised by the Icyswapbtc contract. +type IcyswapbtcOwnershipTransferred struct { + User common.Address + NewOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferred is a free log retrieval operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed user, address indexed newOwner) +func (_Icyswapbtc *IcyswapbtcFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, user []common.Address, newOwner []common.Address) (*IcyswapbtcOwnershipTransferredIterator, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _Icyswapbtc.contract.FilterLogs(opts, "OwnershipTransferred", userRule, newOwnerRule) + if err != nil { + return nil, err + } + return &IcyswapbtcOwnershipTransferredIterator{contract: _Icyswapbtc.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferred is a free log subscription operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed user, address indexed newOwner) +func (_Icyswapbtc *IcyswapbtcFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *IcyswapbtcOwnershipTransferred, user []common.Address, newOwner []common.Address) (event.Subscription, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _Icyswapbtc.contract.WatchLogs(opts, "OwnershipTransferred", userRule, newOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(IcyswapbtcOwnershipTransferred) + if err := _Icyswapbtc.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOwnershipTransferred is a log parse operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed user, address indexed newOwner) +func (_Icyswapbtc *IcyswapbtcFilterer) ParseOwnershipTransferred(log types.Log) (*IcyswapbtcOwnershipTransferred, error) { + event := new(IcyswapbtcOwnershipTransferred) + if err := _Icyswapbtc.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// IcyswapbtcRevertIcyIterator is returned from FilterRevertIcy and is used to iterate over the raw logs and unpacked data for RevertIcy events raised by the Icyswapbtc contract. +type IcyswapbtcRevertIcyIterator struct { + Event *IcyswapbtcRevertIcy // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *IcyswapbtcRevertIcyIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(IcyswapbtcRevertIcy) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(IcyswapbtcRevertIcy) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *IcyswapbtcRevertIcyIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *IcyswapbtcRevertIcyIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// IcyswapbtcRevertIcy represents a RevertIcy event raised by the Icyswapbtc contract. +type IcyswapbtcRevertIcy struct { + IcyAmount *big.Int + BtcAddress string + BtcAmount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRevertIcy is a free log retrieval operation binding the contract event 0xd65289b780c2a5756f2385450f37835d3af0fd779700af98d868c8f952e9acff. +// +// Solidity: event RevertIcy(uint256 icyAmount, string btcAddress, uint256 btcAmount) +func (_Icyswapbtc *IcyswapbtcFilterer) FilterRevertIcy(opts *bind.FilterOpts) (*IcyswapbtcRevertIcyIterator, error) { + + logs, sub, err := _Icyswapbtc.contract.FilterLogs(opts, "RevertIcy") + if err != nil { + return nil, err + } + return &IcyswapbtcRevertIcyIterator{contract: _Icyswapbtc.contract, event: "RevertIcy", logs: logs, sub: sub}, nil +} + +// WatchRevertIcy is a free log subscription operation binding the contract event 0xd65289b780c2a5756f2385450f37835d3af0fd779700af98d868c8f952e9acff. +// +// Solidity: event RevertIcy(uint256 icyAmount, string btcAddress, uint256 btcAmount) +func (_Icyswapbtc *IcyswapbtcFilterer) WatchRevertIcy(opts *bind.WatchOpts, sink chan<- *IcyswapbtcRevertIcy) (event.Subscription, error) { + + logs, sub, err := _Icyswapbtc.contract.WatchLogs(opts, "RevertIcy") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(IcyswapbtcRevertIcy) + if err := _Icyswapbtc.contract.UnpackLog(event, "RevertIcy", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRevertIcy is a log parse operation binding the contract event 0xd65289b780c2a5756f2385450f37835d3af0fd779700af98d868c8f952e9acff. +// +// Solidity: event RevertIcy(uint256 icyAmount, string btcAddress, uint256 btcAmount) +func (_Icyswapbtc *IcyswapbtcFilterer) ParseRevertIcy(log types.Log) (*IcyswapbtcRevertIcy, error) { + event := new(IcyswapbtcRevertIcy) + if err := _Icyswapbtc.contract.UnpackLog(event, "RevertIcy", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// IcyswapbtcSetSignerIterator is returned from FilterSetSigner and is used to iterate over the raw logs and unpacked data for SetSigner events raised by the Icyswapbtc contract. +type IcyswapbtcSetSignerIterator struct { + Event *IcyswapbtcSetSigner // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *IcyswapbtcSetSignerIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(IcyswapbtcSetSigner) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(IcyswapbtcSetSigner) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *IcyswapbtcSetSignerIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *IcyswapbtcSetSignerIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// IcyswapbtcSetSigner represents a SetSigner event raised by the Icyswapbtc contract. +type IcyswapbtcSetSigner struct { + SignerAddress common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterSetSigner is a free log retrieval operation binding the contract event 0xbb10aee7ef5a307b8097c6a7f2892b909ff1736fd24a6a5260640c185f7153b6. +// +// Solidity: event SetSigner(address signerAddress) +func (_Icyswapbtc *IcyswapbtcFilterer) FilterSetSigner(opts *bind.FilterOpts) (*IcyswapbtcSetSignerIterator, error) { + + logs, sub, err := _Icyswapbtc.contract.FilterLogs(opts, "SetSigner") + if err != nil { + return nil, err + } + return &IcyswapbtcSetSignerIterator{contract: _Icyswapbtc.contract, event: "SetSigner", logs: logs, sub: sub}, nil +} + +// WatchSetSigner is a free log subscription operation binding the contract event 0xbb10aee7ef5a307b8097c6a7f2892b909ff1736fd24a6a5260640c185f7153b6. +// +// Solidity: event SetSigner(address signerAddress) +func (_Icyswapbtc *IcyswapbtcFilterer) WatchSetSigner(opts *bind.WatchOpts, sink chan<- *IcyswapbtcSetSigner) (event.Subscription, error) { + + logs, sub, err := _Icyswapbtc.contract.WatchLogs(opts, "SetSigner") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(IcyswapbtcSetSigner) + if err := _Icyswapbtc.contract.UnpackLog(event, "SetSigner", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseSetSigner is a log parse operation binding the contract event 0xbb10aee7ef5a307b8097c6a7f2892b909ff1736fd24a6a5260640c185f7153b6. +// +// Solidity: event SetSigner(address signerAddress) +func (_Icyswapbtc *IcyswapbtcFilterer) ParseSetSigner(log types.Log) (*IcyswapbtcSetSigner, error) { + event := new(IcyswapbtcSetSigner) + if err := _Icyswapbtc.contract.UnpackLog(event, "SetSigner", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// IcyswapbtcSwapIterator is returned from FilterSwap and is used to iterate over the raw logs and unpacked data for Swap events raised by the Icyswapbtc contract. +type IcyswapbtcSwapIterator struct { + Event *IcyswapbtcSwap // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *IcyswapbtcSwapIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(IcyswapbtcSwap) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(IcyswapbtcSwap) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *IcyswapbtcSwapIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *IcyswapbtcSwapIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// IcyswapbtcSwap represents a Swap event raised by the Icyswapbtc contract. +type IcyswapbtcSwap struct { + IcyAmount *big.Int + BtcAddress string + BtcAmount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterSwap is a free log retrieval operation binding the contract event 0x6a7e3add5ba4ffd84c70888b34c2abc4eb346e94dbd82a1ba6dd8e335c682063. +// +// Solidity: event Swap(uint256 icyAmount, string btcAddress, uint256 btcAmount) +func (_Icyswapbtc *IcyswapbtcFilterer) FilterSwap(opts *bind.FilterOpts) (*IcyswapbtcSwapIterator, error) { + + logs, sub, err := _Icyswapbtc.contract.FilterLogs(opts, "Swap") + if err != nil { + return nil, err + } + return &IcyswapbtcSwapIterator{contract: _Icyswapbtc.contract, event: "Swap", logs: logs, sub: sub}, nil +} + +// WatchSwap is a free log subscription operation binding the contract event 0x6a7e3add5ba4ffd84c70888b34c2abc4eb346e94dbd82a1ba6dd8e335c682063. +// +// Solidity: event Swap(uint256 icyAmount, string btcAddress, uint256 btcAmount) +func (_Icyswapbtc *IcyswapbtcFilterer) WatchSwap(opts *bind.WatchOpts, sink chan<- *IcyswapbtcSwap) (event.Subscription, error) { + + logs, sub, err := _Icyswapbtc.contract.WatchLogs(opts, "Swap") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(IcyswapbtcSwap) + if err := _Icyswapbtc.contract.UnpackLog(event, "Swap", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseSwap is a log parse operation binding the contract event 0x6a7e3add5ba4ffd84c70888b34c2abc4eb346e94dbd82a1ba6dd8e335c682063. +// +// Solidity: event Swap(uint256 icyAmount, string btcAddress, uint256 btcAmount) +func (_Icyswapbtc *IcyswapbtcFilterer) ParseSwap(log types.Log) (*IcyswapbtcSwap, error) { + event := new(IcyswapbtcSwap) + if err := _Icyswapbtc.contract.UnpackLog(event, "Swap", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index cebed6587..b791340bd 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -10,10 +10,12 @@ import ( "github.com/dwarvesf/fortress-api/pkg/controller/conversionrate" "github.com/dwarvesf/fortress-api/pkg/controller/deliverymetrics" "github.com/dwarvesf/fortress-api/pkg/controller/discord" + "github.com/dwarvesf/fortress-api/pkg/controller/dynamicevents" "github.com/dwarvesf/fortress-api/pkg/controller/earn" "github.com/dwarvesf/fortress-api/pkg/controller/employee" "github.com/dwarvesf/fortress-api/pkg/controller/event" "github.com/dwarvesf/fortress-api/pkg/controller/icy" + "github.com/dwarvesf/fortress-api/pkg/controller/icyswapbtc" "github.com/dwarvesf/fortress-api/pkg/controller/invoice" "github.com/dwarvesf/fortress-api/pkg/controller/memologs" "github.com/dwarvesf/fortress-api/pkg/controller/news" @@ -21,11 +23,10 @@ import ( "github.com/dwarvesf/fortress-api/pkg/service" "github.com/dwarvesf/fortress-api/pkg/store" "github.com/dwarvesf/fortress-api/pkg/worker" - "github.com/dwarvesf/fortress-api/pkg/controller/dynamicevents" ) type Controller struct { - Auth auth.IController + Auth auth.IController BraineryLog brainerylogs.IController Client client.IController CompanyInfo companyinfo.IController @@ -41,6 +42,7 @@ type Controller struct { News news.IController Event event.IController DynamicEvents dynamicevents.IController + Swap icyswapbtc.IController } func New(store *store.Store, repo store.DBRepo, service *service.Service, worker *worker.Worker, logger logger.Logger, cfg *config.Config) *Controller { @@ -61,5 +63,6 @@ func New(store *store.Store, repo store.DBRepo, service *service.Service, worker News: news.New(store, service, logger, cfg), Event: event.New(store, repo, service, logger, cfg), DynamicEvents: dynamicevents.New(store, service, logger, cfg), + Swap: icyswapbtc.New(store, service, logger, cfg), } } diff --git a/pkg/controller/employee/update_base_salary.go b/pkg/controller/employee/update_base_salary.go index d63e2de9a..f65080a0e 100644 --- a/pkg/controller/employee/update_base_salary.go +++ b/pkg/controller/employee/update_base_salary.go @@ -5,7 +5,7 @@ import ( "time" "gorm.io/gorm" - + "github.com/dwarvesf/fortress-api/pkg/logger" "github.com/dwarvesf/fortress-api/pkg/model" ) diff --git a/pkg/controller/icyswapbtc/icyswapbtc.go b/pkg/controller/icyswapbtc/icyswapbtc.go new file mode 100644 index 000000000..f71836f87 --- /dev/null +++ b/pkg/controller/icyswapbtc/icyswapbtc.go @@ -0,0 +1,259 @@ +package icyswapbtc + +import ( + "errors" + "fmt" + "math" + "strconv" + "strings" + + "github.com/bwmarrin/discordgo" + + "github.com/dwarvesf/fortress-api/pkg/config" + "github.com/dwarvesf/fortress-api/pkg/logger" + "github.com/dwarvesf/fortress-api/pkg/model" + "github.com/dwarvesf/fortress-api/pkg/service" + "github.com/dwarvesf/fortress-api/pkg/service/evm" + "github.com/dwarvesf/fortress-api/pkg/service/mochipay" + "github.com/dwarvesf/fortress-api/pkg/store" + "github.com/dwarvesf/fortress-api/pkg/utils/stringutils" +) + +type controller struct { + store *store.Store + service *service.Service + logger logger.Logger + config *config.Config +} + +const ( + TokenDecimal int = 18 + PlatformDiscord = "discord" +) + +func New(store *store.Store, service *service.Service, logger logger.Logger, cfg *config.Config) IController { + return &controller{ + store: store, + service: service, + logger: logger, + config: cfg, + } +} + +func (c *controller) Swap(transferRequest *model.TransferRequestResponse) (string, error) { + err := c.WithdrawFromVault(transferRequest) + if err != nil { + c.logger.Error(err, "failed to withdraw ICY") + return "", fmt.Errorf("can't withdraw ICY, err: %v", err) + } + + icyInfo, err := c.service.IcyBackend.GetIcyInfo() + if err != nil || icyInfo == nil { + c.logger.Error(err, "failed to get ICY info") + return "", fmt.Errorf("can't get ICY info, err: %v", err) + } + + signature, err := c.GenerateSignature(transferRequest, icyInfo) + if err != nil || signature == nil { + c.logger.Error(err, "failed to generate signature for swap") + return "", fmt.Errorf("can't get signature for swap, err: %w", err) + } + + swapResponse, err := c.service.IcyBackend.Swap(*signature, transferRequest.Description) + if err != nil || swapResponse == nil { + c.logger.Error(err, "failed to swap icy to btc") + return "", fmt.Errorf("cant' swap icy to btc, err: %w", err) + } + + // Calculate satoshiAmountReceived by converting string values to float64 and subtracting fee + btcAmount, err := strconv.ParseFloat(signature.BtcAmount, 64) + if err != nil { + c.logger.Error(err, "failed to parse BtcAmount") + // Continue with the function as the swap was successful + } + + minSatoshiFee, err := strconv.ParseFloat(icyInfo.MinSatoshiFee, 64) + if err != nil { + c.logger.Error(err, "failed to parse MinSatoshiFee") + // Continue with the function as the swap was successful + } + + satoshiAmountReceived := btcAmount - minSatoshiFee + + // Format using %0.f which rounds to nearest integer with no decimal places + satoshiAmountReceivedStr := fmt.Sprintf("%0.f", satoshiAmountReceived) + + if err = c.DmSuccessSwapMessage(transferRequest, satoshiAmountReceivedStr, swapResponse.TxHash); err != nil { + c.logger.Error(err, "failed to send success swap message") + } + //Even if sending success message fails, we still return the txHash without error + //since the swap itself was successful + return swapResponse.TxHash, nil +} + +func (c *controller) RevertIcyToUser(transferRequest *model.TransferRequestResponse) (string, error) { + depositToVault, err := c.service.MochiPay.DepositToVault(&mochipay.DepositToVaultRequest{}) + if err != nil || len(depositToVault) == 0 { + c.logger.Error(err, "failed to revert icy to user") + return "", fmt.Errorf("can't revert icy to user, err: %v", err) + } + + var destinationAddress string + for _, d := range depositToVault { + if d.Token != nil && d.Token.ID == transferRequest.TokenID { + destinationAddress = d.Contract.Address + break + } + } + + if destinationAddress == "" { + c.logger.Error(err, "not found application evm address") + return "", errors.New("not found application evm address") + } + + transferResponse, err := c.service.IcyBackend.Transfer(stringutils.FloatToString(transferRequest.Amount, int64(TokenDecimal)), destinationAddress) + if err != nil || transferResponse == nil || transferResponse.TxHash == "" { + c.logger.Error(err, "failed to transfer icy ") + return "", fmt.Errorf("cant' transfer icy, err: %w", err) + } + + transaction, err := c.service.MochiPay.TransferFromVaultToUser(transferRequest.ProfileID, &mochipay.TransferFromVaultRequest{ + RecipientIDs: []string{transferRequest.ProfileID}, + Amounts: []string{transferRequest.Amount}, + TokenID: transferRequest.TokenID, + Description: "Revert icy to user", + }) + if err != nil || len(transaction) == 0 { + c.logger.Error(err, "failed to revert icy to user") + return transferResponse.TxHash, fmt.Errorf("can't revert icy to user, err: %v", err) + } + + err = c.DmRevertMessage(transferRequest, transaction[0].TxId) + if err != nil { + c.logger.Error(err, "failed to send revert message") + } + + return transferResponse.TxHash, nil +} + +func (c *controller) WithdrawFromVault(transferRequest *model.TransferRequestResponse) error { + _, err := c.service.MochiPay.WithdrawFromVault(&mochipay.WithdrawFromVaultRequest{ + Amount: stringutils.FloatToString(transferRequest.Amount, int64(TokenDecimal)), + TokenID: transferRequest.TokenID, + }) + if err != nil { + c.logger.Error(err, "failed to withdraw from vault") + return err + } + return nil +} + +func (c *controller) GenerateSignature(transferRequest *model.TransferRequestResponse, icyInfo *model.IcyInfo) (*model.GenerateSignature, error) { + if icyInfo == nil { + err := errors.New("failed to get ICY info") + c.logger.Error(err, "Icy info empty") + return nil, err + } + + icyAmount, err := strconv.ParseFloat(transferRequest.Amount, 64) + if err != nil { + c.logger.Error(err, "invalid amount format") + return nil, fmt.Errorf("invalid amount format: %w", err) + } + + icySatoshiRate, err := strconv.ParseFloat(icyInfo.IcySatoshiRate, 64) + if err != nil { + c.logger.Error(err, "invalid ICY-Satoshi rate format") + return nil, fmt.Errorf("invalid ICY-Satoshi rate format: %s - %w", icyInfo.IcySatoshiRate, err) + } + + satoshiAmount := math.Round(icyAmount * icySatoshiRate) + icyAmountWithDecimal := stringutils.FloatToString(transferRequest.Amount, int64(TokenDecimal)) + + signature, err := c.service.IcyBackend.GetSignature(model.GenerateSignatureRequest{ + IcyAmount: icyAmountWithDecimal, // No decimal places + BtcAddress: transferRequest.Description, + BtcAmount: fmt.Sprintf("%.0f", satoshiAmount), // No decimal places + }) + if err != nil { + c.logger.Error(err, "failed to get signature") + } + return signature, err +} + +func (c *controller) getDiscordId(profileId string) string { + profile, err := c.service.MochiProfile.GetProfile(profileId) + if err != nil || profile == nil { + c.logger.Error(err, "failed to get profile") + return "" + } + + var discordID string + for _, account := range profile.AssociatedAccounts { + if account.Platform == PlatformDiscord { + discordID = account.PlatformIdentifier + break + } + } + + if discordID == "" { + c.logger.Error(nil, "discord account not found for profile") + } + + return discordID +} + +func (c *controller) DmRevertMessage(transferRequest *model.TransferRequestResponse, TxId int64) error { + discordId := c.getDiscordId(transferRequest.ProfileID) + if discordId == "" { + c.logger.Error(nil, "not found discord id") + return errors.New("not found discord id") + } + + lines := []string{ + fmt.Sprintf("`TxID. `%d", TxId), + fmt.Sprintf("`Icy Amount. `**%s ICY**", transferRequest.Amount), + } + + embedMessage := &discordgo.MessageEmbed{ + Title: "Successful revert icy", + Description: strings.Join(lines, "\n"), + Color: 0x5cd97d, + } + + err := c.service.Discord.SendDmMessage(discordId, embedMessage) + if err != nil { + c.logger.Error(err, "failed to send DM revert message") + return err + } + + return nil +} + +func (c *controller) DmSuccessSwapMessage(transferRequest *model.TransferRequestResponse, satoshiAmount, txHash string) error { + discordId := c.getDiscordId(transferRequest.ProfileID) + if discordId == "" { + c.logger.Error(nil, "not found discord id") + return errors.New("not found discord id") + } + + lines := []string{ + fmt.Sprintf("`TxID. `[%s](%s/tx/%s)", stringutils.Shorten(txHash), evm.DefaultBaseExplorer, txHash), + fmt.Sprintf("`Icy Amount. `**%s ICY**", transferRequest.Amount), + fmt.Sprintf("`Satoshi Amount. `**%s SAT**", satoshiAmount), + } + + embedMessage := &discordgo.MessageEmbed{ + Title: "Successful swap", + Description: strings.Join(lines, "\n"), + Color: 0x5cd97d, + } + + err := c.service.Discord.SendDmMessage(discordId, embedMessage) + if err != nil { + c.logger.Error(err, "failed to send DM success swap message") + return err + } + + return nil +} diff --git a/pkg/controller/icyswapbtc/interface.go b/pkg/controller/icyswapbtc/interface.go new file mode 100644 index 000000000..34400a968 --- /dev/null +++ b/pkg/controller/icyswapbtc/interface.go @@ -0,0 +1,11 @@ +package icyswapbtc + +import ( + "github.com/dwarvesf/fortress-api/pkg/model" +) + +type IController interface { + GenerateSignature(transferRequest *model.TransferRequestResponse, icyInfo *model.IcyInfo) (*model.GenerateSignature, error) + Swap(transferRequest *model.TransferRequestResponse) (string, error) + RevertIcyToUser(transferRequest *model.TransferRequestResponse) (string, error) +} diff --git a/pkg/handler/communitynft/errs/errors.go b/pkg/handler/communitynft/errs/errors.go index 5d0e516a3..eec507657 100644 --- a/pkg/handler/communitynft/errs/errors.go +++ b/pkg/handler/communitynft/errs/errors.go @@ -3,6 +3,6 @@ package errs import "errors" var ( - ErrInvalidTokenID = errors.New("invalid token id") - ErrTokenNotFound = errors.New("token not found") + ErrInvalidTokenID = errors.New("invalid token id") + ErrTokenNotFound = errors.New("token not found") ) diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index a4ad690a9..813f9c5b7 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -17,6 +17,7 @@ import ( "github.com/dwarvesf/fortress-api/pkg/handler/dashboard/util" "github.com/dwarvesf/fortress-api/pkg/handler/deliverymetric" "github.com/dwarvesf/fortress-api/pkg/handler/discord" + "github.com/dwarvesf/fortress-api/pkg/handler/dynamicevents" "github.com/dwarvesf/fortress-api/pkg/handler/earn" "github.com/dwarvesf/fortress-api/pkg/handler/employee" "github.com/dwarvesf/fortress-api/pkg/handler/engagement" @@ -40,7 +41,6 @@ import ( "github.com/dwarvesf/fortress-api/pkg/service" "github.com/dwarvesf/fortress-api/pkg/store" "github.com/dwarvesf/fortress-api/pkg/worker" - "github.com/dwarvesf/fortress-api/pkg/handler/dynamicevents" ) type Handler struct { diff --git a/pkg/handler/webhook/icyswapbtc.go b/pkg/handler/webhook/icyswapbtc.go new file mode 100644 index 000000000..cd2eef5e1 --- /dev/null +++ b/pkg/handler/webhook/icyswapbtc.go @@ -0,0 +1,123 @@ +package webhook + +import ( + "errors" + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/dwarvesf/fortress-api/pkg/logger" + "github.com/dwarvesf/fortress-api/pkg/model" + "github.com/dwarvesf/fortress-api/pkg/view" +) + +const ( + RequestStatus = "success" +) + +func (h *handler) TransferRequest(c *gin.Context) { + var req TransactionRequestEvent + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, view.CreateResponse[any](nil, nil, err, req, "")) + return + } + + // check approve transfer icy request + if req.Status != RequestStatus { + h.logger.Fields(logger.Fields{ + "data": req, + "event": "Transfer Request", + }).Error(errors.New("invalid request"), "transfer request is invalid") + return + } + + requestIsExist, err := h.store.IcySwapBtcRequest.IsExist(h.repo.DB(), req.RequestCode) + if err != nil { + h.logger.Fields(logger.Fields{ + "data": req, + "event": "Transfer Request", + }).Error(err, "can't check request existed") + return + } + + if requestIsExist { + h.logger.Fields(logger.Fields{ + "data": req, + "event": "Transfer Request", + }).Error(err, "request is existed") + return + } + + // init icy swap btc request record + icySwapBtcRequest, err := h.store.IcySwapBtcRequest.Create(h.repo.DB(), &model.IcySwapBtcRequest{ + ProfileID: req.ProfileID, + RequestCode: req.RequestCode, + Amount: req.Amount, + TokenID: req.TokenID, + Timestamp: req.Timestamp, + TokenName: req.TokenName, + SwapRequestStatus: model.SwapRequestStatusPending, + BtcAddress: req.Description, + TxStatus: req.Status, + TxID: req.TxID, + }) + if err != nil { + h.logger.Fields(logger.Fields{ + "data": req, + "event": "Transfer Request", + }).Error(err, "can't store request") + return + } + + transferRequest := model.TransferRequestResponse{ + ProfileID: req.ProfileID, + Description: req.Description, + RequestCode: req.RequestCode, + Amount: req.Amount, + TokenID: req.TokenID, + Timestamp: req.Timestamp, + TokenName: req.TokenName, + Status: req.Status, + TxID: req.TxID, + } + + // implement swap icy to btc + swapTx, errSwap := h.controller.Swap.Swap(&transferRequest) + if errSwap != nil { + revertStatus := model.RevertRequestStatusSuccess + txDeposit, errRevertIcy := h.controller.Swap.RevertIcyToUser(&transferRequest) + if errRevertIcy != nil { + revertStatus = model.RevertRequestStatusFailed + icySwapBtcRequest.RevertError = errRevertIcy.Error() + } + + // update icy swap btc request failed + icySwapBtcRequest.SwapRequestStatus = model.SwapRequestStatusFailed + icySwapBtcRequest.RevertStatus = revertStatus + icySwapBtcRequest.TxDeposit = txDeposit + icySwapBtcRequest.SwapRequestError = errSwap.Error() + _, err = h.store.IcySwapBtcRequest.Update(h.repo.DB(), icySwapBtcRequest) + if err != nil { + h.logger.Fields(logger.Fields{ + "data": req, + "event": "Transfer Request", + }).Error(err, "can't update failed swap request ") + return + } + + return + } + + // update icy swap btc request success + icySwapBtcRequest.SwapRequestStatus = model.SwapRequestStatusSuccess + icySwapBtcRequest.TxSwap = swapTx + _, err = h.store.IcySwapBtcRequest.Update(h.repo.DB(), icySwapBtcRequest) + if err != nil { + h.logger.Fields(logger.Fields{ + "data": req, + "event": "Transfer Request", + }).Error(err, "can't update success swap request") + return + } +} diff --git a/pkg/handler/webhook/interface.go b/pkg/handler/webhook/interface.go index c327cd862..b1e72341d 100644 --- a/pkg/handler/webhook/interface.go +++ b/pkg/handler/webhook/interface.go @@ -11,4 +11,5 @@ type IHandler interface { ValidateBasecampExpense(c *gin.Context) ValidateOnLeaveRequest(c *gin.Context) ApproveOnLeaveRequest(c *gin.Context) + TransferRequest(c *gin.Context) } diff --git a/pkg/handler/webhook/request.go b/pkg/handler/webhook/request.go index 5a2ea8935..de3bc8432 100644 --- a/pkg/handler/webhook/request.go +++ b/pkg/handler/webhook/request.go @@ -38,3 +38,15 @@ type n8nEvent struct { CalendarData *n8nCalendarEvent `json:"calendarData"` ShouldSyncDiscord string `json:"shouldSyncDiscord"` } + +type TransactionRequestEvent struct { + ProfileID string `json:"profile_id"` + RequestCode string `json:"request_code"` + Status string `json:"status"` + TxID int `json:"tx_id"` + Description string `json:"description"` + Timestamp int64 `json:"timestamp"` + Amount string `json:"amount"` + TokenName string `json:"token_name"` + TokenID string `json:"token_id"` +} diff --git a/pkg/model/icyswapbtc.go b/pkg/model/icyswapbtc.go new file mode 100644 index 000000000..b9d5781c9 --- /dev/null +++ b/pkg/model/icyswapbtc.go @@ -0,0 +1,89 @@ +package model + +import "time" + +type TransferRequestResponse struct { + ProfileID string `json:"profile_id"` + RequestCode string `json:"request_code"` + Status string `json:"status"` + TxID int `json:"tx_id"` + Description string `json:"description"` + Timestamp int64 `json:"timestamp"` + Amount string `json:"amount"` + TokenName string `json:"token_name"` + TokenID string `json:"token_id"` +} + +type IcyInfo struct { + CirculatedIcyBalance string `json:"circulated_icy_balance"` + IcySatoshiRate string `json:"icy_satoshi_rate"` + IcyUsdRate string `json:"icy_usd_rate"` + MinIcyToSwap string `json:"min_icy_to_swap"` + MinSatoshiFee string `json:"min_satoshi_fee"` + SatoshiBalance string `json:"satoshi_balance"` + SatoshiPerUsd float64 `json:"satoshi_per_usd"` + SatoshiUsdRate string `json:"satoshi_usd_rate"` + ServiceFeeRate float64 `json:"service_fee_rate"` +} + +type IcyInfoResponse struct { + Data IcyInfo `json:"data"` + Message string `json:"message"` +} + +type GenerateSignature struct { + BtcAmount string `json:"btc_amount"` + Deadline string `json:"deadline"` + IcyAmount string `json:"icy_amount"` + Nonce string `json:"nonce"` + Signature string `json:"signature"` +} + +type GenerateSignatureResponse struct { + Data GenerateSignature `json:"data"` + Message string `json:"message"` + Error *string `json:"error"` +} + +type GenerateSignatureRequest struct { + BtcAmount string `json:"btc_amount"` + IcyAmount string `json:"icy_amount"` + BtcAddress string `json:"btc_address"` +} + +type SwapResponse struct { + TxHash string `json:"tx_hash"` +} + +type TransferResponse struct { + TxHash string `json:"tx_hash"` +} + +const ( + SwapRequestStatusSuccess = "success" + SwapRequestStatusFailed = "failed" + SwapRequestStatusPending = "pending" + RevertRequestStatusSuccess = "success" + RevertRequestStatusFailed = "failed" +) + +type IcySwapBtcRequest struct { + ID UUID `sql:",type:uuid" json:"id" gorm:"default:uuid()"` + ProfileID string `json:"profile_id" gorm:"profile_id"` + RequestCode string `json:"request_code" gorm:"request_code"` + TxStatus string `json:"tx_status" gorm:"tx_status"` + TxID int `json:"tx_id" gorm:"tx_id"` + BtcAddress string `json:"btc_address" gorm:"btc_address"` + Timestamp int64 `json:"timestamp" gorm:"timestamp"` + Amount string `json:"amount" gorm:"amount"` + TokenName string `json:"token_name" gorm:"token_name"` + TokenID string `json:"token_id" gorm:"token_id"` + SwapRequestStatus string `json:"swap_request_status" gorm:"swap_request_status"` + SwapRequestError string `json:"swap_request_error" gorm:"swap_request_error"` + RevertStatus string `json:"revert_status" gorm:"revert_status"` + RevertError string `json:"revert_error" gorm:"revert_error"` + TxSwap string `json:"tx_swap" gorm:"tx_swap"` + TxDeposit string `json:"tx_deposit" gorm:"tx_deposit"` + CreatedAt time.Time `sql:"default:now()" json:"created_at"` + UpdatedAt *time.Time `sql:"default:now()" json:"updated_at"` +} diff --git a/pkg/mw/mw_test.go b/pkg/mw/mw_test.go index e43d0014a..6658fb0c2 100644 --- a/pkg/mw/mw_test.go +++ b/pkg/mw/mw_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "github.com/golang-jwt/jwt/v4" "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v4" "github.com/stretchr/testify/require" "github.com/dwarvesf/fortress-api/pkg/config" @@ -23,9 +23,9 @@ func generateTestToken(cfg *config.Config) string { StandardClaims: jwt.StandardClaims{ ExpiresAt: time.Now().Add(24 * time.Hour).Unix(), }, - UserID: "26558326e-f009-4b73-a535-64c3a22e558f", - Avatar: "https://s3-ap-southeast-1.amazonaws.com/fortress-images/515357469566395594.png", - Email: "thanh@d.foundation", + UserID: "26558326e-f009-4b73-a535-64c3a22e558f", + Avatar: "https://s3-ap-southeast-1.amazonaws.com/fortress-images/515357469566395594.png", + Email: "thanh@d.foundation", } token, err := authutils.GenerateJWTToken(claims, claims.ExpiresAt, cfg.JWTSecretKey) @@ -44,7 +44,7 @@ func TestWithAuth(t *testing.T) { expectedHTTPCode int expectedError error } - + cfg := config.LoadTestConfig() testToken := generateTestToken(&cfg) diff --git a/pkg/routes/v1.go b/pkg/routes/v1.go index 0d519facd..89cb09ce6 100644 --- a/pkg/routes/v1.go +++ b/pkg/routes/v1.go @@ -45,7 +45,7 @@ func loadV1Routes(r *gin.Engine, h *handler.Handler, repo store.DBRepo, s *store webhook := r.Group("/webhooks") { webhook.POST("/n8n", h.Webhook.N8n) - + webhook.POST("/transfer-request", h.Webhook.TransferRequest) basecampGroup := webhook.Group("/basecamp") { expenseGroup := basecampGroup.Group("/expense") diff --git a/pkg/routes/v1_test.go b/pkg/routes/v1_test.go index e7fdda130..69b22d459 100644 --- a/pkg/routes/v1_test.go +++ b/pkg/routes/v1_test.go @@ -717,6 +717,12 @@ func Test_loadV1Routes(t *testing.T) { Handler: "github.com/dwarvesf/fortress-api/pkg/handler/webhook.IHandler.N8n-fm", }, }, + "/webhooks/transfer-request": { + "POST": { + Method: "POST", + Handler: "github.com/dwarvesf/fortress-api/pkg/handler/webhook.IHandler.TransferRequest-fm", + }, + }, "/webhooks/basecamp/expense/validate": { "POST": { Method: "POST", diff --git a/pkg/service/discord/discord.go b/pkg/service/discord/discord.go index 025e31cd2..e82bc7553 100644 --- a/pkg/service/discord/discord.go +++ b/pkg/service/discord/discord.go @@ -3,6 +3,7 @@ package discord import ( "bytes" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -162,6 +163,58 @@ func (d *discordClient) SendMessage(discordMsg model.DiscordMessage, webhookUrl return &discordMsg, nil } +func (d *discordClient) SendDmMessage(userID string, embed *discordgo.MessageEmbed) error { + // Create a DM channel with the user + channel, err := d.session.UserChannelCreate(userID) + if err != nil { + return fmt.Errorf("failed to create DM channel: %w", err) + } + + if embed == nil { + return errors.New("embed is empty") + } + + // Create the message data + messageData := &discordgo.MessageSend{} + + normalizedEmbed := d.Normalize(embed) + messageData.Embed = normalizedEmbed + + // Send the message to the DM channel + _, err = d.session.ChannelMessageSendComplex(channel.ID, messageData) + if err != nil { + return fmt.Errorf("failed to send DM message: %w", err) + } + + return nil +} + +// Normalize function to maintain consistency with the reference code +func (d *discordClient) Normalize(response *discordgo.MessageEmbed) *discordgo.MessageEmbed { + if response.Timestamp == "" { + response.Timestamp = time.Now().Format(time.RFC3339) + } + + // If timestamp is custom, we don't want to show it + if response.Timestamp == "custom" { + response.Timestamp = "" + } + + if response.Color == 0 { + // default df color #D14960 + response.Color = 13715808 + } + + if response.Footer == nil { + response.Footer = &discordgo.MessageEmbedFooter{ + IconURL: "https://cdn.discordapp.com/avatars/564764617545482251/9c9bd4aaba164fc0b92f13f052405b4d.webp?size=160", + Text: "?help to see all commands", + } + } + + return response +} + func (d *discordClient) SearchMember(discordName string) ([]*discordgo.Member, error) { members := make([]*discordgo.Member, 0) guildMembers, err := d.session.GuildMembersSearch(d.cfg.Discord.IDs.DwarvesGuild, discordName, 1000) diff --git a/pkg/service/discord/service.go b/pkg/service/discord/service.go index 6bc810943..79ddf7c06 100644 --- a/pkg/service/discord/service.go +++ b/pkg/service/discord/service.go @@ -51,6 +51,6 @@ type IService interface { SendMessage(discordMsg model.DiscordMessage, webhookUrl string) (*model.DiscordMessage, error) SendEmbeddedMessageWithChannel(original *model.OriginalDiscordMessage, embed *discordgo.MessageEmbed, channelId string) (*discordgo.Message, error) SendDiscordMessageWithChannel(ses *discordgo.Session, msg *discordgo.Message, channelId string) error - + SendDmMessage(userID string, embed *discordgo.MessageEmbed) error ListActiveThreadsByChannelID(guildID, channelID string) ([]discordgo.Channel, error) } diff --git a/pkg/service/discord/utils.go b/pkg/service/discord/utils.go index edf55e88b..e9dd8a6d4 100644 --- a/pkg/service/discord/utils.go +++ b/pkg/service/discord/utils.go @@ -4,7 +4,7 @@ import ( "time" "github.com/bwmarrin/discordgo" - + "github.com/dwarvesf/fortress-api/pkg/model" ) diff --git a/pkg/service/evm/model.go b/pkg/service/evm/model.go index 013d27ea2..ab5b434c6 100644 --- a/pkg/service/evm/model.go +++ b/pkg/service/evm/model.go @@ -5,5 +5,8 @@ type RpcClient struct { } var ( - DefaultBASEClient = RpcClient{Url: "https://mainnet.base.org"} + DefaultBASEClient = RpcClient{Url: "https://mainnet.base.org"} + TestnetBASEClient = RpcClient{Url: "https://chain-proxy.wallet.coinbase.com?targetName=base-sepolia"} + DefaultBaseExplorer = "https://basescan.org" + TestnetBASEExplorer = "https://sepolia.basescan.org" ) diff --git a/pkg/service/icybackend/icybackend.go b/pkg/service/icybackend/icybackend.go new file mode 100644 index 000000000..69c184aba --- /dev/null +++ b/pkg/service/icybackend/icybackend.go @@ -0,0 +1,319 @@ +package icybackend + +import ( + "bytes" + "context" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "math/big" + "net/http" + "strings" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/dwarvesf/fortress-api/pkg/config" + "github.com/dwarvesf/fortress-api/pkg/contracts/erc20" + "github.com/dwarvesf/fortress-api/pkg/contracts/icyswapbtc" + "github.com/dwarvesf/fortress-api/pkg/logger" + "github.com/dwarvesf/fortress-api/pkg/model" + "github.com/dwarvesf/fortress-api/pkg/service/evm" +) + +const ( + ICYBTCSwapAddress = "0xdA3E22edf0357c781154D8DEDcfC32D7B6B0B12D" + IcyTokenAddress = "0xf289e3b222dd42b185b7e335fa3c5bd6d132441d" +) + +type icybackend struct { + icyswapbtc *icyswapbtc.Icyswapbtc + evm evm.IService + cfg *config.Config + logger logger.Logger +} + +func New(evm evm.IService, cfg *config.Config, l logger.Logger) (IService, error) { + instance, err := icyswapbtc.NewIcyswapbtc(common.HexToAddress(ICYBTCSwapAddress), evm.Client()) + if err != nil { + return nil, err + } + return &icybackend{ + icyswapbtc: instance, + evm: evm, + cfg: cfg, + logger: l, + }, nil +} + +func (i *icybackend) Swap(signature model.GenerateSignature, btcAddress string) (*model.SwapResponse, error) { + // 1. Get required addresses + icyAddress := common.HexToAddress(IcyTokenAddress) + icySwapBtcAddress := common.HexToAddress(ICYBTCSwapAddress) + + // 2. Create private key from config + privateKey, err := crypto.HexToECDSA(i.cfg.MochiPay.IcyPoolPrivateKey) + if err != nil { + i.logger.Errorf(err, "[icybackend.Swap] crypto.HexToECDSA failed") + return nil, err + } + + // 3. Get chain ID + chainID, err := i.evm.Client().ChainID(context.Background()) + if err != nil { + i.logger.Errorf(err, "[icybackend.Swap] client.ChainID failed") + return nil, err + } + + // 4. Create transaction options + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) + if err != nil { + i.logger.Errorf(err, "[icybackend.Swap] NewKeyedTransactorWithChainID failed") + return nil, err + } + + // 5. Get current nonce for the account + nonce, err := i.evm.Client().PendingNonceAt(context.Background(), auth.From) + if err != nil { + i.logger.Errorf(err, "[icybackend.Swap] PendingNonceAt failed") + return nil, err + } + auth.Nonce = big.NewInt(int64(nonce)) + + // 6. Create ERC20 instance for token approval + erc20Instance, err := erc20.NewErc20(icyAddress, i.evm.Client()) + if err != nil { + i.logger.Errorf(err, "[icybackend.Swap] NewErc20 failed") + return nil, err + } + + // 7. Convert string values to appropriate types + icyAmount, ok := new(big.Int).SetString(signature.IcyAmount, 10) + if !ok { + err := fmt.Errorf("invalid icy_amount format") + i.logger.Errorf(err, "[icybackend.Swap] invalid icy_amount format") + return nil, err + } + + btcAmount, ok := new(big.Int).SetString(signature.BtcAmount, 10) + if !ok { + err := fmt.Errorf("invalid btc_amount format") + i.logger.Errorf(err, "[icybackend.Swap] invalid btc_amount format") + return nil, err + } + + sigNonce, ok := new(big.Int).SetString(signature.Nonce, 10) + if !ok { + err := fmt.Errorf("invalid nonce format") + i.logger.Errorf(err, "[icybackend.Swap] invalid nonce format") + return nil, err + } + + deadline, ok := new(big.Int).SetString(signature.Deadline, 10) + if !ok { + err := fmt.Errorf("invalid deadline format") + i.logger.Errorf(err, "[icybackend.Swap] invalid deadline format") + return nil, err + } + + // 8. Convert hex signature string to bytes + signatureBytes, err := hex.DecodeString(strings.TrimPrefix(signature.Signature, "0x")) + if err != nil { + i.logger.Errorf(err, "[icybackend.Swap] hex.DecodeString failed for signature") + return nil, err + } + + // 9. Approve ICY token transfer to the swap contract + txApprove, err := erc20Instance.Approve(auth, icySwapBtcAddress, icyAmount) + if err != nil { + i.logger.Errorf(err, "[icybackend.Swap] erc20.Approve failed") + return nil, err + } + + // 10. Wait for the approval transaction to be mined + receipt, err := bind.WaitMined(context.Background(), i.evm.Client(), txApprove) + if err != nil { + i.logger.Errorf(err, "[icybackend.Swap] WaitMined for approval failed") + return nil, err + } + + if receipt.Status != types.ReceiptStatusSuccessful { + err := fmt.Errorf("approval transaction failed") + i.logger.Errorf(err, "[icybackend.Swap] approval transaction failed") + return nil, err + } + + // 11. Update nonce for the swap transaction + nonce++ + auth.Nonce = big.NewInt(int64(nonce)) + + // 12. Execute the swap using the generated binding + txSwap, err := i.icyswapbtc.Swap( + auth, + icyAmount, + btcAddress, + btcAmount, + sigNonce, + deadline, + signatureBytes, + ) + + if err != nil { + i.logger.Errorf(err, "[icybackend.Swap] icyswapbtc.Swap failed") + return nil, err + } + + // 10. Wait for the approval transaction to be mined + receiptSwap, err := bind.WaitMined(context.Background(), i.evm.Client(), txSwap) + if err != nil { + i.logger.Errorf(err, "[icybackend.Swap] WaitMined for swap failed") + return nil, err + } + + if receiptSwap.Status != types.ReceiptStatusSuccessful { + err := fmt.Errorf("swap transaction failed") + i.logger.Errorf(err, "[icybackend.Swap] swap transaction failed") + return nil, err + } + + // 13. Return transaction hash + return &model.SwapResponse{ + TxHash: txSwap.Hash().String(), + }, nil +} + +func (i *icybackend) Transfer(icyAmount, destinationAddress string) (*model.TransferResponse, error) { + // 1. Get required address + icyAddress := common.HexToAddress(IcyTokenAddress) + destinationAddr := common.HexToAddress(destinationAddress) + + // 2. Create private key from config + privateKey, err := crypto.HexToECDSA(i.cfg.MochiPay.IcyPoolPrivateKey) + if err != nil { + i.logger.Errorf(err, "[icybackend.Transfer] crypto.HexToECDSA failed") + return nil, err + } + + // 3. Get chain ID + chainID, err := i.evm.Client().ChainID(context.Background()) + if err != nil { + i.logger.Errorf(err, "[icybackend.Transfer] client.ChainID failed") + return nil, err + } + + // 4. Create transaction options + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) + if err != nil { + i.logger.Errorf(err, "[icybackend.Transfer] NewKeyedTransactorWithChainID failed") + return nil, err + } + + // 5. Get current nonce for the account + nonce, err := i.evm.Client().PendingNonceAt(context.Background(), auth.From) + if err != nil { + i.logger.Errorf(err, "[icybackend.Transfer] PendingNonceAt failed") + return nil, err + } + auth.Nonce = big.NewInt(int64(nonce)) + + // 6. Create ERC20 instance for token transfer + erc20Instance, err := erc20.NewErc20(icyAddress, i.evm.Client()) + if err != nil { + i.logger.Errorf(err, "[icybackend.Transfer] NewErc20 failed") + return nil, err + } + + // 7. Convert string amount to big.Int + amount, ok := new(big.Int).SetString(icyAmount, 10) + if !ok { + err := fmt.Errorf("invalid icy_amount format") + i.logger.Errorf(err, "[icybackend.Transfer] invalid icy_amount format") + return nil, err + } + + // 8. Execute the transfer transaction + tx, err := erc20Instance.Transfer(auth, destinationAddr, amount) + if err != nil { + i.logger.Errorf(err, "[icybackend.Transfer] erc20.Transfer failed") + return nil, err + } + + // 9. Wait for the transfer transaction to be mined + receipt, err := bind.WaitMined(context.Background(), i.evm.Client(), tx) + if err != nil { + i.logger.Errorf(err, "[icybackend.Transfer] WaitMined for transfer failed") + return nil, err + } + + if receipt.Status != types.ReceiptStatusSuccessful { + err := fmt.Errorf("transfer transaction failed") + i.logger.Errorf(err, "[icybackend.Transfer] transfer transaction failed") + return nil, err + } + + // 10. Return transaction hash + return &model.TransferResponse{ + TxHash: tx.Hash().String(), + }, nil +} + +func (i *icybackend) GetIcyInfo() (*model.IcyInfo, error) { + client := &http.Client{ + Timeout: 20 * time.Second, + } + + url := fmt.Sprintf("%s/api/v1/swap/info", i.cfg.IcyBackend.BaseURL) + + r, err := client.Get(url) + if err != nil { + i.logger.Errorf(err, "[icybackend.GetIcyInfo] client.Get failed") + return nil, err + } + defer r.Body.Close() + + res := &model.IcyInfoResponse{} + if err := json.NewDecoder(r.Body).Decode(res); err != nil { + i.logger.Errorf(err, "[icybackend.GetIcyInfo] decoder.Decode failed") + return nil, err + } + + return &res.Data, nil +} + +func (i *icybackend) GetSignature(request model.GenerateSignatureRequest) (*model.GenerateSignature, error) { + client := &http.Client{ + Timeout: 20 * time.Second, + } + + url := fmt.Sprintf("%s/api/v1/swap/generate-signature", i.cfg.IcyBackend.BaseURL) + + requestBody, err := json.Marshal(request) + + if err != nil { + i.logger.Errorf(err, "[icybackend.GetSignature] json.Marshal failed") + return nil, err + } + + r, err := client.Post(url, "application/json", bytes.NewBuffer(requestBody)) + if err != nil { + i.logger.Errorf(err, "[icybackend.GetSignature] client.Post failed") + return nil, err + } + defer r.Body.Close() + + res := &model.GenerateSignatureResponse{} + if err := json.NewDecoder(r.Body).Decode(res); err != nil { + i.logger.Errorf(err, "[icybackend.GetSignature] decoder.Decode failed") + return nil, err + } + + if res.Error != nil { + return nil, errors.New(*res.Error) + } + + return &res.Data, nil +} diff --git a/pkg/service/icybackend/interface.go b/pkg/service/icybackend/interface.go new file mode 100644 index 000000000..52a60de9a --- /dev/null +++ b/pkg/service/icybackend/interface.go @@ -0,0 +1,10 @@ +package icybackend + +import "github.com/dwarvesf/fortress-api/pkg/model" + +type IService interface { + GetIcyInfo() (*model.IcyInfo, error) + GetSignature(request model.GenerateSignatureRequest) (*model.GenerateSignature, error) + Swap(signature model.GenerateSignature, btcAddress string) (*model.SwapResponse, error) + Transfer(icyAmount, destinationAddress string) (*model.TransferResponse, error) +} diff --git a/pkg/service/mochipay/mochipay.go b/pkg/service/mochipay/mochipay.go index 6c217f6ad..4b3e62ae9 100644 --- a/pkg/service/mochipay/mochipay.go +++ b/pkg/service/mochipay/mochipay.go @@ -2,10 +2,13 @@ package mochipay import ( "bytes" + "encoding/hex" "encoding/json" "fmt" + "golang.org/x/crypto/ed25519" "net/http" "net/url" + "strconv" "strings" "time" @@ -19,11 +22,15 @@ const ( RewardDefaultMsg = "Send money to treasurer" ICYTokenMochiID = "9d25232e-add3-4bd8-b7c6-be6c14debc58" BaseChainName = "BASE" + IcySymbol = "ICY" ) type IService interface { GetListTransactions(req ListTransactionsRequest) (*ListTransactionsResponse, error) GetBatchBalances(profileIds []string) (*BatchBalancesResponse, error) + TransferFromVaultToUser(profileOwnerId string, req *TransferFromVaultRequest) ([]VaultTransaction, error) + WithdrawFromVault(req *WithdrawFromVaultRequest) (*WithdrawFromVaultResponse, error) + DepositToVault(req *DepositToVaultRequest) ([]DepositToVault, error) } type client struct { @@ -143,3 +150,195 @@ func (m *client) GetBatchBalances(profileIds []string) (*BatchBalancesResponse, return res, nil } + +func (c *client) TransferFromVaultToUser(profileOwnerId string, req *TransferFromVaultRequest) ([]VaultTransaction, error) { + var client = &http.Client{ + Timeout: 60 * time.Second, + } + + url := fmt.Sprintf("%s/api/v1/profiles/%s/applications/%s/transfer", c.cfg.MochiPay.BaseURL, profileOwnerId, c.cfg.MochiPay.ApplicationId) + + if req.VaultID == "" { + req.VaultID = c.cfg.MochiPay.ApplicationVaultId + } + requestBody, err := json.Marshal(req) + if err != nil { + c.l.Errorf(err, "[mochipay.TransferFromVaultToUser] json.Marshal failed") + return nil, err + } + + // Create request + httpReq, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(requestBody)) + if err != nil { + c.l.Errorf(err, "[mochipay.TransferFromVaultToUser] http.NewRequest failed") + return nil, err + } + + messageHeader := strconv.FormatInt(time.Now().Unix(), 10) + privateKey, err := hex.DecodeString(c.cfg.MochiPay.ApplicationPrivateKey) + if err != nil { + return nil, err + } + signature := ed25519.Sign(privateKey, []byte(messageHeader)) + + // Set headers + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("X-Message", messageHeader) + httpReq.Header.Set("X-Signature", hex.EncodeToString(signature)) + httpReq.Header.Set("X-Application", c.cfg.MochiPay.ApplicationName) + + // Send request + resp, err := client.Do(httpReq) + if err != nil { + c.l.Errorf(err, "[mochipay.TransferFromVaultToUser] client.Do failed") + return nil, err + } + defer resp.Body.Close() + + // Check status code + if resp.StatusCode != http.StatusOK { + c.l.Error(nil, "[mochipay.TransferFromVaultToUser] received non-OK status code") + return nil, fmt.Errorf("invalid call, code %d", resp.StatusCode) + } + + // Decode response + respData := &TransactionFromVaultResponse{} + if err := json.NewDecoder(resp.Body).Decode(respData); err != nil { + c.l.Errorf(err, "[mochipay.TransferFromVaultToUser] decoder.Decode failed") + return nil, err + } + + return respData.Data, nil +} + +func (c *client) WithdrawFromVault(req *WithdrawFromVaultRequest) (*WithdrawFromVaultResponse, error) { + var client = &http.Client{ + Timeout: 60 * time.Second, + } + + url := fmt.Sprintf("%s/api/v1/profiles/%s/applications/%s/withdraw", c.cfg.MochiPay.BaseURL, c.cfg.MochiPay.ApplicationOwnerId, c.cfg.MochiPay.ApplicationId) + + if req.VaultID == "" { + req.VaultID = c.cfg.MochiPay.ApplicationVaultId + } + if req.Address == "" { + req.Address = c.cfg.MochiPay.IcyPoolPublicKey + } + if req.Platform == "" { + req.Platform = "discord" + } + requestBody, err := json.Marshal(req) + if err != nil { + c.l.Errorf(err, "[mochipay.WithdrawFromVault] json.Marshal failed") + return nil, err + } + + // Create request + httpReq, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(requestBody)) + if err != nil { + c.l.Errorf(err, "[mochipay.WithdrawFromVault] http.NewRequest failed") + return nil, err + } + + messageHeader := strconv.FormatInt(time.Now().Unix(), 10) + privateKey, err := hex.DecodeString(c.cfg.MochiPay.ApplicationPrivateKey) + if err != nil { + return nil, err + } + signature := ed25519.Sign(privateKey, []byte(messageHeader)) + + // Set headers + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("X-Message", messageHeader) + httpReq.Header.Set("X-Signature", hex.EncodeToString(signature)) + httpReq.Header.Set("X-Application", c.cfg.MochiPay.ApplicationName) + + // Send request + resp, err := client.Do(httpReq) + if err != nil { + c.l.Errorf(err, "[mochipay.WithdrawFromVault] client.Do failed") + return nil, err + } + defer resp.Body.Close() + + // Check status code + if resp.StatusCode != http.StatusOK { + c.l.Error(nil, "[mochipay.WithdrawFromVault] received non-OK status code") + return nil, fmt.Errorf("invalid call, code %d", resp.StatusCode) + } + + // Decode response + respData := &WithdrawFromVaultResponse{} + if err := json.NewDecoder(resp.Body).Decode(respData); err != nil { + c.l.Errorf(err, "[mochipay.WithdrawFromVault] decoder.Decode failed") + return nil, err + } + + return respData, nil +} + +func (c *client) DepositToVault(req *DepositToVaultRequest) ([]DepositToVault, error) { + var client = &http.Client{ + Timeout: 60 * time.Second, + } + + url := fmt.Sprintf("%s/api/v1/profiles/%s/applications/%s/deposit", c.cfg.MochiPay.BaseURL, c.cfg.MochiPay.ApplicationOwnerId, c.cfg.MochiPay.ApplicationId) + + if req.Token == "" { + req.Token = IcySymbol + } + if req.VaultID == "" { + req.VaultID = c.cfg.MochiPay.ApplicationVaultId + } + if req.Platform == "" { + req.Platform = "discord" + } + requestBody, err := json.Marshal(req) + if err != nil { + c.l.Errorf(err, "[mochipay.DepositToVault] json.Marshal failed") + return nil, err + } + + // Create request + httpReq, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(requestBody)) + if err != nil { + c.l.Errorf(err, "[mochipay.DepositToVault] http.NewRequest failed") + return nil, err + } + + messageHeader := strconv.FormatInt(time.Now().Unix(), 10) + privateKey, err := hex.DecodeString(c.cfg.MochiPay.ApplicationPrivateKey) + if err != nil { + return nil, err + } + signature := ed25519.Sign(privateKey, []byte(messageHeader)) + + // Set headers + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("X-Message", messageHeader) + httpReq.Header.Set("X-Signature", hex.EncodeToString(signature)) + httpReq.Header.Set("X-Application", c.cfg.MochiPay.ApplicationName) + + // Send request + resp, err := client.Do(httpReq) + if err != nil { + c.l.Errorf(err, "[mochipay.DepositToVault] client.Do failed") + return nil, err + } + defer resp.Body.Close() + + // Check status code + if resp.StatusCode != http.StatusOK { + c.l.Error(nil, "[mochipay.DepositToVault] received non-OK status code") + return nil, fmt.Errorf("invalid call, code %d", resp.StatusCode) + } + + // Decode response + respData := &DepositToVaultResponse{} + if err := json.NewDecoder(resp.Body).Decode(respData); err != nil { + c.l.Errorf(err, "[mochipay.WithdrawFromVault] decoder.Decode failed") + return nil, err + } + + return respData.Data, nil +} diff --git a/pkg/service/mochipay/request.go b/pkg/service/mochipay/request.go index 6d22a67d3..c5dcbc982 100644 --- a/pkg/service/mochipay/request.go +++ b/pkg/service/mochipay/request.go @@ -221,3 +221,75 @@ type BatchBalancesData struct { Amount string `json:"amount"` Token Token `json:"token"` } + +type TransferFromVaultRequest struct { + RecipientIDs []string `json:"recipient_ids"` + Amounts []string `json:"amounts"` + TokenID string `json:"token_id"` + VaultID string `json:"vault_id"` + References string `json:"references"` + Description string `json:"description"` +} + +type TransactionFromVaultResponse struct { + Data []VaultTransaction `json:"data"` +} + +type VaultTransaction struct { + Timestamp int64 `json:"timestamp"` + TxId int64 `json:"tx_id"` + RecipientId string `json:"recipient_id"` + Amount string `json:"amount"` + Status string `json:"status"` + References string `json:"references"` + TokenId string `json:"token_id"` + TxFee string `json:"tx_fee"` + TxFeePercent string `json:"tx_fee_percent"` +} + +type Metadata map[string]interface{} + +type WithdrawFromVaultRequest struct { + Address string `json:"address"` + Amount string `json:"amount"` + Metadata Metadata `json:"metadata,omitempty"` + Platform string `json:"platform"` + TokenID string `json:"token_id"` + VaultID string `json:"vault_id"` +} + +type WithdrawFromVaultResponse struct { + Message string `json:"message"` +} + +type DepositToVaultRequest struct { + Platform string `json:"platform"` + Token string `json:"token"` + VaultID string `json:"vault_id"` +} + +type Contract struct { + Address string `json:"address"` + Chain *Chain `json:"chain"` + ChainID string `json:"chain_id"` + CreatedAt time.Time `json:"created_at"` + ID string `json:"id"` + LastSweepAt time.Time `json:"last_sweep_at"` +} + +type DepositToVault struct { + ChainID string `json:"chain_id"` + Contract Contract `json:"contract"` + ContractID string `json:"contract_id"` + CreatedAt time.Time `json:"created_at"` + ExpiredAt time.Time `json:"expired_at"` + Platform string `json:"platform"` + PlatformUserID string `json:"platform_user_id"` + ProfileID string `json:"profile_id"` + Token *TokenInfo `json:"token"` + TokenID string `json:"token_id"` +} + +type DepositToVaultResponse struct { + Data []DepositToVault `json:"data"` +} diff --git a/pkg/service/service.go b/pkg/service/service.go index 44f8ca4fe..280ac29d9 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -26,8 +26,10 @@ import ( "github.com/dwarvesf/fortress-api/pkg/service/googlemail" "github.com/dwarvesf/fortress-api/pkg/service/googlesheet" "github.com/dwarvesf/fortress-api/pkg/service/googlestorage" + "github.com/dwarvesf/fortress-api/pkg/service/icybackend" "github.com/dwarvesf/fortress-api/pkg/service/icyswap" "github.com/dwarvesf/fortress-api/pkg/service/improvmx" + "github.com/dwarvesf/fortress-api/pkg/service/landingzone" "github.com/dwarvesf/fortress-api/pkg/service/lobsters" "github.com/dwarvesf/fortress-api/pkg/service/mochi" "github.com/dwarvesf/fortress-api/pkg/service/mochipay" @@ -38,7 +40,6 @@ import ( "github.com/dwarvesf/fortress-api/pkg/service/sendgrid" "github.com/dwarvesf/fortress-api/pkg/service/tono" "github.com/dwarvesf/fortress-api/pkg/service/wise" - "github.com/dwarvesf/fortress-api/pkg/service/landingzone" yt "github.com/dwarvesf/fortress-api/pkg/service/youtube" "github.com/dwarvesf/fortress-api/pkg/store" ) @@ -59,6 +60,7 @@ type Service struct { Mochi mochi.IService MochiPay mochipay.IService MochiProfile mochiprofile.IService + IcyBackend icybackend.IService Notion notion.IService Sendgrid sendgrid.IService Wise wise.IService @@ -97,7 +99,6 @@ func New(cfg *config.Config, store *store.Store, repo store.DBRepo) *Service { logger.L.Error(err, "failed to init gcs") } - landingZoneSvc, err := landingzone.New( cfg.Google.GCSLandingZoneCredentials, ) @@ -177,6 +178,11 @@ func New(cfg *config.Config, store *store.Store, repo store.DBRepo) *Service { logger.L.Error(err, "failed to init icyswap service") } + icyBackend, err := icybackend.New(baseClient, cfg, logger.L) + if err != nil { + logger.L.Error(err, "failed to init icyBackend service") + } + communityNft, err := communitynft.New(baseClient, cfg, logger.L) if err != nil { logger.L.Error(err, "failed to init community nft service") @@ -205,6 +211,7 @@ func New(cfg *config.Config, store *store.Store, repo store.DBRepo) *Service { Mochi: mochi.New(cfg, logger.L), MochiPay: mochipay.New(cfg, logger.L), MochiProfile: mochiprofile.New(cfg, logger.L), + IcyBackend: icyBackend, Notion: notion.New(cfg.Notion.Secret, cfg.Notion.Databases.Project, logger.L), Sendgrid: sendgrid.New(cfg.Sendgrid.APIKey, cfg, logger.L), Wise: wise.New(cfg, logger.L), diff --git a/pkg/store/icyswapbtc/icyswapbtc.go b/pkg/store/icyswapbtc/icyswapbtc.go new file mode 100644 index 000000000..bb0103243 --- /dev/null +++ b/pkg/store/icyswapbtc/icyswapbtc.go @@ -0,0 +1,45 @@ +package icyswapbtc + +import ( + "gorm.io/gorm" + + "github.com/dwarvesf/fortress-api/pkg/model" +) + +type store struct{} + +func New() IStore { + return &store{} +} + +// Create creates a new request +func (s *store) Create(db *gorm.DB, e *model.IcySwapBtcRequest) (request *model.IcySwapBtcRequest, err error) { + return e, db.Create(e).Error +} + +// Update update all value +func (s *store) Update(db *gorm.DB, request *model.IcySwapBtcRequest) (*model.IcySwapBtcRequest, error) { + return request, db.Model(&request).Where("id = ?", request.ID).Updates(&request).First(&request).Error +} + +func (s *store) One(db *gorm.DB, query *Query) (*model.IcySwapBtcRequest, error) { + var request *model.IcySwapBtcRequest + if query.ID != "" { + db = db.Where("id = ?", query.ID) + } + if query.SwapRequestStatus != "" { + db = db.Where("request_status = ?", query.SwapRequestStatus) + } + return request, db.First(&request).Error +} + +func (s *store) IsExist(db *gorm.DB, requestCode string) (bool, error) { + type res struct { + Result bool + } + + result := res{} + query := db.Raw("SELECT EXISTS (SELECT * FROM icy_swap_btc_requests WHERE request_code = ?) as result", requestCode) + + return result.Result, query.Scan(&result).Error +} diff --git a/pkg/store/icyswapbtc/interface.go b/pkg/store/icyswapbtc/interface.go new file mode 100644 index 000000000..dab4a22cb --- /dev/null +++ b/pkg/store/icyswapbtc/interface.go @@ -0,0 +1,20 @@ +package icyswapbtc + +import ( + "gorm.io/gorm" + + "github.com/dwarvesf/fortress-api/pkg/model" +) + +type IStore interface { + Create(db *gorm.DB, e *model.IcySwapBtcRequest) (request *model.IcySwapBtcRequest, err error) + One(db *gorm.DB, query *Query) (request *model.IcySwapBtcRequest, err error) + IsExist(db *gorm.DB, requestCode string) (exists bool, err error) + Update(db *gorm.DB, request *model.IcySwapBtcRequest) (a *model.IcySwapBtcRequest, err error) +} + +type Query struct { + ID string + RequestCode string + SwapRequestStatus string +} diff --git a/pkg/store/projectmember/interface.go b/pkg/store/projectmember/interface.go index 044c1ed50..b9df1559b 100644 --- a/pkg/store/projectmember/interface.go +++ b/pkg/store/projectmember/interface.go @@ -4,7 +4,7 @@ import ( "time" "gorm.io/gorm" - + "github.com/dwarvesf/fortress-api/pkg/model" ) diff --git a/pkg/store/store.go b/pkg/store/store.go index d94192bb1..6a7f12244 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -49,6 +49,7 @@ import ( "github.com/dwarvesf/fortress-api/pkg/store/expense" "github.com/dwarvesf/fortress-api/pkg/store/feedbackevent" "github.com/dwarvesf/fortress-api/pkg/store/icydistribution" + "github.com/dwarvesf/fortress-api/pkg/store/icyswapbtc" "github.com/dwarvesf/fortress-api/pkg/store/icytransaction" "github.com/dwarvesf/fortress-api/pkg/store/invoice" "github.com/dwarvesf/fortress-api/pkg/store/invoicenumbercaching" @@ -164,6 +165,7 @@ type Store struct { WorkUnit workunit.IStore WorkUnitMember workunitmember.IStore WorkUnitStack workunitstack.IStore + IcySwapBtcRequest icyswapbtc.IStore } func New() *Store { @@ -248,5 +250,6 @@ func New() *Store { WorkUnit: workunit.New(), WorkUnitMember: workunitmember.New(), WorkUnitStack: workunitstack.New(), + IcySwapBtcRequest: icyswapbtc.New(), } } diff --git a/pkg/utils/stringutils/strings.go b/pkg/utils/stringutils/strings.go index 2f1f34f34..bad4e2384 100644 --- a/pkg/utils/stringutils/strings.go +++ b/pkg/utils/stringutils/strings.go @@ -1,7 +1,10 @@ package stringutils import ( + "math" + "math/big" "regexp" + "strconv" "strings" "github.com/dwarvesf/fortress-api/pkg/constant" @@ -50,3 +53,39 @@ func FormatString(str string) string { return formattedStr } + +// FloatToString convert float to big int string with given decimal +// Ignore negative float +// example: FloatToString("0.000000000000000001", 18) => "1" +func FloatToString(s string, decimal int64) string { + c, _ := strconv.ParseFloat(s, 64) + if c < 0 { + return "0" + } + bigval := new(big.Float) + bigval.SetFloat64(c) + + d := new(big.Float) + d.SetInt(big.NewInt(int64(math.Pow(10, float64(decimal))))) + bigval.Mul(bigval, d) + + r := new(big.Int) + bigval.Int(r) // store converted number in r + return r.String() +} + +func Shorten(s string) string { + // avoid oor error + if len(s) < 12 { + return s + } + + // already shortened -> return + if strings.Contains(s, "..") { + return s + } + + // shorten + // e.g. Shorten("0x7dff46370e9ea5f0bad3c4e29711ad50062ea7a4) = "0x7d..a7a4" + return s[:5] + ".." + s[len(s)-5:] +}