From b09aade2086200a37d38ac875843eab3ff2591e7 Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Fri, 14 Jun 2024 14:07:12 +0200
Subject: [PATCH 01/18] feat: bump openapi version
---
codegen/openapi/aggregation-openapi.json | 130 ++++++++++--------
.../aggregation/aggregation_types.gen.go | 17 ++-
2 files changed, 86 insertions(+), 61 deletions(-)
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/sdk-clients/aggregation/aggregation_types.gen.go b/sdk-clients/aggregation/aggregation_types.gen.go
index 7e8d40b2..36b1192b 100644
--- a/sdk-clients/aggregation/aggregation_types.gen.go
+++ b/sdk-clients/aggregation/aggregation_types.gen.go
@@ -167,11 +167,6 @@ type TokenInfo struct {
Tags []string `json:"tags,omitempty"`
}
-// TokensResponse defines model for TokensResponse.
-type TokensResponse struct {
- Tokens map[string]TokenInfo `json:"tokens"`
-}
-
// TransactionData defines model for TransactionData.
type TransactionData struct {
Data string `json:"data"`
@@ -228,6 +223,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 +237,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 +266,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 +281,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"`
}
From 0eb2b09cebfa0ff016b28aba7005059d0d47900a Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Fri, 14 Jun 2024 14:07:42 +0200
Subject: [PATCH 02/18] feat: add approve call data generation for wallet pkg
---
common/wallet.go | 4 +++-
internal/web3-provider/approve.go | 19 +++++++++++++++++++
2 files changed, 22 insertions(+), 1 deletion(-)
create mode 100644 internal/web3-provider/approve.go
diff --git a/common/wallet.go b/common/wallet.go
index 47bd438c..78abe468 100644
--- a/common/wallet.go
+++ b/common/wallet.go
@@ -25,7 +25,9 @@ type Wallet interface {
GetContractDetailsForPermit(ctx context.Context, token gethCommon.Address, spender gethCommon.Address, amount *big.Int, deadline int64) (*ContractPermitData, error)
GetContractDetailsForPermitDaiLike(ctx context.Context, token gethCommon.Address, spender gethCommon.Address, deadline int64) (*ContractPermitDataDaiLike, error)
TokenPermit(cd ContractPermitData) (string, error)
- TokenPermitDaiLike(cd ContractPermitDataDaiLike) (string, error)
+ TokenPermitDaiLike(cd ContractPermitDataDaiLike) (string, error
+
+ GenerateApproveCallData(addressTo string, amount uint64) (string, error)
IsEIP1559Applicable() bool
ChainId() int64
diff --git a/internal/web3-provider/approve.go b/internal/web3-provider/approve.go
new file mode 100644
index 00000000..de658585
--- /dev/null
+++ b/internal/web3-provider/approve.go
@@ -0,0 +1,19 @@
+package web3_provider
+
+import (
+ "fmt"
+ "math/big"
+
+ "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 common.Bytes2Hex(callData), nil
+}
From 424020626ae05d2f72f51fa3b60f6ca8f8b29b42 Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Fri, 14 Jun 2024 14:08:08 +0200
Subject: [PATCH 03/18] fix(example): wrong network fix
---
sdk-clients/aggregation/examples/swap/main.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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,
})
From 2267fe3bb2ec13303e09cac07bbedbe59c30bc39 Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Fri, 14 Jun 2024 14:08:24 +0200
Subject: [PATCH 04/18] feat(example): permit2 template
---
.../examples/swap-with-permit2/main.go | 136 ++++++++++++++++++
1 file changed, 136 insertions(+)
create mode 100644 sdk-clients/aggregation/examples/swap-with-permit2/main.go
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..f6647705
--- /dev/null
+++ b/sdk-clients/aggregation/examples/swap-with-permit2/main.go
@@ -0,0 +1,136 @@
+package main
+
+import (
+ "context"
+ "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"
+)
+
+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
+
+ if cmp > 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(PolygonFRAX), 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,
+ }
+ 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
+ }
+ }
+}
From ba98544a8772bc45436df2f512c8c9edf663f1e8 Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Fri, 14 Jun 2024 14:10:06 +0200
Subject: [PATCH 05/18] fix(wallet): interface typing
---
common/wallet.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/wallet.go b/common/wallet.go
index 78abe468..7137cdcd 100644
--- a/common/wallet.go
+++ b/common/wallet.go
@@ -25,7 +25,7 @@ type Wallet interface {
GetContractDetailsForPermit(ctx context.Context, token gethCommon.Address, spender gethCommon.Address, amount *big.Int, deadline int64) (*ContractPermitData, error)
GetContractDetailsForPermitDaiLike(ctx context.Context, token gethCommon.Address, spender gethCommon.Address, deadline int64) (*ContractPermitDataDaiLike, error)
TokenPermit(cd ContractPermitData) (string, error)
- TokenPermitDaiLike(cd ContractPermitDataDaiLike) (string, error
+ TokenPermitDaiLike(cd ContractPermitDataDaiLike) (string, error)
GenerateApproveCallData(addressTo string, amount uint64) (string, error)
From 2d16762fe240f426ea15c9ff62165037d1b8821b Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Mon, 17 Jun 2024 13:29:02 +0200
Subject: [PATCH 06/18] chore: example logic
---
common/wallet.go | 2 ++
internal/web3-provider/approve.go | 30 +++++++++++++++++
.../examples/swap-with-permit2/main.go | 33 ++++++++++++++++++-
3 files changed, 64 insertions(+), 1 deletion(-)
diff --git a/common/wallet.go b/common/wallet.go
index 7137cdcd..0257a64f 100644
--- a/common/wallet.go
+++ b/common/wallet.go
@@ -29,6 +29,8 @@ type Wallet interface {
GenerateApproveCallData(addressTo string, amount uint64) (string, error)
+ TokenAllowance(ctx context.Context, tokenAddress string, spenderAddress string) (*big.Int, error)
+
IsEIP1559Applicable() bool
ChainId() int64
//TokenApprove()
diff --git a/internal/web3-provider/approve.go b/internal/web3-provider/approve.go
index de658585..8e3815b2 100644
--- a/internal/web3-provider/approve.go
+++ b/internal/web3-provider/approve.go
@@ -1,9 +1,11 @@
package web3_provider
import (
+ "context"
"fmt"
"math/big"
+ "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
)
@@ -17,3 +19,31 @@ func (w Wallet) GenerateApproveCallData(addressTo string, amount uint64) (string
return 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/sdk-clients/aggregation/examples/swap-with-permit2/main.go b/sdk-clients/aggregation/examples/swap-with-permit2/main.go
index f6647705..d370c56a 100644
--- a/sdk-clients/aggregation/examples/swap-with-permit2/main.go
+++ b/sdk-clients/aggregation/examples/swap-with-permit2/main.go
@@ -31,6 +31,10 @@ const (
PolygonWeth = "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619"
)
+const (
+ UniswapPermit2Polygon = "0x000000000022D473030F116dDEE9F6B43aC78BA3"
+)
+
func main() {
config, err := aggregation.NewConfiguration(aggregation.ConfigurationParams{
NodeUrl: nodeUrl,
@@ -65,6 +69,33 @@ func main() {
var permit string
+ perimit2allowance, err := client.Wallet.TokenAllowance(ctx, PolygonFRAX, UniswapPermit2Polygon)
+ if err != nil {
+ log.Fatalf("Failed to get allowance: %v\n", err)
+ }
+
+ cmp2 := allowance.Cmp(perimit2allowance)
+ if cmp2 > 0 {
+ approveData, err := client.Wallet.GenerateApproveCallData(UniswapPermit2Polygon, amountToSwap.Uint64())
+ if err != nil {
+ log.Fatalf("Failed to generate call data: %v\n", err)
+ }
+ builder := client.TxBuilder.New()
+
+ tx, err := builder.SetData(approveData).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)
+ }
+ }
if cmp > 0 {
spender, err := client.GetApproveSpender(ctx)
if err != nil {
@@ -73,7 +104,7 @@ func main() {
now := time.Now()
twoDaysLater := now.Add(time.Hour * 24 * 2)
- permitData, err := client.Wallet.GetContractDetailsForPermit(ctx, common.HexToAddress(PolygonFRAX), common.HexToAddress(spender.Address), amountToSwap, twoDaysLater.Unix())
+ 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)
}
From cd7395875b2f2fc13a62d7775715229d8555f682 Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Mon, 17 Jun 2024 20:25:39 +0200
Subject: [PATCH 07/18] chore: fixes and debug
---
common/wallet.go | 6 -
internal/web3-provider/approve.go | 2 +-
.../aggregation/aggregation_types.gen.go | 5 +
.../examples/swap-with-permit2/main.go | 160 +++++++++---------
4 files changed, 90 insertions(+), 83 deletions(-)
diff --git a/common/wallet.go b/common/wallet.go
index 0257a64f..b110714d 100644
--- a/common/wallet.go
+++ b/common/wallet.go
@@ -33,12 +33,6 @@ type Wallet interface {
IsEIP1559Applicable() bool
ChainId() int64
- //TokenApprove()
-
- // view functions
- //TokenBalance()
- //TokenAllowance()
-
}
type ContractPermitData struct {
diff --git a/internal/web3-provider/approve.go b/internal/web3-provider/approve.go
index 8e3815b2..f6f68638 100644
--- a/internal/web3-provider/approve.go
+++ b/internal/web3-provider/approve.go
@@ -17,7 +17,7 @@ func (w Wallet) GenerateApproveCallData(addressTo string, amount uint64) (string
return "", fmt.Errorf("failed to pack ABI data: %v", err)
}
- return common.Bytes2Hex(callData), nil
+ return "0x" + common.Bytes2Hex(callData), nil
}
func (w Wallet) TokenAllowance(ctx context.Context, tokenAddress string, spenderAddress string) (*big.Int, error) {
diff --git a/sdk-clients/aggregation/aggregation_types.gen.go b/sdk-clients/aggregation/aggregation_types.gen.go
index 36b1192b..600dd59c 100644
--- a/sdk-clients/aggregation/aggregation_types.gen.go
+++ b/sdk-clients/aggregation/aggregation_types.gen.go
@@ -167,6 +167,11 @@ type TokenInfo struct {
Tags []string `json:"tags,omitempty"`
}
+// TokensResponse defines model for TokensResponse.
+type TokensResponse struct {
+ Tokens map[string]TokenInfo `json:"tokens"`
+}
+
// TransactionData defines model for TransactionData.
type TransactionData struct {
Data string `json:"data"`
diff --git a/sdk-clients/aggregation/examples/swap-with-permit2/main.go b/sdk-clients/aggregation/examples/swap-with-permit2/main.go
index d370c56a..68fbf05f 100644
--- a/sdk-clients/aggregation/examples/swap-with-permit2/main.go
+++ b/sdk-clients/aggregation/examples/swap-with-permit2/main.go
@@ -2,11 +2,10 @@ package main
import (
"context"
- "fmt"
+ "encoding/hex"
"log"
"math/big"
"os"
- "time"
"github.com/ethereum/go-ethereum/common"
@@ -65,26 +64,34 @@ func main() {
allowance := new(big.Int)
allowance.SetString(allowanceData.Allowance, 10)
- cmp := amountToSwap.Cmp(allowance)
-
- var permit string
+ //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)
}
- cmp2 := allowance.Cmp(perimit2allowance)
+ cmp2 := amountToSwap.Cmp(perimit2allowance)
if cmp2 > 0 {
- approveData, err := client.Wallet.GenerateApproveCallData(UniswapPermit2Polygon, amountToSwap.Uint64())
+ 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(&swapData.TxNormalized.To).SetGas(swapData.TxNormalized.Gas).SetValue(swapData.TxNormalized.Value).Build(ctx)
+ tx, err := builder.SetData(approveData).SetTo(&toAddressFrax).SetGas(21_000 * 5).Build(ctx)
if err != nil {
- log.Fatalf("Failed to build transaction: %v\n", err)
+ log.Fatalf("Failed to build approval transaction: %v\n", err)
}
signedTx, err := client.Wallet.Sign(tx)
if err != nil {
@@ -96,72 +103,73 @@ func main() {
log.Fatalf("Failed to broadcast transaction: %v\n", err)
}
}
- if cmp > 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,
- }
- 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
- }
- }
+ //if cmp > 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,
+ //}
+ //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
+ // }
+ //}
}
From bbc7dabad02a82d172ccf7b7d7e7d21e71de8007 Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Wed, 19 Jun 2024 15:26:42 +0200
Subject: [PATCH 08/18] feat: setup permit2 hash for single transfer contract
---
internal/web3-provider/permit2.go | 205 ++++++++++++++++++
internal/web3-provider/permit2_test.go | 39 ++++
.../examples/swap-with-permit2/main.go | 178 ++++++++-------
3 files changed, 344 insertions(+), 78 deletions(-)
create mode 100644 internal/web3-provider/permit2.go
create mode 100644 internal/web3-provider/permit2_test.go
diff --git a/internal/web3-provider/permit2.go b/internal/web3-provider/permit2.go
new file mode 100644
index 00000000..4752e0ed
--- /dev/null
+++ b/internal/web3-provider/permit2.go
@@ -0,0 +1,205 @@
+package web3_provider
+
+import (
+ "bytes"
+ "math/big"
+ "reflect"
+ "strings"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+)
+
+const (
+ PERMIT2_DOMAIN_NAME = "Permit2"
+)
+
+var (
+ MaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
+ MaxSignatureTransferAmount = MaxUint256
+ MaxUnorderedNonce = MaxUint256
+ MaxSigDeadline = MaxUint256
+)
+
+type TypedDataDomain struct {
+ Name string
+ ChainId int
+ VerifyingContract string
+}
+
+type TypedDataField struct {
+ Name string
+ Type string
+}
+
+type TokenPermissions struct {
+ Token string
+ Amount *big.Int
+}
+
+type PermitTransferFrom struct {
+ Permitted TokenPermissions
+ Spender string
+ Nonce *big.Int
+ Deadline *big.Int
+}
+
+type PermitBatchTransferFrom struct {
+ Permitted []TokenPermissions
+ Spender string
+ Nonce *big.Int
+ Deadline *big.Int
+}
+
+type Witness struct {
+ Witness interface{}
+ WitnessTypeName string
+ WitnessType map[string][]TypedDataField
+}
+
+type PermitTransferFromData struct {
+ Domain TypedDataDomain
+ Types map[string][]TypedDataField
+ Values PermitTransferFrom
+}
+
+var TOKEN_PERMISSIONS = []TypedDataField{
+ {Name: "token", Type: "address"},
+ {Name: "amount", Type: "uint256"},
+}
+
+var PERMIT_TRANSFER_FROM_TYPES = map[string][]TypedDataField{
+ "PermitTransferFrom": {
+ {Name: "permitted", Type: "TokenPermissions"},
+ {Name: "spender", Type: "address"},
+ {Name: "nonce", Type: "uint256"},
+ {Name: "deadline", Type: "uint256"},
+ },
+ "TokenPermissions": TOKEN_PERMISSIONS,
+}
+
+func permit2Domain(permit2Address string, chainId int) TypedDataDomain {
+ return TypedDataDomain{
+ Name: PERMIT2_DOMAIN_NAME,
+ ChainId: chainId,
+ VerifyingContract: permit2Address,
+ }
+}
+
+func permitTransferFromWithWitnessType(witness Witness) map[string][]TypedDataField {
+ return map[string][]TypedDataField{
+ "PermitWitnessTransferFrom": {
+ {Name: "permitted", Type: "TokenPermissions"},
+ {Name: "spender", Type: "address"},
+ {Name: "nonce", Type: "uint256"},
+ {Name: "deadline", Type: "uint256"},
+ {Name: "witness", Type: witness.WitnessTypeName},
+ },
+ "TokenPermissions": TOKEN_PERMISSIONS,
+ witness.WitnessTypeName: witness.WitnessType[witness.WitnessTypeName],
+ }
+}
+
+func validateTokenPermissions(permissions TokenPermissions) {
+ if MaxSignatureTransferAmount.Cmp(permissions.Amount) < 0 {
+ panic("AMOUNT_OUT_OF_RANGE")
+ }
+}
+
+func getPermitTransferFromData(
+ permit PermitTransferFrom,
+ permit2Address string,
+ chainId int,
+ witness *Witness,
+) PermitTransferFromData {
+ if MaxSigDeadline.Cmp(permit.Deadline) < 0 {
+ panic("SIG_DEADLINE_OUT_OF_RANGE")
+ }
+ if MaxUnorderedNonce.Cmp(permit.Nonce) < 0 {
+ panic("NONCE_OUT_OF_RANGE")
+ }
+
+ validateTokenPermissions(permit.Permitted)
+
+ domain := permit2Domain(permit2Address, chainId)
+ var types map[string][]TypedDataField
+ var values interface{}
+
+ if witness != nil {
+ types = permitTransferFromWithWitnessType(*witness)
+ values = struct {
+ PermitTransferFrom
+ Witness interface{}
+ }{permit, witness.Witness}
+ } else {
+ types = PERMIT_TRANSFER_FROM_TYPES
+ values = permit
+ }
+
+ return PermitTransferFromData{
+ Domain: domain,
+ Types: types,
+ Values: values.(PermitTransferFrom),
+ }
+}
+
+func hashPermitTransferFrom(
+ permit PermitTransferFrom,
+ permit2Address string,
+ chainId int,
+ witness *Witness,
+) string {
+ permitData := getPermitTransferFromData(permit, permit2Address, chainId, witness)
+ domainHash := hashDomain(permitData.Domain)
+ structHash := hashStruct(permitData.Types, permitData.Values)
+
+ finalHash := crypto.Keccak256Hash(append([]byte{0x19, 0x01}, append(domainHash, structHash...)...)).Hex()
+ return finalHash
+}
+
+func hashDomain(domain TypedDataDomain) []byte {
+ var buffer bytes.Buffer
+
+ buffer.Write(crypto.Keccak256([]byte("EIP712Domain(string name,uint256 chainId,address verifyingContract)")))
+
+ nameHash := crypto.Keccak256([]byte(domain.Name))
+ buffer.Write(nameHash)
+
+ chainIdBytes := common.LeftPadBytes(new(big.Int).SetInt64(int64(domain.ChainId)).Bytes(), 32)
+ buffer.Write(chainIdBytes)
+
+ addressBytes := common.HexToAddress(domain.VerifyingContract).Bytes()
+ buffer.Write(addressBytes)
+
+ return crypto.Keccak256(buffer.Bytes())
+}
+
+func hashStruct(types map[string][]TypedDataField, values PermitTransferFrom) []byte {
+ var buffer bytes.Buffer
+
+ primaryType := ""
+ for t := range types {
+ primaryType = t
+ break
+ }
+
+ buffer.Write(crypto.Keccak256([]byte(primaryType)))
+
+ value := reflect.ValueOf(values)
+ for _, field := range types[primaryType] {
+ fieldName := field.Name
+ fieldValue := value.FieldByName(strings.Title(fieldName))
+
+ if field.Type == "address" {
+ buffer.Write(common.HexToAddress(fieldValue.String()).Bytes())
+ } else if field.Type == "uint256" {
+ buffer.Write(common.LeftPadBytes(fieldValue.Interface().(*big.Int).Bytes(), 32))
+ } else if field.Type == "TokenPermissions" {
+ tokenPerm := fieldValue.Interface().(TokenPermissions)
+ buffer.Write(common.HexToAddress(tokenPerm.Token).Bytes())
+ buffer.Write(common.LeftPadBytes(tokenPerm.Amount.Bytes(), 32))
+ }
+ }
+
+ return crypto.Keccak256(buffer.Bytes())
+}
diff --git a/internal/web3-provider/permit2_test.go b/internal/web3-provider/permit2_test.go
new file mode 100644
index 00000000..d6a7289b
--- /dev/null
+++ b/internal/web3-provider/permit2_test.go
@@ -0,0 +1,39 @@
+package web3_provider
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+func TestHashPermitTransferFrom_MaxValues(t *testing.T) {
+ permit := PermitTransferFrom{
+ Permitted: TokenPermissions{
+ Token: "0x0000000000000000000000000000000000000000",
+ Amount: MaxSignatureTransferAmount,
+ },
+ Spender: "0x0000000000000000000000000000000000000000",
+ Nonce: MaxUnorderedNonce,
+ Deadline: MaxSigDeadline,
+ }
+ permit2Address := "0x0000000000000000000000000000000000000000"
+ chainId := 1
+
+ // Call the hash function
+ hash := hashPermitTransferFrom(permit, permit2Address, chainId, nil)
+
+ // Define the expected hash value
+ expectedHash := "0x99e8cd5cd187c1dcb3c9cb41664cb12c1a3a76143d21b16f7880f4839d2b2ad4"
+
+ // Convert expectedHash to bytes for comparison
+ expectedHashBytes := common.Hex2Bytes(expectedHash)
+
+ // Convert hash to bytes for comparison
+ hashBytes := common.Hex2Bytes(hash)
+
+ // Compare the hashes
+ if !bytes.Equal(hashBytes, expectedHashBytes) {
+ t.Errorf("Expected hash %s, but got %s", expectedHash, hash)
+ }
+}
diff --git a/sdk-clients/aggregation/examples/swap-with-permit2/main.go b/sdk-clients/aggregation/examples/swap-with-permit2/main.go
index 68fbf05f..e1def111 100644
--- a/sdk-clients/aggregation/examples/swap-with-permit2/main.go
+++ b/sdk-clients/aggregation/examples/swap-with-permit2/main.go
@@ -3,9 +3,11 @@ package main
import (
"context"
"encoding/hex"
+ "fmt"
"log"
"math/big"
"os"
+ "time"
"github.com/ethereum/go-ethereum/common"
@@ -53,16 +55,16 @@ func main() {
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)
+ //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)
//
@@ -73,6 +75,8 @@ func main() {
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())
@@ -102,74 +106,92 @@ func main() {
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 cmp > 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,
- //}
- //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
- // }
- //}
+ 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
+ }
+ }
}
From 2ed9f25d389ee6892c208eb91412f65bb80f70ca Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Wed, 19 Jun 2024 15:36:14 +0200
Subject: [PATCH 09/18] feat: add signing permit2
---
internal/web3-provider/permit2.go | 45 +++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/internal/web3-provider/permit2.go b/internal/web3-provider/permit2.go
index 4752e0ed..457302f0 100644
--- a/internal/web3-provider/permit2.go
+++ b/internal/web3-provider/permit2.go
@@ -2,10 +2,12 @@ package web3_provider
import (
"bytes"
+ "fmt"
"math/big"
"reflect"
"strings"
+ "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
@@ -14,6 +16,22 @@ const (
PERMIT2_DOMAIN_NAME = "Permit2"
)
+const (
+ // Update this with the ABI for your contract
+ abiString = `
+[
+ {
+ "type": "function",
+ "name": "permitTransferFrom",
+ "inputs": [
+ { "name": "data", "type": "PermitTransferFromData" },
+ { "name": "witness", "type": "bytes" }
+ ]
+ }
+]
+`
+)
+
var (
MaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
MaxSignatureTransferAmount = MaxUint256
@@ -203,3 +221,30 @@ func hashStruct(types map[string][]TypedDataField, values PermitTransferFrom) []
return crypto.Keccak256(buffer.Bytes())
}
+
+func (w Wallet) SignPermit2TransferFrom(
+ permit PermitTransferFrom,
+ permit2Address string,
+ witness *Witness,
+) ([]byte, error) {
+ permitData := getPermitTransferFromData(permit, permit2Address, int(w.chainId.Uint64()), witness)
+
+ encodedData, err := abi.JSON(strings.NewReader(abiString))
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse ABI: %v", err)
+ }
+ _, err = encodedData.Pack("permitTransferFrom", permitData, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to pack permit data: %v", err)
+ }
+
+ message := fmt.Sprintf("\x19\x01%s", string(hashPermitTransferFrom(permit, permit2Address, chainId, witness)))
+
+ hash := crypto.Keccak256Hash([]byte(message))
+ signature, err := crypto.Sign(hash.Bytes(), w.privateKey)
+ if err != nil {
+ return nil, fmt.Errorf("failed to sign permit: %v", err)
+ }
+
+ return signature, nil
+}
From 9ba3cbacfddc1e37eac79a0e54016bd2aa0f8c39 Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Wed, 19 Jun 2024 15:37:05 +0200
Subject: [PATCH 10/18] fix(var): permit2
---
internal/web3-provider/permit2.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/internal/web3-provider/permit2.go b/internal/web3-provider/permit2.go
index 457302f0..cd274b6e 100644
--- a/internal/web3-provider/permit2.go
+++ b/internal/web3-provider/permit2.go
@@ -227,7 +227,8 @@ func (w Wallet) SignPermit2TransferFrom(
permit2Address string,
witness *Witness,
) ([]byte, error) {
- permitData := getPermitTransferFromData(permit, permit2Address, int(w.chainId.Uint64()), witness)
+ chainId := int(w.chainId.Uint64())
+ permitData := getPermitTransferFromData(permit, permit2Address, chainId, witness)
encodedData, err := abi.JSON(strings.NewReader(abiString))
if err != nil {
From 3ea0460a4350817eaa89d048caaaa7df8397c96b Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Wed, 19 Jun 2024 17:43:00 +0200
Subject: [PATCH 11/18] fix: lint
---
go.mod | 1 +
internal/transaction-builder/transaction_builder_test.go | 7 +++++++
internal/web3-provider/permit2.go | 4 +++-
sdk-clients/aggregation/client_test.go | 7 +++++++
4 files changed, 18 insertions(+), 1 deletion(-)
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/permit2.go b/internal/web3-provider/permit2.go
index cd274b6e..9e864e71 100644
--- a/internal/web3-provider/permit2.go
+++ b/internal/web3-provider/permit2.go
@@ -10,6 +10,8 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
)
const (
@@ -206,7 +208,7 @@ func hashStruct(types map[string][]TypedDataField, values PermitTransferFrom) []
value := reflect.ValueOf(values)
for _, field := range types[primaryType] {
fieldName := field.Name
- fieldValue := value.FieldByName(strings.Title(fieldName))
+ fieldValue := value.FieldByName(cases.Title(language.English).String(fieldName))
if field.Type == "address" {
buffer.Write(common.HexToAddress(fieldValue.String()).Bytes())
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
From 535be5c058f4792319be5c8ca31d51e7685911e3 Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Tue, 25 Jun 2024 16:15:48 +0200
Subject: [PATCH 12/18] feat: allowance draft
---
internal/web3-provider/permit2.go | 503 +++++++++---------
internal/web3-provider/permit2_test.go | 67 ++-
internal/web3-provider/permit2allowance.go | 129 +++++
.../web3-provider/permit2allowance_test.go | 38 ++
4 files changed, 449 insertions(+), 288 deletions(-)
create mode 100644 internal/web3-provider/permit2allowance.go
create mode 100644 internal/web3-provider/permit2allowance_test.go
diff --git a/internal/web3-provider/permit2.go b/internal/web3-provider/permit2.go
index 9e864e71..fef0fdf0 100644
--- a/internal/web3-provider/permit2.go
+++ b/internal/web3-provider/permit2.go
@@ -1,253 +1,254 @@
package web3_provider
-import (
- "bytes"
- "fmt"
- "math/big"
- "reflect"
- "strings"
-
- "github.com/ethereum/go-ethereum/accounts/abi"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/crypto"
- "golang.org/x/text/cases"
- "golang.org/x/text/language"
-)
-
-const (
- PERMIT2_DOMAIN_NAME = "Permit2"
-)
-
-const (
- // Update this with the ABI for your contract
- abiString = `
-[
- {
- "type": "function",
- "name": "permitTransferFrom",
- "inputs": [
- { "name": "data", "type": "PermitTransferFromData" },
- { "name": "witness", "type": "bytes" }
- ]
- }
-]
-`
-)
-
-var (
- MaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
- MaxSignatureTransferAmount = MaxUint256
- MaxUnorderedNonce = MaxUint256
- MaxSigDeadline = MaxUint256
-)
-
-type TypedDataDomain struct {
- Name string
- ChainId int
- VerifyingContract string
-}
-
-type TypedDataField struct {
- Name string
- Type string
-}
-
-type TokenPermissions struct {
- Token string
- Amount *big.Int
-}
-
-type PermitTransferFrom struct {
- Permitted TokenPermissions
- Spender string
- Nonce *big.Int
- Deadline *big.Int
-}
-
-type PermitBatchTransferFrom struct {
- Permitted []TokenPermissions
- Spender string
- Nonce *big.Int
- Deadline *big.Int
-}
-
-type Witness struct {
- Witness interface{}
- WitnessTypeName string
- WitnessType map[string][]TypedDataField
-}
-
-type PermitTransferFromData struct {
- Domain TypedDataDomain
- Types map[string][]TypedDataField
- Values PermitTransferFrom
-}
-
-var TOKEN_PERMISSIONS = []TypedDataField{
- {Name: "token", Type: "address"},
- {Name: "amount", Type: "uint256"},
-}
-
-var PERMIT_TRANSFER_FROM_TYPES = map[string][]TypedDataField{
- "PermitTransferFrom": {
- {Name: "permitted", Type: "TokenPermissions"},
- {Name: "spender", Type: "address"},
- {Name: "nonce", Type: "uint256"},
- {Name: "deadline", Type: "uint256"},
- },
- "TokenPermissions": TOKEN_PERMISSIONS,
-}
-
-func permit2Domain(permit2Address string, chainId int) TypedDataDomain {
- return TypedDataDomain{
- Name: PERMIT2_DOMAIN_NAME,
- ChainId: chainId,
- VerifyingContract: permit2Address,
- }
-}
-
-func permitTransferFromWithWitnessType(witness Witness) map[string][]TypedDataField {
- return map[string][]TypedDataField{
- "PermitWitnessTransferFrom": {
- {Name: "permitted", Type: "TokenPermissions"},
- {Name: "spender", Type: "address"},
- {Name: "nonce", Type: "uint256"},
- {Name: "deadline", Type: "uint256"},
- {Name: "witness", Type: witness.WitnessTypeName},
- },
- "TokenPermissions": TOKEN_PERMISSIONS,
- witness.WitnessTypeName: witness.WitnessType[witness.WitnessTypeName],
- }
-}
-
-func validateTokenPermissions(permissions TokenPermissions) {
- if MaxSignatureTransferAmount.Cmp(permissions.Amount) < 0 {
- panic("AMOUNT_OUT_OF_RANGE")
- }
-}
-
-func getPermitTransferFromData(
- permit PermitTransferFrom,
- permit2Address string,
- chainId int,
- witness *Witness,
-) PermitTransferFromData {
- if MaxSigDeadline.Cmp(permit.Deadline) < 0 {
- panic("SIG_DEADLINE_OUT_OF_RANGE")
- }
- if MaxUnorderedNonce.Cmp(permit.Nonce) < 0 {
- panic("NONCE_OUT_OF_RANGE")
- }
-
- validateTokenPermissions(permit.Permitted)
-
- domain := permit2Domain(permit2Address, chainId)
- var types map[string][]TypedDataField
- var values interface{}
-
- if witness != nil {
- types = permitTransferFromWithWitnessType(*witness)
- values = struct {
- PermitTransferFrom
- Witness interface{}
- }{permit, witness.Witness}
- } else {
- types = PERMIT_TRANSFER_FROM_TYPES
- values = permit
- }
-
- return PermitTransferFromData{
- Domain: domain,
- Types: types,
- Values: values.(PermitTransferFrom),
- }
-}
-
-func hashPermitTransferFrom(
- permit PermitTransferFrom,
- permit2Address string,
- chainId int,
- witness *Witness,
-) string {
- permitData := getPermitTransferFromData(permit, permit2Address, chainId, witness)
- domainHash := hashDomain(permitData.Domain)
- structHash := hashStruct(permitData.Types, permitData.Values)
-
- finalHash := crypto.Keccak256Hash(append([]byte{0x19, 0x01}, append(domainHash, structHash...)...)).Hex()
- return finalHash
-}
-
-func hashDomain(domain TypedDataDomain) []byte {
- var buffer bytes.Buffer
-
- buffer.Write(crypto.Keccak256([]byte("EIP712Domain(string name,uint256 chainId,address verifyingContract)")))
-
- nameHash := crypto.Keccak256([]byte(domain.Name))
- buffer.Write(nameHash)
-
- chainIdBytes := common.LeftPadBytes(new(big.Int).SetInt64(int64(domain.ChainId)).Bytes(), 32)
- buffer.Write(chainIdBytes)
-
- addressBytes := common.HexToAddress(domain.VerifyingContract).Bytes()
- buffer.Write(addressBytes)
-
- return crypto.Keccak256(buffer.Bytes())
-}
-
-func hashStruct(types map[string][]TypedDataField, values PermitTransferFrom) []byte {
- var buffer bytes.Buffer
-
- primaryType := ""
- for t := range types {
- primaryType = t
- break
- }
-
- buffer.Write(crypto.Keccak256([]byte(primaryType)))
-
- value := reflect.ValueOf(values)
- for _, field := range types[primaryType] {
- fieldName := field.Name
- fieldValue := value.FieldByName(cases.Title(language.English).String(fieldName))
-
- if field.Type == "address" {
- buffer.Write(common.HexToAddress(fieldValue.String()).Bytes())
- } else if field.Type == "uint256" {
- buffer.Write(common.LeftPadBytes(fieldValue.Interface().(*big.Int).Bytes(), 32))
- } else if field.Type == "TokenPermissions" {
- tokenPerm := fieldValue.Interface().(TokenPermissions)
- buffer.Write(common.HexToAddress(tokenPerm.Token).Bytes())
- buffer.Write(common.LeftPadBytes(tokenPerm.Amount.Bytes(), 32))
- }
- }
-
- return crypto.Keccak256(buffer.Bytes())
-}
-
-func (w Wallet) SignPermit2TransferFrom(
- permit PermitTransferFrom,
- permit2Address string,
- witness *Witness,
-) ([]byte, error) {
- chainId := int(w.chainId.Uint64())
- permitData := getPermitTransferFromData(permit, permit2Address, chainId, witness)
-
- encodedData, err := abi.JSON(strings.NewReader(abiString))
- if err != nil {
- return nil, fmt.Errorf("failed to parse ABI: %v", err)
- }
- _, err = encodedData.Pack("permitTransferFrom", permitData, nil)
- if err != nil {
- return nil, fmt.Errorf("failed to pack permit data: %v", err)
- }
-
- message := fmt.Sprintf("\x19\x01%s", string(hashPermitTransferFrom(permit, permit2Address, chainId, witness)))
-
- hash := crypto.Keccak256Hash([]byte(message))
- signature, err := crypto.Sign(hash.Bytes(), w.privateKey)
- if err != nil {
- return nil, fmt.Errorf("failed to sign permit: %v", err)
- }
-
- return signature, nil
-}
+//
+//import (
+// "bytes"
+// "fmt"
+// "math/big"
+// "reflect"
+// "strings"
+//
+// "github.com/ethereum/go-ethereum/accounts/abi"
+// "github.com/ethereum/go-ethereum/common"
+// "github.com/ethereum/go-ethereum/crypto"
+// "golang.org/x/text/cases"
+// "golang.org/x/text/language"
+//)
+//
+//const (
+// PERMIT2_DOMAIN_NAME = "Permit2"
+//)
+//
+//const (
+// // Update this with the ABI for your contract
+// abiString = `
+//[
+// {
+// "type": "function",
+// "name": "permitTransferFrom",
+// "inputs": [
+// { "name": "data", "type": "PermitTransferFromData" },
+// { "name": "witness", "type": "bytes" }
+// ]
+// }
+//]
+//`
+//)
+//
+//var (
+// MaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
+// MaxSignatureTransferAmount = MaxUint256
+// MaxUnorderedNonce = MaxUint256
+// MaxSigDeadline = MaxUint256
+//)
+//
+//type TypedDataDomain struct {
+// Name string
+// ChainId int
+// VerifyingContract string
+//}
+//
+//type TypedDataField struct {
+// Name string
+// Type string
+//}
+//
+//type TokenPermissions struct {
+// Token string
+// Amount *big.Int
+//}
+//
+//type PermitTransferFrom struct {
+// Permitted TokenPermissions
+// Spender string
+// Nonce *big.Int
+// Deadline *big.Int
+//}
+//
+//type PermitBatchTransferFrom struct {
+// Permitted []TokenPermissions
+// Spender string
+// Nonce *big.Int
+// Deadline *big.Int
+//}
+//
+//type Witness struct {
+// Witness interface{}
+// WitnessTypeName string
+// WitnessType map[string][]TypedDataField
+//}
+//
+//type PermitTransferFromData struct {
+// Domain TypedDataDomain
+// Types map[string][]TypedDataField
+// Values PermitTransferFrom
+//}
+//
+//var TOKEN_PERMISSIONS = []TypedDataField{
+// {Name: "token", Type: "address"},
+// {Name: "amount", Type: "uint256"},
+//}
+//
+//var PERMIT_TRANSFER_FROM_TYPES = map[string][]TypedDataField{
+// "PermitTransferFrom": {
+// {Name: "permitted", Type: "TokenPermissions"},
+// {Name: "spender", Type: "address"},
+// {Name: "nonce", Type: "uint256"},
+// {Name: "deadline", Type: "uint256"},
+// },
+// "TokenPermissions": TOKEN_PERMISSIONS,
+//}
+//
+//func permit2Domain(permit2Address string, chainId int) TypedDataDomain {
+// return TypedDataDomain{
+// Name: PERMIT2_DOMAIN_NAME,
+// ChainId: chainId,
+// VerifyingContract: permit2Address,
+// }
+//}
+//
+//func permitTransferFromWithWitnessType(witness Witness) map[string][]TypedDataField {
+// return map[string][]TypedDataField{
+// "PermitWitnessTransferFrom": {
+// {Name: "permitted", Type: "TokenPermissions"},
+// {Name: "spender", Type: "address"},
+// {Name: "nonce", Type: "uint256"},
+// {Name: "deadline", Type: "uint256"},
+// {Name: "witness", Type: witness.WitnessTypeName},
+// },
+// "TokenPermissions": TOKEN_PERMISSIONS,
+// witness.WitnessTypeName: witness.WitnessType[witness.WitnessTypeName],
+// }
+//}
+//
+//func validateTokenPermissions(permissions TokenPermissions) {
+// if MaxSignatureTransferAmount.Cmp(permissions.Amount) < 0 {
+// panic("AMOUNT_OUT_OF_RANGE")
+// }
+//}
+//
+//func getPermitTransferFromData(
+// permit PermitTransferFrom,
+// permit2Address string,
+// chainId int,
+// witness *Witness,
+//) PermitTransferFromData {
+// if MaxSigDeadline.Cmp(permit.Deadline) < 0 {
+// panic("SIG_DEADLINE_OUT_OF_RANGE")
+// }
+// if MaxUnorderedNonce.Cmp(permit.Nonce) < 0 {
+// panic("NONCE_OUT_OF_RANGE")
+// }
+//
+// validateTokenPermissions(permit.Permitted)
+//
+// domain := permit2Domain(permit2Address, chainId)
+// var types map[string][]TypedDataField
+// var values interface{}
+//
+// if witness != nil {
+// types = permitTransferFromWithWitnessType(*witness)
+// values = struct {
+// PermitTransferFrom
+// Witness interface{}
+// }{permit, witness.Witness}
+// } else {
+// types = PERMIT_TRANSFER_FROM_TYPES
+// values = permit
+// }
+//
+// return PermitTransferFromData{
+// Domain: domain,
+// Types: types,
+// Values: values.(PermitTransferFrom),
+// }
+//}
+//
+//func hashPermitTransferFrom(
+// permit PermitTransferFrom,
+// permit2Address string,
+// chainId int,
+// witness *Witness,
+//) string {
+// permitData := getPermitTransferFromData(permit, permit2Address, chainId, witness)
+// domainHash := hashDomain(permitData.Domain)
+// structHash := hashStruct(permitData.Types, permitData.Values)
+//
+// finalHash := crypto.Keccak256Hash(append([]byte{0x19, 0x01}, append(domainHash, structHash...)...)).Hex()
+// return finalHash
+//}
+//
+//func hashDomain(domain TypedDataDomain) []byte {
+// var buffer bytes.Buffer
+//
+// buffer.Write(crypto.Keccak256([]byte("EIP712Domain(string name,uint256 chainId,address verifyingContract)")))
+//
+// nameHash := crypto.Keccak256([]byte(domain.Name))
+// buffer.Write(nameHash)
+//
+// chainIdBytes := common.LeftPadBytes(new(big.Int).SetInt64(int64(domain.ChainId)).Bytes(), 32)
+// buffer.Write(chainIdBytes)
+//
+// addressBytes := common.HexToAddress(domain.VerifyingContract).Bytes()
+// buffer.Write(addressBytes)
+//
+// return crypto.Keccak256(buffer.Bytes())
+//}
+//
+//func hashStruct(types map[string][]TypedDataField, values PermitTransferFrom) []byte {
+// var buffer bytes.Buffer
+//
+// primaryType := ""
+// for t := range types {
+// primaryType = t
+// break
+// }
+//
+// buffer.Write(crypto.Keccak256([]byte(primaryType)))
+//
+// value := reflect.ValueOf(values)
+// for _, field := range types[primaryType] {
+// fieldName := field.Name
+// fieldValue := value.FieldByName(cases.Title(language.English).String(fieldName))
+//
+// if field.Type == "address" {
+// buffer.Write(common.HexToAddress(fieldValue.String()).Bytes())
+// } else if field.Type == "uint256" {
+// buffer.Write(common.LeftPadBytes(fieldValue.Interface().(*big.Int).Bytes(), 32))
+// } else if field.Type == "TokenPermissions" {
+// tokenPerm := fieldValue.Interface().(TokenPermissions)
+// buffer.Write(common.HexToAddress(tokenPerm.Token).Bytes())
+// buffer.Write(common.LeftPadBytes(tokenPerm.Amount.Bytes(), 32))
+// }
+// }
+//
+// return crypto.Keccak256(buffer.Bytes())
+//}
+//
+//func (w Wallet) SignPermit2TransferFrom(
+// permit PermitTransferFrom,
+// permit2Address string,
+// witness *Witness,
+//) ([]byte, error) {
+// chainId := int(w.chainId.Uint64())
+// permitData := getPermitTransferFromData(permit, permit2Address, chainId, witness)
+//
+// encodedData, err := abi.JSON(strings.NewReader(abiString))
+// if err != nil {
+// return nil, fmt.Errorf("failed to parse ABI: %v", err)
+// }
+// _, err = encodedData.Pack("permitTransferFrom", permitData, nil)
+// if err != nil {
+// return nil, fmt.Errorf("failed to pack permit data: %v", err)
+// }
+//
+// message := fmt.Sprintf("\x19\x01%s", string(hashPermitTransferFrom(permit, permit2Address, chainId, witness)))
+//
+// hash := crypto.Keccak256Hash([]byte(message))
+// signature, err := crypto.Sign(hash.Bytes(), w.privateKey)
+// if err != nil {
+// return nil, fmt.Errorf("failed to sign permit: %v", err)
+// }
+//
+// return signature, nil
+//}
diff --git a/internal/web3-provider/permit2_test.go b/internal/web3-provider/permit2_test.go
index d6a7289b..ff16111d 100644
--- a/internal/web3-provider/permit2_test.go
+++ b/internal/web3-provider/permit2_test.go
@@ -1,39 +1,32 @@
package web3_provider
-import (
- "bytes"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-func TestHashPermitTransferFrom_MaxValues(t *testing.T) {
- permit := PermitTransferFrom{
- Permitted: TokenPermissions{
- Token: "0x0000000000000000000000000000000000000000",
- Amount: MaxSignatureTransferAmount,
- },
- Spender: "0x0000000000000000000000000000000000000000",
- Nonce: MaxUnorderedNonce,
- Deadline: MaxSigDeadline,
- }
- permit2Address := "0x0000000000000000000000000000000000000000"
- chainId := 1
-
- // Call the hash function
- hash := hashPermitTransferFrom(permit, permit2Address, chainId, nil)
-
- // Define the expected hash value
- expectedHash := "0x99e8cd5cd187c1dcb3c9cb41664cb12c1a3a76143d21b16f7880f4839d2b2ad4"
-
- // Convert expectedHash to bytes for comparison
- expectedHashBytes := common.Hex2Bytes(expectedHash)
-
- // Convert hash to bytes for comparison
- hashBytes := common.Hex2Bytes(hash)
-
- // Compare the hashes
- if !bytes.Equal(hashBytes, expectedHashBytes) {
- t.Errorf("Expected hash %s, but got %s", expectedHash, hash)
- }
-}
+//func TestHashPermitTransferFrom_MaxValues(t *testing.T) {
+// permit := PermitTransferFrom{
+// Permitted: TokenPermissions{
+// Token: "0x0000000000000000000000000000000000000000",
+// Amount: MaxSignatureTransferAmount,
+// },
+// Spender: "0x0000000000000000000000000000000000000000",
+// Nonce: MaxUnorderedNonce,
+// Deadline: MaxSigDeadline,
+// }
+// permit2Address := "0x0000000000000000000000000000000000000000"
+// chainId := 1
+//
+// // Call the hash function
+// hash := hashPermitTransferFrom(permit, permit2Address, chainId, nil)
+//
+// // Define the expected hash value
+// expectedHash := "0x99e8cd5cd187c1dcb3c9cb41664cb12c1a3a76143d21b16f7880f4839d2b2ad4"
+//
+// // Convert expectedHash to bytes for comparison
+// expectedHashBytes := common.Hex2Bytes(expectedHash)
+//
+// // Convert hash to bytes for comparison
+// hashBytes := common.Hex2Bytes(hash)
+//
+// // Compare the hashes
+// if !bytes.Equal(hashBytes, expectedHashBytes) {
+// t.Errorf("Expected hash %s, but got %s", expectedHash, hash)
+// }
+//}
diff --git a/internal/web3-provider/permit2allowance.go b/internal/web3-provider/permit2allowance.go
new file mode 100644
index 00000000..2c59ec51
--- /dev/null
+++ b/internal/web3-provider/permit2allowance.go
@@ -0,0 +1,129 @@
+package web3_provider
+
+import (
+ "log"
+ "math/big"
+
+ "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 PermitDetails struct {
+ Token common.Address
+ Amount *big.Int
+ Expiration *big.Int
+ Nonce *big.Int
+}
+
+type PermitSingle struct {
+ Details PermitDetails
+ Spender common.Address
+ SigDeadline *big.Int
+}
+
+type PermitSingleData struct {
+ Domain apitypes.TypedDataDomain
+ Types apitypes.Types
+ Values apitypes.TypedDataMessage
+}
+
+var PERMIT_DETAILS = []apitypes.Type{
+ {Name: "token", Type: "address"},
+ {Name: "amount", Type: "uint160"},
+ {Name: "expiration", Type: "uint48"},
+ {Name: "nonce", Type: "uint48"},
+}
+
+var PERMIT_TYPES = apitypes.Types{
+ "PermitSingle": {
+ {Name: "details", Type: "PermitDetails"},
+ {Name: "spender", Type: "address"},
+ {Name: "sigDeadline", Type: "uint256"},
+ },
+ "PermitDetails": PERMIT_DETAILS,
+ "EIP712Domain": []apitypes.Type{
+ {Name: "name", Type: "string"},
+ {Name: "version", Type: "string"},
+ {Name: "chainId", Type: "uint256"},
+ {Name: "verifyingContract", Type: "address"},
+ },
+}
+
+func getPermitData(permit PermitSingle, permit2Address common.Address, chainId *big.Int) PermitSingleData {
+ validatePermitDetails(permit.Details)
+
+ domain := apitypes.TypedDataDomain{
+ Name: "Permit2",
+ Version: "1",
+ ChainId: math.NewHexOrDecimal256(chainId.Int64()),
+ VerifyingContract: permit2Address.Hex(),
+ }
+
+ values := map[string]interface{}{
+ "details": map[string]interface{}{
+ "token": permit.Details.Token.Hex(),
+ "amount": permit.Details.Amount,
+ "expiration": permit.Details.Expiration,
+ "nonce": permit.Details.Nonce,
+ },
+ "spender": permit.Spender.Hex(),
+ "sigDeadline": permit.SigDeadline,
+ }
+
+ return PermitSingleData{
+ Domain: domain,
+ Types: PERMIT_TYPES,
+ Values: values,
+ }
+}
+
+func validatePermitDetails(details PermitDetails) {
+ maxUint48 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 48), big.NewInt(1))
+ maxUint160 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 160), big.NewInt(1))
+
+ if details.Amount.Cmp(maxUint160) > 0 {
+ log.Fatal("AMOUNT_OUT_OF_RANGE")
+ }
+ if details.Expiration.Cmp(maxUint48) > 0 {
+ log.Fatal("EXPIRATION_OUT_OF_RANGE")
+ }
+ if details.Nonce.Cmp(maxUint48) > 0 {
+ log.Fatal("NONCE_OUT_OF_RANGE")
+ }
+}
+
+func hashPermitSingle(permit PermitSingle, permit2Address common.Address, chainId *big.Int) (string, error) {
+ permitData := getPermitData(permit, permit2Address, chainId)
+
+ typedData := apitypes.TypedData{
+ Types: permitData.Types,
+ PrimaryType: "PermitSingle",
+ Domain: permitData.Domain,
+ Message: permitData.Values,
+ }
+
+ typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
+ if err != nil {
+ return "", err
+ }
+
+ domainSeparator, err := typedData.HashStruct("EIP712Domain", map[string]interface{}{
+ "name": typedData.Domain.Name,
+ "version": typedData.Domain.Version,
+ "chainId": typedData.Domain.ChainId,
+ "verifyingContract": typedData.Domain.VerifyingContract,
+ })
+ if err != nil {
+ return "", err
+ }
+
+ digest := crypto.Keccak256Hash(
+ []byte("\x19\x01"),
+ domainSeparator,
+ typedDataHash,
+ )
+
+ return digest.Hex(), nil
+}
diff --git a/internal/web3-provider/permit2allowance_test.go b/internal/web3-provider/permit2allowance_test.go
new file mode 100644
index 00000000..61e3580d
--- /dev/null
+++ b/internal/web3-provider/permit2allowance_test.go
@@ -0,0 +1,38 @@
+package web3_provider
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/stretchr/testify/assert"
+)
+
+// Define the maximum values
+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))
+)
+
+func TestMaxValuesPass(t *testing.T) {
+ permitDetails := PermitDetails{
+ Token: common.HexToAddress("0x0000000000000000000000000000000000000000"),
+ Amount: MaxAllowanceTransferAmount,
+ Expiration: MaxAllowanceExpiration,
+ Nonce: MaxOrderedNonce,
+ }
+
+ permit := PermitSingle{
+ Details: permitDetails,
+ Spender: common.HexToAddress("0x0000000000000000000000000000000000000000"),
+ SigDeadline: MaxSigDeadline,
+ }
+
+ permit2Address := common.HexToAddress("0x0000000000000000000000000000000000000000")
+ chainId := big.NewInt(1) // For example, 1 for Ethereum mainnet
+
+ _, err := hashPermitSingle(permit, permit2Address, chainId)
+ assert.NoError(t, err)
+}
From a7b8256b3a56db3f0cfb991ec9fbbc4f2715c213 Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Tue, 25 Jun 2024 18:45:05 +0200
Subject: [PATCH 13/18] chore: some more changes of allowance logic
---
internal/web3-provider/permit2allowance.go | 58 +++++++++----------
.../web3-provider/permit2allowance_test.go | 3 +-
2 files changed, 28 insertions(+), 33 deletions(-)
diff --git a/internal/web3-provider/permit2allowance.go b/internal/web3-provider/permit2allowance.go
index 2c59ec51..47a3023e 100644
--- a/internal/web3-provider/permit2allowance.go
+++ b/internal/web3-provider/permit2allowance.go
@@ -1,12 +1,12 @@
package web3_provider
import (
- "log"
+ "errors"
+ "fmt"
"math/big"
"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"
)
@@ -51,8 +51,11 @@ var PERMIT_TYPES = apitypes.Types{
},
}
-func getPermitData(permit PermitSingle, permit2Address common.Address, chainId *big.Int) PermitSingleData {
- validatePermitDetails(permit.Details)
+func getPermitData(permit PermitSingle, permit2Address common.Address, chainId *big.Int) (PermitSingleData, error) {
+ err := validatePermitDetails(permit.Details)
+ if err != nil {
+ return PermitSingleData{}, err
+ }
domain := apitypes.TypedDataDomain{
Name: "Permit2",
@@ -68,35 +71,42 @@ func getPermitData(permit PermitSingle, permit2Address common.Address, chainId *
"expiration": permit.Details.Expiration,
"nonce": permit.Details.Nonce,
},
- "spender": permit.Spender.Hex(),
- "sigDeadline": permit.SigDeadline,
+ "spender": permit.Spender.Hex(),
+ "sigDeadline": permit.SigDeadline,
+ "name": domain.Name,
+ "version": domain.Version,
+ "chainId": domain.ChainId,
+ "verifyingContract": domain.VerifyingContract,
}
return PermitSingleData{
Domain: domain,
Types: PERMIT_TYPES,
Values: values,
- }
+ }, nil
}
-func validatePermitDetails(details PermitDetails) {
+func validatePermitDetails(details PermitDetails) error {
maxUint48 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 48), big.NewInt(1))
maxUint160 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 160), big.NewInt(1))
if details.Amount.Cmp(maxUint160) > 0 {
- log.Fatal("AMOUNT_OUT_OF_RANGE")
+ return errors.New("AMOUNT_OUT_OF_RANGE")
}
if details.Expiration.Cmp(maxUint48) > 0 {
- log.Fatal("EXPIRATION_OUT_OF_RANGE")
+ return errors.New("EXPIRATION_OUT_OF_RANGE")
}
if details.Nonce.Cmp(maxUint48) > 0 {
- log.Fatal("NONCE_OUT_OF_RANGE")
+ return errors.New("NONCE_OUT_OF_RANGE")
}
+ return nil
}
func hashPermitSingle(permit PermitSingle, permit2Address common.Address, chainId *big.Int) (string, error) {
- permitData := getPermitData(permit, permit2Address, chainId)
-
+ permitData, err := getPermitData(permit, permit2Address, chainId)
+ if err != nil {
+ return "", err
+ }
typedData := apitypes.TypedData{
Types: permitData.Types,
PrimaryType: "PermitSingle",
@@ -104,26 +114,10 @@ func hashPermitSingle(permit PermitSingle, permit2Address common.Address, chainI
Message: permitData.Values,
}
- typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
+ challengeHash, _, err := apitypes.TypedDataAndHash(typedData)
if err != nil {
- return "", err
- }
-
- domainSeparator, err := typedData.HashStruct("EIP712Domain", map[string]interface{}{
- "name": typedData.Domain.Name,
- "version": typedData.Domain.Version,
- "chainId": typedData.Domain.ChainId,
- "verifyingContract": typedData.Domain.VerifyingContract,
- })
- if err != nil {
- return "", err
+ return "", fmt.Errorf("error using TypedDataAndHash: %v", err)
}
- digest := crypto.Keccak256Hash(
- []byte("\x19\x01"),
- domainSeparator,
- typedDataHash,
- )
-
- return digest.Hex(), nil
+ return fmt.Sprintf("%x", challengeHash), nil
}
diff --git a/internal/web3-provider/permit2allowance_test.go b/internal/web3-provider/permit2allowance_test.go
index 61e3580d..c008a9c5 100644
--- a/internal/web3-provider/permit2allowance_test.go
+++ b/internal/web3-provider/permit2allowance_test.go
@@ -33,6 +33,7 @@ func TestMaxValuesPass(t *testing.T) {
permit2Address := common.HexToAddress("0x0000000000000000000000000000000000000000")
chainId := big.NewInt(1) // For example, 1 for Ethereum mainnet
- _, err := hashPermitSingle(permit, permit2Address, chainId)
+ hash, err := hashPermitSingle(permit, permit2Address, chainId)
assert.NoError(t, err)
+ assert.Equal(t, "0x7c7685afe45d5d39b6279f05214f9bb9aa275f541f950d0a97d0c18aa43158c8", hash)
}
From f4f6728654da89add89e4b6f2c3ce7db15de4b39 Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Wed, 26 Jun 2024 12:46:34 +0200
Subject: [PATCH 14/18] fix: make allowance hash works
---
internal/web3-provider/permit2allowance.go | 238 +++++++++++++-----
.../web3-provider/permit2allowance_test.go | 37 ++-
2 files changed, 200 insertions(+), 75 deletions(-)
diff --git a/internal/web3-provider/permit2allowance.go b/internal/web3-provider/permit2allowance.go
index 47a3023e..278c19a6 100644
--- a/internal/web3-provider/permit2allowance.go
+++ b/internal/web3-provider/permit2allowance.go
@@ -1,109 +1,235 @@
package web3_provider
import (
- "errors"
+ "bytes"
"fmt"
"math/big"
+ "reflect"
"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"
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
)
-type PermitDetails struct {
- Token common.Address
- Amount *big.Int
- Expiration *big.Int
- Nonce *big.Int
+type TypedDataField struct {
+ Name string `json:"name"`
+ Type string `json:"type"`
}
-type PermitSingle struct {
- Details PermitDetails
- Spender common.Address
- SigDeadline *big.Int
+type TypedDataDomain struct {
+ Name string `json:"name"`
+ ChainId int `json:"chainId"`
+ VerifyingContract string `json:"verifyingContract"`
+}
+
+type PermitDetails struct {
+ Token string `json:"token"`
+ Amount string `json:"amount"`
+ Expiration string `json:"expiration"`
+ Nonce string `json:"nonce"`
}
-type PermitSingleData struct {
- Domain apitypes.TypedDataDomain
- Types apitypes.Types
- Values apitypes.TypedDataMessage
+type PermitSingle struct {
+ Details PermitDetails `json:"details"`
+ Spender string `json:"spender"`
+ SigDeadline string `json:"sigDeadline"`
}
-var PERMIT_DETAILS = []apitypes.Type{
- {Name: "token", Type: "address"},
- {Name: "amount", Type: "uint160"},
- {Name: "expiration", Type: "uint48"},
- {Name: "nonce", Type: "uint48"},
+type TypedData struct {
+ Domain apitypes.TypedDataDomain `json:"domain"`
+ Types map[string][]apitypes.Type `json:"types"`
+ Values apitypes.TypedDataMessage `json:"values"`
}
-var PERMIT_TYPES = apitypes.Types{
+var PERMIT_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": PERMIT_DETAILS,
- "EIP712Domain": []apitypes.Type{
- {Name: "name", Type: "string"},
- {Name: "version", Type: "string"},
- {Name: "chainId", Type: "uint256"},
- {Name: "verifyingContract", Type: "address"},
+ "PermitDetails": {
+ {Name: "token", Type: "address"},
+ {Name: "amount", Type: "uint160"},
+ {Name: "expiration", Type: "uint48"},
+ {Name: "nonce", Type: "uint48"},
},
}
-func getPermitData(permit PermitSingle, permit2Address common.Address, chainId *big.Int) (PermitSingleData, error) {
- err := validatePermitDetails(permit.Details)
- if err != nil {
- return PermitSingleData{}, err
- }
+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))
+)
- domain := apitypes.TypedDataDomain{
- Name: "Permit2",
- Version: "1",
- ChainId: math.NewHexOrDecimal256(chainId.Int64()),
- VerifyingContract: permit2Address.Hex(),
+func GetPermitData(permit PermitSingle, permit2Address common.Address, chainId int) (TypedData, error) {
+ sigDeadline, ok := new(big.Int).SetString(permit.SigDeadline, 10)
+ if !ok {
+ return TypedData{}, fmt.Errorf("invalid sigDeadline")
+ }
+ if sigDeadline.Cmp(MaxSigDeadline) > 0 {
+ return TypedData{}, fmt.Errorf("SIG_DEADLINE_OUT_OF_RANGE")
}
+ err := validatePermitDetails(permit.Details)
+ if err != nil {
+ return TypedData{}, err
+ }
values := map[string]interface{}{
"details": map[string]interface{}{
- "token": permit.Details.Token.Hex(),
+ "token": permit.Details.Token,
"amount": permit.Details.Amount,
"expiration": permit.Details.Expiration,
"nonce": permit.Details.Nonce,
},
- "spender": permit.Spender.Hex(),
- "sigDeadline": permit.SigDeadline,
- "name": domain.Name,
- "version": domain.Version,
- "chainId": domain.ChainId,
- "verifyingContract": domain.VerifyingContract,
+ "spender": permit.Spender,
+ "sigDeadline": permit.SigDeadline,
}
- return PermitSingleData{
- Domain: domain,
+ return TypedData{
+ Domain: apitypes.TypedDataDomain{
+ Name: "Permit2",
+ ChainId: math.NewHexOrDecimal256(int64(chainId)),
+ VerifyingContract: permit2Address.Hex(),
+ },
Types: PERMIT_TYPES,
Values: values,
}, nil
}
func validatePermitDetails(details PermitDetails) error {
- maxUint48 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 48), big.NewInt(1))
- maxUint160 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 160), big.NewInt(1))
+ nonce, ok := new(big.Int).SetString(details.Nonce, 10)
+ if !ok {
+ return fmt.Errorf("invalid nonce")
+ }
+ if nonce.Cmp(MaxOrderedNonce) > 0 {
+ return fmt.Errorf("NONCE_OUT_OF_RANGE")
+ }
- if details.Amount.Cmp(maxUint160) > 0 {
- return errors.New("AMOUNT_OUT_OF_RANGE")
+ amount, ok := new(big.Int).SetString(details.Amount, 10)
+ if !ok {
+ return fmt.Errorf("invalid amount")
}
- if details.Expiration.Cmp(maxUint48) > 0 {
- return errors.New("EXPIRATION_OUT_OF_RANGE")
+ if amount.Cmp(MaxAllowanceTransferAmount) > 0 {
+ return fmt.Errorf("AMOUNT_OUT_OF_RANGE")
}
- if details.Nonce.Cmp(maxUint48) > 0 {
- return errors.New("NONCE_OUT_OF_RANGE")
+
+ expiration, ok := new(big.Int).SetString(details.Expiration, 10)
+ if !ok {
+ return fmt.Errorf("invalid expiration")
+ }
+ if expiration.Cmp(MaxAllowanceExpiration) > 0 {
+ return fmt.Errorf("EXPIRATION_OUT_OF_RANGE")
}
+
return nil
}
-func hashPermitSingle(permit PermitSingle, permit2Address common.Address, chainId *big.Int) (string, error) {
- permitData, err := getPermitData(permit, permit2Address, chainId)
+func hashPermitDetails(types map[string][]TypedDataField, details PermitDetails) []byte {
+ var buffer bytes.Buffer
+
+ buffer.Write(crypto.Keccak256([]byte("PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)")))
+
+ value := reflect.ValueOf(details)
+ for _, field := range types["PermitDetails"] {
+ fieldName := field.Name
+ fieldValue := value.FieldByName(cases.Title(language.English).String(fieldName))
+
+ switch field.Type {
+ case "address":
+ buffer.Write(common.HexToAddress(fieldValue.String()).Bytes())
+ case "uint160":
+ intValue, ok := new(big.Int).SetString(fieldValue.String(), 10)
+ if !ok {
+ panic(fmt.Sprintf("Invalid uint160 value for field %s", fieldName))
+ }
+ buffer.Write(common.LeftPadBytes(intValue.Bytes(), 20))
+ case "uint48":
+ intValue, ok := new(big.Int).SetString(fieldValue.String(), 10)
+ if !ok {
+ panic(fmt.Sprintf("Invalid uint48 value for field %s", fieldName))
+ }
+ buffer.Write(common.LeftPadBytes(intValue.Bytes(), 6))
+ }
+ }
+
+ return crypto.Keccak256(buffer.Bytes())
+}
+
+func hashStruct(types map[string][]TypedDataField, values PermitSingle) []byte {
+ var buffer bytes.Buffer
+
+ primaryType := "PermitSingle"
+ buffer.Write(crypto.Keccak256([]byte("PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)")))
+
+ value := reflect.ValueOf(values)
+ for _, field := range types[primaryType] {
+ fieldName := field.Name
+ // Manually handle the specific case for "sigDeadline" to "SigDeadline"
+ var fieldValue reflect.Value
+ if fieldName == "sigDeadline" {
+ fieldValue = value.FieldByName("SigDeadline")
+ } else {
+ fieldValue = value.FieldByName(cases.Title(language.English).String(fieldName))
+ }
+
+ switch field.Type {
+ case "address":
+ buffer.Write(common.HexToAddress(fieldValue.String()).Bytes())
+ case "uint160":
+ intValue, ok := new(big.Int).SetString(fieldValue.String(), 10)
+ if !ok {
+ panic(fmt.Sprintf("Invalid uint160 value for field %s", fieldName))
+ }
+ buffer.Write(common.LeftPadBytes(intValue.Bytes(), 20))
+ case "uint256":
+ intValue, ok := new(big.Int).SetString(fieldValue.String(), 10)
+ if !ok {
+ panic(fmt.Sprintf("Invalid uint256 value for field %s", fieldName))
+ }
+ buffer.Write(common.LeftPadBytes(intValue.Bytes(), 32))
+ case "uint48":
+ intValue, ok := new(big.Int).SetString(fieldValue.String(), 10)
+ if !ok {
+ panic(fmt.Sprintf("Invalid uint48 value for field %s", fieldName))
+ }
+ buffer.Write(common.LeftPadBytes(intValue.Bytes(), 6))
+ case "PermitDetails":
+ buffer.Write(hashPermitDetails(types, fieldValue.Interface().(PermitDetails)))
+
+ }
+ }
+
+ return crypto.Keccak256(buffer.Bytes())
+}
+
+func hashDomain(domain TypedDataDomain) []byte {
+ var buffer bytes.Buffer
+
+ buffer.Write(crypto.Keccak256([]byte("EIP712Domain(string name,uint256 chainId,address verifyingContract)")))
+
+ nameHash := crypto.Keccak256([]byte(domain.Name))
+ buffer.Write(nameHash)
+
+ chainIdBytes := common.LeftPadBytes(new(big.Int).SetInt64(int64(domain.ChainId)).Bytes(), 32)
+ buffer.Write(chainIdBytes)
+
+ addressBytes := common.HexToAddress(domain.VerifyingContract).Bytes()
+ buffer.Write(addressBytes)
+
+ return crypto.Keccak256(buffer.Bytes())
+}
+
+func hashPermitData(permit PermitSingle, permit2Address common.Address, chainId int) (string, error) {
+ permitData, err := GetPermitData(permit, permit2Address, chainId)
if err != nil {
return "", err
}
@@ -119,5 +245,5 @@ func hashPermitSingle(permit PermitSingle, permit2Address common.Address, chainI
return "", fmt.Errorf("error using TypedDataAndHash: %v", err)
}
- return fmt.Sprintf("%x", challengeHash), nil
+ return "0x" + common.Bytes2Hex(challengeHash), nil
}
diff --git a/internal/web3-provider/permit2allowance_test.go b/internal/web3-provider/permit2allowance_test.go
index c008a9c5..bfe474f0 100644
--- a/internal/web3-provider/permit2allowance_test.go
+++ b/internal/web3-provider/permit2allowance_test.go
@@ -1,6 +1,7 @@
package web3_provider
import (
+ "fmt"
"math/big"
"testing"
@@ -8,32 +9,30 @@ import (
"github.com/stretchr/testify/assert"
)
-// Define the maximum values
-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))
-)
-
func TestMaxValuesPass(t *testing.T) {
- permitDetails := PermitDetails{
- Token: common.HexToAddress("0x0000000000000000000000000000000000000000"),
- Amount: MaxAllowanceTransferAmount,
- Expiration: MaxAllowanceExpiration,
- Nonce: MaxOrderedNonce,
- }
permit := PermitSingle{
- Details: permitDetails,
- Spender: common.HexToAddress("0x0000000000000000000000000000000000000000"),
- SigDeadline: MaxSigDeadline,
+ Details: PermitDetails{
+ Token: "0x0000000000000000000000000000000000000000",
+ Amount: "1461501637330902918203684832716283019655932542975",
+ Expiration: "281474976710655",
+ Nonce: "281474976710655",
+ },
+ Spender: "0x0000000000000000000000000000000000000000",
+ SigDeadline: "115792089237316195423570985008687907853269984665640564039457584007913129639935",
}
permit2Address := common.HexToAddress("0x0000000000000000000000000000000000000000")
- chainId := big.NewInt(1) // For example, 1 for Ethereum mainnet
+ chainId := big.NewInt(1)
- hash, err := hashPermitSingle(permit, permit2Address, chainId)
+ _, err := GetPermitData(permit, permit2Address, int(chainId.Int64()))
assert.NoError(t, err)
+
+ hash, err := hashPermitData(permit, permit2Address, int(chainId.Int64()))
+ if err != nil {
+ fmt.Println("Error:", err)
+ return
+ }
+
assert.Equal(t, "0x7c7685afe45d5d39b6279f05214f9bb9aa275f541f950d0a97d0c18aa43158c8", hash)
}
From 9aa7863c8bebb3b33fdbc48d03f8c8e12ee75b2e Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Wed, 26 Jun 2024 12:53:51 +0200
Subject: [PATCH 15/18] feat: refactor
---
internal/web3-provider/permit2.go | 254 ------------------
internal/web3-provider/permit2_test.go | 32 ---
internal/web3-provider/permit2allowance.go | 174 ++----------
.../web3-provider/permit2allowance_test.go | 6 +-
4 files changed, 29 insertions(+), 437 deletions(-)
delete mode 100644 internal/web3-provider/permit2.go
delete mode 100644 internal/web3-provider/permit2_test.go
diff --git a/internal/web3-provider/permit2.go b/internal/web3-provider/permit2.go
deleted file mode 100644
index fef0fdf0..00000000
--- a/internal/web3-provider/permit2.go
+++ /dev/null
@@ -1,254 +0,0 @@
-package web3_provider
-
-//
-//import (
-// "bytes"
-// "fmt"
-// "math/big"
-// "reflect"
-// "strings"
-//
-// "github.com/ethereum/go-ethereum/accounts/abi"
-// "github.com/ethereum/go-ethereum/common"
-// "github.com/ethereum/go-ethereum/crypto"
-// "golang.org/x/text/cases"
-// "golang.org/x/text/language"
-//)
-//
-//const (
-// PERMIT2_DOMAIN_NAME = "Permit2"
-//)
-//
-//const (
-// // Update this with the ABI for your contract
-// abiString = `
-//[
-// {
-// "type": "function",
-// "name": "permitTransferFrom",
-// "inputs": [
-// { "name": "data", "type": "PermitTransferFromData" },
-// { "name": "witness", "type": "bytes" }
-// ]
-// }
-//]
-//`
-//)
-//
-//var (
-// MaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
-// MaxSignatureTransferAmount = MaxUint256
-// MaxUnorderedNonce = MaxUint256
-// MaxSigDeadline = MaxUint256
-//)
-//
-//type TypedDataDomain struct {
-// Name string
-// ChainId int
-// VerifyingContract string
-//}
-//
-//type TypedDataField struct {
-// Name string
-// Type string
-//}
-//
-//type TokenPermissions struct {
-// Token string
-// Amount *big.Int
-//}
-//
-//type PermitTransferFrom struct {
-// Permitted TokenPermissions
-// Spender string
-// Nonce *big.Int
-// Deadline *big.Int
-//}
-//
-//type PermitBatchTransferFrom struct {
-// Permitted []TokenPermissions
-// Spender string
-// Nonce *big.Int
-// Deadline *big.Int
-//}
-//
-//type Witness struct {
-// Witness interface{}
-// WitnessTypeName string
-// WitnessType map[string][]TypedDataField
-//}
-//
-//type PermitTransferFromData struct {
-// Domain TypedDataDomain
-// Types map[string][]TypedDataField
-// Values PermitTransferFrom
-//}
-//
-//var TOKEN_PERMISSIONS = []TypedDataField{
-// {Name: "token", Type: "address"},
-// {Name: "amount", Type: "uint256"},
-//}
-//
-//var PERMIT_TRANSFER_FROM_TYPES = map[string][]TypedDataField{
-// "PermitTransferFrom": {
-// {Name: "permitted", Type: "TokenPermissions"},
-// {Name: "spender", Type: "address"},
-// {Name: "nonce", Type: "uint256"},
-// {Name: "deadline", Type: "uint256"},
-// },
-// "TokenPermissions": TOKEN_PERMISSIONS,
-//}
-//
-//func permit2Domain(permit2Address string, chainId int) TypedDataDomain {
-// return TypedDataDomain{
-// Name: PERMIT2_DOMAIN_NAME,
-// ChainId: chainId,
-// VerifyingContract: permit2Address,
-// }
-//}
-//
-//func permitTransferFromWithWitnessType(witness Witness) map[string][]TypedDataField {
-// return map[string][]TypedDataField{
-// "PermitWitnessTransferFrom": {
-// {Name: "permitted", Type: "TokenPermissions"},
-// {Name: "spender", Type: "address"},
-// {Name: "nonce", Type: "uint256"},
-// {Name: "deadline", Type: "uint256"},
-// {Name: "witness", Type: witness.WitnessTypeName},
-// },
-// "TokenPermissions": TOKEN_PERMISSIONS,
-// witness.WitnessTypeName: witness.WitnessType[witness.WitnessTypeName],
-// }
-//}
-//
-//func validateTokenPermissions(permissions TokenPermissions) {
-// if MaxSignatureTransferAmount.Cmp(permissions.Amount) < 0 {
-// panic("AMOUNT_OUT_OF_RANGE")
-// }
-//}
-//
-//func getPermitTransferFromData(
-// permit PermitTransferFrom,
-// permit2Address string,
-// chainId int,
-// witness *Witness,
-//) PermitTransferFromData {
-// if MaxSigDeadline.Cmp(permit.Deadline) < 0 {
-// panic("SIG_DEADLINE_OUT_OF_RANGE")
-// }
-// if MaxUnorderedNonce.Cmp(permit.Nonce) < 0 {
-// panic("NONCE_OUT_OF_RANGE")
-// }
-//
-// validateTokenPermissions(permit.Permitted)
-//
-// domain := permit2Domain(permit2Address, chainId)
-// var types map[string][]TypedDataField
-// var values interface{}
-//
-// if witness != nil {
-// types = permitTransferFromWithWitnessType(*witness)
-// values = struct {
-// PermitTransferFrom
-// Witness interface{}
-// }{permit, witness.Witness}
-// } else {
-// types = PERMIT_TRANSFER_FROM_TYPES
-// values = permit
-// }
-//
-// return PermitTransferFromData{
-// Domain: domain,
-// Types: types,
-// Values: values.(PermitTransferFrom),
-// }
-//}
-//
-//func hashPermitTransferFrom(
-// permit PermitTransferFrom,
-// permit2Address string,
-// chainId int,
-// witness *Witness,
-//) string {
-// permitData := getPermitTransferFromData(permit, permit2Address, chainId, witness)
-// domainHash := hashDomain(permitData.Domain)
-// structHash := hashStruct(permitData.Types, permitData.Values)
-//
-// finalHash := crypto.Keccak256Hash(append([]byte{0x19, 0x01}, append(domainHash, structHash...)...)).Hex()
-// return finalHash
-//}
-//
-//func hashDomain(domain TypedDataDomain) []byte {
-// var buffer bytes.Buffer
-//
-// buffer.Write(crypto.Keccak256([]byte("EIP712Domain(string name,uint256 chainId,address verifyingContract)")))
-//
-// nameHash := crypto.Keccak256([]byte(domain.Name))
-// buffer.Write(nameHash)
-//
-// chainIdBytes := common.LeftPadBytes(new(big.Int).SetInt64(int64(domain.ChainId)).Bytes(), 32)
-// buffer.Write(chainIdBytes)
-//
-// addressBytes := common.HexToAddress(domain.VerifyingContract).Bytes()
-// buffer.Write(addressBytes)
-//
-// return crypto.Keccak256(buffer.Bytes())
-//}
-//
-//func hashStruct(types map[string][]TypedDataField, values PermitTransferFrom) []byte {
-// var buffer bytes.Buffer
-//
-// primaryType := ""
-// for t := range types {
-// primaryType = t
-// break
-// }
-//
-// buffer.Write(crypto.Keccak256([]byte(primaryType)))
-//
-// value := reflect.ValueOf(values)
-// for _, field := range types[primaryType] {
-// fieldName := field.Name
-// fieldValue := value.FieldByName(cases.Title(language.English).String(fieldName))
-//
-// if field.Type == "address" {
-// buffer.Write(common.HexToAddress(fieldValue.String()).Bytes())
-// } else if field.Type == "uint256" {
-// buffer.Write(common.LeftPadBytes(fieldValue.Interface().(*big.Int).Bytes(), 32))
-// } else if field.Type == "TokenPermissions" {
-// tokenPerm := fieldValue.Interface().(TokenPermissions)
-// buffer.Write(common.HexToAddress(tokenPerm.Token).Bytes())
-// buffer.Write(common.LeftPadBytes(tokenPerm.Amount.Bytes(), 32))
-// }
-// }
-//
-// return crypto.Keccak256(buffer.Bytes())
-//}
-//
-//func (w Wallet) SignPermit2TransferFrom(
-// permit PermitTransferFrom,
-// permit2Address string,
-// witness *Witness,
-//) ([]byte, error) {
-// chainId := int(w.chainId.Uint64())
-// permitData := getPermitTransferFromData(permit, permit2Address, chainId, witness)
-//
-// encodedData, err := abi.JSON(strings.NewReader(abiString))
-// if err != nil {
-// return nil, fmt.Errorf("failed to parse ABI: %v", err)
-// }
-// _, err = encodedData.Pack("permitTransferFrom", permitData, nil)
-// if err != nil {
-// return nil, fmt.Errorf("failed to pack permit data: %v", err)
-// }
-//
-// message := fmt.Sprintf("\x19\x01%s", string(hashPermitTransferFrom(permit, permit2Address, chainId, witness)))
-//
-// hash := crypto.Keccak256Hash([]byte(message))
-// signature, err := crypto.Sign(hash.Bytes(), w.privateKey)
-// if err != nil {
-// return nil, fmt.Errorf("failed to sign permit: %v", err)
-// }
-//
-// return signature, nil
-//}
diff --git a/internal/web3-provider/permit2_test.go b/internal/web3-provider/permit2_test.go
deleted file mode 100644
index ff16111d..00000000
--- a/internal/web3-provider/permit2_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package web3_provider
-
-//func TestHashPermitTransferFrom_MaxValues(t *testing.T) {
-// permit := PermitTransferFrom{
-// Permitted: TokenPermissions{
-// Token: "0x0000000000000000000000000000000000000000",
-// Amount: MaxSignatureTransferAmount,
-// },
-// Spender: "0x0000000000000000000000000000000000000000",
-// Nonce: MaxUnorderedNonce,
-// Deadline: MaxSigDeadline,
-// }
-// permit2Address := "0x0000000000000000000000000000000000000000"
-// chainId := 1
-//
-// // Call the hash function
-// hash := hashPermitTransferFrom(permit, permit2Address, chainId, nil)
-//
-// // Define the expected hash value
-// expectedHash := "0x99e8cd5cd187c1dcb3c9cb41664cb12c1a3a76143d21b16f7880f4839d2b2ad4"
-//
-// // Convert expectedHash to bytes for comparison
-// expectedHashBytes := common.Hex2Bytes(expectedHash)
-//
-// // Convert hash to bytes for comparison
-// hashBytes := common.Hex2Bytes(hash)
-//
-// // Compare the hashes
-// if !bytes.Equal(hashBytes, expectedHashBytes) {
-// t.Errorf("Expected hash %s, but got %s", expectedHash, hash)
-// }
-//}
diff --git a/internal/web3-provider/permit2allowance.go b/internal/web3-provider/permit2allowance.go
index 278c19a6..e9b837dc 100644
--- a/internal/web3-provider/permit2allowance.go
+++ b/internal/web3-provider/permit2allowance.go
@@ -1,47 +1,25 @@
package web3_provider
import (
- "bytes"
"fmt"
"math/big"
- "reflect"
"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"
- "golang.org/x/text/cases"
- "golang.org/x/text/language"
)
-type TypedDataField struct {
- Name string `json:"name"`
- Type string `json:"type"`
-}
-
-type TypedDataDomain struct {
- Name string `json:"name"`
- ChainId int `json:"chainId"`
- VerifyingContract string `json:"verifyingContract"`
-}
-
-type PermitDetails struct {
+type AllowancePermitDetails struct {
Token string `json:"token"`
Amount string `json:"amount"`
Expiration string `json:"expiration"`
Nonce string `json:"nonce"`
}
-type PermitSingle struct {
- Details PermitDetails `json:"details"`
- Spender string `json:"spender"`
- SigDeadline string `json:"sigDeadline"`
-}
-
-type TypedData struct {
- Domain apitypes.TypedDataDomain `json:"domain"`
- Types map[string][]apitypes.Type `json:"types"`
- Values apitypes.TypedDataMessage `json:"values"`
+type AllowancePermitSingle struct {
+ Details AllowancePermitDetails `json:"details"`
+ Spender string `json:"spender"`
+ SigDeadline string `json:"sigDeadline"`
}
var PERMIT_TYPES = map[string][]apitypes.Type{
@@ -70,21 +48,13 @@ var (
MaxSigDeadline = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
)
-func GetPermitData(permit PermitSingle, permit2Address common.Address, chainId int) (TypedData, error) {
- sigDeadline, ok := new(big.Int).SetString(permit.SigDeadline, 10)
- if !ok {
- return TypedData{}, fmt.Errorf("invalid sigDeadline")
- }
- if sigDeadline.Cmp(MaxSigDeadline) > 0 {
- return TypedData{}, fmt.Errorf("SIG_DEADLINE_OUT_OF_RANGE")
- }
-
- err := validatePermitDetails(permit.Details)
+func GetTypedDataAllowancePermitSingle(permit AllowancePermitSingle, permit2Address common.Address, chainId int) (apitypes.TypedData, error) {
+ err := validatePermit(permit)
if err != nil {
- return TypedData{}, err
+ return apitypes.TypedData{}, err
}
- values := map[string]interface{}{
- "details": map[string]interface{}{
+ values := apitypes.TypedDataMessage{
+ "details": apitypes.TypedDataMessage{
"token": permit.Details.Token,
"amount": permit.Details.Amount,
"expiration": permit.Details.Expiration,
@@ -94,19 +64,20 @@ func GetPermitData(permit PermitSingle, permit2Address common.Address, chainId i
"sigDeadline": permit.SigDeadline,
}
- return TypedData{
+ return apitypes.TypedData{
Domain: apitypes.TypedDataDomain{
Name: "Permit2",
ChainId: math.NewHexOrDecimal256(int64(chainId)),
VerifyingContract: permit2Address.Hex(),
},
- Types: PERMIT_TYPES,
- Values: values,
+ Types: PERMIT_TYPES,
+ Message: values,
+ PrimaryType: "PermitSingle",
}, nil
}
-func validatePermitDetails(details PermitDetails) error {
- nonce, ok := new(big.Int).SetString(details.Nonce, 10)
+func validatePermit(permit AllowancePermitSingle) error {
+ nonce, ok := new(big.Int).SetString(permit.Details.Nonce, 10)
if !ok {
return fmt.Errorf("invalid nonce")
}
@@ -114,7 +85,7 @@ func validatePermitDetails(details PermitDetails) error {
return fmt.Errorf("NONCE_OUT_OF_RANGE")
}
- amount, ok := new(big.Int).SetString(details.Amount, 10)
+ amount, ok := new(big.Int).SetString(permit.Details.Amount, 10)
if !ok {
return fmt.Errorf("invalid amount")
}
@@ -122,7 +93,7 @@ func validatePermitDetails(details PermitDetails) error {
return fmt.Errorf("AMOUNT_OUT_OF_RANGE")
}
- expiration, ok := new(big.Int).SetString(details.Expiration, 10)
+ expiration, ok := new(big.Int).SetString(permit.Details.Expiration, 10)
if !ok {
return fmt.Errorf("invalid expiration")
}
@@ -130,115 +101,22 @@ func validatePermitDetails(details PermitDetails) error {
return fmt.Errorf("EXPIRATION_OUT_OF_RANGE")
}
- return nil
-}
-
-func hashPermitDetails(types map[string][]TypedDataField, details PermitDetails) []byte {
- var buffer bytes.Buffer
-
- buffer.Write(crypto.Keccak256([]byte("PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)")))
-
- value := reflect.ValueOf(details)
- for _, field := range types["PermitDetails"] {
- fieldName := field.Name
- fieldValue := value.FieldByName(cases.Title(language.English).String(fieldName))
-
- switch field.Type {
- case "address":
- buffer.Write(common.HexToAddress(fieldValue.String()).Bytes())
- case "uint160":
- intValue, ok := new(big.Int).SetString(fieldValue.String(), 10)
- if !ok {
- panic(fmt.Sprintf("Invalid uint160 value for field %s", fieldName))
- }
- buffer.Write(common.LeftPadBytes(intValue.Bytes(), 20))
- case "uint48":
- intValue, ok := new(big.Int).SetString(fieldValue.String(), 10)
- if !ok {
- panic(fmt.Sprintf("Invalid uint48 value for field %s", fieldName))
- }
- buffer.Write(common.LeftPadBytes(intValue.Bytes(), 6))
- }
+ sigDeadline, ok := new(big.Int).SetString(permit.SigDeadline, 10)
+ if !ok {
+ return fmt.Errorf("invalid sigDeadline")
}
-
- return crypto.Keccak256(buffer.Bytes())
-}
-
-func hashStruct(types map[string][]TypedDataField, values PermitSingle) []byte {
- var buffer bytes.Buffer
-
- primaryType := "PermitSingle"
- buffer.Write(crypto.Keccak256([]byte("PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)")))
-
- value := reflect.ValueOf(values)
- for _, field := range types[primaryType] {
- fieldName := field.Name
- // Manually handle the specific case for "sigDeadline" to "SigDeadline"
- var fieldValue reflect.Value
- if fieldName == "sigDeadline" {
- fieldValue = value.FieldByName("SigDeadline")
- } else {
- fieldValue = value.FieldByName(cases.Title(language.English).String(fieldName))
- }
-
- switch field.Type {
- case "address":
- buffer.Write(common.HexToAddress(fieldValue.String()).Bytes())
- case "uint160":
- intValue, ok := new(big.Int).SetString(fieldValue.String(), 10)
- if !ok {
- panic(fmt.Sprintf("Invalid uint160 value for field %s", fieldName))
- }
- buffer.Write(common.LeftPadBytes(intValue.Bytes(), 20))
- case "uint256":
- intValue, ok := new(big.Int).SetString(fieldValue.String(), 10)
- if !ok {
- panic(fmt.Sprintf("Invalid uint256 value for field %s", fieldName))
- }
- buffer.Write(common.LeftPadBytes(intValue.Bytes(), 32))
- case "uint48":
- intValue, ok := new(big.Int).SetString(fieldValue.String(), 10)
- if !ok {
- panic(fmt.Sprintf("Invalid uint48 value for field %s", fieldName))
- }
- buffer.Write(common.LeftPadBytes(intValue.Bytes(), 6))
- case "PermitDetails":
- buffer.Write(hashPermitDetails(types, fieldValue.Interface().(PermitDetails)))
-
- }
+ if sigDeadline.Cmp(MaxSigDeadline) > 0 {
+ return fmt.Errorf("SIG_DEADLINE_OUT_OF_RANGE")
}
- return crypto.Keccak256(buffer.Bytes())
-}
-
-func hashDomain(domain TypedDataDomain) []byte {
- var buffer bytes.Buffer
-
- buffer.Write(crypto.Keccak256([]byte("EIP712Domain(string name,uint256 chainId,address verifyingContract)")))
-
- nameHash := crypto.Keccak256([]byte(domain.Name))
- buffer.Write(nameHash)
-
- chainIdBytes := common.LeftPadBytes(new(big.Int).SetInt64(int64(domain.ChainId)).Bytes(), 32)
- buffer.Write(chainIdBytes)
-
- addressBytes := common.HexToAddress(domain.VerifyingContract).Bytes()
- buffer.Write(addressBytes)
-
- return crypto.Keccak256(buffer.Bytes())
+ return nil
}
-func hashPermitData(permit PermitSingle, permit2Address common.Address, chainId int) (string, error) {
- permitData, err := GetPermitData(permit, permit2Address, chainId)
+func hashPermitData(permit AllowancePermitSingle, permit2Address common.Address, chainId int) (string, error) {
+ typedData, err := GetTypedDataAllowancePermitSingle(permit, permit2Address, chainId)
if err != nil {
return "", err
}
- typedData := apitypes.TypedData{
- Types: permitData.Types,
- PrimaryType: "PermitSingle",
- Domain: permitData.Domain,
- Message: permitData.Values,
- }
challengeHash, _, err := apitypes.TypedDataAndHash(typedData)
if err != nil {
diff --git a/internal/web3-provider/permit2allowance_test.go b/internal/web3-provider/permit2allowance_test.go
index bfe474f0..7bc58776 100644
--- a/internal/web3-provider/permit2allowance_test.go
+++ b/internal/web3-provider/permit2allowance_test.go
@@ -11,8 +11,8 @@ import (
func TestMaxValuesPass(t *testing.T) {
- permit := PermitSingle{
- Details: PermitDetails{
+ permit := AllowancePermitSingle{
+ Details: AllowancePermitDetails{
Token: "0x0000000000000000000000000000000000000000",
Amount: "1461501637330902918203684832716283019655932542975",
Expiration: "281474976710655",
@@ -25,7 +25,7 @@ func TestMaxValuesPass(t *testing.T) {
permit2Address := common.HexToAddress("0x0000000000000000000000000000000000000000")
chainId := big.NewInt(1)
- _, err := GetPermitData(permit, permit2Address, int(chainId.Int64()))
+ _, err := GetTypedDataAllowancePermitSingle(permit, permit2Address, int(chainId.Int64()))
assert.NoError(t, err)
hash, err := hashPermitData(permit, permit2Address, int(chainId.Int64()))
From d8a3661d90a7d2d818ab4ce73311235941084d36 Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Wed, 26 Jun 2024 13:05:24 +0200
Subject: [PATCH 16/18] chore: more cleanup
---
internal/web3-provider/permit2allowance.go | 61 +++++++++----------
.../web3-provider/permit2allowance_test.go | 2 +-
2 files changed, 30 insertions(+), 33 deletions(-)
diff --git a/internal/web3-provider/permit2allowance.go b/internal/web3-provider/permit2allowance.go
index e9b837dc..3b89ac15 100644
--- a/internal/web3-provider/permit2allowance.go
+++ b/internal/web3-provider/permit2allowance.go
@@ -22,25 +22,6 @@ type AllowancePermitSingle struct {
SigDeadline string `json:"sigDeadline"`
}
-var PERMIT_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"},
- },
-}
-
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))
@@ -53,16 +34,6 @@ func GetTypedDataAllowancePermitSingle(permit AllowancePermitSingle, permit2Addr
if err != nil {
return apitypes.TypedData{}, err
}
- values := 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,
- }
return apitypes.TypedData{
Domain: apitypes.TypedDataDomain{
@@ -70,8 +41,34 @@ func GetTypedDataAllowancePermitSingle(permit AllowancePermitSingle, permit2Addr
ChainId: math.NewHexOrDecimal256(int64(chainId)),
VerifyingContract: permit2Address.Hex(),
},
- Types: PERMIT_TYPES,
- Message: values,
+ 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
}
@@ -112,7 +109,7 @@ func validatePermit(permit AllowancePermitSingle) error {
return nil
}
-func hashPermitData(permit AllowancePermitSingle, permit2Address common.Address, chainId int) (string, error) {
+func AllowancePermitSingleTypedDataHash(permit AllowancePermitSingle, permit2Address common.Address, chainId int) (string, error) {
typedData, err := GetTypedDataAllowancePermitSingle(permit, permit2Address, chainId)
if err != nil {
return "", err
diff --git a/internal/web3-provider/permit2allowance_test.go b/internal/web3-provider/permit2allowance_test.go
index 7bc58776..50a8fac6 100644
--- a/internal/web3-provider/permit2allowance_test.go
+++ b/internal/web3-provider/permit2allowance_test.go
@@ -28,7 +28,7 @@ func TestMaxValuesPass(t *testing.T) {
_, err := GetTypedDataAllowancePermitSingle(permit, permit2Address, int(chainId.Int64()))
assert.NoError(t, err)
- hash, err := hashPermitData(permit, permit2Address, int(chainId.Int64()))
+ hash, err := AllowancePermitSingleTypedDataHash(permit, permit2Address, int(chainId.Int64()))
if err != nil {
fmt.Println("Error:", err)
return
From 1871b658cf28527159a480a0f14fc99751466b4a Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Thu, 27 Jun 2024 13:32:19 +0200
Subject: [PATCH 17/18] feat: add fetch-nonce logic
---
internal/web3-provider/permit2allowance.go | 67 ++++++++++++++++++++--
1 file changed, 61 insertions(+), 6 deletions(-)
diff --git a/internal/web3-provider/permit2allowance.go b/internal/web3-provider/permit2allowance.go
index 3b89ac15..c337a7c9 100644
--- a/internal/web3-provider/permit2allowance.go
+++ b/internal/web3-provider/permit2allowance.go
@@ -1,14 +1,25 @@
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/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"`
@@ -22,12 +33,49 @@ type AllowancePermitSingle struct {
SigDeadline string `json:"sigDeadline"`
}
-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))
-)
+func (w Wallet) GetAllowancePermitSingle(ctx context.Context, params AllowancePermitParams) (apitypes.TypedData, error) {
+ callData, err := w.erc20ABI.Pack("allowance", 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)
@@ -74,6 +122,13 @@ func GetTypedDataAllowancePermitSingle(permit AllowancePermitSingle, permit2Addr
}
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")
From 2839e0cc3e2491ae21d1fb74a5532d347ba13718 Mon Sep 17 00:00:00 2001
From: Nick Kozlov <22479658+EnoRage@users.noreply.github.com>
Date: Thu, 27 Jun 2024 13:51:36 +0200
Subject: [PATCH 18/18] feat: start working on final permit2 func for sign +
call generation
---
internal/web3-provider/permit2allowance.go | 28 +++++++++++++++++++++-
internal/web3-provider/provider.go | 1 +
2 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/internal/web3-provider/permit2allowance.go b/internal/web3-provider/permit2allowance.go
index c337a7c9..5a86a287 100644
--- a/internal/web3-provider/permit2allowance.go
+++ b/internal/web3-provider/permit2allowance.go
@@ -8,6 +8,7 @@ import (
"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"
)
@@ -34,7 +35,7 @@ type AllowancePermitSingle struct {
}
func (w Wallet) GetAllowancePermitSingle(ctx context.Context, params AllowancePermitParams) (apitypes.TypedData, error) {
- callData, err := w.erc20ABI.Pack("allowance", w.address.Hex(), params.Token, params.Spender)
+ 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)
}
@@ -177,3 +178,28 @@ func AllowancePermitSingleTypedDataHash(permit AllowancePermitSingle, permit2Add
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/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) {