diff --git a/codegen/openapi/aggregation-openapi.json b/codegen/openapi/aggregation-openapi.json index 8edf3821..6890eba2 100644 --- a/codegen/openapi/aggregation-openapi.json +++ b/codegen/openapi/aggregation-openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "paths": { - "/v5.2/1/quote": { + "/v6.0/1/quote": { "get": { "operationId": "getQuote", "summary": "Find the best quote to swap with 1inch Router", @@ -12,8 +12,7 @@ "in": "query", "example": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "schema": { - "type": "string", - "x-go-type-skip-optional-pointer": true + "type": "string" } }, { @@ -22,8 +21,7 @@ "in": "query", "example": "0x111111111117dc0aa78b770fa6a738034120c302", "schema": { - "type": "string", - "x-go-type-skip-optional-pointer": true + "type": "string" } }, { @@ -32,8 +30,7 @@ "in": "query", "example": "10000000000000000", "schema": { - "type": "string", - "x-go-type-skip-optional-pointer": true + "type": "string" } }, { @@ -152,6 +149,16 @@ "type": "string", "x-go-type-skip-optional-pointer": true } + }, + { + "name": "excludedProtocols", + "required": false, + "in": "query", + "description": "excluded supported liquidity sources", + "schema": { + "type": "string", + "x-go-type-skip-optional-pointer": true + } } ], "responses": { @@ -181,7 +188,7 @@ ] } }, - "/v5.2/1/swap": { + "/v6.0/1/swap": { "get": { "operationId": "getSwap", "summary": "Generate calldata to swap on 1inch Router", @@ -192,8 +199,7 @@ "in": "query", "example": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "schema": { - "type": "string", - "x-go-type-skip-optional-pointer": true + "type": "string" } }, { @@ -202,8 +208,7 @@ "in": "query", "example": "0x111111111117dc0aa78b770fa6a738034120c302", "schema": { - "type": "string", - "x-go-type-skip-optional-pointer": true + "type": "string" } }, { @@ -212,8 +217,7 @@ "in": "query", "example": "10000000000000000", "schema": { - "type": "string", - "x-go-type-skip-optional-pointer": true + "type": "string" } }, { @@ -222,8 +226,16 @@ "in": "query", "description": "The address that calls the 1inch contract", "schema": { - "type": "string", - "x-go-type-skip-optional-pointer": true + "type": "string" + } + }, + { + "name": "origin", + "required": true, + "in": "query", + "description": "An EOA address that initiate the transaction", + "schema": { + "type": "string" } }, { @@ -235,8 +247,7 @@ "schema": { "minimum": 0, "maximum": 50, - "type": "number", - "x-go-type-skip-optional-pointer": true + "type": "number" } }, { @@ -356,6 +367,16 @@ "x-go-type-skip-optional-pointer": true } }, + { + "name": "excludedProtocols", + "required": false, + "in": "query", + "description": "excluded supported liquidity sources", + "schema": { + "type": "string", + "x-go-type-skip-optional-pointer": true + } + }, { "name": "permit", "required": false, @@ -404,6 +425,16 @@ "type": "boolean", "x-go-type-skip-optional-pointer": true } + }, + { + "name": "usePermit2", + "required": false, + "in": "query", + "description": "Enable this flag in case you did an approval to permit2 smart contract", + "schema": { + "type": "boolean", + "x-go-type-skip-optional-pointer": true + } } ], "responses": { @@ -433,7 +464,7 @@ ] } }, - "/v5.2/1/approve/spender": { + "/v6.0/1/approve/spender": { "get": { "operationId": "getSpender", "summary": "Address of the 1inch Router that is trusted to spend funds for the swap", @@ -455,7 +486,7 @@ ] } }, - "/v5.2/1/approve/transaction": { + "/v6.0/1/approve/transaction": { "get": { "operationId": "getApprove", "summary": "Generate approve calldata to allow 1inch Router to perform a swap", @@ -467,8 +498,7 @@ "example": "0x111111111117dc0aa78b770fa6a738034120c302", "description": "Token address you want to swap", "schema": { - "type": "string", - "x-go-type-skip-optional-pointer": true + "type": "string" } }, { @@ -500,7 +530,7 @@ ] } }, - "/v5.2/1/approve/allowance": { + "/v6.0/1/approve/allowance": { "get": { "operationId": "getAllowance", "summary": "Get the number of tokens that the 1inch Router is allowed to swap", @@ -512,8 +542,7 @@ "example": "0x111111111117dc0aa78b770fa6a738034120c302", "description": "Token address you want to swap", "schema": { - "type": "string", - "x-go-type-skip-optional-pointer": true + "type": "string" } }, { @@ -522,8 +551,7 @@ "in": "query", "description": "Wallet address for which you want to check", "schema": { - "type": "string", - "x-go-type-skip-optional-pointer": true + "type": "string" } } ], @@ -544,7 +572,7 @@ ] } }, - "/v5.2/1/liquidity-sources": { + "/v6.0/1/liquidity-sources": { "get": { "operationId": "getLiquiditySources", "summary": "List of liquidity sources that are available for routing in the 1inch Aggregation Protocol", @@ -566,7 +594,7 @@ ] } }, - "/v5.2/1/tokens": { + "/v6.0/1/tokens": { "get": { "operationId": "getTokens", "summary": "List of tokens that are available for swap in the 1inch Aggregation protocol", @@ -577,7 +605,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TokensResponse" + "type": "object", + "properties": { + "tokens": { + "type": "object", + "properties": { + "0x111111111117dc0aa78b770fa6a738034120c302": { + "$ref": "#/components/schemas/TokenInfo" + } + } + } + } } } } @@ -592,28 +630,13 @@ "info": { "title": "1inch Swap API", "description": "\n

Ethereum Network

\nUsing 1inch Swap API, you can find the best route to exchange assets and make the exchange.\n

\nStep by step:\n1. Lookup addresses of tokens you want to swap, for example ‘0xxx’ , ‘0xxxx’ for DAI -> 1INCH\n2. Check for allowance of 1inch router contract to spend source asset (/approve/allowance)\n3. If necessary, give approval for 1inch router to spend source token (/approve/transaction)\n4. Monitor the best exchange route using (/quote)\n5. When you ready use to perform swap (/swap)", - "version": "5.2", + "version": "6.0", "contact": {} }, "tags": [], "servers": [], "components": { "schemas": { - "TokensResponse": { - "type": "object", - "properties": { - "tokens": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/TokenInfo" - }, - "x-go-type-skip-optional-pointer": true - } - }, - "required": [ - "tokens" - ] - }, "TokenInfo": { "type": "object", "properties": { @@ -679,12 +702,10 @@ "type": "object", "properties": { "fromToken": { - "$ref": "#/components/schemas/TokenInfo", - "x-go-type-skip-optional-pointer": true + "$ref": "#/components/schemas/TokenInfo" }, "toToken": { - "$ref": "#/components/schemas/TokenInfo", - "x-go-type-skip-optional-pointer": true + "$ref": "#/components/schemas/TokenInfo" }, "toAmount": { "type": "string", @@ -829,12 +850,10 @@ "type": "object", "properties": { "fromToken": { - "$ref": "#/components/schemas/TokenInfo", - "x-go-type-skip-optional-pointer": true + "$ref": "#/components/schemas/TokenInfo" }, "toToken": { - "$ref": "#/components/schemas/TokenInfo", - "x-go-type-skip-optional-pointer": true + "$ref": "#/components/schemas/TokenInfo" }, "toAmount": { "type": "string", @@ -857,8 +876,7 @@ "x-go-type-skip-optional-pointer": true }, "tx": { - "$ref": "#/components/schemas/TransactionData", - "x-go-type-skip-optional-pointer": true + "$ref": "#/components/schemas/TransactionData" } }, "required": [ diff --git a/common/wallet.go b/common/wallet.go index 47bd438c..b110714d 100644 --- a/common/wallet.go +++ b/common/wallet.go @@ -27,14 +27,12 @@ type Wallet interface { TokenPermit(cd ContractPermitData) (string, error) TokenPermitDaiLike(cd ContractPermitDataDaiLike) (string, error) - IsEIP1559Applicable() bool - ChainId() int64 - //TokenApprove() + GenerateApproveCallData(addressTo string, amount uint64) (string, error) - // view functions - //TokenBalance() - //TokenAllowance() + TokenAllowance(ctx context.Context, tokenAddress string, spenderAddress string) (*big.Int, error) + IsEIP1559Applicable() bool + ChainId() int64 } type ContractPermitData struct { diff --git a/go.mod b/go.mod index fd432f81..66774b05 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/google/go-querystring v1.1.0 github.com/oapi-codegen/runtime v1.1.1 github.com/stretchr/testify v1.9.0 + golang.org/x/text v0.14.0 ) require ( diff --git a/internal/transaction-builder/transaction_builder_test.go b/internal/transaction-builder/transaction_builder_test.go index 802ec5d7..aa7d8b04 100644 --- a/internal/transaction-builder/transaction_builder_test.go +++ b/internal/transaction-builder/transaction_builder_test.go @@ -229,3 +229,10 @@ func (w *MyWallet) IsEIP1559Applicable() bool { func (w *MyWallet) ChainId() int64 { return w.chainID.Int64() } + +func (w *MyWallet) GenerateApproveCallData(addressTo string, amount uint64) (string, error) { + return "", nil +} +func (w *MyWallet) TokenAllowance(ctx context.Context, tokenAddress string, spenderAddress string) (*big.Int, error) { + return big.NewInt(1), nil +} diff --git a/internal/web3-provider/approve.go b/internal/web3-provider/approve.go new file mode 100644 index 00000000..f6f68638 --- /dev/null +++ b/internal/web3-provider/approve.go @@ -0,0 +1,49 @@ +package web3_provider + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" +) + +func (w Wallet) GenerateApproveCallData(addressTo string, amount uint64) (string, error) { + spenderAddress := common.HexToAddress(addressTo) + + callData, err := w.erc20ABI.Pack("approve", spenderAddress, big.NewInt(int64(amount))) + if err != nil { + return "", fmt.Errorf("failed to pack ABI data: %v", err) + } + + return "0x" + common.Bytes2Hex(callData), nil +} + +func (w Wallet) TokenAllowance(ctx context.Context, tokenAddress string, spenderAddress string) (*big.Int, error) { + token := common.HexToAddress(tokenAddress) + spender := common.HexToAddress(spenderAddress) + + callData, err := w.erc20ABI.Pack("allowance", w.address, spender) + if err != nil { + return nil, fmt.Errorf("failed to pack ABI data: %v", err) + } + + msg := ethereum.CallMsg{ + To: &token, + Data: callData, + } + + result, err := w.ethClient.CallContract(ctx, msg, nil) + if err != nil { + return nil, fmt.Errorf("failed to call contract: %v", err) + } + + var allowance *big.Int + err = w.erc20ABI.UnpackIntoInterface(&allowance, "allowance", result) + if err != nil { + return nil, fmt.Errorf("failed to unpack result: %v", err) + } + + return allowance, nil +} diff --git a/internal/web3-provider/permit2allowance.go b/internal/web3-provider/permit2allowance.go new file mode 100644 index 00000000..5a86a287 --- /dev/null +++ b/internal/web3-provider/permit2allowance.go @@ -0,0 +1,205 @@ +package web3_provider + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/signer/core/apitypes" +) + +type AllowancePermitParams struct { + Token string `json:"token"` + Amount string `json:"amount"` + Expiration string `json:"expiration"` + Spender string `json:"spender"` + SigDeadline string `json:"sigDeadline"` + Permit2Address string +} + +type AllowancePermitDetails struct { + Token string `json:"token"` + Amount string `json:"amount"` + Expiration string `json:"expiration"` + Nonce string `json:"nonce"` +} + +type AllowancePermitSingle struct { + Details AllowancePermitDetails `json:"details"` + Spender string `json:"spender"` + SigDeadline string `json:"sigDeadline"` +} + +func (w Wallet) GetAllowancePermitSingle(ctx context.Context, params AllowancePermitParams) (apitypes.TypedData, error) { + callData, err := w.erc20ABI.Pack("nonce", w.address.Hex(), params.Token, params.Spender) + if err != nil { + return apitypes.TypedData{}, fmt.Errorf("failed to pack allowance call data: %v", err) + } + + permitAddress := common.HexToAddress(params.Permit2Address) + + msg := ethereum.CallMsg{ + To: &permitAddress, + Data: callData, + } + + result, err := w.ethClient.CallContract(ctx, msg, nil) + if err != nil { + return apitypes.TypedData{}, fmt.Errorf("failed to call contract: %v", err) + } + + var nonce *big.Int + err = w.erc20ABI.UnpackIntoInterface(&nonce, "nonce", result) + if err != nil { + return apitypes.TypedData{}, fmt.Errorf("failed to unpack result: %v", err) + } + + // Construct the permit details + d := AllowancePermitSingle{ + Details: AllowancePermitDetails{ + Token: params.Token, + Amount: params.Amount, + Expiration: params.Expiration, + Nonce: nonce.String(), + }, + Spender: params.Spender, + SigDeadline: params.SigDeadline, + } + + permit, err := GetTypedDataAllowancePermitSingle(d, permitAddress, int(w.chainId.Int64())) + if err != nil { + return apitypes.TypedData{}, fmt.Errorf("failed to generate permit: %v", err) + } + + return permit, nil +} + +func GetTypedDataAllowancePermitSingle(permit AllowancePermitSingle, permit2Address common.Address, chainId int) (apitypes.TypedData, error) { + err := validatePermit(permit) + if err != nil { + return apitypes.TypedData{}, err + } + + return apitypes.TypedData{ + Domain: apitypes.TypedDataDomain{ + Name: "Permit2", + ChainId: math.NewHexOrDecimal256(int64(chainId)), + VerifyingContract: permit2Address.Hex(), + }, + Types: map[string][]apitypes.Type{ + "EIP712Domain": { + {Name: "name", Type: "string"}, + {Name: "chainId", Type: "uint256"}, + {Name: "verifyingContract", Type: "address"}, + }, + "PermitSingle": { + {Name: "details", Type: "PermitDetails"}, + {Name: "spender", Type: "address"}, + {Name: "sigDeadline", Type: "uint256"}, + }, + "PermitDetails": { + {Name: "token", Type: "address"}, + {Name: "amount", Type: "uint160"}, + {Name: "expiration", Type: "uint48"}, + {Name: "nonce", Type: "uint48"}, + }, + }, + Message: apitypes.TypedDataMessage{ + "details": apitypes.TypedDataMessage{ + "token": permit.Details.Token, + "amount": permit.Details.Amount, + "expiration": permit.Details.Expiration, + "nonce": permit.Details.Nonce, + }, + "spender": permit.Spender, + "sigDeadline": permit.SigDeadline, + }, + PrimaryType: "PermitSingle", + }, nil +} + +func validatePermit(permit AllowancePermitSingle) error { + var ( + MaxAllowanceTransferAmount = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 160), big.NewInt(1)) + MaxAllowanceExpiration = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 48), big.NewInt(1)) + MaxOrderedNonce = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 48), big.NewInt(1)) + MaxSigDeadline = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) + ) + + nonce, ok := new(big.Int).SetString(permit.Details.Nonce, 10) + if !ok { + return fmt.Errorf("invalid nonce") + } + if nonce.Cmp(MaxOrderedNonce) > 0 { + return fmt.Errorf("NONCE_OUT_OF_RANGE") + } + + amount, ok := new(big.Int).SetString(permit.Details.Amount, 10) + if !ok { + return fmt.Errorf("invalid amount") + } + if amount.Cmp(MaxAllowanceTransferAmount) > 0 { + return fmt.Errorf("AMOUNT_OUT_OF_RANGE") + } + + expiration, ok := new(big.Int).SetString(permit.Details.Expiration, 10) + if !ok { + return fmt.Errorf("invalid expiration") + } + if expiration.Cmp(MaxAllowanceExpiration) > 0 { + return fmt.Errorf("EXPIRATION_OUT_OF_RANGE") + } + + sigDeadline, ok := new(big.Int).SetString(permit.SigDeadline, 10) + if !ok { + return fmt.Errorf("invalid sigDeadline") + } + if sigDeadline.Cmp(MaxSigDeadline) > 0 { + return fmt.Errorf("SIG_DEADLINE_OUT_OF_RANGE") + } + + return nil +} + +func AllowancePermitSingleTypedDataHash(permit AllowancePermitSingle, permit2Address common.Address, chainId int) (string, error) { + typedData, err := GetTypedDataAllowancePermitSingle(permit, permit2Address, chainId) + if err != nil { + return "", err + } + + challengeHash, _, err := apitypes.TypedDataAndHash(typedData) + if err != nil { + return "", fmt.Errorf("error using TypedDataAndHash: %v", err) + } + + return "0x" + common.Bytes2Hex(challengeHash), nil +} + +func (w Wallet) SignPermit2AllowanceAndPackToContract(permit AllowancePermitSingle) (string, error) { + challengeHash, err := AllowancePermitSingleTypedDataHash(permit, *w.address, int(w.chainId.Int64())) + if err != nil { + return "", err + } + challengeHashWithoutPrefix := challengeHash[2:] + challengeHashWithoutPrefixRaw := common.Hex2Bytes(challengeHashWithoutPrefix) + + signature, err := crypto.Sign(challengeHashWithoutPrefixRaw, w.privateKey) + if err != nil { + return "", fmt.Errorf("error signing challenge hash: %v", err) + } + signature[64] += 27 // Adjust the `v` value + + // Convert createPermitSignature to hex string + signatureHex := fmt.Sprintf("%x", signature) + + // Step 5: Encode the permit data with the signature + permitCall, err := w.permit2ABI.Pack("permit", w.address, permit, convertSignatureToVRSString(signatureHex)) + if err != nil { + return "", fmt.Errorf("failed to encode function data: %v", err) + } + return padStringWithZeroes(common.Bytes2Hex(permitCall)), nil +} diff --git a/internal/web3-provider/permit2allowance_test.go b/internal/web3-provider/permit2allowance_test.go new file mode 100644 index 00000000..50a8fac6 --- /dev/null +++ b/internal/web3-provider/permit2allowance_test.go @@ -0,0 +1,38 @@ +package web3_provider + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" +) + +func TestMaxValuesPass(t *testing.T) { + + permit := AllowancePermitSingle{ + Details: AllowancePermitDetails{ + Token: "0x0000000000000000000000000000000000000000", + Amount: "1461501637330902918203684832716283019655932542975", + Expiration: "281474976710655", + Nonce: "281474976710655", + }, + Spender: "0x0000000000000000000000000000000000000000", + SigDeadline: "115792089237316195423570985008687907853269984665640564039457584007913129639935", + } + + permit2Address := common.HexToAddress("0x0000000000000000000000000000000000000000") + chainId := big.NewInt(1) + + _, err := GetTypedDataAllowancePermitSingle(permit, permit2Address, int(chainId.Int64())) + assert.NoError(t, err) + + hash, err := AllowancePermitSingleTypedDataHash(permit, permit2Address, int(chainId.Int64())) + if err != nil { + fmt.Println("Error:", err) + return + } + + assert.Equal(t, "0x7c7685afe45d5d39b6279f05214f9bb9aa275f541f950d0a97d0c18aa43158c8", hash) +} diff --git a/internal/web3-provider/provider.go b/internal/web3-provider/provider.go index 745c30a6..fd393f61 100644 --- a/internal/web3-provider/provider.go +++ b/internal/web3-provider/provider.go @@ -23,6 +23,7 @@ type Wallet struct { chainId *big.Int erc20ABI *abi.ABI seriesNonceManagerABI *abi.ABI + permit2ABI *abi.ABI } func DefaultWalletProvider(pk string, nodeURL string, chainId uint64) (*Wallet, error) { diff --git a/sdk-clients/aggregation/aggregation_types.gen.go b/sdk-clients/aggregation/aggregation_types.gen.go index 7e8d40b2..600dd59c 100644 --- a/sdk-clients/aggregation/aggregation_types.gen.go +++ b/sdk-clients/aggregation/aggregation_types.gen.go @@ -228,6 +228,9 @@ type GetQuoteParams struct { // IncludeGas Return approximated gas in response IncludeGas bool `url:"includeGas,omitempty" json:"includeGas,omitempty"` ConnectorTokens string `url:"connectorTokens,omitempty" json:"connectorTokens,omitempty"` + + // ExcludedProtocols excluded supported liquidity sources + ExcludedProtocols string `url:"excludedProtocols,omitempty" json:"excludedProtocols,omitempty"` } // GetSwapParams defines parameters for GetSwap. @@ -239,6 +242,9 @@ type GetSwapParams struct { // From The address that calls the 1inch contract From string `url:"from" json:"from"` + // Origin An EOA address that initiate the transaction + Origin string `url:"origin" json:"origin"` + // Slippage min: 0; max: 50 Slippage float32 `url:"slippage" json:"slippage"` @@ -265,6 +271,9 @@ type GetSwapParams struct { IncludeGas bool `url:"includeGas,omitempty" json:"includeGas,omitempty"` ConnectorTokens string `url:"connectorTokens,omitempty" json:"connectorTokens,omitempty"` + // ExcludedProtocols excluded supported liquidity sources + ExcludedProtocols string `url:"excludedProtocols,omitempty" json:"excludedProtocols,omitempty"` + // Permit https://eips.ethereum.org/EIPS/eip-2612 Permit string `url:"permit,omitempty" json:"permit,omitempty"` @@ -277,4 +286,7 @@ type GetSwapParams struct { // DisableEstimate Enable this flag to disable onchain simulation DisableEstimate bool `url:"disableEstimate,omitempty" json:"disableEstimate,omitempty"` + + // UsePermit2 Enable this flag in case you did an approval to permit2 smart contract + UsePermit2 bool `url:"usePermit2,omitempty" json:"usePermit2,omitempty"` } diff --git a/sdk-clients/aggregation/client_test.go b/sdk-clients/aggregation/client_test.go index 2ea8bae4..364f33a8 100644 --- a/sdk-clients/aggregation/client_test.go +++ b/sdk-clients/aggregation/client_test.go @@ -177,6 +177,13 @@ func (w *MyWallet) ChainId() int64 { return w.chainID.Int64() } +func (w *MyWallet) GenerateApproveCallData(addressTo string, amount uint64) (string, error) { + return "", nil +} +func (w *MyWallet) TokenAllowance(ctx context.Context, tokenAddress string, spenderAddress string) (*big.Int, error) { + return big.NewInt(1), nil +} + type mockHttpExecutor struct { Called bool ExecuteErr error diff --git a/sdk-clients/aggregation/examples/swap-with-permit2/main.go b/sdk-clients/aggregation/examples/swap-with-permit2/main.go new file mode 100644 index 00000000..e1def111 --- /dev/null +++ b/sdk-clients/aggregation/examples/swap-with-permit2/main.go @@ -0,0 +1,197 @@ +package main + +import ( + "context" + "encoding/hex" + "fmt" + "log" + "math/big" + "os" + "time" + + "github.com/ethereum/go-ethereum/common" + + "github.com/1inch/1inch-sdk-go/constants" + "github.com/1inch/1inch-sdk-go/sdk-clients/aggregation" +) + +/* +This example demonstrates how to swap tokens on the PolygonChainId network using the 1inch SDK. +The only thing you need to provide is your wallet address, wallet key, and dev portal token. +This can be done through your environment, or you can directly set them in the variables below +*/ + +var ( + privateKey = os.Getenv("WALLET_KEY") + nodeUrl = os.Getenv("NODE_URL") + devPortalToken = os.Getenv("DEV_PORTAL_TOKEN") +) + +const ( + PolygonFRAX = "0x45c32fa6df82ead1e2ef74d17b76547eddfaff89" + PolygonWeth = "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619" +) + +const ( + UniswapPermit2Polygon = "0x000000000022D473030F116dDEE9F6B43aC78BA3" +) + +func main() { + config, err := aggregation.NewConfiguration(aggregation.ConfigurationParams{ + NodeUrl: nodeUrl, + PrivateKey: privateKey, + ChainId: constants.PolygonChainId, + ApiUrl: "https://api.1inch.dev", + ApiKey: devPortalToken, + }) + if err != nil { + log.Fatalf("Failed to create configuration: %v\n", err) + } + client, err := aggregation.NewClient(config) + if err != nil { + log.Fatalf("Failed to create client: %v\n", err) + } + ctx := context.Background() + + amountToSwap := big.NewInt(1e17) + + //allowanceData, err := client.GetApproveAllowance(ctx, aggregation.GetAllowanceParams{ + // TokenAddress: PolygonFRAX, + // WalletAddress: client.Wallet.Address().Hex(), + //}) + //if err != nil { + // log.Fatalf("Failed to get allowance: %v\n", err) + //} + // + //allowance := new(big.Int) + //allowance.SetString(allowanceData.Allowance, 10) + + //cmp := amountToSwap.Cmp(allowance) + // + //var permit string + + perimit2allowance, err := client.Wallet.TokenAllowance(ctx, PolygonFRAX, UniswapPermit2Polygon) + if err != nil { + log.Fatalf("Failed to get allowance: %v\n", err) + } + + var permit string + + cmp2 := amountToSwap.Cmp(perimit2allowance) + if cmp2 > 0 { + approveDataString, err := client.Wallet.GenerateApproveCallData(UniswapPermit2Polygon, amountToSwap.Uint64()) + if err != nil { + log.Fatalf("Failed to generate call data: %v\n", err) + } + + approveData, err := hex.DecodeString(approveDataString[2:]) + if err != nil { + log.Fatalf("Failed to decode approveDataString: %v\n", err) + } + + toAddressFrax := common.HexToAddress(PolygonFRAX) + + builder := client.TxBuilder.New() + + tx, err := builder.SetData(approveData).SetTo(&toAddressFrax).SetGas(21_000 * 5).Build(ctx) + if err != nil { + log.Fatalf("Failed to build approval transaction: %v\n", err) + } + signedTx, err := client.Wallet.Sign(tx) + if err != nil { + log.Fatalf("Failed to sign transaction: %v\n", err) + } + + err = client.Wallet.BroadcastTransaction(ctx, signedTx) + if err != nil { + log.Fatalf("Failed to broadcast transaction: %v\n", err) + } + + spender, err := client.GetApproveSpender(ctx) + if err != nil { + panic(err) + } + now := time.Now() + twoDaysLater := now.Add(time.Hour * 24 * 2) + + permitData, err := client.Wallet.GetContractDetailsForPermit(ctx, common.HexToAddress(UniswapPermit2Polygon), common.HexToAddress(spender.Address), amountToSwap, twoDaysLater.Unix()) + if err != nil { + log.Fatalf("Failed to get permit data: %v\n", err) + } + + permit, err = client.Wallet.TokenPermit(*permitData) + if err != nil { + log.Fatalf("Failed to sign permit: %v\n", err) + } + } + + if cmp2 == 0 { + spender, err := client.GetApproveSpender(ctx) + if err != nil { + panic(err) + } + now := time.Now() + twoDaysLater := now.Add(time.Hour * 24 * 2) + + permitData, err := client.Wallet.GetContractDetailsForPermit(ctx, common.HexToAddress(UniswapPermit2Polygon), common.HexToAddress(spender.Address), amountToSwap, twoDaysLater.Unix()) + if err != nil { + log.Fatalf("Failed to get permit data: %v\n", err) + } + + permit, err = client.Wallet.TokenPermit(*permitData) + if err != nil { + log.Fatalf("Failed to sign permit: %v\n", err) + } + } + + swapParams := aggregation.GetSwapParams{ + Src: PolygonFRAX, + Dst: PolygonWeth, + Amount: amountToSwap.String(), + From: client.Wallet.Address().Hex(), + Slippage: 1, + UsePermit2: true, + } + if permit != "" { + swapParams.Permit = permit + } + swapData, err := client.GetSwap(ctx, swapParams) + if err != nil { + log.Fatalf("Failed to get swap data: %v\n", err) + } + + builder := client.TxBuilder.New() + + tx, err := builder.SetData(swapData.TxNormalized.Data).SetTo(&swapData.TxNormalized.To).SetGas(swapData.TxNormalized.Gas).SetValue(swapData.TxNormalized.Value).Build(ctx) + if err != nil { + log.Fatalf("Failed to build transaction: %v\n", err) + } + signedTx, err := client.Wallet.Sign(tx) + if err != nil { + log.Fatalf("Failed to sign transaction: %v\n", err) + } + + err = client.Wallet.BroadcastTransaction(ctx, signedTx) + if err != nil { + log.Fatalf("Failed to broadcast transaction: %v\n", err) + } + + // Waiting for transaction, just an example of it + fmt.Printf("Transaction has been broadcast. View it on Polygonscan here: %v\n", fmt.Sprintf("https://polygonscan.com/tx/%v", signedTx.Hash().Hex())) + for { + receipt, err := client.Wallet.TransactionReceipt(ctx, signedTx.Hash()) + if receipt != nil { + fmt.Println("Transaction complete!") + return + } + if err != nil { + fmt.Println("Waiting for transaction to be mined") + } + select { + case <-time.After(1000 * time.Millisecond): // check again after a delay + case <-ctx.Done(): + fmt.Println("Context cancelled") + return + } + } +} diff --git a/sdk-clients/aggregation/examples/swap/main.go b/sdk-clients/aggregation/examples/swap/main.go index c76d9d57..1038dd31 100644 --- a/sdk-clients/aggregation/examples/swap/main.go +++ b/sdk-clients/aggregation/examples/swap/main.go @@ -27,7 +27,7 @@ func main() { config, err := aggregation.NewConfiguration(aggregation.ConfigurationParams{ NodeUrl: nodeUrl, PrivateKey: privateKey, - ChainId: constants.EthereumChainId, + ChainId: constants.PolygonChainId, ApiUrl: "https://api.1inch.dev", ApiKey: devPortalToken, })