diff --git a/chain/filecoin/client/client.go b/chain/filecoin/client/client.go index 8ae30556..d9670e36 100644 --- a/chain/filecoin/client/client.go +++ b/chain/filecoin/client/client.go @@ -18,7 +18,7 @@ import ( txinfo "github.com/cordialsys/crosschain/client/tx_info" xctypes "github.com/cordialsys/crosschain/client/types" log "github.com/sirupsen/logrus" - "github.com/stellar/go/support/time" + "github.com/stellar/go-stellar-sdk/support/time" ) // Client for Filecoin diff --git a/chain/xlm/builder/builder.go b/chain/xlm/builder/builder.go index e0032935..3643395f 100644 --- a/chain/xlm/builder/builder.go +++ b/chain/xlm/builder/builder.go @@ -10,7 +10,7 @@ import ( common "github.com/cordialsys/crosschain/chain/xlm/common" xlmtx "github.com/cordialsys/crosschain/chain/xlm/tx" xlminput "github.com/cordialsys/crosschain/chain/xlm/tx_input" - "github.com/stellar/go/xdr" + "github.com/stellar/go-stellar-sdk/xdr" ) type TxBuilder struct { @@ -93,57 +93,8 @@ func (builder TxBuilder) Transfer(args xcbuilder.TransferArgs, input xc.TxInput) txe.Tx.Memo = xdrMemo } - destinationMuxedAccount, err := common.MuxedAccountFromAddress(to) - if err != nil { - return &xlmtx.Tx{}, fmt.Errorf("invalid `to` address: %w", err) - } xdrAmount := xdr.Int64(amount.Int().Int64()) - var xdrOperationBody xdr.OperationBody - _, isToken := args.GetContract() - - if !txInput.DestinationFunded && !isToken { - // Use CreateAccount for new/unfunded native XLM destinations - destinationAccountId, err := xdr.AddressToAccountId(string(to)) - if err != nil { - return &xlmtx.Tx{}, fmt.Errorf("invalid `to` address: %w", err) - } - xdrCreateAccount := xdr.CreateAccountOp{ - Destination: destinationAccountId, - StartingBalance: xdrAmount, - } - xdrOperationBody, err = xdr.NewOperationBody(xdr.OperationTypeCreateAccount, xdrCreateAccount) - if err != nil { - return &xlmtx.Tx{}, fmt.Errorf("failed to create operation body: %w", err) - } - } else { - // Use Payment for funded accounts or token transfers - var xdrAsset xdr.Asset - if contract, ok := args.GetContract(); ok { - contractDetails, err := common.GetAssetAndIssuerFromContract(string(contract)) - if err != nil { - return &xlmtx.Tx{}, fmt.Errorf("failed to get contract details: %w", err) - } - - xdrAsset, err = common.CreateAssetFromContractDetails(contractDetails) - if err != nil { - return &xlmtx.Tx{}, fmt.Errorf("failed to create token details: %w", err) - } - } else { - xdrAsset.Type = xdr.AssetTypeAssetTypeNative - } - - xdrPayment := xdr.PaymentOp{ - Destination: destinationMuxedAccount, - Amount: xdrAmount, - Asset: xdrAsset, - } - xdrOperationBody, err = xdr.NewOperationBody(xdr.OperationTypePayment, xdrPayment) - if err != nil { - return &xlmtx.Tx{}, fmt.Errorf("failed to create operation body: %w", err) - } - } - var operations []xdr.Operation // If the sender needs a trustline for the token, prepend a ChangeTrust operation. @@ -171,12 +122,82 @@ func (builder TxBuilder) Transfer(args xcbuilder.TransferArgs, input xc.TxInput) } } - // Skip the payment/createAccount operation when amount is 0 (trustline-only transaction) - if xdrAmount > 0 { + if common.IsContractAddress(to) { + // Soroban SAC invocation for contract (C...) address destinations + sacOp, sorobanData, err := builder.buildSACTransfer(args, txInput, from, to) + if err != nil { + return &xlmtx.Tx{}, fmt.Errorf("failed to build SAC transfer: %w", err) + } operations = append(operations, xdr.Operation{ SourceAccount: &opSourceAccount, - Body: xdrOperationBody, + Body: *sacOp, }) + // Attach Soroban data to the transaction + txe.Tx.Ext, err = xdr.NewTransactionExt(1, *sorobanData) + if err != nil { + return &xlmtx.Tx{}, fmt.Errorf("failed to set soroban transaction data: %w", err) + } + // Soroban fee = base inclusion fee + resource fee + txe.Tx.Fee = xdr.Uint32(txInput.MaxFee) + xdr.Uint32(txInput.SorobanResourceFee) + } else { + _, isToken := args.GetContract() + if !txInput.DestinationFunded && !isToken { + // Use CreateAccount for new/unfunded native XLM destinations + destinationAccountId, err := xdr.AddressToAccountId(string(to)) + if err != nil { + return &xlmtx.Tx{}, fmt.Errorf("invalid `to` address: %w", err) + } + xdrCreateAccount := xdr.CreateAccountOp{ + Destination: destinationAccountId, + StartingBalance: xdrAmount, + } + xdrOperationBody, err := xdr.NewOperationBody(xdr.OperationTypeCreateAccount, xdrCreateAccount) + if err != nil { + return &xlmtx.Tx{}, fmt.Errorf("failed to create operation body: %w", err) + } + if xdrAmount > 0 { + operations = append(operations, xdr.Operation{ + SourceAccount: &opSourceAccount, + Body: xdrOperationBody, + }) + } + } else { + // Use Payment for funded accounts or token transfers + destinationMuxedAccount, err := common.MuxedAccountFromAddress(to) + if err != nil { + return &xlmtx.Tx{}, fmt.Errorf("invalid `to` address: %w", err) + } + var xdrAsset xdr.Asset + if contract, ok := args.GetContract(); ok { + contractDetails, err := common.GetAssetAndIssuerFromContract(string(contract)) + if err != nil { + return &xlmtx.Tx{}, fmt.Errorf("failed to get contract details: %w", err) + } + xdrAsset, err = common.CreateAssetFromContractDetails(contractDetails) + if err != nil { + return &xlmtx.Tx{}, fmt.Errorf("failed to create token details: %w", err) + } + } else { + xdrAsset.Type = xdr.AssetTypeAssetTypeNative + } + + xdrPayment := xdr.PaymentOp{ + Destination: destinationMuxedAccount, + Amount: xdrAmount, + Asset: xdrAsset, + } + xdrOperationBody, err := xdr.NewOperationBody(xdr.OperationTypePayment, xdrPayment) + if err != nil { + return &xlmtx.Tx{}, fmt.Errorf("failed to create operation body: %w", err) + } + // Skip the payment operation when amount is 0 (trustline-only transaction) + if xdrAmount > 0 { + operations = append(operations, xdr.Operation{ + SourceAccount: &opSourceAccount, + Body: xdrOperationBody, + }) + } + } } txe.Tx.Operations = operations @@ -196,6 +217,151 @@ func (builder TxBuilder) Transfer(args xcbuilder.TransferArgs, input xc.TxInput) return tx, nil } +// buildSACTransfer builds an InvokeHostFunction operation that calls the SAC transfer function. +// The SAC transfer signature is: transfer(from: Address, to: Address, amount: i128) +// All Soroban data (auth, footprint, resources) is constructed natively — no simulation RPC needed. +func (builder TxBuilder) buildSACTransfer(args xcbuilder.TransferArgs, txInput *TxInput, from xc.Address, to xc.Address) (*xdr.OperationBody, *xdr.SorobanTransactionData, error) { + // Derive the SAC contract address from the token asset + contract, ok := args.GetContract() + if !ok { + return nil, nil, fmt.Errorf("contract is required for SAC transfers") + } + contractDetails, err := common.GetAssetAndIssuerFromContract(string(contract)) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse contract: %w", err) + } + xdrAsset, err := common.CreateAssetFromContractDetails(contractDetails) + if err != nil { + return nil, nil, fmt.Errorf("failed to create asset: %w", err) + } + sacId, err := xdrAsset.ContractID(txInput.Passphrase) + if err != nil { + return nil, nil, fmt.Errorf("failed to derive SAC contract ID: %w", err) + } + contractId := xdr.ContractId(sacId) + contractAddr := xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeContract, + ContractId: &contractId, + } + + // Build the SAC transfer arguments: from, to, amount + fromScVal, err := common.ScValAddress(string(from)) + if err != nil { + return nil, nil, fmt.Errorf("failed to encode from address: %w", err) + } + toScVal, err := common.ScValAddress(string(to)) + if err != nil { + return nil, nil, fmt.Errorf("failed to encode to address: %w", err) + } + amountScVal := common.ScValI128(args.GetAmount().Int().Int64()) + + invokeArgs := xdr.InvokeContractArgs{ + ContractAddress: contractAddr, + FunctionName: "transfer", + Args: []xdr.ScVal{fromScVal, toScVal, amountScVal}, + } + + hostFn, err := xdr.NewHostFunction(xdr.HostFunctionTypeHostFunctionTypeInvokeContract, invokeArgs) + if err != nil { + return nil, nil, fmt.Errorf("failed to create host function: %w", err) + } + + // Construct auth entry natively. + // For SAC transfer where sender = transaction source, credentials are SOURCE_ACCOUNT + // and the invocation is the transfer call with no sub-invocations. + contractFn, err := xdr.NewSorobanAuthorizedFunction( + xdr.SorobanAuthorizedFunctionTypeSorobanAuthorizedFunctionTypeContractFn, + invokeArgs, + ) + if err != nil { + return nil, nil, fmt.Errorf("failed to create authorized function: %w", err) + } + authEntry := xdr.SorobanAuthorizationEntry{ + Credentials: xdr.SorobanCredentials{ + Type: xdr.SorobanCredentialsTypeSorobanCredentialsSourceAccount, + }, + RootInvocation: xdr.SorobanAuthorizedInvocation{ + Function: contractFn, + SubInvocations: nil, + }, + } + + invokeOp := xdr.InvokeHostFunctionOp{ + HostFunction: hostFn, + Auth: []xdr.SorobanAuthorizationEntry{authEntry}, + } + + opBody, err := xdr.NewOperationBody(xdr.OperationTypeInvokeHostFunction, invokeOp) + if err != nil { + return nil, nil, fmt.Errorf("failed to create invoke host function operation: %w", err) + } + + // Construct the ledger footprint natively. + // SAC transfer touches: + // ReadOnly: SAC contract instance + // ReadWrite: sender's trustline (G-addr balance), receiver's contract data (C-addr balance) + fromAccountId, err := xdr.AddressToAccountId(string(from)) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse from account: %w", err) + } + toContractAddr, err := common.ScAddressFromString(string(to)) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse to contract address: %w", err) + } + + // SAC contract instance key + sacInstanceKey := xdr.LedgerKey{ + Type: xdr.LedgerEntryTypeContractData, + ContractData: &xdr.LedgerKeyContractData{ + Contract: contractAddr, + Key: xdr.ScVal{Type: xdr.ScValTypeScvLedgerKeyContractInstance}, + Durability: xdr.ContractDataDurabilityPersistent, + }, + } + + // Sender's classic trustline (SAC uses TrustLine entries for G-address balances) + senderTrustlineKey := xdr.LedgerKey{ + Type: xdr.LedgerEntryTypeTrustline, + TrustLine: &xdr.LedgerKeyTrustLine{ + AccountId: fromAccountId, + Asset: xdrAsset.ToTrustLineAsset(), + }, + } + + // Receiver's contract balance (SAC uses ContractData for C-address balances) + // Key format: Vec[Symbol("Balance"), Address(to)] + balanceSym := common.ScValSymbol("Balance") + toAddrScVal := xdr.ScVal{Type: xdr.ScValTypeScvAddress, Address: &toContractAddr} + balanceVec := xdr.ScVec{balanceSym, toAddrScVal} + balanceVecPtr := &balanceVec + receiverBalanceKey := xdr.LedgerKey{ + Type: xdr.LedgerEntryTypeContractData, + ContractData: &xdr.LedgerKeyContractData{ + Contract: contractAddr, + Key: xdr.ScVal{ + Type: xdr.ScValTypeScvVec, + Vec: &balanceVecPtr, + }, + Durability: xdr.ContractDataDurabilityPersistent, + }, + } + + sorobanData := xdr.SorobanTransactionData{ + Resources: xdr.SorobanResources{ + Footprint: xdr.LedgerFootprint{ + ReadOnly: []xdr.LedgerKey{sacInstanceKey}, + ReadWrite: []xdr.LedgerKey{senderTrustlineKey, receiverBalanceKey}, + }, + Instructions: xdr.Uint32(txInput.SorobanInstructions), + DiskReadBytes: xdr.Uint32(txInput.SorobanDiskReadBytes), + WriteBytes: xdr.Uint32(txInput.SorobanWriteBytes), + }, + ResourceFee: xdr.Int64(txInput.SorobanResourceFee), + } + + return &opBody, &sorobanData, nil +} + func (txBuilder TxBuilder) SupportsMemo() xc.MemoSupport { // XLM supports memo return xc.MemoSupportString diff --git a/chain/xlm/builder/builder_test.go b/chain/xlm/builder/builder_test.go index 2b316f56..ef3f95ac 100644 --- a/chain/xlm/builder/builder_test.go +++ b/chain/xlm/builder/builder_test.go @@ -9,7 +9,7 @@ import ( "github.com/cordialsys/crosschain/chain/xlm/common" "github.com/cordialsys/crosschain/chain/xlm/tx" "github.com/cordialsys/crosschain/chain/xlm/tx_input" - "github.com/stellar/go/xdr" + "github.com/stellar/go-stellar-sdk/xdr" "github.com/test-go/testify/require" ) diff --git a/chain/xlm/client/client.go b/chain/xlm/client/client.go index 283754ee..28717386 100644 --- a/chain/xlm/client/client.go +++ b/chain/xlm/client/client.go @@ -24,7 +24,8 @@ import ( "github.com/cordialsys/crosschain/client/errors" txinfo "github.com/cordialsys/crosschain/client/tx_info" xctypes "github.com/cordialsys/crosschain/client/types" - "github.com/stellar/go/xdr" + "github.com/sirupsen/logrus" + "github.com/stellar/go-stellar-sdk/xdr" ) type Client struct { @@ -103,18 +104,36 @@ func (client *Client) FetchTransferInput(ctx context.Context, args xcbuilder.Tra feeAccountDetails = feePayerDetails } - // Check if destination account exists on the network. - // Stellar requires CreateAccount for new accounts instead of Payment. - _, destErr := client.FetchAccountDetails(args.GetTo()) - if destErr != nil { - var queryErr *types.QueryProblem - if stderrors.As(destErr, &queryErr) && queryErr.Status == 404 { - txInput.DestinationFunded = false - } else { - return nil, fmt.Errorf("failed to fetch destination account details: %w", destErr) + // Check if destination is a contract (C...) address or a regular account (G...). + if common.IsContractAddress(args.GetTo()) { + // Contract addresses require Soroban SAC invocation — skip account existence check. + // The builder will use InvokeHostFunction instead of Payment. + txInput.DestinationFunded = true + // Conservative defaults for Soroban SAC transfers. + txInput.SorobanResourceFee = 100_000 + txInput.SorobanInstructions = 500_000 + txInput.SorobanDiskReadBytes = 2000 + txInput.SorobanWriteBytes = 500 + // If Soroban RPC is configured, simulate to get accurate resource estimates. + if sorobanUrl := config.IndexerUrl; sorobanUrl != "" { + if err := client.estimateSorobanResourceFee(sorobanUrl, args, txInput); err != nil { + logrus.WithError(err).Warn("soroban simulation failed, using default resource fee") + } } } else { - txInput.DestinationFunded = true + // Check if destination account exists on the network. + // Stellar requires CreateAccount for new accounts instead of Payment. + _, destErr := client.FetchAccountDetails(args.GetTo()) + if destErr != nil { + var queryErr *types.QueryProblem + if stderrors.As(destErr, &queryErr) && queryErr.Status == 404 { + txInput.DestinationFunded = false + } else { + return nil, fmt.Errorf("failed to fetch destination account details: %w", destErr) + } + } else { + txInput.DestinationFunded = true + } } // Check if the sender needs a trustline for the token asset. @@ -153,16 +172,25 @@ func (client *Client) FetchTransferInput(ctx context.Context, args xcbuilder.Tra } } - // Stellar requires the MaxFee specification, which defines the maximum amount - // we are willing to spend on the transaction fee. - maxFee := config.GasBudgetDefault.ToBlockchain(config.Decimals) + // Set MaxFee (inclusion fee). + // For Soroban transactions, MaxFee may already be set from fee stats — only override + // with the gas budget default if it hasn't been set yet. + if txInput.MaxFee == 0 { + maxFee := config.GasBudgetDefault.ToBlockchain(config.Decimals) + if feeAccountBalance.Cmp(&maxFee) > 0 { + txInput.MaxFee = uint32(maxFee.Uint64()) + } else { + txInput.MaxFee = uint32(feeAccountBalance.Uint64()) + } + } - // If balance is greater than blockchainFee, we can safely use specified MaxFee - // Use remaining balance as a max fee otherwise - if feeAccountBalance.Cmp(&maxFee) > 0 { - txInput.MaxFee = uint32(maxFee.Uint64()) - } else { - txInput.MaxFee = uint32(feeAccountBalance.Uint64()) + // Apply chain gas-price multiplier to fees if configured. + // This allows remote adjustment of fee estimates. + if config.ChainGasMultiplier > 0 && config.ChainGasMultiplier != 1.0 { + txInput.MaxFee = uint32(float64(txInput.MaxFee) * config.ChainGasMultiplier) + if txInput.SorobanResourceFee > 0 { + txInput.SorobanResourceFee = uint32(float64(txInput.SorobanResourceFee) * config.ChainGasMultiplier) + } } txInput.TransactionActiveTime = config.TransactionActiveTime diff --git a/chain/xlm/client/client_test.go b/chain/xlm/client/client_test.go index 09e5615c..e7208bdb 100644 --- a/chain/xlm/client/client_test.go +++ b/chain/xlm/client/client_test.go @@ -22,7 +22,7 @@ import ( txinfo "github.com/cordialsys/crosschain/client/tx_info" xctypes "github.com/cordialsys/crosschain/client/types" "github.com/cordialsys/crosschain/factory/defaults/chains" - "github.com/stellar/go/xdr" + "github.com/stellar/go-stellar-sdk/xdr" "github.com/stretchr/testify/require" ) diff --git a/chain/xlm/client/soroban.go b/chain/xlm/client/soroban.go new file mode 100644 index 00000000..16e217b7 --- /dev/null +++ b/chain/xlm/client/soroban.go @@ -0,0 +1,265 @@ +package client + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + + xcbuilder "github.com/cordialsys/crosschain/builder" + "github.com/cordialsys/crosschain/chain/xlm/common" + xlminput "github.com/cordialsys/crosschain/chain/xlm/tx_input" + "github.com/stellar/go-stellar-sdk/xdr" +) + +type sorobanRpcRequest struct { + Jsonrpc string `json:"jsonrpc"` + Id int `json:"id"` + Method string `json:"method"` + Params interface{} `json:"params"` +} + +type sorobanSimulateParams struct { + Transaction string `json:"transaction"` +} + +type sorobanSimulateResponse struct { + Jsonrpc string `json:"jsonrpc"` + Id int `json:"id"` + Result *sorobanSimulateResult `json:"result,omitempty"` + Error *sorobanRpcError `json:"error,omitempty"` +} + +type sorobanSimulateResult struct { + TransactionData string `json:"transactionData"` + MinResourceFee json.Number `json:"minResourceFee"` + Error string `json:"error,omitempty"` +} + +type sorobanRpcError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +// estimateSorobanResourceFee builds a temporary InvokeHostFunction transaction +// with natively constructed auth and footprint, then simulates it via Soroban RPC +// to get an accurate resource fee estimate. Only the fee is used from the response. +func (client *Client) estimateSorobanResourceFee(sorobanUrl string, args xcbuilder.TransferArgs, txInput *xlminput.TxInput) error { + from := args.GetFrom() + to := args.GetTo() + + contract, ok := args.GetContract() + if !ok { + return fmt.Errorf("contract is required for Soroban SAC transfers") + } + contractDetails, err := common.GetAssetAndIssuerFromContract(string(contract)) + if err != nil { + return fmt.Errorf("failed to parse contract: %w", err) + } + xdrAsset, err := common.CreateAssetFromContractDetails(contractDetails) + if err != nil { + return fmt.Errorf("failed to create asset: %w", err) + } + sacId, err := xdrAsset.ContractID(txInput.Passphrase) + if err != nil { + return fmt.Errorf("failed to derive SAC contract ID: %w", err) + } + contractId := xdr.ContractId(sacId) + contractAddr := xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeContract, + ContractId: &contractId, + } + + fromScVal, err := common.ScValAddress(string(from)) + if err != nil { + return fmt.Errorf("failed to encode from address: %w", err) + } + toScVal, err := common.ScValAddress(string(to)) + if err != nil { + return fmt.Errorf("failed to encode to address: %w", err) + } + amountScVal := common.ScValI128(args.GetAmount().Int().Int64()) + + invokeArgs := xdr.InvokeContractArgs{ + ContractAddress: contractAddr, + FunctionName: "transfer", + Args: []xdr.ScVal{fromScVal, toScVal, amountScVal}, + } + + hostFn, err := xdr.NewHostFunction(xdr.HostFunctionTypeHostFunctionTypeInvokeContract, invokeArgs) + if err != nil { + return fmt.Errorf("failed to create host function: %w", err) + } + + opBody, err := xdr.NewOperationBody(xdr.OperationTypeInvokeHostFunction, xdr.InvokeHostFunctionOp{ + HostFunction: hostFn, + }) + if err != nil { + return fmt.Errorf("failed to create operation body: %w", err) + } + + txSourceAccount, err := common.MuxedAccountFromAddress(from) + if err != nil { + return fmt.Errorf("invalid source address: %w", err) + } + + simTxe := xdr.TransactionV1Envelope{ + Tx: xdr.Transaction{ + SourceAccount: txSourceAccount, + Fee: xdr.Uint32(txInput.MaxFee), + SeqNum: xdr.SequenceNumber(txInput.GetXlmSequence()), + Cond: xdr.Preconditions{Type: xdr.PreconditionTypePrecondNone}, + Operations: []xdr.Operation{ + { + SourceAccount: &txSourceAccount, + Body: opBody, + }, + }, + }, + } + + simEnvelope, err := xdr.NewTransactionEnvelope(xdr.EnvelopeTypeEnvelopeTypeTx, simTxe) + if err != nil { + return fmt.Errorf("failed to create simulation envelope: %w", err) + } + + envBytes, err := simEnvelope.MarshalBinary() + if err != nil { + return fmt.Errorf("failed to marshal simulation envelope: %w", err) + } + + // Call Soroban RPC simulateTransaction + reqBody := sorobanRpcRequest{ + Jsonrpc: "2.0", + Id: 1, + Method: "simulateTransaction", + Params: sorobanSimulateParams{ + Transaction: base64.StdEncoding.EncodeToString(envBytes), + }, + } + + bodyBytes, err := json.Marshal(reqBody) + if err != nil { + return fmt.Errorf("failed to marshal simulate request: %w", err) + } + + resp, err := client.HttpClient.Post(sorobanUrl, "application/json", bytes.NewReader(bodyBytes)) + if err != nil { + return fmt.Errorf("failed to call soroban simulateTransaction: %w", err) + } + defer resp.Body.Close() + + respBytes, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read simulate response: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("soroban simulateTransaction returned status %d: %s", resp.StatusCode, string(respBytes)) + } + + var simResp sorobanSimulateResponse + if err := json.Unmarshal(respBytes, &simResp); err != nil { + return fmt.Errorf("failed to unmarshal simulate response: %w", err) + } + + if simResp.Error != nil { + return fmt.Errorf("soroban rpc error (code %d): %s", simResp.Error.Code, simResp.Error.Message) + } + + if simResp.Result == nil { + return fmt.Errorf("soroban simulation returned no result") + } + + if simResp.Result.Error != "" { + return fmt.Errorf("soroban simulation error: %s", simResp.Result.Error) + } + + // Use the resource fee from simulation + resourceFee, err := simResp.Result.MinResourceFee.Int64() + if err != nil { + return fmt.Errorf("failed to parse resource fee: %w", err) + } + txInput.SorobanResourceFee = uint32(resourceFee) + + // Parse resource limits from the simulation's transactionData + if simResp.Result.TransactionData != "" { + dataBz, err := base64.StdEncoding.DecodeString(simResp.Result.TransactionData) + if err == nil { + var simData xdr.SorobanTransactionData + if err := simData.UnmarshalBinary(dataBz); err == nil { + txInput.SorobanInstructions = uint32(simData.Resources.Instructions) + txInput.SorobanDiskReadBytes = uint32(simData.Resources.DiskReadBytes) + txInput.SorobanWriteBytes = uint32(simData.Resources.WriteBytes) + } + } + } + + // Fetch fee stats for an accurate inclusion fee + inclusionFee, err := client.fetchSorobanInclusionFee(sorobanUrl) + if err == nil && inclusionFee > 0 { + txInput.MaxFee = inclusionFee + } + + return nil +} + +type sorobanFeeStatsResponse struct { + Jsonrpc string `json:"jsonrpc"` + Id int `json:"id"` + Result *struct { + SorobanInclusionFee struct { + P90 json.Number `json:"p90"` + P80 json.Number `json:"p80"` + P50 json.Number `json:"p50"` + Mode json.Number `json:"mode"` + } `json:"sorobanInclusionFee"` + } `json:"result,omitempty"` +} + +// fetchSorobanInclusionFee gets the current network inclusion fee from Soroban RPC. +// Prefers p90, falls back to p80, p50, then mode. +func (client *Client) fetchSorobanInclusionFee(sorobanUrl string) (uint32, error) { + reqBody := sorobanRpcRequest{ + Jsonrpc: "2.0", + Id: 1, + Method: "getFeeStats", + Params: nil, + } + + bodyBytes, err := json.Marshal(reqBody) + if err != nil { + return 0, err + } + + resp, err := client.HttpClient.Post(sorobanUrl, "application/json", bytes.NewReader(bodyBytes)) + if err != nil { + return 0, err + } + defer resp.Body.Close() + + respBytes, err := io.ReadAll(resp.Body) + if err != nil { + return 0, err + } + + var feeResp sorobanFeeStatsResponse + if err := json.Unmarshal(respBytes, &feeResp); err != nil { + return 0, err + } + + if feeResp.Result == nil { + return 0, fmt.Errorf("no fee stats result") + } + + fees := feeResp.Result.SorobanInclusionFee + for _, candidate := range []json.Number{fees.P90, fees.P80, fees.P50, fees.Mode} { + if v, err := candidate.Int64(); err == nil && v > 0 { + return uint32(v), nil + } + } + + return 0, fmt.Errorf("no valid inclusion fee in fee stats") +} diff --git a/chain/xlm/client/types/rpc.go b/chain/xlm/client/types/rpc.go index 762a97ad..ad126c82 100644 --- a/chain/xlm/client/types/rpc.go +++ b/chain/xlm/client/types/rpc.go @@ -5,7 +5,7 @@ import ( "fmt" xc "github.com/cordialsys/crosschain" - "github.com/stellar/go/xdr" + "github.com/stellar/go-stellar-sdk/xdr" ) const ( @@ -50,11 +50,11 @@ type GetTransactionResult struct { FeeCharged string `json:"fee_charged"` MaxFee string `json:"max_fee"` OperationCount int `json:"operation_count"` - // base64 encoded github.com/stellar/go/xdr.TransactionEnvelope XDR binary + // base64 encoded github.com/stellar/go-stellar-sdk/xdr.TransactionEnvelope XDR binary EnvelopeXdr string `json:"envelope_xdr,omitempty"` - // base64 encoded github.com/stellar/go/xdr.TransactionResult XDR binary + // base64 encoded github.com/stellar/go-stellar-sdk/xdr.TransactionResult XDR binary ResultXdr string `json:"result_xdr,omitempty"` - // base64 encoded github.com/stellar/go/xdr.TransactionResultMeta XDR binary + // base64 encoded github.com/stellar/go-stellar-sdk/xdr.TransactionResultMeta XDR binary ResultMetaXdr string `json:"result_meta_xdr,omitempty"` PagingToken string `json:"paging_token,omitempty"` } diff --git a/chain/xlm/common/common.go b/chain/xlm/common/common.go index 9581c565..5539a038 100644 --- a/chain/xlm/common/common.go +++ b/chain/xlm/common/common.go @@ -5,7 +5,8 @@ import ( "strings" xc "github.com/cordialsys/crosschain" - "github.com/stellar/go/xdr" + "github.com/stellar/go-stellar-sdk/strkey" + "github.com/stellar/go-stellar-sdk/xdr" ) // XLM Asset representation @@ -122,6 +123,71 @@ func CreateChangeTrustAsset(details AssetDetails) (xdr.ChangeTrustAsset, error) } } +// IsContractAddress returns true if the address is a Soroban contract (C...) address. +func IsContractAddress(address xc.Address) bool { + return strkey.IsValidContractAddress(string(address)) +} + +// ScAddressFromString creates an ScAddress from either a G (account) or C (contract) address. +func ScAddressFromString(address string) (xdr.ScAddress, error) { + if strkey.IsValidContractAddress(address) { + contractBytes, err := strkey.Decode(strkey.VersionByteContract, address) + if err != nil { + return xdr.ScAddress{}, fmt.Errorf("failed to decode contract address: %w", err) + } + var contractId xdr.Hash + copy(contractId[:], contractBytes) + cid := xdr.ContractId(contractId) + return xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeContract, + ContractId: &cid, + }, nil + } + accountId, err := xdr.AddressToAccountId(address) + if err != nil { + return xdr.ScAddress{}, fmt.Errorf("failed to decode account address: %w", err) + } + return xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeAccount, + AccountId: &accountId, + }, nil +} + +// ScValAddress creates an ScVal of type SCV_ADDRESS from a Stellar address string. +func ScValAddress(address string) (xdr.ScVal, error) { + scAddr, err := ScAddressFromString(address) + if err != nil { + return xdr.ScVal{}, err + } + return xdr.ScVal{ + Type: xdr.ScValTypeScvAddress, + Address: &scAddr, + }, nil +} + +// ScValI128 creates an ScVal of type SCV_I128 from an int64 value. +func ScValI128(amount int64) xdr.ScVal { + hi := xdr.Int64(0) + if amount < 0 { + hi = -1 + } + lo := xdr.Uint64(amount) + parts := xdr.Int128Parts{Hi: hi, Lo: lo} + return xdr.ScVal{ + Type: xdr.ScValTypeScvI128, + I128: &parts, + } +} + +// ScValSymbol creates an ScVal of type SCV_SYMBOL. +func ScValSymbol(s string) xdr.ScVal { + sym := xdr.ScSymbol(s) + return xdr.ScVal{ + Type: xdr.ScValTypeScvSymbol, + Sym: &sym, + } +} + func MuxedAccountFromAddress(address xc.Address) (xdr.MuxedAccount, error) { var account xdr.MuxedAccount err := account.SetAddress(string(address)) diff --git a/chain/xlm/common/common_test.go b/chain/xlm/common/common_test.go index 118a6e26..1b6dcc18 100644 --- a/chain/xlm/common/common_test.go +++ b/chain/xlm/common/common_test.go @@ -5,7 +5,7 @@ import ( xc "github.com/cordialsys/crosschain" "github.com/cordialsys/crosschain/chain/xlm/common" - "github.com/stellar/go/xdr" + "github.com/stellar/go-stellar-sdk/xdr" "github.com/stretchr/testify/require" ) diff --git a/chain/xlm/tx/tx.go b/chain/xlm/tx/tx.go index 67d04eea..cb3f24ef 100644 --- a/chain/xlm/tx/tx.go +++ b/chain/xlm/tx/tx.go @@ -9,7 +9,7 @@ import ( "strings" xc "github.com/cordialsys/crosschain" - "github.com/stellar/go/xdr" + "github.com/stellar/go-stellar-sdk/xdr" ) type Tx struct { diff --git a/chain/xlm/tx/tx_test.go b/chain/xlm/tx/tx_test.go index 3cbdf535..9f3f2384 100644 --- a/chain/xlm/tx/tx_test.go +++ b/chain/xlm/tx/tx_test.go @@ -7,7 +7,7 @@ import ( "github.com/cordialsys/crosschain/chain/xlm" "github.com/cordialsys/crosschain/chain/xlm/common" tx "github.com/cordialsys/crosschain/chain/xlm/tx" - "github.com/stellar/go/xdr" + "github.com/stellar/go-stellar-sdk/xdr" "github.com/stretchr/testify/require" ) diff --git a/chain/xlm/tx_input/tx_input.go b/chain/xlm/tx_input/tx_input.go index 6ab5e580..ef9dd911 100644 --- a/chain/xlm/tx_input/tx_input.go +++ b/chain/xlm/tx_input/tx_input.go @@ -53,6 +53,13 @@ type TxInput struct { // NeedsCreateTrustline indicates that the sender needs a trustline for the token asset. // When true, a ChangeTrust operation is prepended to the transaction. NeedsCreateTrustline bool `json:"needs_create_trustline,omitempty"` + // SorobanResourceFee is the resource fee (in stroops) for Soroban InvokeHostFunction transactions. + // It is added to MaxFee (the inclusion fee) to form the total transaction fee. + SorobanResourceFee uint32 `json:"soroban_resource_fee,omitempty"` + // Soroban resource limits for InvokeHostFunction transactions. + SorobanInstructions uint32 `json:"soroban_instructions,omitempty"` + SorobanDiskReadBytes uint32 `json:"soroban_disk_read_bytes,omitempty"` + SorobanWriteBytes uint32 `json:"soroban_write_bytes,omitempty"` } func init() { @@ -110,16 +117,25 @@ func (input *TxInput) SetGasFeePriority(priority xc.GasFeePriority) error { if asInt > math.MaxUint32 { return fmt.Errorf("multiplied (x%s) max fee exceeds XLM limit, consider decreasing fee priority", multiplier.String()) } - input.MaxFee = uint32(asInt) + + if input.SorobanResourceFee > 0 { + multipliedResourceFee := multiplier.Mul(decimal.NewFromInt(int64(input.SorobanResourceFee))) + asInt := multipliedResourceFee.IntPart() + if asInt > math.MaxUint32 { + return fmt.Errorf("multiplied (x%s) soroban resource fee exceeds limit", multiplier.String()) + } + input.SorobanResourceFee = uint32(asInt) + } + return nil } func (input *TxInput) GetFeeLimit() (xc.AmountBlockchain, xc.ContractAddress) { - return xc.NewAmountBlockchainFromUint64(uint64(input.MaxFee)), "" + totalFee := uint64(input.MaxFee) + uint64(input.SorobanResourceFee) + return xc.NewAmountBlockchainFromUint64(totalFee), "" } func (input *TxInput) IsFeeLimitAccurate() bool { - // currently getting a ~2 XLM fixed fee, not really accurate - return false + return true } diff --git a/chain/xlm/xlm.go b/chain/xlm/xlm.go index 31ac2604..0c86c37c 100644 --- a/chain/xlm/xlm.go +++ b/chain/xlm/xlm.go @@ -5,7 +5,7 @@ import ( "time" "github.com/cordialsys/crosschain/client/errors" - "github.com/stellar/go/xdr" + "github.com/stellar/go-stellar-sdk/xdr" ) // TimeBounds represents the time window during which a Stellar transaction is considered valid. diff --git a/factory/defaults/chains/mainnet.yaml b/factory/defaults/chains/mainnet.yaml index 3a097bf9..0f011bfd 100644 --- a/factory/defaults/chains/mainnet.yaml +++ b/factory/defaults/chains/mainnet.yaml @@ -1625,6 +1625,7 @@ chains: support: memo: string fee: + accurate: true payer: true driver: xlm chain_name: xlm diff --git a/factory/defaults/chains/testnet.yaml b/factory/defaults/chains/testnet.yaml index 87581451..a47a5cd8 100644 --- a/factory/defaults/chains/testnet.yaml +++ b/factory/defaults/chains/testnet.yaml @@ -859,6 +859,7 @@ chains: support: memo: string fee: + accurate: true payer: true driver: xlm chain_name: xlm diff --git a/go.mod b/go.mod index 0cdd9a6e..1c7c97d1 100644 --- a/go.mod +++ b/go.mod @@ -37,14 +37,13 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 - github.com/stellar/go v0.0.0-20220406183204-45b6f52202f3 github.com/stretchr/testify v1.10.0 github.com/test-go/testify v1.1.4 github.com/tidwall/btree v1.7.0 github.com/vedhavyas/go-subkey/v2 v2.0.0 github.com/xssnick/tonutils-go v1.9.8 github.com/xyield/xrpl-go v0.0.0-20230914223425-9abe75c05830 - golang.org/x/crypto v0.40.0 + golang.org/x/crypto v0.45.0 google.golang.org/api v0.185.0 google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b @@ -72,6 +71,7 @@ require ( github.com/holiman/uint256 v1.3.2 github.com/kaspanet/kaspad v0.12.22 github.com/pkg/errors v0.9.1 + github.com/stellar/go-stellar-sdk v0.4.0 github.com/vmihailenco/msgpack/v5 v5.4.1 golang.org/x/time v0.9.0 ) @@ -248,7 +248,7 @@ require ( github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.6 // indirect - github.com/stellar/go-xdr v0.0.0-20211103144802-8017fc4bdfee // indirect + github.com/stellar/go-xdr v0.0.0-20260312225820-cc2b0611aabf // indirect github.com/streamingfast/logging v0.0.0-20250404134358-92b15d2fbd2e // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -280,12 +280,12 @@ require ( go.uber.org/ratelimit v0.2.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/term v0.33.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect diff --git a/go.sum b/go.sum index ba6e7629..4db31171 100644 --- a/go.sum +++ b/go.sum @@ -536,8 +536,8 @@ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -575,7 +575,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 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.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -833,7 +832,6 @@ github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/klauspost/cpuid v0.0.0-20160302075316-09cded8978dc h1:WW8B7p7QBnFlqRVv/k6ro/S8Z7tCnYjJHcQNScx9YVs= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -1056,10 +1054,10 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/stellar/go v0.0.0-20220406183204-45b6f52202f3 h1:PLVF895y1tGeDj1L2Wzocl6Vn60zYCSAKtuATczbLYc= -github.com/stellar/go v0.0.0-20220406183204-45b6f52202f3/go.mod h1:XDw7zGAJCmEuZVmcr7PvLioASsqQRN80dL+OkOxV50A= -github.com/stellar/go-xdr v0.0.0-20211103144802-8017fc4bdfee h1:fbVs0xmXpBvVS4GBeiRmAE3Le70ofAqFMch1GTiq/e8= -github.com/stellar/go-xdr v0.0.0-20211103144802-8017fc4bdfee/go.mod h1:yoxyU/M8nl9LKeWIoBrbDPQ7Cy+4jxRcWcOayZ4BMps= +github.com/stellar/go-stellar-sdk v0.4.0 h1:WBLeJk7EllU7WhwrTH1L5Mu2EhZor/xy5sVi/82JzNQ= +github.com/stellar/go-stellar-sdk v0.4.0/go.mod h1:tLKAQPxa2I5UvGMabBbUXcY3fmgYnfDudrMeK7CDX4w= +github.com/stellar/go-xdr v0.0.0-20260312225820-cc2b0611aabf h1:GY1RVbX3Hg7poPXEf6yojjP0hyypvgUgZmCqQU9D0xg= +github.com/stellar/go-xdr v0.0.0-20260312225820-cc2b0611aabf/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU= github.com/streamingfast/logging v0.0.0-20250404134358-92b15d2fbd2e h1:qGVGDR2/bXLyR498un1hvhDQPUJ/m14JBRTJz+c67Bc= github.com/streamingfast/logging v0.0.0-20250404134358-92b15d2fbd2e/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU= @@ -1202,8 +1200,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1297,8 +1295,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1340,8 +1338,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1430,13 +1428,13 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1447,8 +1445,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=