diff --git a/trails-api/api.gen.go b/trails-api/api.gen.go new file mode 100644 index 00000000..59538c98 --- /dev/null +++ b/trails-api/api.gen.go @@ -0,0 +1,1124 @@ +// trails-api v0.4.0 28a9163b0bcbdf5cecfc8f2b668550cc1ccadaf3 +// -- +// Code generated by webrpc-gen@v0.25.3 with golang generator. DO NOT EDIT. +// +// webrpc-gen -schema=api.ridl -target=golang -pkg=api -client -out=./clients/api.gen.go +package api + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/0xsequence/go-sequence/lib/prototyp" +) + +const WebrpcHeader = "Webrpc" + +const WebrpcHeaderValue = "webrpc@v0.25.3;gen-golang@v0.18.4;trails-api@v0.4.0" + +// WebRPC description and code-gen version +func WebRPCVersion() string { + return "v1" +} + +// Schema version of your RIDL schema +func WebRPCSchemaVersion() string { + return "v0.4.0" +} + +// Schema hash generated from your RIDL schema +func WebRPCSchemaHash() string { + return "28a9163b0bcbdf5cecfc8f2b668550cc1ccadaf3" +} + +type WebrpcGenVersions struct { + WebrpcGenVersion string + CodeGenName string + CodeGenVersion string + SchemaName string + SchemaVersion string +} + +func VersionFromHeader(h http.Header) (*WebrpcGenVersions, error) { + if h.Get(WebrpcHeader) == "" { + return nil, fmt.Errorf("header is empty or missing") + } + + versions, err := parseWebrpcGenVersions(h.Get(WebrpcHeader)) + if err != nil { + return nil, fmt.Errorf("webrpc header is invalid: %w", err) + } + + return versions, nil +} + +func parseWebrpcGenVersions(header string) (*WebrpcGenVersions, error) { + versions := strings.Split(header, ";") + if len(versions) < 3 { + return nil, fmt.Errorf("expected at least 3 parts while parsing webrpc header: %v", header) + } + + _, webrpcGenVersion, ok := strings.Cut(versions[0], "@") + if !ok { + return nil, fmt.Errorf("webrpc gen version could not be parsed from: %s", versions[0]) + } + + tmplTarget, tmplVersion, ok := strings.Cut(versions[1], "@") + if !ok { + return nil, fmt.Errorf("tmplTarget and tmplVersion could not be parsed from: %s", versions[1]) + } + + schemaName, schemaVersion, ok := strings.Cut(versions[2], "@") + if !ok { + return nil, fmt.Errorf("schema name and schema version could not be parsed from: %s", versions[2]) + } + + return &WebrpcGenVersions{ + WebrpcGenVersion: webrpcGenVersion, + CodeGenName: tmplTarget, + CodeGenVersion: tmplVersion, + SchemaName: schemaName, + SchemaVersion: schemaVersion, + }, nil +} + +// +// Common types +// + +type TradeType string + +const ( + TradeType_EXACT_INPUT TradeType = "EXACT_INPUT" + TradeType_EXACT_OUTPUT TradeType = "EXACT_OUTPUT" +) + +func (x TradeType) MarshalText() ([]byte, error) { + return []byte(x), nil +} + +func (x *TradeType) UnmarshalText(b []byte) error { + *x = TradeType(string(b)) + return nil +} + +func (x *TradeType) Is(values ...TradeType) bool { + if x == nil { + return false + } + for _, v := range values { + if *x == v { + return true + } + } + return false +} + +type SortOrder uint32 + +const ( + SortOrder_DESC SortOrder = 0 + SortOrder_ASC SortOrder = 1 +) + +var SortOrder_name = map[uint32]string{ + 0: "DESC", + 1: "ASC", +} + +var SortOrder_value = map[string]uint32{ + "DESC": 0, + "ASC": 1, +} + +func (x SortOrder) String() string { + return SortOrder_name[uint32(x)] +} + +func (x SortOrder) MarshalText() ([]byte, error) { + return []byte(SortOrder_name[uint32(x)]), nil +} + +func (x *SortOrder) UnmarshalText(b []byte) error { + *x = SortOrder(SortOrder_value[string(b)]) + return nil +} + +func (x *SortOrder) Is(values ...SortOrder) bool { + if x == nil { + return false + } + for _, v := range values { + if *x == v { + return true + } + } + return false +} + +type Version struct { + WebrpcVersion string `json:"webrpcVersion"` + SchemaVersion string `json:"schemaVersion"` + SchemaHash string `json:"schemaHash"` + AppVersion string `json:"appVersion"` +} + +type RuntimeStatus struct { + // overall status, true/false + HealthOK bool `json:"healthOK"` + StartTime time.Time `json:"startTime"` + Uptime uint64 `json:"uptime"` + Ver string `json:"ver"` + Branch string `json:"branch"` + CommitHash string `json:"commitHash"` + Checks *RuntimeChecks `json:"checks"` + NumTxnsRelayed map[string]*NumTxnsRelayed `json:"numTxnsRelayed"` +} + +type NumTxnsRelayed struct { + ChainID uint64 `json:"chainID"` + Prev uint64 `json:"prev"` + Current uint64 `json:"current"` + Period uint64 `json:"period"` +} + +type RuntimeChecks struct { +} + +// From: `0xsequence/relayer`: https://github.com/0xsequence/relayer/blob/2c695b820970c68c57bfe71810f469412cdec675/proto/relayer.ridl#L502 +// TODO: rename this to MetaTxnRaw (eventually), we can leave it for now to not break compat +// or name it, MetaTxnArgs ..? +type MetaTxn struct { + ID string `json:"id"` + ChainId prototyp.BigInt `json:"chainId"` + WalletAddress string `json:"walletAddress" db:"wallet_address"` + // TODO (later): rename this to `to: string` + Contract string `json:"contract" db:"to_address"` + // TODO: rename to 'execdata' + Input string `json:"input" db:"tx_data"` +} + +type Call struct { + To prototyp.Hash `json:"to"` + Value prototyp.BigInt `json:"value"` + Data prototyp.Hash `json:"data"` + GasLimit prototyp.BigInt `json:"gasLimit"` + DelegateCall *bool `json:"delegateCall"` + OnlyFallback *bool `json:"onlyFallback"` + BehaviorOnError *uint8 `json:"behaviorOnError"` +} + +type IntentCallsPayload struct { + ChainId prototyp.BigInt `json:"chainId"` + Space prototyp.BigInt `json:"space"` + Nonce prototyp.BigInt `json:"nonce"` + Calls []*Call `json:"calls"` +} + +// IntentConfig +type IntentConfig struct { + ID uint64 `json:"id" db:"id,omitempty"` + ConfigHash prototyp.Hash `json:"configHash" db:"config_hash"` + OriginIntentAddress prototyp.Hash `json:"originIntentAddress" db:"origin_intent_address"` + DestinationIntentAddress prototyp.Hash `json:"destinationIntentAddress" db:"destination_intent_address"` + MainSigner prototyp.Hash `json:"mainSigner" db:"main_signer"` + Calls prototyp.JSONString `json:"calls" db:"calls"` + Preconditions prototyp.JSONString `json:"preconditions" db:"preconditions"` + ExecutionStatus *string `json:"executionStatus" db:"execution_status,omitempty"` + MetaTxnID *string `json:"metaTxnId" db:"meta_txn_id,omitempty"` + TxnHash *string `json:"txnHash" db:"txn_hash,omitempty"` + UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at,omitempty"` + CreatedAt *time.Time `json:"createdAt,omitempty" db:"created_at,omitempty"` +} + +type AddressOverrides struct { + TrailsLiFiSapientSignerAddress *string `json:"trailsLiFiSapientSignerAddress"` + TrailsRelaySapientSignerAddress *string `json:"trailsRelaySapientSignerAddress"` + TrailsCCTPV2SapientSignerAddress *string `json:"trailsCCTPV2SapientSignerAddress"` +} + +type TakerFee struct { + Address prototyp.Hash `json:"address"` + Bps uint64 `json:"bps"` +} + +type IntentPrecondition struct { + Type string `json:"type"` + ChainId prototyp.BigInt `json:"chainId"` + Data interface{} `json:"data"` +} + +type Token struct { + ChainId uint64 `json:"chainId"` + ContractAddress prototyp.Hash `json:"contractAddress"` + TokenId *string `json:"tokenId"` +} + +type Price struct { + Value float64 `json:"value"` + Currency string `json:"currency"` +} + +type TokenPrice struct { + Token *Token `json:"token"` + Price *Price `json:"price"` + Price24hChange *Price `json:"price24hChange"` + Price24hVol *Price `json:"price24hVol"` + FloorPrice *Price `json:"floorPrice,omitempty"` + BuyPrice *Price `json:"buyPrice,omitempty"` + SellPrice *Price `json:"sellPrice,omitempty"` + UpdatedAt *time.Time `json:"updatedAt"` +} + +type ExchangeRate struct { + Name string `json:"name"` + Symbol string `json:"symbol"` + Value float64 `json:"value"` + VsCurrency string `json:"vsCurrency"` + CurrencyType string `json:"currencyType"` +} + +// TOOD: refactor, we should be returning a cursor always.. +// see indexer/indexer.ridl Page object.. +// +// --- +// +// Page represents a results page. This can be used both to request a page and +// to store the state of a page. +type Page struct { + // Common for both numbered pages and cursor: Number of items per page + // TODO: REMOVE.. + PageSize *uint32 `json:"pageSize"` + // Numbered pages: Page number, this is multiplied by the value of the parameter. + // TODO: REMOVE.. + Page *uint32 `json:"page"` + // Number of total items on this query. + // TODO: REMOVE.. + TotalRecords *uint64 `json:"totalRecords"` + // Cursor: column to compare before/after to + Column *string `json:"column"` + // Cursor: return column < before - include to get previous page + Before *interface{} `json:"before"` + // Cursor: return column > after - include to get next page + After *interface{} `json:"after"` + // Sorting filter + Sort []*SortBy `json:"sort"` + // Indicates if there are more results available + More *bool `json:"more,omitempty"` +} + +type SortBy struct { + Column string `json:"column"` + Order SortOrder `json:"order"` +} + +type CCTPTransfer struct { + ID string `json:"id"` + SourceTxHash string `json:"sourceTxHash"` + SourceChainID uint64 `json:"sourceChainId"` + DestinationChainID uint64 `json:"destinationChainId"` + Message string `json:"message"` + Attestation string `json:"attestation"` + Status string `json:"status"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type CrossChainFee struct { + ProviderFee prototyp.BigInt `json:"providerFee"` + TrailsSwapFee prototyp.BigInt `json:"trailsSwapFee"` + ProviderFeeUSD float64 `json:"providerFeeUSD"` + TrailsSwapFeeUSD float64 `json:"trailsSwapFeeUSD"` + TotalFeeAmount prototyp.BigInt `json:"totalFeeAmount"` + TotalFeeUSD float64 `json:"totalFeeUSD"` +} + +type MetaTxnFeeDetail struct { + MetaTxnID string `json:"metaTxnID"` + EstimatedGasLimit prototyp.BigInt `json:"estimatedGasLimit"` + FeeNative prototyp.BigInt `json:"feeNative"` +} + +type ChainExecuteQuote struct { + ChainId prototyp.BigInt `json:"chainId"` + TotalGasLimit prototyp.BigInt `json:"totalGasLimit"` + GasPrice prototyp.BigInt `json:"gasPrice"` + TotalFeeAmount prototyp.BigInt `json:"totalFeeAmount"` + NativeTokenSymbol string `json:"nativeTokenSymbol"` + NativeTokenPrice float64 `json:"nativeTokenPrice"` + MetaTxnFeeDetails []*MetaTxnFeeDetail `json:"metaTxnFeeDetails"` + TotalFeeUSD float64 `json:"totalFeeUSD"` +} + +type ExecuteQuote struct { + ChainQuotes []*ChainExecuteQuote `json:"chainQuotes"` +} + +type TrailsFee struct { + ExecuteQuote *ExecuteQuote `json:"executeQuote"` + CrossChainFee *CrossChainFee `json:"crossChainFee"` + TakerFeeAmount prototyp.BigInt `json:"takerFeeAmount"` + TakerFeeUSD *float64 `json:"takerFeeUSD"` + TrailsFixedFeeUSD float64 `json:"trailsFixedFeeUSD"` + FeeToken prototyp.Hash `json:"feeToken"` + OriginTokenTotalAmount prototyp.BigInt `json:"originTokenTotalAmount"` + TotalFeeAmount prototyp.BigInt `json:"totalFeeAmount"` + TotalFeeUSD float64 `json:"totalFeeUSD"` + QuoteProvider *string `json:"quoteProvider"` +} + +type IntentQuote struct { + FromAmount prototyp.BigInt `json:"fromAmount"` + FromAmountMin prototyp.BigInt `json:"fromAmountMin"` + ToAmount prototyp.BigInt `json:"toAmount"` + ToAmountMin prototyp.BigInt `json:"toAmountMin"` + PriceImpact float64 `json:"priceImpact"` + PriceImpactUsd string `json:"priceImpactUsd"` + MaxSlippage float64 `json:"maxSlippage"` + QuoteProvider string `json:"quoteProvider"` + QuoteProviderRequestId string `json:"quoteProviderRequestId"` + QuoteProviderFeeUsd string `json:"quoteProviderFeeUsd"` + FeeQuotes map[string]string `json:"feeQuotes"` +} + +var methods = map[string]method{ + "/rpc/API/Ping": { + Name: "Ping", + Service: "API", + Annotations: map[string]string{}, + }, + "/rpc/API/Version": { + Name: "Version", + Service: "API", + Annotations: map[string]string{}, + }, + "/rpc/API/RuntimeStatus": { + Name: "RuntimeStatus", + Service: "API", + Annotations: map[string]string{}, + }, + "/rpc/API/Clock": { + Name: "Clock", + Service: "API", + Annotations: map[string]string{}, + }, + "/rpc/API/GetIntentCallsPayloads": { + Name: "GetIntentCallsPayloads", + Service: "API", + Annotations: map[string]string{}, + }, + "/rpc/API/CommitIntentConfig": { + Name: "CommitIntentConfig", + Service: "API", + Annotations: map[string]string{}, + }, + "/rpc/API/GetIntentConfig": { + Name: "GetIntentConfig", + Service: "API", + Annotations: map[string]string{}, + }, + "/rpc/API/GetCCTPTransfer": { + Name: "GetCCTPTransfer", + Service: "API", + Annotations: map[string]string{"public": ""}, + }, + "/rpc/API/QueueCCTPTransfer": { + Name: "QueueCCTPTransfer", + Service: "API", + Annotations: map[string]string{"public": ""}, + }, + "/rpc/API/QueueIntentConfigExecution": { + Name: "QueueIntentConfigExecution", + Service: "API", + Annotations: map[string]string{"public": ""}, + }, + "/rpc/API/GetIntentConfigExecutionStatus": { + Name: "GetIntentConfigExecutionStatus", + Service: "API", + Annotations: map[string]string{"public": ""}, + }, + "/rpc/API/ListIntentConfigs": { + Name: "ListIntentConfigs", + Service: "API", + Annotations: map[string]string{"public": ""}, + }, + "/rpc/API/QueueMetaTxnReceipt": { + Name: "QueueMetaTxnReceipt", + Service: "API", + Annotations: map[string]string{"public": ""}, + }, +} + +func WebrpcMethods() map[string]method { + res := make(map[string]method, len(methods)) + for k, v := range methods { + res[k] = v + } + + return res +} + +var WebRPCServices = map[string][]string{ + "API": { + "Ping", + "Version", + "RuntimeStatus", + "Clock", + "GetIntentCallsPayloads", + "CommitIntentConfig", + "GetIntentConfig", + "GetCCTPTransfer", + "QueueCCTPTransfer", + "QueueIntentConfigExecution", + "GetIntentConfigExecutionStatus", + "ListIntentConfigs", + "QueueMetaTxnReceipt", + }, +} + +// +// Server types +// + +type API interface { + // + // Runtime + // + Ping(ctx context.Context) (bool, error) + Version(ctx context.Context) (*Version, error) + RuntimeStatus(ctx context.Context) (*RuntimeStatus, error) + Clock(ctx context.Context) (time.Time, error) + // + // Chain abstraction + // + GetIntentCallsPayloads(ctx context.Context, userAddress string, destinationChainId uint64, destinationTokenAddress string, destinationTokenAmount string, destinationToAddress string, originChainId uint64, originTokenAddress string, originTokenAmount string, destinationCallData *string, destinationCallValue *string, provider *string, addressOverrides *AddressOverrides, destinationSalt *string, takerFee *TakerFee, slippageTolerance *float64, tradeType *TradeType) ([]*IntentCallsPayload, []*IntentPrecondition, []*MetaTxn, *TrailsFee, *IntentQuote, map[string]string, string, string, error) + CommitIntentConfig(ctx context.Context, originIntentAddress string, destinationIntentAddress string, mainSigner string, calls []*IntentCallsPayload, preconditions []*IntentPrecondition, addressOverrides *AddressOverrides) (*IntentConfig, error) + GetIntentConfig(ctx context.Context, intentAddress string) (*IntentConfig, error) + // + // CCTP + // + GetCCTPTransfer(ctx context.Context, id string) (*CCTPTransfer, error) + QueueCCTPTransfer(ctx context.Context, sourceTxHash *string, metaTxHash *string, sourceChainId uint64, destinationChainId uint64) (*CCTPTransfer, error) + // + // Intent Machine Worker + // + QueueIntentConfigExecution(ctx context.Context, intentConfigId uint64) (bool, error) + GetIntentConfigExecutionStatus(ctx context.Context, intentConfigId uint64) (string, error) + ListIntentConfigs(ctx context.Context, page *Page, executionStatus *string) (*Page, []*IntentConfig, error) + QueueMetaTxnReceipt(ctx context.Context, metaTxID string) (bool, error) +} + +// +// Client types +// + +type APIClient interface { + // + // Runtime + // + Ping(ctx context.Context) (bool, error) + Version(ctx context.Context) (*Version, error) + RuntimeStatus(ctx context.Context) (*RuntimeStatus, error) + Clock(ctx context.Context) (time.Time, error) + // + // Chain abstraction + // + GetIntentCallsPayloads(ctx context.Context, userAddress string, destinationChainId uint64, destinationTokenAddress string, destinationTokenAmount string, destinationToAddress string, originChainId uint64, originTokenAddress string, originTokenAmount string, destinationCallData *string, destinationCallValue *string, provider *string, addressOverrides *AddressOverrides, destinationSalt *string, takerFee *TakerFee, slippageTolerance *float64, tradeType *TradeType) ([]*IntentCallsPayload, []*IntentPrecondition, []*MetaTxn, *TrailsFee, *IntentQuote, map[string]string, string, string, error) + CommitIntentConfig(ctx context.Context, originIntentAddress string, destinationIntentAddress string, mainSigner string, calls []*IntentCallsPayload, preconditions []*IntentPrecondition, addressOverrides *AddressOverrides) (*IntentConfig, error) + GetIntentConfig(ctx context.Context, intentAddress string) (*IntentConfig, error) + // + // CCTP + // + GetCCTPTransfer(ctx context.Context, id string) (*CCTPTransfer, error) + QueueCCTPTransfer(ctx context.Context, sourceTxHash *string, metaTxHash *string, sourceChainId uint64, destinationChainId uint64) (*CCTPTransfer, error) + // + // Intent Machine Worker + // + QueueIntentConfigExecution(ctx context.Context, intentConfigId uint64) (bool, error) + GetIntentConfigExecutionStatus(ctx context.Context, intentConfigId uint64) (string, error) + ListIntentConfigs(ctx context.Context, page *Page, executionStatus *string) (*Page, []*IntentConfig, error) + QueueMetaTxnReceipt(ctx context.Context, metaTxID string) (bool, error) +} + +// +// Client +// + +const APIPathPrefix = "/rpc/API/" + +type aPIClient struct { + client HTTPClient + urls [13]string +} + +func NewAPIClient(addr string, client HTTPClient) APIClient { + prefix := urlBase(addr) + APIPathPrefix + urls := [13]string{ + prefix + "Ping", + prefix + "Version", + prefix + "RuntimeStatus", + prefix + "Clock", + prefix + "GetIntentCallsPayloads", + prefix + "CommitIntentConfig", + prefix + "GetIntentConfig", + prefix + "GetCCTPTransfer", + prefix + "QueueCCTPTransfer", + prefix + "QueueIntentConfigExecution", + prefix + "GetIntentConfigExecutionStatus", + prefix + "ListIntentConfigs", + prefix + "QueueMetaTxnReceipt", + } + return &aPIClient{ + client: client, + urls: urls, + } +} + +func (c *aPIClient) Ping(ctx context.Context) (bool, error) { + out := struct { + Ret0 bool `json:"status"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[0], nil, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *aPIClient) Version(ctx context.Context) (*Version, error) { + out := struct { + Ret0 *Version `json:"version"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[1], nil, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *aPIClient) RuntimeStatus(ctx context.Context) (*RuntimeStatus, error) { + out := struct { + Ret0 *RuntimeStatus `json:"status"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[2], nil, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *aPIClient) Clock(ctx context.Context) (time.Time, error) { + out := struct { + Ret0 time.Time `json:"serverTime"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[3], nil, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *aPIClient) GetIntentCallsPayloads(ctx context.Context, userAddress string, destinationChainId uint64, destinationTokenAddress string, destinationTokenAmount string, destinationToAddress string, originChainId uint64, originTokenAddress string, originTokenAmount string, destinationCallData *string, destinationCallValue *string, provider *string, addressOverrides *AddressOverrides, destinationSalt *string, takerFee *TakerFee, slippageTolerance *float64, tradeType *TradeType) ([]*IntentCallsPayload, []*IntentPrecondition, []*MetaTxn, *TrailsFee, *IntentQuote, map[string]string, string, string, error) { + in := struct { + Arg0 string `json:"userAddress"` + Arg1 uint64 `json:"destinationChainId"` + Arg2 string `json:"destinationTokenAddress"` + Arg3 string `json:"destinationTokenAmount"` + Arg4 string `json:"destinationToAddress"` + Arg5 uint64 `json:"originChainId"` + Arg6 string `json:"originTokenAddress"` + Arg7 string `json:"originTokenAmount"` + Arg8 *string `json:"destinationCallData"` + Arg9 *string `json:"destinationCallValue"` + Arg10 *string `json:"provider"` + Arg11 *AddressOverrides `json:"addressOverrides"` + Arg12 *string `json:"destinationSalt"` + Arg13 *TakerFee `json:"takerFee"` + Arg14 *float64 `json:"slippageTolerance"` + Arg15 *TradeType `json:"tradeType"` + }{userAddress, destinationChainId, destinationTokenAddress, destinationTokenAmount, destinationToAddress, originChainId, originTokenAddress, originTokenAmount, destinationCallData, destinationCallValue, provider, addressOverrides, destinationSalt, takerFee, slippageTolerance, tradeType} + out := struct { + Ret0 []*IntentCallsPayload `json:"calls"` + Ret1 []*IntentPrecondition `json:"preconditions"` + Ret2 []*MetaTxn `json:"metaTxns"` + Ret3 *TrailsFee `json:"trailsFee"` + Ret4 *IntentQuote `json:"quote"` + Ret5 map[string]string `json:"feeQuotes"` + Ret6 string `json:"originIntentAddress"` + Ret7 string `json:"destinationIntentAddress"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[4], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, out.Ret1, out.Ret2, out.Ret3, out.Ret4, out.Ret5, out.Ret6, out.Ret7, err +} + +func (c *aPIClient) CommitIntentConfig(ctx context.Context, originIntentAddress string, destinationIntentAddress string, mainSigner string, calls []*IntentCallsPayload, preconditions []*IntentPrecondition, addressOverrides *AddressOverrides) (*IntentConfig, error) { + in := struct { + Arg0 string `json:"originIntentAddress"` + Arg1 string `json:"destinationIntentAddress"` + Arg2 string `json:"mainSigner"` + Arg3 []*IntentCallsPayload `json:"calls"` + Arg4 []*IntentPrecondition `json:"preconditions"` + Arg5 *AddressOverrides `json:"addressOverrides"` + }{originIntentAddress, destinationIntentAddress, mainSigner, calls, preconditions, addressOverrides} + out := struct { + Ret0 *IntentConfig `json:"config"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[5], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *aPIClient) GetIntentConfig(ctx context.Context, intentAddress string) (*IntentConfig, error) { + in := struct { + Arg0 string `json:"intentAddress"` + }{intentAddress} + out := struct { + Ret0 *IntentConfig `json:"config"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[6], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *aPIClient) GetCCTPTransfer(ctx context.Context, id string) (*CCTPTransfer, error) { + in := struct { + Arg0 string `json:"id"` + }{id} + out := struct { + Ret0 *CCTPTransfer `json:"transfer"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[7], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *aPIClient) QueueCCTPTransfer(ctx context.Context, sourceTxHash *string, metaTxHash *string, sourceChainId uint64, destinationChainId uint64) (*CCTPTransfer, error) { + in := struct { + Arg0 *string `json:"sourceTxHash"` + Arg1 *string `json:"metaTxHash"` + Arg2 uint64 `json:"sourceChainId"` + Arg3 uint64 `json:"destinationChainId"` + }{sourceTxHash, metaTxHash, sourceChainId, destinationChainId} + out := struct { + Ret0 *CCTPTransfer `json:"transfer"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[8], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *aPIClient) QueueIntentConfigExecution(ctx context.Context, intentConfigId uint64) (bool, error) { + in := struct { + Arg0 uint64 `json:"intentConfigId"` + }{intentConfigId} + out := struct { + Ret0 bool `json:"status"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[9], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *aPIClient) GetIntentConfigExecutionStatus(ctx context.Context, intentConfigId uint64) (string, error) { + in := struct { + Arg0 uint64 `json:"intentConfigId"` + }{intentConfigId} + out := struct { + Ret0 string `json:"executionStatus"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[10], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *aPIClient) ListIntentConfigs(ctx context.Context, page *Page, executionStatus *string) (*Page, []*IntentConfig, error) { + in := struct { + Arg0 *Page `json:"page"` + Arg1 *string `json:"executionStatus"` + }{page, executionStatus} + out := struct { + Ret0 *Page `json:"page"` + Ret1 []*IntentConfig `json:"intentConfigs"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[11], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, out.Ret1, err +} + +func (c *aPIClient) QueueMetaTxnReceipt(ctx context.Context, metaTxID string) (bool, error) { + in := struct { + Arg0 string `json:"metaTxID"` + }{metaTxID} + out := struct { + Ret0 bool `json:"status"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[12], in, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +// HTTPClient is the interface used by generated clients to send HTTP requests. +// It is fulfilled by *(net/http).Client, which is sufficient for most users. +// Users can provide their own implementation for special retry policies. +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +// urlBase helps ensure that addr specifies a scheme. If it is unparsable +// as a URL, it returns addr unchanged. +func urlBase(addr string) string { + // If the addr specifies a scheme, use it. If not, default to + // http. If url.Parse fails on it, return it unchanged. + url, err := url.Parse(addr) + if err != nil { + return addr + } + if url.Scheme == "" { + url.Scheme = "http" + } + return url.String() +} + +// newRequest makes an http.Request from a client, adding common headers. +func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType string) (*http.Request, error) { + req, err := http.NewRequestWithContext(ctx, "POST", url, reqBody) + if err != nil { + return nil, err + } + req.Header.Set("Accept", contentType) + req.Header.Set("Content-Type", contentType) + req.Header.Set(WebrpcHeader, WebrpcHeaderValue) + if headers, ok := HTTPRequestHeaders(ctx); ok { + for k := range headers { + for _, v := range headers[k] { + req.Header.Add(k, v) + } + } + } + return req, nil +} + +// doHTTPRequest is common code to make a request to the remote service. +func doHTTPRequest(ctx context.Context, client HTTPClient, url string, in, out interface{}) (*http.Response, error) { + reqBody, err := json.Marshal(in) + if err != nil { + return nil, ErrWebrpcRequestFailed.WithCausef("failed to marshal JSON body: %w", err) + } + if err = ctx.Err(); err != nil { + return nil, ErrWebrpcRequestFailed.WithCausef("aborted because context was done: %w", err) + } + + req, err := newRequest(ctx, url, bytes.NewBuffer(reqBody), "application/json") + if err != nil { + return nil, ErrWebrpcRequestFailed.WithCausef("could not build request: %w", err) + } + + resp, err := client.Do(req) + if err != nil { + return nil, ErrWebrpcRequestFailed.WithCause(err) + } + + if resp.StatusCode != 200 { + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, ErrWebrpcBadResponse.WithCausef("failed to read server error response body: %w", err) + } + + var rpcErr WebRPCError + if err := json.Unmarshal(respBody, &rpcErr); err != nil { + return nil, ErrWebrpcBadResponse.WithCausef("failed to unmarshal server error: %w", err) + } + if rpcErr.Cause != "" { + rpcErr.cause = errors.New(rpcErr.Cause) + } + return nil, rpcErr + } + + if out != nil { + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, ErrWebrpcBadResponse.WithCausef("failed to read response body: %w", err) + } + + err = json.Unmarshal(respBody, &out) + if err != nil { + return nil, ErrWebrpcBadResponse.WithCausef("failed to unmarshal JSON response body: %w", err) + } + } + + return resp, nil +} + +func WithHTTPRequestHeaders(ctx context.Context, h http.Header) (context.Context, error) { + if _, ok := h["Accept"]; ok { + return nil, errors.New("provided header cannot set Accept") + } + if _, ok := h["Content-Type"]; ok { + return nil, errors.New("provided header cannot set Content-Type") + } + + copied := make(http.Header, len(h)) + for k, vv := range h { + if vv == nil { + copied[k] = nil + continue + } + copied[k] = make([]string, len(vv)) + copy(copied[k], vv) + } + + return context.WithValue(ctx, HTTPClientRequestHeadersCtxKey, copied), nil +} + +func HTTPRequestHeaders(ctx context.Context) (http.Header, bool) { + h, ok := ctx.Value(HTTPClientRequestHeadersCtxKey).(http.Header) + return h, ok +} + +// +// Helpers +// + +type method struct { + Name string + Service string + Annotations map[string]string +} + +type contextKey struct { + name string +} + +func (k *contextKey) String() string { + return "webrpc context value " + k.name +} + +var ( + HTTPClientRequestHeadersCtxKey = &contextKey{"HTTPClientRequestHeaders"} + HTTPRequestCtxKey = &contextKey{"HTTPRequest"} + + ServiceNameCtxKey = &contextKey{"ServiceName"} + + MethodNameCtxKey = &contextKey{"MethodName"} +) + +func ServiceNameFromContext(ctx context.Context) string { + service, _ := ctx.Value(ServiceNameCtxKey).(string) + return service +} + +func MethodNameFromContext(ctx context.Context) string { + method, _ := ctx.Value(MethodNameCtxKey).(string) + return method +} + +func RequestFromContext(ctx context.Context) *http.Request { + r, _ := ctx.Value(HTTPRequestCtxKey).(*http.Request) + return r +} + +func MethodCtx(ctx context.Context) (method, bool) { + req := RequestFromContext(ctx) + if req == nil { + return method{}, false + } + + m, ok := methods[req.URL.Path] + if !ok { + return method{}, false + } + + return m, true +} + +// +// Errors +// + +type WebRPCError struct { + Name string `json:"error"` + Code int `json:"code"` + Message string `json:"msg"` + Cause string `json:"cause,omitempty"` + HTTPStatus int `json:"status"` + cause error +} + +var _ error = WebRPCError{} + +func (e WebRPCError) Error() string { + if e.cause != nil { + return fmt.Sprintf("%s %d: %s: %v", e.Name, e.Code, e.Message, e.cause) + } + return fmt.Sprintf("%s %d: %s", e.Name, e.Code, e.Message) +} + +func (e WebRPCError) Is(target error) bool { + if target == nil { + return false + } + if rpcErr, ok := target.(WebRPCError); ok { + return rpcErr.Code == e.Code + } + return errors.Is(e.cause, target) +} + +func (e WebRPCError) Unwrap() error { + return e.cause +} + +func (e WebRPCError) WithCause(cause error) WebRPCError { + err := e + err.cause = cause + err.Cause = cause.Error() + return err +} + +func (e WebRPCError) WithCausef(format string, args ...interface{}) WebRPCError { + cause := fmt.Errorf(format, args...) + err := e + err.cause = cause + err.Cause = cause.Error() + return err +} + +// Deprecated: Use .WithCause() method on WebRPCError. +func ErrorWithCause(rpcErr WebRPCError, cause error) WebRPCError { + return rpcErr.WithCause(cause) +} + +// Webrpc errors +var ( + ErrWebrpcEndpoint = WebRPCError{Code: 0, Name: "WebrpcEndpoint", Message: "endpoint error", HTTPStatus: 400} + ErrWebrpcRequestFailed = WebRPCError{Code: -1, Name: "WebrpcRequestFailed", Message: "request failed", HTTPStatus: 400} + ErrWebrpcBadRoute = WebRPCError{Code: -2, Name: "WebrpcBadRoute", Message: "bad route", HTTPStatus: 404} + ErrWebrpcBadMethod = WebRPCError{Code: -3, Name: "WebrpcBadMethod", Message: "bad method", HTTPStatus: 405} + ErrWebrpcBadRequest = WebRPCError{Code: -4, Name: "WebrpcBadRequest", Message: "bad request", HTTPStatus: 400} + ErrWebrpcBadResponse = WebRPCError{Code: -5, Name: "WebrpcBadResponse", Message: "bad response", HTTPStatus: 500} + ErrWebrpcServerPanic = WebRPCError{Code: -6, Name: "WebrpcServerPanic", Message: "server panic", HTTPStatus: 500} + ErrWebrpcInternalError = WebRPCError{Code: -7, Name: "WebrpcInternalError", Message: "internal error", HTTPStatus: 500} + ErrWebrpcClientDisconnected = WebRPCError{Code: -8, Name: "WebrpcClientDisconnected", Message: "client disconnected", HTTPStatus: 400} + ErrWebrpcStreamLost = WebRPCError{Code: -9, Name: "WebrpcStreamLost", Message: "stream lost", HTTPStatus: 400} + ErrWebrpcStreamFinished = WebRPCError{Code: -10, Name: "WebrpcStreamFinished", Message: "stream finished", HTTPStatus: 200} +) + +// Schema errors +var ( + ErrUnauthorized = WebRPCError{Code: 1000, Name: "Unauthorized", Message: "Unauthorized access", HTTPStatus: 401} + ErrPermissionDenied = WebRPCError{Code: 1001, Name: "PermissionDenied", Message: "Permission denied", HTTPStatus: 403} + ErrSessionExpired = WebRPCError{Code: 1002, Name: "SessionExpired", Message: "Session expired", HTTPStatus: 403} + ErrMethodNotFound = WebRPCError{Code: 1003, Name: "MethodNotFound", Message: "Method not found", HTTPStatus: 404} + ErrRequestConflict = WebRPCError{Code: 1004, Name: "RequestConflict", Message: "Conflict with target resource", HTTPStatus: 409} + ErrAborted = WebRPCError{Code: 1005, Name: "Aborted", Message: "Request aborted", HTTPStatus: 400} + ErrGeoblocked = WebRPCError{Code: 1006, Name: "Geoblocked", Message: "Geoblocked region", HTTPStatus: 451} + ErrRateLimited = WebRPCError{Code: 1007, Name: "RateLimited", Message: "Rate-limited. Please slow down.", HTTPStatus: 429} + ErrProjectNotFound = WebRPCError{Code: 1008, Name: "ProjectNotFound", Message: "Project not found", HTTPStatus: 401} + ErrAccessKeyNotFound = WebRPCError{Code: 1101, Name: "AccessKeyNotFound", Message: "Access key not found", HTTPStatus: 401} + ErrAccessKeyMismatch = WebRPCError{Code: 1102, Name: "AccessKeyMismatch", Message: "Access key mismatch", HTTPStatus: 409} + ErrInvalidOrigin = WebRPCError{Code: 1103, Name: "InvalidOrigin", Message: "Invalid origin for Access Key", HTTPStatus: 403} + ErrInvalidService = WebRPCError{Code: 1104, Name: "InvalidService", Message: "Service not enabled for Access key", HTTPStatus: 403} + ErrUnauthorizedUser = WebRPCError{Code: 1105, Name: "UnauthorizedUser", Message: "Unauthorized user", HTTPStatus: 403} + ErrQuotaExceeded = WebRPCError{Code: 1200, Name: "QuotaExceeded", Message: "Quota request exceeded", HTTPStatus: 429} + ErrQuotaRateLimit = WebRPCError{Code: 1201, Name: "QuotaRateLimit", Message: "Quota rate limit exceeded", HTTPStatus: 429} + ErrNoDefaultKey = WebRPCError{Code: 1300, Name: "NoDefaultKey", Message: "No default access key found", HTTPStatus: 403} + ErrMaxAccessKeys = WebRPCError{Code: 1301, Name: "MaxAccessKeys", Message: "Access keys limit reached", HTTPStatus: 403} + ErrAtLeastOneKey = WebRPCError{Code: 1302, Name: "AtLeastOneKey", Message: "You need at least one Access Key", HTTPStatus: 403} + ErrTimeout = WebRPCError{Code: 1900, Name: "Timeout", Message: "Request timed out", HTTPStatus: 408} + ErrInvalidArgument = WebRPCError{Code: 2000, Name: "InvalidArgument", Message: "Invalid argument", HTTPStatus: 400} + ErrUnavailable = WebRPCError{Code: 2002, Name: "Unavailable", Message: "Unavailable resource", HTTPStatus: 400} + ErrQueryFailed = WebRPCError{Code: 2003, Name: "QueryFailed", Message: "Query failed", HTTPStatus: 400} + ErrNotFound = WebRPCError{Code: 8000, Name: "NotFound", Message: "Resource not found", HTTPStatus: 400} + ErrUnsupportedNetwork = WebRPCError{Code: 8008, Name: "UnsupportedNetwork", Message: "Unsupported network", HTTPStatus: 422} +) diff --git a/trails-api/trails-api.gen.go b/trails-api/trails-api.gen.go new file mode 100644 index 00000000..a9055166 --- /dev/null +++ b/trails-api/trails-api.gen.go @@ -0,0 +1,1931 @@ +// trails-api v1-25.11.21+6e5ef30 ddf30c2ffbc5d024003bda5bb08a1802eccd355b +// -- +// Code generated by webrpc-gen@v0.31.2 with golang generator. DO NOT EDIT. +// +// webrpc-gen -schema=trails-api.ridl -target=golang -pkg=api -client -out=./clients/trails-api.gen.go +package api + +import ( + "bytes" + "context" + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + "net/http" + "net/url" + "strings" + "time" + + "github.com/0xsequence/go-sequence/lib/prototyp" +) + +// WebRPC description and code-gen version +func WebRPCVersion() string { + return "v1" +} + +// Schema version of your RIDL schema +func WebRPCSchemaVersion() string { + return "v1-25.11.21+6e5ef30" +} + +// Schema hash generated from your RIDL schema +func WebRPCSchemaHash() string { + return "ddf30c2ffbc5d024003bda5bb08a1802eccd355b" +} + +// +// Client interface +// + +type TrailsClient interface { + Ping(ctx context.Context) (bool, error) + Version(ctx context.Context) (*Version, error) + RuntimeStatus(ctx context.Context) (*RuntimeStatus, error) + Clock(ctx context.Context) (time.Time, error) + QuoteIntent(ctx context.Context, quoteIntentRequest QuoteIntentRequest) (*QuoteIntentResponse, error) + CommitIntent(ctx context.Context, commitIntentRequest CommitIntentRequest) (*CommitIntentResponse, error) + ExecuteIntent(ctx context.Context, executeIntentRequest ExecuteIntentRequest) (*ExecuteIntentResponse, error) + WaitIntentReceipt(ctx context.Context, waitIntentReceiptRequest WaitIntentReceiptRequest) (*WaitIntentReceiptResponse, error) + GetIntentReceipt(ctx context.Context, getIntentReceiptRequest GetIntentReceiptRequest) (*GetIntentReceiptResponse, error) + GetIntent(ctx context.Context, getIntentRequest GetIntentRequest) (*GetIntentResponse, error) + SearchIntents(ctx context.Context, searchIntentsRequest SearchIntentsRequest) (*SearchIntentsResponse, error) + GetIntentTransactionHistory(ctx context.Context, getIntentTransactionHistoryRequest GetIntentTransactionHistoryRequest) (*GetIntentTransactionHistoryResponse, error) + // GetChains will return the list of supported chains by Trails. + GetChains(ctx context.Context, getChainsRequest GetChainsRequest) (*GetChainsResponse, error) + // GetExactOutputRoutes will return a list of origin tokens, when given a destination chain and token, + // that can be used to pay/send from an origin chain the exact output amount on the + // destination chain. + // + // The request will include the destination chain and token desired. Optionally, the + // user can specify an origin chain and token to filter results to only that specific + // origin token. Additionally, an optional owner address can be provided to filter + // results to only tokens the owner has a balance on (requires indexer gateway to be + // configured). + // + // The response is a list of origin tokens and their chains which can be used to fulfill + // the exact output request. These are tokens the user can send FROM to achieve the desired + // destination token amount. + // + // aka, the 'pay' routes + GetExactOutputRoutes(ctx context.Context, getExactOutputRoutesRequest GetExactOutputRoutesRequest) (*GetExactOutputRoutesResponse, error) + // GetExactInputRoutes will return a list of destination tokens, when given an origin chain and token, + // that can be used to send/swap to a destination chain and token. + // + // The request will include the origin chain and token used for input. Optionally, the + // user can specify a destination chain and token to further filter the results. + // + // The response is a list of destination tokens and their chains which can be reached from + // the origin token and chain. These are tokens the user can send TO from the given origin token. + // + // aka, the 'swap' routes + GetExactInputRoutes(ctx context.Context, getExactInputRoutesRequest GetExactInputRoutesRequest) (*GetExactInputRoutesResponse, error) + // GetPopularTokens will return a list of popular tokens based on the provided filters. + // This is useful for populating token selection lists. + GetPopularTokens(ctx context.Context, getPopularTokensRequest GetPopularTokensRequest) (*GetPopularTokensResponse, error) + // SearchTokens will search token lists for the specific token. This is useful + // when a user wants to input a specific token name, symbol or address to find, + // outside of the visible list. + SearchTokens(ctx context.Context, searchTokensRequest SearchTokensRequest) (*SearchTokensResponse, error) + // GetTokenPrices will return the live prices for a list of tokens. + GetTokenPrices(ctx context.Context, getTokenPricesRequest GetTokenPricesRequest) (*GetTokenPricesResponse, error) +} + +// +// Schema types +// + +type TradeType string + +const ( + TradeType_EXACT_INPUT TradeType = "EXACT_INPUT" + TradeType_EXACT_OUTPUT TradeType = "EXACT_OUTPUT" +) + +func (x TradeType) MarshalText() ([]byte, error) { + return []byte(x), nil +} + +func (x *TradeType) UnmarshalText(b []byte) error { + *x = TradeType(string(b)) + return nil +} + +func (x *TradeType) Is(values ...TradeType) bool { + if x == nil { + return false + } + for _, v := range values { + if *x == v { + return true + } + } + return false +} + +type QuoteProviderType string + +const ( + QuoteProviderType_RELAY QuoteProviderType = "RELAY" + QuoteProviderType_CCTPV2 QuoteProviderType = "CCTPV2" + QuoteProviderType_LIFI QuoteProviderType = "LIFI" +) + +func (x QuoteProviderType) MarshalText() ([]byte, error) { + return []byte(x), nil +} + +func (x *QuoteProviderType) UnmarshalText(b []byte) error { + *x = QuoteProviderType(string(b)) + return nil +} + +func (x *QuoteProviderType) Is(values ...QuoteProviderType) bool { + if x == nil { + return false + } + for _, v := range values { + if *x == v { + return true + } + } + return false +} + +type IntentStatus uint8 + +const ( + IntentStatus_QUOTED IntentStatus = 0 + IntentStatus_COMMITTED IntentStatus = 1 + IntentStatus_EXECUTING IntentStatus = 2 + IntentStatus_FAILED IntentStatus = 3 + IntentStatus_SUCCEEDED IntentStatus = 4 +) + +var IntentStatus_name = map[uint8]string{ + 0: "QUOTED", + 1: "COMMITTED", + 2: "EXECUTING", + 3: "FAILED", + 4: "SUCCEEDED", +} + +var IntentStatus_value = map[string]uint8{ + "QUOTED": 0, + "COMMITTED": 1, + "EXECUTING": 2, + "FAILED": 3, + "SUCCEEDED": 4, +} + +func (x IntentStatus) String() string { + return IntentStatus_name[uint8(x)] +} + +func (x IntentStatus) MarshalText() ([]byte, error) { + return []byte(IntentStatus_name[uint8(x)]), nil +} + +func (x *IntentStatus) UnmarshalText(b []byte) error { + *x = IntentStatus(IntentStatus_value[string(b)]) + return nil +} + +func (x *IntentStatus) Is(values ...IntentStatus) bool { + if x == nil { + return false + } + for _, v := range values { + if *x == v { + return true + } + } + return false +} + +type TransactionType uint8 + +const ( + TransactionType_UNKNOWN TransactionType = 0 + // Initial token transfer from owner to origin intent address on origin chain, funding the intent execution. + TransactionType_DEPOSIT TransactionType = 1 + // Origin chain transaction executing intent's origin calls (swaps, bridge preparation, token transfers). + TransactionType_ORIGIN TransactionType = 2 + // Destination chain transaction executing intent's destination calls (receiving bridged tokens, final swaps/transfers). + TransactionType_DESTINATION TransactionType = 3 + // Route provider transaction, see TransactionContext for more details. + TransactionType_ROUTE TransactionType = 4 +) + +var TransactionType_name = map[uint8]string{ + 0: "UNKNOWN", + 1: "DEPOSIT", + 2: "ORIGIN", + 3: "DESTINATION", + 4: "ROUTE", +} + +var TransactionType_value = map[string]uint8{ + "UNKNOWN": 0, + "DEPOSIT": 1, + "ORIGIN": 2, + "DESTINATION": 3, + "ROUTE": 4, +} + +func (x TransactionType) String() string { + return TransactionType_name[uint8(x)] +} + +func (x TransactionType) MarshalText() ([]byte, error) { + return []byte(TransactionType_name[uint8(x)]), nil +} + +func (x *TransactionType) UnmarshalText(b []byte) error { + *x = TransactionType(TransactionType_value[string(b)]) + return nil +} + +func (x *TransactionType) Is(values ...TransactionType) bool { + if x == nil { + return false + } + for _, v := range values { + if *x == v { + return true + } + } + return false +} + +type TransactionContext uint8 + +const ( + TransactionContext_NONE TransactionContext = 0 + // Relays Circle's attested CCTP burn message to destination chain, verifying attestation and minting USDC. + TransactionContext_CCTPV2_MESSAGE TransactionContext = 1 +) + +var TransactionContext_name = map[uint8]string{ + 0: "NONE", + 1: "CCTPV2_MESSAGE", +} + +var TransactionContext_value = map[string]uint8{ + "NONE": 0, + "CCTPV2_MESSAGE": 1, +} + +func (x TransactionContext) String() string { + return TransactionContext_name[uint8(x)] +} + +func (x TransactionContext) MarshalText() ([]byte, error) { + return []byte(TransactionContext_name[uint8(x)]), nil +} + +func (x *TransactionContext) UnmarshalText(b []byte) error { + *x = TransactionContext(TransactionContext_value[string(b)]) + return nil +} + +func (x *TransactionContext) Is(values ...TransactionContext) bool { + if x == nil { + return false + } + for _, v := range values { + if *x == v { + return true + } + } + return false +} + +type TransactionStatus uint8 + +const ( + TransactionStatus_UNKNOWN TransactionStatus = 0 + // on hold, ie. waiting for bridge (off-chain burn attestation) + TransactionStatus_ON_HOLD TransactionStatus = 1 + // ready to be dequeued & sent by the worker + TransactionStatus_PENDING TransactionStatus = 2 + // worker: sending to Relayer + TransactionStatus_RELAYING TransactionStatus = 3 + // sent to the blockchain, whether direct, or via Relayer + TransactionStatus_SENT TransactionStatus = 4 + // encountered error during sending (will be retried) + TransactionStatus_ERRORED TransactionStatus = 5 + // worker: waiting for receipt + TransactionStatus_MINING TransactionStatus = 6 + // terminal state: txn is mined onchain with success status + TransactionStatus_SUCCEEDED TransactionStatus = 7 + // terminal state: txn is mined onchain with failure status + TransactionStatus_FAILED TransactionStatus = 8 + // terminal state: aborted at one of the stages, and never completed onchain txn + TransactionStatus_ABORTED TransactionStatus = 9 +) + +var TransactionStatus_name = map[uint8]string{ + 0: "UNKNOWN", + 1: "ON_HOLD", + 2: "PENDING", + 3: "RELAYING", + 4: "SENT", + 5: "ERRORED", + 6: "MINING", + 7: "SUCCEEDED", + 8: "FAILED", + 9: "ABORTED", +} + +var TransactionStatus_value = map[string]uint8{ + "UNKNOWN": 0, + "ON_HOLD": 1, + "PENDING": 2, + "RELAYING": 3, + "SENT": 4, + "ERRORED": 5, + "MINING": 6, + "SUCCEEDED": 7, + "FAILED": 8, + "ABORTED": 9, +} + +func (x TransactionStatus) String() string { + return TransactionStatus_name[uint8(x)] +} + +func (x TransactionStatus) MarshalText() ([]byte, error) { + return []byte(TransactionStatus_name[uint8(x)]), nil +} + +func (x *TransactionStatus) UnmarshalText(b []byte) error { + *x = TransactionStatus(TransactionStatus_value[string(b)]) + return nil +} + +func (x *TransactionStatus) Is(values ...TransactionStatus) bool { + if x == nil { + return false + } + for _, v := range values { + if *x == v { + return true + } + } + return false +} + +type ChainGasUsageStatus string + +const ( + ChainGasUsageStatus_NORMAL ChainGasUsageStatus = "NORMAL" + ChainGasUsageStatus_BUSY ChainGasUsageStatus = "BUSY" + ChainGasUsageStatus_VERY_BUSY ChainGasUsageStatus = "VERY_BUSY" +) + +func (x ChainGasUsageStatus) MarshalText() ([]byte, error) { + return []byte(x), nil +} + +func (x *ChainGasUsageStatus) UnmarshalText(b []byte) error { + *x = ChainGasUsageStatus(string(b)) + return nil +} + +func (x *ChainGasUsageStatus) Is(values ...ChainGasUsageStatus) bool { + if x == nil { + return false + } + for _, v := range values { + if *x == v { + return true + } + } + return false +} + +// QuoteIntentRequest represents a user's request to get a quote for a +// Trails same-chain or cross-chain intent. +type QuoteIntentRequest struct { + // intent owner, aka the user address processing the quote + OwnerAddress prototyp.Hash `json:"ownerAddress"` + // origin chain and token details + OriginChainID uint64 `json:"originChainId"` + OriginTokenAddress prototyp.Hash `json:"originTokenAddress"` + // destination chain, token, and execution details + DestinationChainID uint64 `json:"destinationChainId"` + DestinationTokenAddress prototyp.Hash `json:"destinationTokenAddress"` + DestinationToAddress prototyp.Hash `json:"destinationToAddress"` + DestinationCallData *string `json:"destinationCallData"` + DestinationCallValue *prototyp.BigInt `json:"destinationCallValue"` + // originTokenAmount is used with EXACT_INPUT txns + OriginTokenAmount *prototyp.BigInt `json:"originTokenAmount"` + // destinationTokenAmount is used with EXACT_OUTPUT txns + DestinationTokenAmount *prototyp.BigInt `json:"destinationTokenAmount"` + // tradeType indicates whether the trade is an exact input or exact output trade. + // EXACT_OUTPUT is the default if unspecified. + TradeType *TradeType `json:"tradeType,omitempty"` + // onlyNativeGasFee indicates that only the native gas fee (ie. eth) + // option will be returned, even if the user has no balance. + // this option is useful for smart wallets which have their own gas abstraction. + OnlyNativeGasFee bool `json:"onlyNativeGasFee,omitempty"` + // options for additional auxiliary params + Options *QuoteIntentRequestOptions `json:"options,omitempty"` +} + +type QuoteIntentRequestOptions struct { + // Temporary: using string until proper enum is specified. + QuoteProvider *string `json:"quoteProvider,omitempty"` + SlippageTolerance *float64 `json:"slippageTolerance,omitempty"` + TrailsAddressOverrides *TrailsAddressOverrides `json:"trailsAddressOverrides,omitempty"` +} + +// Fully formed/quoted/solved, executable intent. +// +// NOTE: was previously named IntentCallsPayloads, and as well IntentConfig +// was pretty much the same thing too, so consolidated into a single type. +// +// db table: 'intents' +type Intent struct { + // id is an internal-only sequential primary key. + ID uint64 `json:"-" db:"id,omitempty"` + // Trails/Sequence project id intent originated from + ProjectID uint64 `json:"-" db:"project_id"` + // intentId is a deterministic public intent identifier + // based on the hash of the origin and destination intent addresses. + IntentID prototyp.Hash `json:"intentId" db:"intent_id"` + // intent status of different stages of the intent to keep track. + // note: we also track status in IntentReceipt for execution status. + Status IntentStatus `json:"status" db:"status,omitempty"` + QuoteRequest *QuoteIntentRequest `json:"quoteRequest" db:"quote_request"` + // intent owner / main signer + OwnerAddress prototyp.Hash `json:"ownerAddress" db:"owner_address"` + OriginChainID uint64 `json:"originChainId" db:"orig_chain_id"` + DestinationChainID uint64 `json:"destinationChainId" db:"dest_chain_id"` + OriginIntentAddress prototyp.Hash `json:"originIntentAddress" db:"orig_intent_address"` + DestinationIntentAddress *prototyp.Hash `json:"destinationIntentAddress,omitempty" db:"dest_intent_address,omitempty"` + Salt prototyp.BigInt `json:"salt" db:"salt"` + DepositTransaction *DepositTransaction `json:"depositTransaction" db:"deposit_txn"` + OriginCalls *IntentCalls `json:"originCalls" db:"orig_calls"` + DestinationCalls *IntentCalls `json:"destinationCalls" db:"dest_calls,omitempty"` + OriginPrecondition *TransactionPrecondition `json:"originPrecondition" db:"orig_precondition"` + DestinationPrecondition *TransactionPrecondition `json:"destinationPrecondition" db:"dest_precondition,omitempty"` + OriginMetaTxn *MetaTxn `json:"originMetaTxn" db:"orig_meta_txn"` + DestinationMetaTxn *MetaTxn `json:"destinationMetaTxn" db:"dest_meta_txn,omitempty"` + Quote *IntentProviderQuote `json:"quote" db:"quote"` + Fees *IntentFees `json:"fees" db:"fees"` + TrailsVersion string `json:"trailsVersion" db:"trails_version"` + ExpiresAt time.Time `json:"expiresAt" db:"expires_at"` + UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at,omitempty"` + CreatedAt *time.Time `json:"createdAt,omitempty" db:"created_at,omitempty"` +} + +// DepositTransaction represents a deposit transfer transaction of the token amount +// to the origin intent address. +type DepositTransaction struct { + // toAddress is the origin intent address where the deposit is sent to. + ToAddress prototyp.Hash `json:"toAddress"` + // tokenAddress is the token being deposited. + TokenAddress prototyp.Hash `json:"tokenAddress"` + // decimals is the number of decimals for the token. If the value is null, it + // means we were unable to retrieve the decimals for the token. + Decimals *uint8 `json:"decimals"` + // amount is the token amount being deposited. + Amount prototyp.BigInt `json:"amount"` +} + +// NOTE: was previously named IntentCallsPayload +type IntentCalls struct { + ChainID uint64 `json:"chainId"` + Space prototyp.BigInt `json:"space"` + Nonce prototyp.BigInt `json:"nonce"` + Calls []*TransactionCall `json:"calls"` +} + +type TransactionCall struct { + To prototyp.Hash `json:"to"` + Value prototyp.BigInt `json:"value"` + Data prototyp.Hash `json:"data"` + GasLimit prototyp.BigInt `json:"gasLimit"` + DelegateCall *bool `json:"delegateCall"` + OnlyFallback *bool `json:"onlyFallback"` + BehaviorOnError *uint8 `json:"behaviorOnError"` +} + +// TransactionPrecondition preconditions based on https://eips.ethereum.org/EIPS/eip-7795 +// NOTE: make sure this struct type matches the relayer TransactionPrecondition type. +type TransactionPrecondition struct { + // value must be 'tokenMinBalance' or '' + Type string `json:"type"` + ChainID uint64 `json:"chainId"` + OwnerAddress prototyp.Hash `json:"ownerAddress"` + TokenAddress prototyp.Hash `json:"tokenAddress"` + MinAmount prototyp.BigInt `json:"minAmount"` +} + +// MetaTxn is a meta-transaction to be submitted to Sequence Relayer. +// +// From: `0xsequence/relayer`: https://github.com/0xsequence/relayer/blob/2c695b820970c68c57bfe71810f469412cdec675/proto/relayer.ridl#L502 +// TODO: rename this to MetaTxnRaw (eventually), we can leave it for now to not break compat +// or name it, MetaTxnArgs ..? +// +// NOTE: this was previously named MetaTxn +type MetaTxn struct { + ID string `json:"id"` + ChainID uint64 `json:"chainId"` + // TODO: rename this to 'from: string' ? ... name...?? walletAddress... from..? + // fromAddress ...? + WalletAddress string `json:"walletAddress"` + // TODO (later): rename this to `to: string` + // TODO: name it.. toAddress ? + Contract string `json:"contract"` + // TODO: rename to 'execdata' ? + Input string `json:"input"` +} + +// IntentReceipt represents an intent that went through trails, and fully executed +// as a set of transactions, and is now complete. This method is used to fetch the +// status of an intent, and also used by the GetIntentTransactionHistory endpoint. +// +// NOTE: this was previously named IntentTransaction +// +// db table: 'intent_receipts' +type IntentReceipt struct { + // id is an internal-only sequential primary key. + ID uint64 `json:"-" db:"id,omitempty"` + // Trails/Sequence project id intent originated from + ProjectID uint64 `json:"-" db:"project_id"` + // intent deterministic id + IntentID prototyp.Hash `json:"intentId" db:"intent_id"` + // intent execution status + Status IntentStatus `json:"status" db:"status,omitempty"` + // intent owner / main signer + OwnerAddress prototyp.Hash `json:"ownerAddress" db:"owner_address"` + // origin chain id where the intent originated from + OriginChainID uint64 `json:"originChainId" db:"orig_chain_id"` + // destination chain id where the intent is targetting as final execution + DestinationChainID uint64 `json:"destinationChainId" db:"dest_chain_id"` + // deposit transaction transaction is the user-initiated transfer into + // the origin intent address on the origin chain. + // + // also note, that if a user is using the 'gasless' deposit method, + // then they supply us with an 'intentEntry' signature, and then + // we relay the deposit as a meta-transaction on their behalf to + // the intent entrypoint contract. + // + // there will also be a deposit transaction to the origin intent address. + DepositTransactionID uint64 `json:"-" db:"deposit_txn_id"` + DepositTransaction *IntentTransaction `json:"depositTransaction" db:"-"` + // origin intent transaction db reference and runtime type + OriginTransactionID uint64 `json:"-" db:"orig_txn_id"` + OriginTransaction *IntentTransaction `json:"originTransaction" db:"-"` + // destination intent transaction db reference and runtime type + // note: this may be null if the intent did not require a destination txn, + // ie. simple swap on origin chain only with no destination calldata. + DestinationTransactionID *uint64 `json:"-" db:"dest_txn_id,omitempty"` + DestinationTransaction *IntentTransaction `json:"destinationTransaction,omitempty" db:"-"` + // timestamp when the intent receipt was updated, usually from status change + UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at,omitempty"` + // timestamp of when the intent receipt was created + CreatedAt *time.Time `json:"createdAt,omitempty" db:"created_at,omitempty"` +} + +// IntentTransaction represents a single transaction within an intent's execution flow. +// +// db table: 'intent_transactions' +type IntentTransaction struct { + // Auto-incrementing id used for table pk + ID uint64 `json:"-" db:"id,omitempty"` + // we don't return it in the json, no need. its internal. + // Intent id this transaction is associated with + IntentID prototyp.Hash `json:"intentId" db:"intent_id"` + // Chain where this transaction executes + ChainID uint64 `json:"chainId" db:"chain_id"` + Type TransactionType `json:"type" db:"type"` + Context TransactionContext `json:"context" db:"context"` + FromAddress prototyp.Hash `json:"fromAddress" db:"from_address"` + // ie. originIntentAddress, TrailsRouter, or TrailsIntentEntrypoint + ToAddress prototyp.Hash `json:"toAddress" db:"to_address"` + // Token being transferred in this transaction + TokenAddress prototyp.Hash `json:"tokenAddress" db:"token_address"` + // Amount transferred in this transaction + TokenAmount prototyp.BigInt `json:"tokenAmount" db:"token_amount"` + Calldata prototyp.Hash `json:"-" db:"calldata,omitempty"` + // for user-sent deposit txn, this will be null + MetaTxnID prototyp.Hash `json:"metaTxnId,omitempty" db:"meta_txn_id,omitempty"` + MetaTxnFeeQuote *string `json:"metaTxnFeeQuote" db:"fee_quote,omitempty"` + Precondition *TransactionPrecondition `json:"precondition" db:"precondition,omitempty"` + // Gasless deposit metadata (nullable) + DepositIntentEntry *DepositIntentEntry `json:"depositIntentEntry" db:"deposit_intent_entry,omitempty"` + // Transaction hash received from the relayer after it's mined. + TxnHash prototyp.Hash `json:"txnHash,omitempty" db:"txn_hash,omitempty"` + // On-chain block timestamp when txn was mined + TxnMinedAt *time.Time `json:"txnMinedAt" db:"txn_mined_at,omitempty"` + // Status of this specific transaction + Status TransactionStatus `json:"status" db:"status"` + StatusReason *string `json:"statusReason" db:"status_reason"` + UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at,omitempty"` + CreatedAt *time.Time `json:"createdAt,omitempty" db:"created_at,omitempty"` +} + +// IntentProviderQuote represents the quotes from the underlining providers for both +// the swap and/or bridge external providers. If we do both a swap and a bridge, then +// the total amount is the sum of both providers. The 'quoteProvider' enum, will indiciate +// multiple providers in that case. +// NOTE: previously named IntentQuote +type IntentProviderQuote struct { + QuoteProvider string `json:"quoteProvider"` + QuoteProviderRequestID string `json:"quoteProviderRequestId"` + QuoteProviderFeeUSD float64 `json:"quoteProviderFeeUsd"` + FromAmount prototyp.BigInt `json:"fromAmount"` + FromAmountMin prototyp.BigInt `json:"fromAmountMin"` + ToAmount prototyp.BigInt `json:"toAmount"` + ToAmountMin prototyp.BigInt `json:"toAmountMin"` + MaxSlippage float64 `json:"maxSlippage"` + PriceImpact float64 `json:"priceImpact"` + PriceImpactUSD float64 `json:"priceImpactUsd"` +} + +// NOTE: previously named TrailsFee +type IntentFees struct { + // gas fees on origin and destination chains + OriginGas *IntentTransactionGasFee `json:"originGas"` + DestinationGas *IntentTransactionGasFee `json:"destinationGas"` + // provider fees (swap + bridge + trails) + Provider *IntentProviderFees `json:"provider"` + // total fees including provider fees + gas fees + FeeTokenAddress prototyp.Hash `json:"feeTokenAddress"` + FeeTokenTotal prototyp.BigInt `json:"feeTokenTotal"` + TotalFeeAmount prototyp.BigInt `json:"totalFeeAmount"` + TotalFeeUSD float64 `json:"totalFeeUsd"` +} + +// ProviderFees is the swap+bridge provider fee, trails fee summary and totals +// NOTE: previously named the CrossChainFee +type IntentProviderFees struct { + // quote provider fee component (swap and/or bridge) + // TODO: what is the providerFee token denomination? is it always origin token..? I think no. + QuoteProvider string `json:"quoteProvider"` + QuoteProviderFee prototyp.BigInt `json:"quoteProviderFee"` + QuoteProviderFeeUSD float64 `json:"quoteProviderFeeUsd"` + // trails provider fee component, denominated in the origin token + TrailsFee prototyp.BigInt `json:"trailsFee"` + TrailsFeeUSD float64 `json:"trailsFeeUsd"` + // total provider fees, aka, swap + bridge + trails fees + // TODO: what is this denomination? is it always origin token..? ... + // TODO: rename to totalFees ? and totalFeesUsd ? .. + TotalFeeAmount prototyp.BigInt `json:"totalFeeAmount"` + TotalFeeUSD float64 `json:"totalFeeUsd"` +} + +// NOTE: previously named ChainExecuteQuote +type IntentTransactionGasFee struct { + ChainID uint64 `json:"chainId"` + TotalGasLimit prototyp.BigInt `json:"totalGasLimit"` + GasPrice prototyp.BigInt `json:"gasPrice"` + NativeTokenSymbol string `json:"nativeTokenSymbol"` + NativeTokenPriceUSD *float64 `json:"nativeTokenPriceUsd"` + ChainGasUsageStatus ChainGasUsageStatus `json:"chainGasUsageStatus"` + TotalFeeAmount prototyp.BigInt `json:"totalFeeAmount"` + TotalFeeUSD float64 `json:"totalFeeUsd"` + // sequence relayer gas fee quotes + // AKA, meta transaction gas fee quotes for pre-sponsored + MetaTxnFeeDetails *MetaTxnFeeDetails `json:"metaTxnFeeDetails"` + // metaTxnGasQuote is like a voucher for gas sponsorship from the relayer + MetaTxnGasQuote string `json:"metaTxnGasQuote"` +} + +type MetaTxnFeeDetails struct { + MetaTxnID string `json:"metaTxnId"` + EstimatedGasLimit prototyp.BigInt `json:"estimatedGasLimit"` + // TODO: whats up with this name? + FeeNative prototyp.BigInt `json:"feeNative"` +} + +// IntentSummary represents a summary view of an intent for listing purposes. +type IntentSummary struct { + ID uint64 `json:"-"` + // intent deterministic id + IntentID prototyp.Hash `json:"intentId"` + // intent status + Status IntentStatus `json:"status"` + // intent owner / main signer + OwnerAddress prototyp.Hash `json:"ownerAddress"` + // origin and destination chain ids + OriginChainID uint64 `json:"originChainId"` + // - originChainMetadata: ChainMetadata + DestinationChainID uint64 `json:"destinationChainId"` + // origin and destination intent addresses + OriginIntentAddress prototyp.Hash `json:"originIntentAddress"` + DestinationIntentAddress prototyp.Hash `json:"destinationIntentAddress"` + // deposit transfer transaction hash and status, which is the init + // transfer from the owner / main signer into the origin intent + // address on the origin chain id + DepositTransactionHash *prototyp.Hash `json:"depositTransactionHash"` + DepositTransactionStatus TransactionStatus `json:"depositTransactionStatus"` + // origin and destination execution transaction hashes and statuses + OriginTransactionHash *prototyp.Hash `json:"originTransactionHash"` + OriginTransactionStatus TransactionStatus `json:"originTransactionStatus"` + DestinationTransactionHash *prototyp.Hash `json:"destinationTransactionHash"` + DestinationTransactionStatus TransactionStatus `json:"destinationTransactionStatus"` + OriginTokenAddress prototyp.Hash `json:"originTokenAddress"` + OriginTokenAmount prototyp.BigInt `json:"originTokenAmount"` + OriginTokenMetadata *TokenMetadata `json:"originTokenMetadata"` + DestinationTokenAddress prototyp.Hash `json:"destinationTokenAddress"` + DestinationTokenAmount prototyp.BigInt `json:"destinationTokenAmount"` + DestinationTokenMetadata *TokenMetadata `json:"destinationTokenMetadata"` + // timestamp when the intent receipt was updated, usually from status change + UpdatedAt *time.Time `json:"updatedAt,omitempty"` + // timestamp of when the intent receipt was created + CreatedAt time.Time `json:"createdAt"` +} + +type ChainMetadata struct { + ChainID uint64 `json:"chainId"` + Name string `json:"name"` + LogoURI string `json:"logoUri,omitempty"` + Testnet *bool `json:"testnet,omitempty"` +} + +type TokenMetadata struct { + ChainID uint64 `json:"chainId"` + TokenAddress prototyp.Hash `json:"tokenAddress"` + Name string `json:"name"` + Symbol string `json:"symbol"` + Decimals *uint8 `json:"decimals,omitempty"` + LogoURI string `json:"logoUri,omitempty"` +} + +type Token struct { + ChainID uint64 `json:"chainId"` + TokenAddress prototyp.Hash `json:"tokenAddress"` + TokenSymbol *string `json:"tokenSymbol,omitempty"` +} + +type TokenPrice struct { + Token *Token `json:"token"` + PriceUSD *float64 `json:"priceUsd"` + // - price?: float64 + // - currency: string + UpdatedAt time.Time `json:"updatedAt"` +} + +type CCTPTransfer struct { + ID string `json:"id"` + SourceTxHash string `json:"sourceTxHash"` + SourceChainID uint64 `json:"sourceChainId"` + DestinationChainID uint64 `json:"destinationChainId"` + Message string `json:"message"` + Attestation string `json:"attestation"` + // TODO use an enum instead? + Status string `json:"status"` + UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at,omitempty"` + CreatedAt *time.Time `json:"createdAt,omitempty" db:"created_at,omitempty"` +} + +type GasFeeOptions struct { + GasEstimate *GasEstimate `json:"gasEstimate"` + FeeOptions []*FeeOption `json:"feeOptions"` + ExpiresAt time.Time `json:"expiresAt"` + FeeCollectorAddress prototyp.Hash `json:"feeCollectorAddress"` +} + +type GasEstimate struct { + TotalGas uint64 `json:"totalGas"` + GasPrice string `json:"gasPrice"` + NativeCost string `json:"nativeCost"` + NativeCostUSD float64 `json:"nativeCostUsd"` +} + +type FeeOption struct { + // if value is 0x000...000 or 0xeee...eeee, it is native token + TokenAddress prototyp.Hash `json:"tokenAddress"` + TokenSymbol string `json:"tokenSymbol"` + TokenDecimals uint8 `json:"tokenDecimals"` + Amount prototyp.BigInt `json:"amount"` + AmountUSD float64 `json:"amountUsd"` + FeeCollectorAddress prototyp.Hash `json:"feeCollectorAddress"` + // - isNative: bool + // - supportsPermit: bool + Is2612 bool `json:"is2612"` +} + +// DepositSignature contains all gasless deposit signature parameters for ExecuteIntent +type DepositSignature struct { + // Required: EIP-712 TrailsIntent signature (hex) + IntentSignature prototyp.Hash `json:"intentSignature"` + // Optional: EIP-2612 permit signature (hex, if token approval needed) + PermitSignature *prototyp.Hash `json:"permitSignature"` + // Optional: Permit expiration timestamp + PermitDeadline *uint64 `json:"permitDeadline"` + // Optional: Permit amount (usually max uint256) + PermitAmount prototyp.BigInt `json:"permitAmount"` + // Required: Selected fee option from gasFeeOptions + SelectedGasFeeOption *FeeOption `json:"selectedGasFeeOption"` + // Required: User nonce from Intent Entrypoint contract (fetched fresh at execute time) + UserNonce uint64 `json:"userNonce"` + // Required: Intent deadline timestamp + Deadline uint64 `json:"deadline"` +} + +// DepositIntentEntry contains gasless deposit metadata stored in JSONB +type DepositIntentEntry struct { + // EIP-712 TrailsIntent signature (hex) + IntentSignature prototyp.Hash `json:"intentSignature"` + // EIP-2612 permit signature (hex, if approval needed) + PermitSignature *prototyp.Hash `json:"permitSignature"` + // Permit expiration timestamp + PermitDeadline *uint64 `json:"permitDeadline"` + // Permit amount (usually max uint256) + PermitAmount prototyp.BigInt `json:"permitAmount"` + // Fee amount in deposit token + FeeAmount prototyp.BigInt `json:"feeAmount"` + // Fee token address (same as deposit token) + FeeToken prototyp.Hash `json:"feeToken"` + // Address that receives the fee + FeeCollector prototyp.Hash `json:"feeCollector"` + // User nonce from Intent Entrypoint contract (provided by client) + UserNonce uint64 `json:"userNonce"` + // Intent deadline timestamp + Deadline uint64 `json:"deadline"` +} + +type TrailsAddressOverrides struct { + SequenceWalletFactoryAddress *string `json:"sequenceWalletFactoryAddress"` + SequenceWalletMainModuleAddress *string `json:"sequenceWalletMainModuleAddress"` + SequenceWalletMainModuleUpgradableAddress *string `json:"sequenceWalletMainModuleUpgradableAddress"` + SequenceWalletGuestModuleAddress *string `json:"sequenceWalletGuestModuleAddress"` + SequenceWalletUtilsAddress *string `json:"sequenceWalletUtilsAddress"` +} + +type ChainInfo struct { + // id is the chain id + ID uint64 `json:"id"` + // id is the chain full name + Name string `json:"name"` + // tokenName/Symbol/Decimals are the native currency details + TokenName string `json:"tokenName"` + TokenSymbol string `json:"tokenSymbol"` + TokenDecimals uint8 `json:"tokenDecimals"` + // is testnet flag informs whether the chain is a testnet + IsTestnet bool `json:"isTestnet"` + // supportsBridging indicates whether the chain supports bridging operations + SupportsBridging bool `json:"supportsBridging"` + // additional metadata + LogoURI string `json:"logoUri"` + BlockExplorerURL string `json:"blockExplorerUrl"` +} + +type TokenInfo struct { + ChainID uint64 `json:"chainId"` + Address prototyp.Hash `json:"address"` + Name string `json:"name"` + Symbol string `json:"symbol"` + Decimals uint8 `json:"decimals"` + // supportsBridging indicates whether the chain supports bridging operations + SupportsBridging bool `json:"supportsBridging"` + // additional metadata + LogoURI string `json:"logoUri"` +} + +type SortOrder uint32 + +const ( + SortOrder_DESC SortOrder = 0 + SortOrder_ASC SortOrder = 1 +) + +var SortOrder_name = map[uint32]string{ + 0: "DESC", + 1: "ASC", +} + +var SortOrder_value = map[string]uint32{ + "DESC": 0, + "ASC": 1, +} + +func (x SortOrder) String() string { + return SortOrder_name[uint32(x)] +} + +func (x SortOrder) MarshalText() ([]byte, error) { + return []byte(SortOrder_name[uint32(x)]), nil +} + +func (x *SortOrder) UnmarshalText(b []byte) error { + *x = SortOrder(SortOrder_value[string(b)]) + return nil +} + +func (x *SortOrder) Is(values ...SortOrder) bool { + if x == nil { + return false + } + for _, v := range values { + if *x == v { + return true + } + } + return false +} + +type Version struct { + WebrpcVersion string `json:"webrpcVersion"` + SchemaVersion string `json:"schemaVersion"` + SchemaHash string `json:"schemaHash"` + AppVersion string `json:"appVersion"` +} + +type RuntimeStatus struct { + // overall status, true/false + HealthOK bool `json:"healthOK"` + StartTime time.Time `json:"startTime"` + Uptime uint64 `json:"uptime"` + Ver string `json:"ver"` + Branch string `json:"branch"` + CommitHash string `json:"commitHash"` + Runnables interface{} `json:"runnables"` + Services []*ServiceStatus `json:"services"` +} + +type ServiceStatus struct { + Name string `json:"name"` + Healthy bool `json:"healthy"` + Error string `json:"error,omitempty"` + Latency string `json:"latency"` +} + +// QuoteIntent accepts an intent request from the user/app and returns +// a fully formed/quoted/solved and executable Intent. +// +// NOTE: we use the IntentRequest directly for the QuoteIntent request type. +// And do not create a QuoteIntentRequest additional type, as its unnecessary. +type QuoteIntentResponse struct { + Intent *Intent `json:"intent"` + GasFeeOptions *GasFeeOptions `json:"gasFeeOptions"` +} + +// CommitIntent accepts a fully formed Intent and commits it for execution. CommitIntent +// is called by clients to confirm they wish to commit to the intent and will execute it. +// We return the intent ID for tracking. +type CommitIntentRequest struct { + Intent *Intent `json:"intent"` +} + +type CommitIntentResponse struct { + IntentID prototyp.Hash `json:"intentId"` +} + +// ExecuteIntent relays the intent execution request to the intent execution engine. +// Clients provide the transfer transaction hash that executed the transfer to the +// intent origin address. +type ExecuteIntentRequest struct { + IntentID prototyp.Hash `json:"intentId"` + // Traditional deposit: provide transaction hash + DepositTransactionHash prototyp.Hash `json:"depositTransactionHash"` + // Gasless deposit: provide signatures (mutually exclusive with depositTransactionHash) + DepositSignature *DepositSignature `json:"depositSignature"` +} + +type ExecuteIntentResponse struct { + IntentID prototyp.Hash `json:"intentId"` + IntentStatus IntentStatus `json:"intentStatus"` +} + +// GetIntentReceipt returns the current receipt/status of an intent immediately. +// If you are waiting for a intent to complete, use WaitIntentReceipt. +type GetIntentReceiptRequest struct { + IntentID prototyp.Hash `json:"intentId"` +} + +type GetIntentReceiptResponse struct { + IntentReceipt *IntentReceipt `json:"intentReceipt"` +} + +// WaitIntentReceipt will block until the intent reaches a terminal status or timeout. +type WaitIntentReceiptRequest struct { + IntentID prototyp.Hash `json:"intentId"` + // lastReceiptStates is the previously returned status array from a prior + // WaitIntentReceipt call. This allows the server to only return + // updates since the last call, allowing for more efficient polling. + LastReceiptStates []TransactionStatus `json:"lastReceiptStates"` +} + +type WaitIntentReceiptResponse struct { + IntentReceipt *IntentReceipt `json:"intentReceipt"` + // receiptStates is an array of key statuses within the intentReceipt object + // for all sub intent transactions.this can be passed back to subsequent + // WaitIntentReceipt calls to as a form of a stateless cursor to only + // get updates since last call. + ReceiptStates []TransactionStatus `json:"receiptStates"` + // done flag informs a client whether the wait completed successfully (true) or timed out (false). + // as the clients will keep polling until done is true. NOTE: done does not indicate success, + // it just indicates the execution is done, to check status see intentReceipt.status. + Done bool `json:"done"` +} + +// GetIntent queries the database for solved and committed 'intents' +type GetIntentRequest struct { + IntentID prototyp.Hash `json:"intentId"` +} + +type GetIntentResponse struct { + Intent *Intent `json:"intent"` +} + +// SearchIntents searches past intents based on filters. +// We do not return 'solved' status intents here, only committed/executed/failed/succeeded. +type SearchIntentsRequest struct { + ByIntentID prototyp.Hash `json:"byIntentId"` + ByProjectID uint64 `json:"byProjectId"` + ByTransactionHash prototyp.Hash `json:"byTransactionHash"` + ByOwnerAddress prototyp.Hash `json:"byOwnerAddress"` + ByOriginIntentAddress prototyp.Hash `json:"byOriginIntentAddress"` + ByDestinationIntentAddress prototyp.Hash `json:"byDestinationIntentAddress"` +} + +type SearchIntentsResponse struct { + // NOTE: we intentionally do not return a page here, and limit it to 10 latest results + Intents []*Intent `json:"intents"` +} + +// GetIntentTransactionHistory returns the discrete transactions related to an intent. +type GetIntentTransactionHistoryRequest struct { + // cursor paging parameters + Page *Page `json:"page"` + // optional project id scope filter + ByProjectID uint64 `json:"byProjectId"` + ByOwnerAddress prototyp.Hash `json:"byOwnerAddress"` +} + +type GetIntentTransactionHistoryResponse struct { + Intents []*IntentSummary `json:"intents"` + NextPage *Page `json:"nextPage"` +} + +type GetTokenPricesRequest struct { + Tokens []*Token `json:"tokens"` +} + +type GetTokenPricesResponse struct { + TokenPrices []*TokenPrice `json:"tokenPrices"` +} + +type GetChainsRequest struct { + // TODO: use enum eventually.. + RouteProvider *string `json:"routeProvider"` +} + +type GetChainsResponse struct { + Chains []*ChainInfo `json:"chains"` +} + +type GetExactOutputRoutesRequest struct { + DestinationChainId uint64 `json:"destinationChainId"` + DestinationTokenAddress prototyp.Hash `json:"destinationTokenAddress"` + // optionally passed to filter results to only tokens from this origin chain + OriginChainId *uint64 `json:"originChainId"` + // optionally passed to filter results to only this specific origin token + OriginTokenAddress prototyp.Hash `json:"originTokenAddress"` + // owner address is used to filter tokens the owner has a balance on. + // + // if unspecified, then we filter origin tokens based on popular options + // as is useful for a qr code deposit flow. + OwnerAddress prototyp.Hash `json:"ownerAddress"` +} + +type GetExactOutputRoutesResponse struct { + Tokens []*TokenInfo `json:"tokens"` +} + +type GetExactInputRoutesRequest struct { + OriginChainId uint64 `json:"originChainId"` + OriginTokenAddress prototyp.Hash `json:"originTokenAddress"` + // optionally passed to filter results + DestinationChainId *uint64 `json:"destinationChainId"` + DestinationTokenAddress prototyp.Hash `json:"destinationTokenAddress"` +} + +type GetExactInputRoutesResponse struct { + Tokens []*TokenInfo `json:"tokens"` +} + +type GetPopularTokensRequest struct { + // all of these are optional..... + // TODO: still thinking about filters + RouteProvider *string `json:"routeProvider"` + Limit *uint32 `json:"limit"` + ChainIds []uint64 `json:"chainIds"` +} + +type GetPopularTokensResponse struct { + Tokens []*TokenInfo `json:"tokens"` +} + +type SearchTokensRequest struct { + // name, symbol, address + Query string `json:"query"` + RouteProvider *string `json:"routeProvider"` + Limit *uint32 `json:"limit"` + ChainIds []uint64 `json:"chainIds"` +} + +type SearchTokensResponse struct { + Tokens []*TokenInfo `json:"tokens"` +} + +// Page represents a results page. This can be used both to request a page and +// to store the state of a page. +type Page struct { + // Cursor: column to compare before/after to + Column *string `json:"column,omitempty"` + // Cursor: return column < before - include to get previous page + Before *uint64 `json:"before,omitempty"` + // Cursor: return column > after - include to get next page + After *uint64 `json:"after,omitempty"` + // Sorting filter + Sort []*SortBy `json:"sort,omitempty"` + // Number of items per page + PageSize *uint32 `json:"pageSize,omitempty"` + // Indicates if there are more results available + More *bool `json:"more,omitempty"` +} + +type SortBy struct { + Column string `json:"column"` + Order SortOrder `json:"order"` +} + +// +// Client +// + +const TrailsPathPrefix = "/rpc/Trails/" + +type trailsClient struct { + client HTTPClient + urls [18]string +} + +func NewTrailsClient(addr string, client HTTPClient) TrailsClient { + prefix := urlBase(addr) + TrailsPathPrefix + urls := [18]string{ + prefix + "Ping", + prefix + "Version", + prefix + "RuntimeStatus", + prefix + "Clock", + prefix + "QuoteIntent", + prefix + "CommitIntent", + prefix + "ExecuteIntent", + prefix + "WaitIntentReceipt", + prefix + "GetIntentReceipt", + prefix + "GetIntent", + prefix + "SearchIntents", + prefix + "GetIntentTransactionHistory", + prefix + "GetChains", + prefix + "GetExactOutputRoutes", + prefix + "GetExactInputRoutes", + prefix + "GetPopularTokens", + prefix + "SearchTokens", + prefix + "GetTokenPrices", + } + return &trailsClient{ + client: client, + urls: urls, + } +} + +func (c *trailsClient) Ping(ctx context.Context) (bool, error) { + out := struct { + Ret0 bool `json:"status"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[0], nil, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) Version(ctx context.Context) (*Version, error) { + out := struct { + Ret0 *Version `json:"version"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[1], nil, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) RuntimeStatus(ctx context.Context) (*RuntimeStatus, error) { + out := struct { + Ret0 *RuntimeStatus `json:"status"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[2], nil, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) Clock(ctx context.Context) (time.Time, error) { + out := struct { + Ret0 time.Time `json:"serverTime"` + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[3], nil, &out) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) QuoteIntent(ctx context.Context, quoteIntentRequest QuoteIntentRequest) (*QuoteIntentResponse, error) { + out := struct { + Ret0 *QuoteIntentResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[4], quoteIntentRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) CommitIntent(ctx context.Context, commitIntentRequest CommitIntentRequest) (*CommitIntentResponse, error) { + out := struct { + Ret0 *CommitIntentResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[5], commitIntentRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) ExecuteIntent(ctx context.Context, executeIntentRequest ExecuteIntentRequest) (*ExecuteIntentResponse, error) { + out := struct { + Ret0 *ExecuteIntentResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[6], executeIntentRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) WaitIntentReceipt(ctx context.Context, waitIntentReceiptRequest WaitIntentReceiptRequest) (*WaitIntentReceiptResponse, error) { + out := struct { + Ret0 *WaitIntentReceiptResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[7], waitIntentReceiptRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) GetIntentReceipt(ctx context.Context, getIntentReceiptRequest GetIntentReceiptRequest) (*GetIntentReceiptResponse, error) { + out := struct { + Ret0 *GetIntentReceiptResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[8], getIntentReceiptRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) GetIntent(ctx context.Context, getIntentRequest GetIntentRequest) (*GetIntentResponse, error) { + out := struct { + Ret0 *GetIntentResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[9], getIntentRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) SearchIntents(ctx context.Context, searchIntentsRequest SearchIntentsRequest) (*SearchIntentsResponse, error) { + out := struct { + Ret0 *SearchIntentsResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[10], searchIntentsRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) GetIntentTransactionHistory(ctx context.Context, getIntentTransactionHistoryRequest GetIntentTransactionHistoryRequest) (*GetIntentTransactionHistoryResponse, error) { + out := struct { + Ret0 *GetIntentTransactionHistoryResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[11], getIntentTransactionHistoryRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) GetChains(ctx context.Context, getChainsRequest GetChainsRequest) (*GetChainsResponse, error) { + out := struct { + Ret0 *GetChainsResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[12], getChainsRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) GetExactOutputRoutes(ctx context.Context, getExactOutputRoutesRequest GetExactOutputRoutesRequest) (*GetExactOutputRoutesResponse, error) { + out := struct { + Ret0 *GetExactOutputRoutesResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[13], getExactOutputRoutesRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) GetExactInputRoutes(ctx context.Context, getExactInputRoutesRequest GetExactInputRoutesRequest) (*GetExactInputRoutesResponse, error) { + out := struct { + Ret0 *GetExactInputRoutesResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[14], getExactInputRoutesRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) GetPopularTokens(ctx context.Context, getPopularTokensRequest GetPopularTokensRequest) (*GetPopularTokensResponse, error) { + out := struct { + Ret0 *GetPopularTokensResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[15], getPopularTokensRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) SearchTokens(ctx context.Context, searchTokensRequest SearchTokensRequest) (*SearchTokensResponse, error) { + out := struct { + Ret0 *SearchTokensResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[16], searchTokensRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +func (c *trailsClient) GetTokenPrices(ctx context.Context, getTokenPricesRequest GetTokenPricesRequest) (*GetTokenPricesResponse, error) { + out := struct { + Ret0 *GetTokenPricesResponse + }{} + + resp, err := doHTTPRequest(ctx, c.client, c.urls[17], getTokenPricesRequest, &out.Ret0) + if resp != nil { + cerr := resp.Body.Close() + if err == nil && cerr != nil { + err = ErrWebrpcRequestFailed.WithCausef("failed to close response body: %w", cerr) + } + } + + return out.Ret0, err +} + +// +// Client helpers +// + +// HTTPClient is the interface used by generated clients to send HTTP requests. +// It is fulfilled by *(net/http).Client, which is sufficient for most users. +// Users can provide their own implementation for special retry policies. +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +// urlBase helps ensure that addr specifies a scheme. If it is unparsable +// as a URL, it returns addr unchanged. +func urlBase(addr string) string { + // If the addr specifies a scheme, use it. If not, default to + // http. If url.Parse fails on it, return it unchanged. + url, err := url.Parse(addr) + if err != nil { + return addr + } + if url.Scheme == "" { + url.Scheme = "http" + } + return url.String() +} + +// newRequest makes an http.Request from a client, adding common headers. +func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType string) (*http.Request, error) { + req, err := http.NewRequestWithContext(ctx, "POST", url, reqBody) + if err != nil { + return nil, err + } + req.Header.Set("Accept", contentType) + req.Header.Set("Content-Type", contentType) + req.Header.Set(WebrpcHeader, WebrpcHeaderValue) + if headers, ok := HTTPRequestHeaders(ctx); ok { + for k := range headers { + for _, v := range headers[k] { + req.Header.Add(k, v) + } + } + } + return req, nil +} + +// doHTTPRequest is common code to make a request to the remote service. +func doHTTPRequest(ctx context.Context, client HTTPClient, url string, in, out interface{}) (*http.Response, error) { + reqBody, err := json.Marshal(in) + if err != nil { + return nil, ErrWebrpcRequestFailed.WithCausef("failed to marshal JSON body: %w", err) + } + if err = ctx.Err(); err != nil { + return nil, ErrWebrpcRequestFailed.WithCausef("aborted because context was done: %w", err) + } + + req, err := newRequest(ctx, url, bytes.NewBuffer(reqBody), "application/json") + if err != nil { + return nil, ErrWebrpcRequestFailed.WithCausef("could not build request: %w", err) + } + + resp, err := client.Do(req) + if err != nil { + return nil, ErrWebrpcRequestFailed.WithCause(err) + } + + if resp.StatusCode != 200 { + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, ErrWebrpcBadResponse.WithCausef("failed to read server error response body: %w", err) + } + + var rpcErr WebRPCError + if err := json.Unmarshal(respBody, &rpcErr); err != nil { + return nil, ErrWebrpcBadResponse.WithCausef("failed to unmarshal server error: %w", err) + } + if rpcErr.Cause != "" { + rpcErr.cause = errors.New(rpcErr.Cause) + } + return nil, rpcErr + } + + if out != nil { + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, ErrWebrpcBadResponse.WithCausef("failed to read response body: %w", err) + } + + err = json.Unmarshal(respBody, &out) + if err != nil { + return nil, ErrWebrpcBadResponse.WithCausef("failed to unmarshal JSON response body: %w", err) + } + } + + return resp, nil +} + +func WithHTTPRequestHeaders(ctx context.Context, h http.Header) (context.Context, error) { + if _, ok := h["Accept"]; ok { + return nil, errors.New("provided header cannot set Accept") + } + if _, ok := h["Content-Type"]; ok { + return nil, errors.New("provided header cannot set Content-Type") + } + + copied := make(http.Header, len(h)) + for k, vv := range h { + if vv == nil { + copied[k] = nil + continue + } + copied[k] = make([]string, len(vv)) + copy(copied[k], vv) + } + + return context.WithValue(ctx, HTTPClientRequestHeadersCtxKey, copied), nil +} + +func HTTPRequestHeaders(ctx context.Context) (http.Header, bool) { + h, ok := ctx.Value(HTTPClientRequestHeadersCtxKey).(http.Header) + return h, ok +} + +// +// Webrpc helpers +// + +type contextKey struct { + name string +} + +func (k *contextKey) String() string { + return "webrpc context value " + k.name +} + +var ( + HTTPClientRequestHeadersCtxKey = &contextKey{"HTTPClientRequestHeaders"} +) + +// PtrTo is a useful helper when constructing values for optional fields. +func PtrTo[T any](v T) *T { return &v } + +// +// BigInt helpers +// + +// BigInt is an alias of big.Int with custom JSON (decimal string) encoding. +type BigInt big.Int + +func NewBigInt(v int64) BigInt { var bi big.Int; bi.SetInt64(v); return BigInt(bi) } + +// AsInt exposes the underlying *big.Int. +func (b *BigInt) AsInt() *big.Int { return (*big.Int)(b) } + +// String returns the decimal string representation of the BigInt. +func (b BigInt) String() string { return b.AsInt().String() } + +// MarshalText implements encoding.TextMarshaler. +func (b BigInt) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("\"%s\"", b.String())), nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (b *BigInt) UnmarshalText(text []byte) error { + if len(text) == 0 { + return nil + } + if len(text) == 4 && text[0] == 'n' && string(text) == "null" { + return nil + } + for _, c := range text { + if c == ' ' || c == '\t' || c == '\n' || c == '\r' { + return fmt.Errorf("BigInt.UnmarshalText: unexpected whitespace in %q", text) + } + } + var digits []byte + if text[0] == '-' || (text[0] >= '0' && text[0] <= '9') { + digits = text + } else { + if len(text) < 2 || text[0] != '"' || text[len(text)-1] != '"' { + return fmt.Errorf("BigInt.UnmarshalText: unsupported format %q", text) + } + digits = text[1 : len(text)-1] + } + i, ok := big.NewInt(0).SetString(string(digits), 10) + if !ok { + return fmt.Errorf("BigInt.UnmarshalText: failed to parse %q", text) + } + *b = BigInt(*i) + return nil +} + +// MarshalJSON implements json.Marshaler +func (b BigInt) MarshalJSON() ([]byte, error) { + return b.MarshalText() +} + +// UnmarshalJSON implements json.Unmarshaler +func (b *BigInt) UnmarshalJSON(text []byte) error { + if string(text) == "null" { + return nil + } + return b.UnmarshalText(text) +} + +// MarshalBinary implements encoding.BinaryMarshaler. The first byte is the sign byte +// to represent positive or negative numbers. +func (b BigInt) MarshalBinary() ([]byte, error) { + bytes := b.AsInt().Bytes() + out := make([]byte, len(bytes)+1) + copy(out[1:], bytes) + if b.AsInt().Sign() < 0 { + // Prepend a sign byte (0xFF for negative) + out[0] = 0xFF + } else { + // For zero or positive numbers, prepend 0x00 + out[0] = 0x00 + } + return out, nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. The first byte is the sign byte +// to represent positive or negative numbers. +func (b *BigInt) UnmarshalBinary(buff []byte) error { + if len(buff) == 0 { + *b = BigInt(*big.NewInt(0)) + return nil + } + // Extract the sign byte + signByte := buff[0] + i := new(big.Int) + if len(buff) > 1 { + i.SetBytes(buff[1:]) + } + // Apply sign if negative + if signByte == 0xFF { + i.Neg(i) + } + *b = BigInt(*i) + return nil +} + +func (b BigInt) Value() (driver.Value, error) { + return b.String(), nil +} + +func (b *BigInt) Scan(src interface{}) error { + if src == nil { + return nil + } + + var svalue string + switch v := src.(type) { + case string: + svalue = v + case []byte: + svalue = string(v) + default: + return fmt.Errorf("BigInt.Scan: unexpected type %T", src) + } + + // pgx driver returns NeX where N is digits and X is exponent + parts := strings.SplitN(svalue, "e", 2) + + var ok bool + i := &big.Int{} + i, ok = i.SetString(parts[0], 10) + if !ok { + return fmt.Errorf("BigInt.Scan: failed to scan value %q", svalue) + } + + if len(parts) >= 2 { + exp := big.NewInt(0) + exp, ok = exp.SetString(parts[1], 10) + if !ok { + return fmt.Errorf("BigInt.Scan failed to scan exp component %q", svalue) + } + i = i.Mul(i, big.NewInt(1).Exp(big.NewInt(10), exp, nil)) + } + + *b = BigInt(*i) + return nil +} + +// +// Errors +// + +type WebRPCError struct { + Name string `json:"error"` + Code int `json:"code"` + Message string `json:"msg"` + Cause string `json:"cause,omitempty"` + HTTPStatus int `json:"status"` + cause error +} + +var _ error = WebRPCError{} + +func (e WebRPCError) Error() string { + if e.cause != nil { + return fmt.Sprintf("%s %d: %s: %v", e.Name, e.Code, e.Message, e.cause) + } + return fmt.Sprintf("%s %d: %s", e.Name, e.Code, e.Message) +} + +func (e WebRPCError) Is(target error) bool { + if target == nil { + return false + } + if rpcErr, ok := target.(WebRPCError); ok { + return rpcErr.Code == e.Code + } + return errors.Is(e.cause, target) +} + +func (e WebRPCError) Unwrap() error { + return e.cause +} + +func (e WebRPCError) WithCause(cause error) WebRPCError { + err := e + err.cause = cause + err.Cause = cause.Error() + return err +} + +func (e WebRPCError) WithCausef(format string, args ...interface{}) WebRPCError { + cause := fmt.Errorf(format, args...) + err := e + err.cause = cause + err.Cause = cause.Error() + return err +} + +// Deprecated: Use .WithCause() method on WebRPCError. +func ErrorWithCause(rpcErr WebRPCError, cause error) WebRPCError { + return rpcErr.WithCause(cause) +} + +// Webrpc errors +var ( + ErrWebrpcEndpoint = WebRPCError{Code: 0, Name: "WebrpcEndpoint", Message: "endpoint error", HTTPStatus: 400} + ErrWebrpcRequestFailed = WebRPCError{Code: -1, Name: "WebrpcRequestFailed", Message: "request failed", HTTPStatus: 400} + ErrWebrpcBadRoute = WebRPCError{Code: -2, Name: "WebrpcBadRoute", Message: "bad route", HTTPStatus: 404} + ErrWebrpcBadMethod = WebRPCError{Code: -3, Name: "WebrpcBadMethod", Message: "bad method", HTTPStatus: 405} + ErrWebrpcBadRequest = WebRPCError{Code: -4, Name: "WebrpcBadRequest", Message: "bad request", HTTPStatus: 400} + ErrWebrpcBadResponse = WebRPCError{Code: -5, Name: "WebrpcBadResponse", Message: "bad response", HTTPStatus: 500} + ErrWebrpcServerPanic = WebRPCError{Code: -6, Name: "WebrpcServerPanic", Message: "server panic", HTTPStatus: 500} + ErrWebrpcInternalError = WebRPCError{Code: -7, Name: "WebrpcInternalError", Message: "internal error", HTTPStatus: 500} + ErrWebrpcClientAborted = WebRPCError{Code: -8, Name: "WebrpcClientAborted", Message: "request aborted by client", HTTPStatus: 400} + ErrWebrpcStreamLost = WebRPCError{Code: -9, Name: "WebrpcStreamLost", Message: "stream lost", HTTPStatus: 400} + ErrWebrpcStreamFinished = WebRPCError{Code: -10, Name: "WebrpcStreamFinished", Message: "stream finished", HTTPStatus: 200} +) + +// Schema errors +var ( + ErrUnauthorized = WebRPCError{Code: 1000, Name: "Unauthorized", Message: "Unauthorized access", HTTPStatus: 401} + ErrPermissionDenied = WebRPCError{Code: 1001, Name: "PermissionDenied", Message: "Permission denied", HTTPStatus: 403} + ErrSessionExpired = WebRPCError{Code: 1002, Name: "SessionExpired", Message: "Session expired", HTTPStatus: 403} + ErrMethodNotFound = WebRPCError{Code: 1003, Name: "MethodNotFound", Message: "Method not found", HTTPStatus: 404} + ErrRequestConflict = WebRPCError{Code: 1004, Name: "RequestConflict", Message: "Conflict with target resource", HTTPStatus: 409} + ErrAborted = WebRPCError{Code: 1005, Name: "Aborted", Message: "Request aborted", HTTPStatus: 400} + ErrGeoblocked = WebRPCError{Code: 1006, Name: "Geoblocked", Message: "Geoblocked region", HTTPStatus: 451} + ErrRateLimited = WebRPCError{Code: 1007, Name: "RateLimited", Message: "Rate-limited. Please slow down.", HTTPStatus: 429} + ErrProjectNotFound = WebRPCError{Code: 1008, Name: "ProjectNotFound", Message: "Project not found", HTTPStatus: 401} + ErrAccessKeyNotFound = WebRPCError{Code: 1101, Name: "AccessKeyNotFound", Message: "Access key not found", HTTPStatus: 401} + ErrAccessKeyMismatch = WebRPCError{Code: 1102, Name: "AccessKeyMismatch", Message: "Access key mismatch", HTTPStatus: 409} + ErrInvalidOrigin = WebRPCError{Code: 1103, Name: "InvalidOrigin", Message: "Invalid origin for Access Key", HTTPStatus: 403} + ErrInvalidService = WebRPCError{Code: 1104, Name: "InvalidService", Message: "Service not enabled for Access key", HTTPStatus: 403} + ErrUnauthorizedUser = WebRPCError{Code: 1105, Name: "UnauthorizedUser", Message: "Unauthorized user", HTTPStatus: 403} + ErrQuotaExceeded = WebRPCError{Code: 1200, Name: "QuotaExceeded", Message: "Quota request exceeded", HTTPStatus: 429} + ErrQuotaRateLimit = WebRPCError{Code: 1201, Name: "QuotaRateLimit", Message: "Quota rate limit exceeded", HTTPStatus: 429} + ErrNoDefaultKey = WebRPCError{Code: 1300, Name: "NoDefaultKey", Message: "No default access key found", HTTPStatus: 403} + ErrMaxAccessKeys = WebRPCError{Code: 1301, Name: "MaxAccessKeys", Message: "Access keys limit reached", HTTPStatus: 403} + ErrAtLeastOneKey = WebRPCError{Code: 1302, Name: "AtLeastOneKey", Message: "You need at least one Access Key", HTTPStatus: 403} + ErrTimeout = WebRPCError{Code: 1900, Name: "Timeout", Message: "Request timed out", HTTPStatus: 408} + ErrInvalidArgument = WebRPCError{Code: 2000, Name: "InvalidArgument", Message: "Invalid argument", HTTPStatus: 400} + ErrUnexpected = WebRPCError{Code: 2001, Name: "Unexpected", Message: "Unexpected server error", HTTPStatus: 500} + ErrUnavailable = WebRPCError{Code: 2002, Name: "Unavailable", Message: "Unavailable resource", HTTPStatus: 400} + ErrQueryFailed = WebRPCError{Code: 2003, Name: "QueryFailed", Message: "Query failed", HTTPStatus: 400} + ErrIntentStatus = WebRPCError{Code: 2004, Name: "IntentStatus", Message: "Invalid intent status", HTTPStatus: 422} + ErrNotFound = WebRPCError{Code: 8000, Name: "NotFound", Message: "Resource not found", HTTPStatus: 400} + ErrUnsupportedNetwork = WebRPCError{Code: 8008, Name: "UnsupportedNetwork", Message: "Unsupported network", HTTPStatus: 422} + ErrIntentsSkipped = WebRPCError{Code: 7000, Name: "IntentsSkipped", Message: "Intents skipped as client is attempting a transaction that does not require intents", HTTPStatus: 400} + ErrIntentsDisabled = WebRPCError{Code: 9000, Name: "IntentsDisabled", Message: "Intents service is currently unavailable", HTTPStatus: 400} +) + +const WebrpcHeader = "Webrpc" + +const WebrpcHeaderValue = "webrpc@v0.31.2;gen-golang@v0.23.3;trails-api@v1-25.11.21+6e5ef30" + +type WebrpcGenVersions struct { + WebrpcGenVersion string + CodeGenName string + CodeGenVersion string + SchemaName string + SchemaVersion string +} + +func VersionFromHeader(h http.Header) (*WebrpcGenVersions, error) { + if h.Get(WebrpcHeader) == "" { + return nil, fmt.Errorf("header is empty or missing") + } + + versions, err := parseWebrpcGenVersions(h.Get(WebrpcHeader)) + if err != nil { + return nil, fmt.Errorf("webrpc header is invalid: %w", err) + } + + return versions, nil +} + +func parseWebrpcGenVersions(header string) (*WebrpcGenVersions, error) { + versions := strings.Split(header, ";") + if len(versions) < 3 { + return nil, fmt.Errorf("expected at least 3 parts while parsing webrpc header: %v", header) + } + + _, webrpcGenVersion, ok := strings.Cut(versions[0], "@") + if !ok { + return nil, fmt.Errorf("webrpc gen version could not be parsed from: %s", versions[0]) + } + + tmplTarget, tmplVersion, ok := strings.Cut(versions[1], "@") + if !ok { + return nil, fmt.Errorf("tmplTarget and tmplVersion could not be parsed from: %s", versions[1]) + } + + schemaName, schemaVersion, ok := strings.Cut(versions[2], "@") + if !ok { + return nil, fmt.Errorf("schema name and schema version could not be parsed from: %s", versions[2]) + } + + return &WebrpcGenVersions{ + WebrpcGenVersion: webrpcGenVersion, + CodeGenName: tmplTarget, + CodeGenVersion: tmplVersion, + SchemaName: schemaName, + SchemaVersion: schemaVersion, + }, nil +}