Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .run/[git] worker.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<env name="RPC_XRP_URL" value="https://xrplcluster.com" />
<env name="ONEINCH_BASEURL" value="https://api.vultisig.com/1inch" />
<env name="THORCHAIN_URL" value="https://thornode.ninerealms.com" />
<env name="THORCHAIN_TENDERMINTURL" value="https://rpc.ninerealms.com" />
<env name="VAULTSERVICE_DOSETUPMSG" value="true" />
<env name="VAULTSERVICE_ENCRYPTIONSECRET" value="test123" />
<env name="VAULTSERVICE_LOCALPARTYPREFIX" value="vultisig-dca-0000" />
Expand Down
28 changes: 27 additions & 1 deletion cmd/worker/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import (
"github.com/vultisig/dca/internal/oneinch"
"github.com/vultisig/dca/internal/solana"
"github.com/vultisig/dca/internal/thorchain"
"github.com/vultisig/dca/internal/thorchain_native"
"github.com/vultisig/dca/internal/xrp"
btcsdk "github.com/vultisig/recipes/sdk/btc"
evmsdk "github.com/vultisig/recipes/sdk/evm"
thorchainSDK "github.com/vultisig/recipes/sdk/thorchain"
xrplsdk "github.com/vultisig/recipes/sdk/xrpl"
"github.com/vultisig/verifier/plugin"
plugin_config "github.com/vultisig/verifier/plugin/config"
Expand Down Expand Up @@ -230,6 +232,27 @@ func main() {
xrpClient,
)

// Initialize THORChain native network
thorchainNativeClient := thorchain_native.NewClient(cfg.ThorChain.TendermintURL, cfg.ThorChain.URL)

// Initialize THORChain SDK for signing and broadcasting
thorchainRpcClient, err := thorchainSDK.NewCometBFTRPCClient(cfg.ThorChain.TendermintURL)
if err != nil {
logger.Fatalf("failed to initialize THORChain RPC client: %v", err)
}
thorchainSDKInstance := thorchainSDK.NewSDK(thorchainRpcClient)

// Create THORChain native provider (uses THORChain API for quotes + native tx building)
thorchainNativeProvider := thorchain.NewProviderThorchainNative(thorchainClient, thorchainNativeClient)

// Create THORChain native network with SDK
thorchainNativeNetwork := thorchain_native.NewNetwork(
thorchain_native.NewSwapService([]thorchain_native.SwapProvider{thorchainNativeProvider}),
thorchain_native.NewSendService(thorchainNativeClient),
thorchain_native.NewSignerService(thorchainSDKInstance, signer, txIndexerService),
thorchainNativeClient,
)

jup, err := jupiter.NewProvider(cfg.Solana.JupiterAPIURL, solanarpc.New(cfg.Rpc.Solana.URL))
if err != nil {
logger.Fatalf("failed to initialize Jupiter provider: %v", err)
Expand Down Expand Up @@ -261,6 +284,7 @@ func main() {
),
solanaNetwork,
xrpNetwork,
thorchainNativeNetwork,
vaultStorage,
cfg.VaultService.EncryptionSecret,
)
Expand Down Expand Up @@ -303,7 +327,8 @@ type oneInchConfig struct {
}

type thorChainConfig struct {
URL string
URL string `envconfig:"THORCHAIN_URL"` // THORChain application API (thornode.ninerealms.com) - for business logic, quotes, pools
TendermintURL string `envconfig:"THORCHAIN_TENDERMINTURL"` // Tendermint RPC endpoint (rpc.ninerealms.com) - for blockchain operations, broadcasting
}

type rpc struct {
Expand All @@ -318,6 +343,7 @@ type rpc struct {
BTC rpcItem
XRP rpcItem
Solana rpcItem
THORChain rpcItem
}

type rpcItem struct {
Expand Down
5 changes: 5 additions & 0 deletions deploy/02_worker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ spec:
configMapKeyRef:
name: thorchain
key: url
- name: THORCHAIN_TENDERMINTURL
valueFrom:
configMapKeyRef:
name: thorchain
key: tendermint-url
- name: BTC_BLOCKCHAIRURL
valueFrom:
configMapKeyRef:
Expand Down
1 change: 1 addition & 0 deletions deploy/dev/01_thorchain.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ metadata:
name: thorchain
data:
url: "https://thornode.ninerealms.com"
tendermint-url: "https://rpc.ninerealms.com"
1 change: 1 addition & 0 deletions deploy/prod/01_thorchain.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ metadata:
name: thorchain
data:
url: "https://thornode.ninerealms.com"
tendermint-url: "https://rpc.ninerealms.com"
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ module github.com/vultisig/dca
go 1.24.2

require (
cosmossdk.io/math v1.5.3
github.com/btcsuite/btcd v0.24.2
github.com/btcsuite/btcd/btcutil v1.1.6
github.com/btcsuite/btcd/btcutil/psbt v1.1.10
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
github.com/cosmos/cosmos-sdk v0.50.11
github.com/ethereum/go-ethereum v1.15.11
github.com/gagliardetto/solana-go v1.14.0
github.com/google/uuid v1.6.0
Expand All @@ -17,8 +19,8 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.10.0
github.com/vultisig/mobile-tss-lib v0.0.0-20250316003201-2e7e570a4a74
github.com/vultisig/recipes v0.0.0-20251028124244-a61e31f3c2ee
github.com/vultisig/verifier v0.0.0-20251028124740-28151222d390
github.com/vultisig/recipes v0.0.0-20251112091748-7899bec2a8ec
github.com/vultisig/verifier v0.0.0-20251113095713-b811fd7525b0
github.com/vultisig/vultisig-go v0.0.0-20251004125942-60b3b1898d15
github.com/xyield/xrpl-go v0.0.0-20230914223425-9abe75c05830
golang.org/x/sync v0.14.0
Expand All @@ -32,7 +34,6 @@ require (
cosmossdk.io/depinject v1.2.1 // indirect
cosmossdk.io/errors v1.0.2 // indirect
cosmossdk.io/log v1.6.0 // indirect
cosmossdk.io/math v1.5.3 // indirect
cosmossdk.io/schema v1.1.0 // indirect
cosmossdk.io/store v1.1.2 // indirect
cosmossdk.io/x/tx v0.14.0 // indirect
Expand Down Expand Up @@ -74,7 +75,6 @@ require (
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/cosmos-db v1.1.1 // indirect
github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect
github.com/cosmos/cosmos-sdk v0.50.11 // indirect
github.com/cosmos/go-bip39 v1.0.0 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/gogoproto v1.7.0 // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1013,10 +1013,10 @@ github.com/vultisig/go-wrappers v0.0.0-20250716071337-34a5c0f4d6e0 h1:EdgQHZjzkY
github.com/vultisig/go-wrappers v0.0.0-20250716071337-34a5c0f4d6e0/go.mod h1:UfGCxUQW08kiwxyNBiHwXe+ePPuBmHVVS+BS51aU/Jg=
github.com/vultisig/mobile-tss-lib v0.0.0-20250316003201-2e7e570a4a74 h1:goqwk4nQ/NEVIb3OPP9SUx7/u9ZfsUIcd5fIN/e4DVU=
github.com/vultisig/mobile-tss-lib v0.0.0-20250316003201-2e7e570a4a74/go.mod h1:nOykk4nOy1L3yXtLSlYvVsgizBnCQ3tR2N5uwGPdvaM=
github.com/vultisig/recipes v0.0.0-20251028124244-a61e31f3c2ee h1:j8KoUO6wZAP/D/G5F9a60u++ri0WIg3aZZhnF/e0bZE=
github.com/vultisig/recipes v0.0.0-20251028124244-a61e31f3c2ee/go.mod h1:gYRvnnDwj04CqNawzAZky0Do63Cmdf1RN9rNbcQqPUE=
github.com/vultisig/verifier v0.0.0-20251028124740-28151222d390 h1:W2wAIG2TFfRNI89T4zgettXANCT+LFFrD52nJieZzUc=
github.com/vultisig/verifier v0.0.0-20251028124740-28151222d390/go.mod h1:vAkhFF9OzhGfEoezrHShmVpgq5Jyw4UMzVytBmTfA30=
github.com/vultisig/recipes v0.0.0-20251112091748-7899bec2a8ec h1:9gEhm+cmAbq6Vb65lAI7EKvqM5qK0b/3hW7NLQvV8AU=
github.com/vultisig/recipes v0.0.0-20251112091748-7899bec2a8ec/go.mod h1:gYRvnnDwj04CqNawzAZky0Do63Cmdf1RN9rNbcQqPUE=
github.com/vultisig/verifier v0.0.0-20251113095713-b811fd7525b0 h1:gs6sv7WfEPDj4dq8dF6nBWR1OwcOQTq1odOaL/iXA80=
github.com/vultisig/verifier v0.0.0-20251113095713-b811fd7525b0/go.mod h1:IApsss46kg0Oqv6jzfFQ5R9Cyfy6RTgRSyfI8pf2Wt0=
github.com/vultisig/vultiserver v0.0.0-20250825042420-c6e6ac281110 h1:7WDQ92FAdu08Byjgm3RNS8Sok49sK521PzPcbRpbzCE=
github.com/vultisig/vultiserver v0.0.0-20250825042420-c6e6ac281110/go.mod h1:HwP2IgW6Mcu/gX8paFuKvfibrGE9UmPgkOFTub6dskM=
github.com/vultisig/vultisig-go v0.0.0-20251004125942-60b3b1898d15 h1:wdRFnDMLdbaWXExUR/88WBAZ9sY9i9ldzurrYJWQeuw=
Expand Down
105 changes: 105 additions & 0 deletions internal/dca/consumer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/vultisig/dca/internal/btc"
"github.com/vultisig/dca/internal/evm"
"github.com/vultisig/dca/internal/solana"
"github.com/vultisig/dca/internal/thorchain_native"
"github.com/vultisig/dca/internal/util"
"github.com/vultisig/dca/internal/xrp"
"github.com/vultisig/mobile-tss-lib/tss"
Expand All @@ -42,6 +43,7 @@ type Consumer struct {
btc *btc.Network
xrp *xrp.Network
solana *solana.Network
thorchain *thorchain_native.Network
vault vault.Storage
vaultSecret string
}
Expand All @@ -53,6 +55,7 @@ func NewConsumer(
btc *btc.Network,
solana *solana.Network,
xrp *xrp.Network,
thorchain *thorchain_native.Network,
vault vault.Storage,
vaultSecret string,
) *Consumer {
Expand All @@ -63,6 +66,7 @@ func NewConsumer(
btc: btc,
xrp: xrp,
solana: solana,
thorchain: thorchain,
vault: vault,
vaultSecret: vaultSecret,
}
Expand Down Expand Up @@ -188,6 +192,14 @@ func (c *Consumer) handle(ctx context.Context, t *asynq.Task) error {
return nil
}

if fromChainTyped == common.THORChain {
er := c.handleThorchainSwap(ctx, pol, toAssetMap, fromAmountStr, fromAssetTokenStr, toAssetTokenStr, toAddressStr)
if er != nil {
return fmt.Errorf("failed to handle THORChain swap: %w", er)
}
return nil
}

err = c.handleEvmSwap(
ctx,
pol,
Expand Down Expand Up @@ -777,3 +789,96 @@ func findSpender(chain common.Chain, rawRules []*rtypes.Rule) (ecommon.Address,
}
return ecommon.Address{}, fmt.Errorf("rule not found")
}

func (c *Consumer) handleThorchainSwap(
ctx context.Context,
pol *types.PluginPolicy,
toAssetMap map[string]any,
fromAmountStr string,
fromAssetTokenStr string,
toAssetTokenStr string,
toAddressStr string,
) error {
// Get THORChain address from policy public key
fromAddressStr, childPubKey, err := c.thorchainPubToAddress(pol.PublicKey)
if err != nil {
return fmt.Errorf("failed to get THORChain address from policy PublicKey: %w", err)
}

// Parse amount
fromAmountRune, err := parseUint64(fromAmountStr)
if err != nil {
return fmt.Errorf("failed to parse fromAmount: %w", err)
}

// Parse destination chain
toChainStr, ok := toAssetMap["chain"].(string)
if !ok {
return fmt.Errorf("failed to get toAsset.chain")
}

toChainTyped, err := common.FromString(toChainStr)
if err != nil {
return fmt.Errorf("failed to parse toAsset.chain: %w", err)
}

// Create THORChain native From and To structs
from := thorchain_native.From{
Address: fromAddressStr,
AssetID: fromAssetTokenStr,
Amount: fromAmountRune,
PubKey: childPubKey,
// Sequence will be auto-fetched by network
}

to := thorchain_native.To{
Chain: toChainTyped,
AssetID: toAssetTokenStr,
Address: toAddressStr,
}

c.logger.WithFields(logrus.Fields{
"policyID": pol.ID.String(),
"fromAddress": fromAddressStr,
"fromAmount": fromAmountRune,
"fromAsset": fromAssetTokenStr,
"toChain": toChainTyped.String(),
"toAsset": toAssetTokenStr,
"toAddress": toAddressStr,
}).Info("handling THORChain swap")

// Execute the swap using THORChain native network
txHash, err := c.thorchain.SwapAssets(ctx, *pol, from, to)
if err != nil {
return fmt.Errorf("failed to execute THORChain swap: %w", err)
}

c.logger.WithField("txHash", txHash).Info("THORChain swap signed & broadcasted successfully")
return nil
}

func (c *Consumer) thorchainPubToAddress(rootPub string) (string, string, error) {
vaultContent, err := c.vault.GetVault(common.GetVaultBackupFilename(rootPub, string(types.PluginVultisigDCA_0000)))
if err != nil {
return "", "", fmt.Errorf("failed to get vault content: %w", err)
}

vlt, err := common.DecryptVaultFromBackup(c.vaultSecret, vaultContent)
if err != nil {
return "", "", fmt.Errorf("failed to decrypt vault: %w", err)
}

// Get THORChain-specific child key derivation
childPub, err := tss.GetDerivedPubKey(rootPub, vlt.GetHexChainCode(), common.THORChain.GetDerivePath(), false)
if err != nil {
return "", "", fmt.Errorf("failed to get derived pubkey: %w", err)
}

// Convert child public key to THORChain address (bech32 format with "thor" prefix)
addr, err := address.GetBech32Address(childPub, "thor")
if err != nil {
return "", "", fmt.Errorf("failed to get THORChain address: %w", err)
}

return addr, childPub, nil
}
1 change: 1 addition & 0 deletions internal/dca/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var supportedChains = []common.Chain{
common.Bitcoin,
common.Solana,
common.XRP,
common.THORChain,
}

const (
Expand Down
3 changes: 3 additions & 0 deletions internal/thorchain/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func parseThorNetwork(c common.Chain) (thorNetwork, error) {
return avax, nil
case common.XRP:
return xrp, nil
case common.THORChain:
return thor, nil
default:
return "", errors.New("unknown chain")
}
Expand All @@ -40,4 +42,5 @@ const (
base thorNetwork = "BASE"
avax thorNetwork = "AVAX"
xrp thorNetwork = "XRP"
thor thorNetwork = "THOR"
)
Loading