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,
})