From 18f51d957c319f78a5800cce08381400e7ca3da0 Mon Sep 17 00:00:00 2001 From: Tanner Moore Date: Wed, 22 Oct 2025 18:01:37 -0600 Subject: [PATCH 1/3] small update and improvement to examples --- .../examples/approve_and_swap/main.go | 148 ++++++++++++++++++ sdk-clients/aggregation/examples/swap/main.go | 14 +- 2 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 sdk-clients/aggregation/examples/approve_and_swap/main.go diff --git a/sdk-clients/aggregation/examples/approve_and_swap/main.go b/sdk-clients/aggregation/examples/approve_and_swap/main.go new file mode 100644 index 0000000..fdf421b --- /dev/null +++ b/sdk-clients/aggregation/examples/approve_and_swap/main.go @@ -0,0 +1,148 @@ +package main + +import ( + "context" + "fmt" + "log" + "math/big" + "os" + "time" + + "github.com/1inch/1inch-sdk-go/constants" + "github.com/1inch/1inch-sdk-go/sdk-clients/aggregation" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var ( + privateKey = os.Getenv("WALLET_KEY") + nodeUrl = os.Getenv("NODE_URL") + devPortalToken = os.Getenv("DEV_PORTAL_TOKEN") +) + +const ( + UsdcBase = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" + WethBase = "0x4200000000000000000000000000000000000006" + amountUsdc = "100000" // 0.1 USDC (6 decimals) +) + +func main() { + config, err := aggregation.NewConfiguration(aggregation.ConfigurationParams{ + NodeUrl: nodeUrl, + PrivateKey: privateKey, + ChainId: constants.BaseChainId, + 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() + + walletAddr := client.Wallet.Address().Hex() + + // Step 1: Check Allowance + allowanceData, err := client.GetApproveAllowance(ctx, aggregation.GetAllowanceParams{ + TokenAddress: UsdcBase, + WalletAddress: walletAddr, + }) + if err != nil { + log.Fatalf("Failed to get allowance: %v\n", err) + } + allowance := new(big.Int) + allowance.SetString(allowanceData.Allowance, 10) + + amountToSwap := new(big.Int) + amountToSwap.SetString(amountUsdc, 10) + + // Step 2: Approve if needed + if allowance.Cmp(amountToSwap) < 0 { + fmt.Println("Insufficient allowance. Approving...") + approveData, err := client.GetApproveTransaction(ctx, aggregation.GetApproveParams{ + TokenAddress: UsdcBase, + Amount: amountUsdc, + }) + if err != nil { + log.Fatalf("Failed to get approve data: %v\n", err) + } + data, err := hexutil.Decode(approveData.Data) + if err != nil { + log.Fatalf("Failed to decode approve data: %v\n", err) + } + to := common.HexToAddress(approveData.To) + + tx, err := client.TxBuilder.New().SetData(data).SetTo(&to).Build(ctx) + if err != nil { + log.Fatalf("Failed to build approve transaction: %v\n", err) + } + signedTx, err := client.Wallet.Sign(tx) + if err != nil { + log.Fatalf("Failed to sign approve transaction: %v\n", err) + } + err = client.Wallet.BroadcastTransaction(ctx, signedTx) + if err != nil { + log.Fatalf("Failed to broadcast approve transaction: %v\n", err) + } + + fmt.Printf("Approve transaction sent: https://basescan.org/tx/%s\n", signedTx.Hash().Hex()) + + // Wait for approval to be mined + for { + receipt, _ := client.Wallet.TransactionReceipt(ctx, signedTx.Hash()) + if receipt != nil { + fmt.Println("Approve transaction confirmed.") + break + } + time.Sleep(2 * time.Second) + } + } else { + fmt.Println("Sufficient allowance already present.") + } + + // Step 3: Perform Swap + swapData, err := client.GetSwap(ctx, aggregation.GetSwapParams{ + Src: UsdcBase, + Dst: WethBase, + Amount: amountUsdc, + From: walletAddr, + Slippage: 1, // 1% slippage + }) + if err != nil { + log.Fatalf("Failed to get swap data: %v\n", err) + } + + tx, err := client.TxBuilder.New(). + 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) + } + + fmt.Printf("Swap transaction sent: https://basescan.org/tx/%s\n", signedTx.Hash().Hex()) + + // Wait for swap transaction to be mined + for { + receipt, _ := client.Wallet.TransactionReceipt(ctx, signedTx.Hash()) + if receipt != nil { + fmt.Println("Swap transaction confirmed!") + break + } + time.Sleep(2 * time.Second) + } +} diff --git a/sdk-clients/aggregation/examples/swap/main.go b/sdk-clients/aggregation/examples/swap/main.go index 87a390e..d069247 100644 --- a/sdk-clients/aggregation/examples/swap/main.go +++ b/sdk-clients/aggregation/examples/swap/main.go @@ -24,17 +24,17 @@ var ( ) const ( - PolygonPol = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - PolygonUsdc = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" - amountPol = "1000000000000000000" - amountUsdc = "100000" + UsdcBase = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" + WethBase = "0x4200000000000000000000000000000000000006" + amountWeth = "10000000000000000" + amountUsdc = "100000" ) func main() { config, err := aggregation.NewConfiguration(aggregation.ConfigurationParams{ NodeUrl: nodeUrl, PrivateKey: privateKey, - ChainId: constants.PolygonChainId, + ChainId: constants.BaseChainId, ApiUrl: "https://api.1inch.dev", ApiKey: devPortalToken, }) @@ -48,8 +48,8 @@ func main() { ctx := context.Background() swapData, err := client.GetSwap(ctx, aggregation.GetSwapParams{ - Src: PolygonUsdc, - Dst: PolygonPol, + Src: UsdcBase, + Dst: WethBase, Amount: amountUsdc, From: client.Wallet.Address().Hex(), Slippage: 1, From dbe9023153ba12689e5eb6f84b92aeaea2b8040f Mon Sep 17 00:00:00 2001 From: Tanner Moore Date: Wed, 29 Oct 2025 16:18:28 -0600 Subject: [PATCH 2/3] Limit order v4.1 support added --- sdk-clients/orderbook/api.go | 125 +-- .../examples/create_and_fill_order/main.go | 165 ---- .../orderbook/examples/create_order/main.go | 73 +- .../examples/create_order_permit/main.go | 88 +- .../orderbook/examples/fill_order/main.go | 101 -- .../orderbook/examples/get_all_orders/main.go | 50 + .../orderbook/examples/get_order/main.go | 46 + .../examples/get_order_count/main.go | 42 + sdk-clients/orderbook/makerTraits.go | 23 + sdk-clients/orderbook/makerTraits_test.go | 4 +- sdk-clients/orderbook/orderbook_extension.go | 322 ++++++ .../orderbook/orderbook_extension_test.go | 924 ++++++++++++++++++ .../orderbook/orderbook_types_manual.go | 111 ++- sdk-clients/orderbook/salt.go | 70 ++ sdk-clients/orderbook/validate.go | 17 + sdk-clients/orderbook/web3data.go | 2 +- 16 files changed, 1728 insertions(+), 435 deletions(-) delete mode 100644 sdk-clients/orderbook/examples/create_and_fill_order/main.go delete mode 100644 sdk-clients/orderbook/examples/fill_order/main.go create mode 100644 sdk-clients/orderbook/examples/get_all_orders/main.go create mode 100644 sdk-clients/orderbook/examples/get_order/main.go create mode 100644 sdk-clients/orderbook/examples/get_order_count/main.go create mode 100644 sdk-clients/orderbook/orderbook_extension.go create mode 100644 sdk-clients/orderbook/orderbook_extension_test.go create mode 100644 sdk-clients/orderbook/salt.go diff --git a/sdk-clients/orderbook/api.go b/sdk-clients/orderbook/api.go index 72d7471..5fdd7ea 100644 --- a/sdk-clients/orderbook/api.go +++ b/sdk-clients/orderbook/api.go @@ -12,7 +12,7 @@ import ( // CreateOrder creates an order in the Limit Order Protocol func (api *api) CreateOrder(ctx context.Context, params CreateOrderParams) (*CreateOrderResponse, error) { - u := fmt.Sprintf("/orderbook/v4.0/%d", api.chainId) + u := fmt.Sprintf("/orderbook/v4.1/%d", api.chainId) err := params.Validate() if err != nil { @@ -49,8 +49,6 @@ func (api *api) CreateOrder(ctx context.Context, params CreateOrderParams) (*Cre return &createOrderResponse, nil } -// TODO Reusing the same request/response objects until the openapi spec is updated to include the correct object definitions - // GetOrdersByCreatorAddress returns all orders created by a given address in the Limit Order Protocol func (api *api) GetOrdersByCreatorAddress(ctx context.Context, params GetOrdersByCreatorAddressParams) ([]*OrderResponse, error) { u := fmt.Sprintf("/orderbook/v4.0/%d/address/%s", api.chainId, params.CreatorAddress) @@ -77,7 +75,7 @@ func (api *api) GetOrdersByCreatorAddress(ctx context.Context, params GetOrdersB // GetOrder returns an order from Limit Order Protocol that matches a specific hash func (api *api) GetOrder(ctx context.Context, params GetOrderParams) (*GetOrderByHashResponseExtended, error) { - u := fmt.Sprintf("/orderbook/v4.0/%d/order/%s", api.chainId, params.OrderHash) + u := fmt.Sprintf("/orderbook/v4.1/%d/order/%s", api.chainId, params.OrderHash) err := params.Validate() if err != nil { @@ -99,6 +97,30 @@ func (api *api) GetOrder(ctx context.Context, params GetOrderParams) (*GetOrderB return NormalizeGetOrderByHashResponse(getOrderByHashResponse) } +// GetOrderCount returns the number of orders for a given trading pair with a specific status +func (api *api) GetOrderCount(ctx context.Context, params GetOrderCountParams) (*GetOrderCountResponse, error) { + u := fmt.Sprintf("/orderbook/v4.1/%d/count", api.chainId) + + err := params.Validate() + if err != nil { + return nil, err + } + + payload := common.RequestPayload{ + Method: "GET", + Params: params, + U: u, + } + + var response GetOrderCountResponse + err = api.httpExecutor.ExecuteRequest(ctx, payload, &response) + if err != nil { + return nil, err + } + + return &response, nil +} + // GetOrderWithSignature first looks up an order by hash, then does a second request to get the signature data func (api *api) GetOrderWithSignature(ctx context.Context, params GetOrderParams) (*OrderExtendedWithSignature, error) { @@ -115,7 +137,7 @@ func (api *api) GetOrderWithSignature(ctx context.Context, params GetOrderParams // Second, lookup all orders by that creator (these orders will contain the signature data) allOrdersByCreator, err := api.GetOrdersByCreatorAddress(ctx, GetOrdersByCreatorAddressParams{ - CreatorAddress: order.OrderMaker, + CreatorAddress: order.Data.Maker, }) if err != nil { return nil, err @@ -136,8 +158,8 @@ func (api *api) GetOrderWithSignature(ctx context.Context, params GetOrderParams } // GetAllOrders returns all orders in the Limit Order Protocol -func (api *api) GetAllOrders(ctx context.Context, params GetAllOrdersParams) ([]OrderResponse, error) { - u := fmt.Sprintf("/orderbook/v3.0/%d/all", api.chainId) +func (api *api) GetAllOrders(ctx context.Context, params GetAllOrdersParams) (*Orders, error) { + u := fmt.Sprintf("/orderbook/v4.1/%d/all", api.chainId) err := params.Validate() if err != nil { @@ -150,92 +172,17 @@ func (api *api) GetAllOrders(ctx context.Context, params GetAllOrdersParams) ([] U: u, } - var allOrdersResponse []OrderResponse + var allOrdersResponse Orders err = api.httpExecutor.ExecuteRequest(ctx, payload, &allOrdersResponse) if err != nil { return nil, err } - return allOrdersResponse, nil -} - -// GetCount returns the number of orders in the Limit Order Protocol -func (api *api) GetCount(ctx context.Context, params GetCountParams) (*CountResponse, error) { - u := fmt.Sprintf("/orderbook/v3.0/%d/count", api.chainId) - - err := params.Validate() - if err != nil { - return nil, err - } - - payload := common.RequestPayload{ - Method: "GET", - Params: params, - U: u, - } - - var count CountResponse - err = api.httpExecutor.ExecuteRequest(ctx, payload, &count) - if err != nil { - return nil, err - } - - return &count, nil + return &allOrdersResponse, nil } -// GetEvent returns an event in the Limit Order Protocol by order hash -func (api *api) GetEvent(ctx context.Context, params GetEventParams) (*EventResponse, error) { - u := fmt.Sprintf("/orderbook/v3.0/%d/events/%s", api.chainId, params.OrderHash) - - err := params.Validate() - if err != nil { - return nil, err - } - - payload := common.RequestPayload{ - Method: "GET", - Params: params, - U: u, - } - - var event EventResponse - err = api.httpExecutor.ExecuteRequest(ctx, payload, &event) - if err != nil { - return nil, err - } - - return &event, nil -} - -// GetEvents returns all events in the Limit Order Protocol -func (api *api) GetEvents(ctx context.Context, params GetEventsParams) ([]EventResponse, error) { - u := fmt.Sprintf("/orderbook/v3.0/%d/events", api.chainId) - - err := params.Validate() - if err != nil { - return nil, err - } - - payload := common.RequestPayload{ - Method: "GET", - Params: params, - U: u, - } - - var events []EventResponse - err = api.httpExecutor.ExecuteRequest(ctx, payload, &events) - if err != nil { - return nil, err - } - - return events, nil -} - -// TODO untested endpoint - -// GetActiveOrdersWithPermit returns all orders in the Limit Order Protocol that are active and have a valid permit -func (api *api) GetActiveOrdersWithPermit(ctx context.Context, params GetActiveOrdersWithPermitParams) ([]OrderResponse, error) { - u := fmt.Sprintf("/orderbook/v3.0/%d/has-active-orders-with-permit/%s/%s", api.chainId, params.Token, params.Wallet) +func (api *api) GetFeeInfo(ctx context.Context, params GetFeeInfoParams) (*FeeInfoResponse, error) { + u := fmt.Sprintf("/orderbook/v4.0/%d/fee-info", api.chainId) err := params.Validate() if err != nil { @@ -248,11 +195,11 @@ func (api *api) GetActiveOrdersWithPermit(ctx context.Context, params GetActiveO U: u, } - var orders []OrderResponse - err = api.httpExecutor.ExecuteRequest(ctx, payload, &orders) + var feeInfo *FeeInfoResponse + err = api.httpExecutor.ExecuteRequest(ctx, payload, &feeInfo) if err != nil { return nil, err } - return orders, nil + return feeInfo, nil } diff --git a/sdk-clients/orderbook/examples/create_and_fill_order/main.go b/sdk-clients/orderbook/examples/create_and_fill_order/main.go deleted file mode 100644 index 16bae60..0000000 --- a/sdk-clients/orderbook/examples/create_and_fill_order/main.go +++ /dev/null @@ -1,165 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - "time" - - "github.com/1inch/1inch-sdk-go/constants" - "github.com/1inch/1inch-sdk-go/sdk-clients/orderbook" - gethCommon "github.com/ethereum/go-ethereum/common" -) - -var ( - privateKey = os.Getenv("WALLET_KEY") - nodeUrl = os.Getenv("NODE_URL") - devPortalToken = os.Getenv("DEV_PORTAL_TOKEN") -) - -const ( - wmatic = "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270" - usdc = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" - ten16 = "10000000000000000" - ten6 = "1000000" - zeroAddress = "0x0000000000000000000000000000000000000000" - chainId = 137 -) - -func main() { - ctx := context.Background() - - config, err := orderbook.NewConfiguration(orderbook.ConfigurationParams{ - NodeUrl: nodeUrl, - PrivateKey: privateKey, - ChainId: chainId, - ApiUrl: "https://api.1inch.dev", - ApiKey: devPortalToken, - }) - if err != nil { - log.Fatal(err) - } - client, err := orderbook.NewClient(config) - - expireAfter := time.Now().Add(time.Hour).Unix() - - seriesNonce, err := client.GetSeriesNonce(ctx, client.Wallet.Address()) - if err != nil { - log.Fatal(fmt.Errorf("failed to get series nonce: %v", err)) - } - - makerTraits, err := orderbook.NewMakerTraits(orderbook.MakerTraitsParams{ - AllowedSender: zeroAddress, - ShouldCheckEpoch: false, - UsePermit2: false, - UnwrapWeth: false, - HasExtension: false, - HasPreInteraction: false, - HasPostInteraction: false, - AllowMultipleFills: true, - AllowPartialFills: true, - Expiry: expireAfter, - Nonce: seriesNonce.Int64(), - Series: 0, - }) - if err != nil { - log.Fatalf("Failed to create maker traits: %v", err) - } - - salt, err := orderbook.GenerateSalt("", nil) - if err != nil { - log.Fatalf("Failed to generate salt: %v", err) - } - - createOrderResponse, err := client.CreateOrder(ctx, orderbook.CreateOrderParams{ - Wallet: client.Wallet, - SeriesNonce: seriesNonce, - MakerTraits: makerTraits, - MakerTraitsEncoded: makerTraits.Encode(), - Salt: salt, - Maker: client.Wallet.Address().Hex(), - MakerAsset: wmatic, - TakerAsset: usdc, - MakingAmount: ten16, - TakingAmount: ten6, - Taker: zeroAddress, - SkipWarnings: false, - EnableOnchainApprovalsIfNeeded: false, - }) - if err != nil { - log.Fatal(fmt.Errorf("Failed to create order: %v\n", err)) - } - if !createOrderResponse.Success { - log.Fatalf("Request completed, but order creation status was a failure: %v\n", createOrderResponse) - } - - fmt.Println("Order created! Getting order hash...") - - // Sleep to accommodate free-tier API keys - time.Sleep(time.Second) - - ordersByCreatorResponse, err := client.GetOrdersByCreatorAddress(ctx, orderbook.GetOrdersByCreatorAddressParams{ - CreatorAddress: client.Wallet.Address().Hex(), - }) - - fmt.Printf("Order hash: %v\n", ordersByCreatorResponse[0].OrderHash) - fmt.Println("Getting signature...") - - // Sleep to accommodate free-tier API keys - time.Sleep(time.Second) - - orderWithSignature, err := client.GetOrderWithSignature(ctx, orderbook.GetOrderParams{ - OrderHash: ordersByCreatorResponse[0].OrderHash, - SleepBetweenSubrequests: true, - }) - - fmt.Println("Getting retrieved! Filling order...") - - fillOrderData, err := client.GetFillOrderCalldata(orderWithSignature, nil) - if err != nil { - log.Fatalf("Failed to get fill order calldata: %v", err) - } - - aggregationRouter, err := constants.Get1inchRouterFromChainId(chainId) - if err != nil { - log.Fatalf("Failed to get 1inch router address: %v", err) - } - aggregationRouterAddress := gethCommon.HexToAddress(aggregationRouter) - - tx, err := client.TxBuilder.New().SetData(fillOrderData).SetTo(&aggregationRouterAddress).SetGas(150000).Build(ctx) - if err != nil { - fmt.Printf("Failed to build transaction: %v\n", err) - return - } - signedTx, err := client.Wallet.Sign(tx) - if err != nil { - fmt.Printf("Failed to sign transaction: %v\n", err) - return - } - - err = client.Wallet.BroadcastTransaction(ctx, signedTx) - if err != nil { - fmt.Printf("Failed to broadcast transaction: %v\n", err) - return - } - - // Waiting for transaction, just an example of it - fmt.Printf("Transaction has been broadcast. View it on Polygonscan here: %v\n", fmt.Sprintf("https://polygonscan.com/tx/%v", signedTx.Hash().Hex())) - for { - receipt, err := client.Wallet.TransactionReceipt(ctx, signedTx.Hash()) - if receipt != nil { - fmt.Println("Transaction complete!") - return - } - if err != nil { - fmt.Println("Waiting for transaction to be mined") - } - select { - case <-time.After(1000 * time.Millisecond): // check again after a delay - case <-ctx.Done(): - fmt.Println("Context cancelled") - return - } - } -} diff --git a/sdk-clients/orderbook/examples/create_order/main.go b/sdk-clients/orderbook/examples/create_order/main.go index c34fea3..b193d78 100644 --- a/sdk-clients/orderbook/examples/create_order/main.go +++ b/sdk-clients/orderbook/examples/create_order/main.go @@ -23,12 +23,19 @@ var ( const ( wmatic = "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270" usdc = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" - ten16 = "10000000000000000" - ten6 = "1000000" + ten18 = "1000000000000000000" + ten8 = "100000000" zeroAddress = "0x0000000000000000000000000000000000000000" chainId = 137 ) +var ( + makerAsset = wmatic + takerAsset = usdc + makerAmount = ten18 + takerAmount = ten8 +) + func main() { ctx := context.Background() @@ -51,41 +58,59 @@ func main() { publicKey := ecdsaPrivateKey.Public() publicAddress := crypto.PubkeyToAddress(*publicKey.(*ecdsa.PublicKey)) - expireAfter := time.Now().Add(time.Hour).Unix() + feeInfo, err := client.GetFeeInfo(ctx, orderbook.GetFeeInfoParams{ + MakerAsset: makerAsset, + TakerAsset: takerAsset, + MakerAmount: makerAmount, + TakerAmount: takerAmount, + }) + if err != nil { + log.Fatalf("Failed to get fee info: %v", err) + } + + buildOrderExtensionBytesParams := &orderbook.BuildOrderExtensionBytesParams{ + ExtensionTarget: feeInfo.ExtensionAddress, + IntegratorFee: &orderbook.IntegratorFee{ + Integrator: zeroAddress, + Protocol: zeroAddress, + Fee: 0, + Share: 0, + }, + ResolverFee: &orderbook.ResolverFee{ + Receiver: feeInfo.ProtocolFeeReceiver, + Fee: feeInfo.FeeBps, + WhitelistDiscount: feeInfo.WhitelistDiscountPercent, + }, + Whitelist: feeInfo.Whitelist, + CustomReceiver: publicAddress.Hex(), + } - seriesNonce, err := client.GetSeriesNonce(ctx, publicAddress) + extensionEncoded, err := orderbook.BuildOrderExtensionBytes(buildOrderExtensionBytesParams) if err != nil { - log.Fatal(fmt.Errorf("failed to get series nonce: %v", err)) + log.Fatalf("Failed to create extension: %v\n", err) } - makerTraits, err := orderbook.NewMakerTraits(orderbook.MakerTraitsParams{ - AllowedSender: zeroAddress, - ShouldCheckEpoch: false, - UsePermit2: false, - UnwrapWeth: false, - HasExtension: false, - Expiry: expireAfter, - Nonce: seriesNonce.Int64(), - Series: 0, - AllowMultipleFills: true, - AllowPartialFills: true, + salt, err := orderbook.GenerateSaltNew(&orderbook.GetSaltParams{ + Extension: extensionEncoded, }) if err != nil { - log.Fatalf("Failed to create maker traits: %v", err) + log.Fatalf("Failed to generate salt: %v", err) } createOrderResponse, err := client.CreateOrder(ctx, orderbook.CreateOrderParams{ Wallet: client.Wallet, - SeriesNonce: seriesNonce, + Salt: fmt.Sprintf("%d", salt), Maker: publicAddress.Hex(), - MakerAsset: wmatic, - TakerAsset: usdc, - MakingAmount: ten16, - TakingAmount: ten6, - Taker: zeroAddress, + MakerAsset: makerAsset, + TakerAsset: takerAsset, + MakingAmount: makerAmount, + TakingAmount: takerAmount, + Taker: feeInfo.ExtensionAddress, SkipWarnings: false, EnableOnchainApprovalsIfNeeded: false, - MakerTraits: makerTraits, + MakerTraits: orderbook.NewMakerTraitsDefault(), + MakerTraitsEncoded: orderbook.NewMakerTraitsDefault().Encode(), + ExtensionEncoded: extensionEncoded, }) if err != nil { log.Fatal(fmt.Errorf("Failed to create order: %v\n", err)) diff --git a/sdk-clients/orderbook/examples/create_order_permit/main.go b/sdk-clients/orderbook/examples/create_order_permit/main.go index ff3cc67..90660f9 100644 --- a/sdk-clients/orderbook/examples/create_order_permit/main.go +++ b/sdk-clients/orderbook/examples/create_order_permit/main.go @@ -29,10 +29,18 @@ const ( PolygonUsdc = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" ten16 = "10000000000000000" ten6 = "1000000" + ten4 = "10000" zeroAddress = "0x0000000000000000000000000000000000000000" chainId = 137 ) +var ( + makerAsset = PolygonUsdc + takerAsset = PolygonFRAX + makerAmount = ten4 + takerAmount = ten16 +) + func main() { ctx := context.Background() @@ -47,6 +55,9 @@ func main() { log.Fatal(err) } client, err := orderbook.NewClient(config) + if err != nil { + log.Fatalf("Failed to create client: %v\n", err) + } ecdsaPrivateKey, err := crypto.HexToECDSA(privateKey) if err != nil { @@ -57,67 +68,80 @@ func main() { expireAfter := time.Now().Add(time.Hour).Unix() - seriesNonce, err := client.GetSeriesNonce(ctx, publicAddress) - if err != nil { - log.Fatal(fmt.Errorf("failed to get series nonce: %v", err)) - } - router, err := constants.Get1inchRouterFromChainId(chainId) if err != nil { log.Fatal(fmt.Errorf("failed to get 1inch router address: %v", err)) } - makingAmount := ten16 - makingAmountInt, err := strconv.ParseInt(makingAmount, 10, 64) + makingAmountInt, err := strconv.ParseInt(makerAmount, 10, 64) if err != nil { fmt.Println("Error converting string to int:", err) return } - permitData, err := client.Wallet.GetContractDetailsForPermit(ctx, common.HexToAddress(PolygonFRAX), common.HexToAddress(router), big.NewInt(makingAmountInt), expireAfter) + permitData, err := client.Wallet.GetContractDetailsForPermit(ctx, common.HexToAddress(makerAsset), common.HexToAddress(router), big.NewInt(makingAmountInt), expireAfter) if err != nil { - panic(err) + log.Fatal("failed to get permit data:", err) } permit, err := client.Wallet.TokenPermit(*permitData) if err != nil { log.Fatal(fmt.Errorf("Failed to get permit: %v\n", err)) } - extension, err := orderbook.NewExtension(orderbook.ExtensionParams{ - MakerAsset: PolygonFRAX, - Permit: permit, + fmt.Printf("Permit: %v\n", permit) + + feeInfo, err := client.GetFeeInfo(ctx, orderbook.GetFeeInfoParams{ + MakerAsset: makerAsset, + TakerAsset: takerAsset, + MakerAmount: makerAmount, + TakerAmount: takerAmount, }) + if err != nil { + log.Fatalf("Failed to get fee info: %v", err) + } + + buildOrderExtensionBytesParams := &orderbook.BuildOrderExtensionBytesParams{ + ExtensionTarget: feeInfo.ExtensionAddress, + IntegratorFee: &orderbook.IntegratorFee{ + Integrator: zeroAddress, + Protocol: zeroAddress, + Fee: 0, + Share: 0, + }, + MakerPermit: []byte(permit), + ResolverFee: &orderbook.ResolverFee{ + Receiver: feeInfo.ProtocolFeeReceiver, + Fee: feeInfo.FeeBps, + WhitelistDiscount: feeInfo.WhitelistDiscountPercent, + }, + Whitelist: feeInfo.Whitelist, + CustomReceiver: publicAddress.Hex(), + } + + extensionEncoded, err := orderbook.BuildOrderExtensionBytes(buildOrderExtensionBytesParams) if err != nil { log.Fatalf("Failed to create extension: %v\n", err) } - makerTraits, err := orderbook.NewMakerTraits(orderbook.MakerTraitsParams{ - AllowedSender: zeroAddress, - ShouldCheckEpoch: false, - UsePermit2: false, - UnwrapWeth: false, - HasExtension: true, - AllowMultipleFills: true, - AllowPartialFills: true, - Expiry: expireAfter, - Nonce: seriesNonce.Int64(), - Series: 0, + salt, err := orderbook.GenerateSaltNew(&orderbook.GetSaltParams{ + Extension: extensionEncoded, }) if err != nil { - log.Fatalf("Failed to create maker traits: %v", err) + log.Fatalf("Failed to generate salt: %v", err) } createOrderResponse, err := client.CreateOrder(ctx, orderbook.CreateOrderParams{ Wallet: client.Wallet, - SeriesNonce: seriesNonce, - MakerTraits: makerTraits, - Extension: *extension, + Salt: fmt.Sprintf("%d", salt), + MakerTraits: orderbook.NewMakerTraitsDefault(), // Defaults to a 1 hour expiration + MakerTraitsEncoded: orderbook.NewMakerTraitsDefault().Encode(), + ExtensionEncoded: extensionEncoded, Maker: publicAddress.Hex(), - MakerAsset: PolygonFRAX, - TakerAsset: PolygonUsdc, - MakingAmount: makingAmount, - TakingAmount: ten6, - Taker: zeroAddress, + MakerAsset: makerAsset, + TakerAsset: takerAsset, + MakingAmount: makerAmount, + TakingAmount: takerAmount, + Taker: feeInfo.ExtensionAddress, SkipWarnings: false, EnableOnchainApprovalsIfNeeded: false, }) diff --git a/sdk-clients/orderbook/examples/fill_order/main.go b/sdk-clients/orderbook/examples/fill_order/main.go deleted file mode 100644 index 0f6fa0e..0000000 --- a/sdk-clients/orderbook/examples/fill_order/main.go +++ /dev/null @@ -1,101 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - "time" - - gethCommon "github.com/ethereum/go-ethereum/common" - - "github.com/1inch/1inch-sdk-go/constants" - "github.com/1inch/1inch-sdk-go/sdk-clients/orderbook" -) - -var ( - privateKey = os.Getenv("WALLET_KEY") - nodeUrl = os.Getenv("NODE_URL") - devPortalToken = os.Getenv("DEV_PORTAL_TOKEN") -) - -const ( - limitOrderHash = "0x585e2e2488d9654926a80356ce135e0ef890cbb6ecec156cad347e091635029e" - chainId = 137 -) - -func main() { - ctx := context.Background() - - config, err := orderbook.NewConfiguration(orderbook.ConfigurationParams{ - NodeUrl: nodeUrl, - PrivateKey: privateKey, - ChainId: chainId, - ApiUrl: "https://api.1inch.dev", - ApiKey: devPortalToken, - }) - if err != nil { - log.Fatal(err) - } - client, err := orderbook.NewClient(config) - - params := orderbook.GetOrderParams{ - OrderHash: limitOrderHash, - SleepBetweenSubrequests: true, - } - getOrderResponse, err := client.GetOrderWithSignature(ctx, params) - if err != nil { - log.Fatalf("Failed to get order: %v", err) - } - - takerTraits := orderbook.NewTakerTraits(orderbook.TakerTraitsParams{ - Extension: getOrderResponse.Data.Extension, - }) - - fillOrderData, err := client.GetFillOrderCalldata(getOrderResponse, takerTraits) - if err != nil { - log.Fatalf("Failed to get fill order calldata: %v", err) - } - - aggregationRouter, err := constants.Get1inchRouterFromChainId(chainId) - if err != nil { - log.Fatalf("Failed to get 1inch router address: %v", err) - } - aggregationRouterAddress := gethCommon.HexToAddress(aggregationRouter) - - tx, err := client.TxBuilder.New().SetData(fillOrderData).SetTo(&aggregationRouterAddress).SetGas(150000).Build(ctx) - if err != nil { - fmt.Printf("Failed to build transaction: %v\n", err) - return - } - signedTx, err := client.Wallet.Sign(tx) - if err != nil { - fmt.Printf("Failed to sign transaction: %v\n", err) - return - } - - err = client.Wallet.BroadcastTransaction(ctx, signedTx) - if err != nil { - fmt.Printf("Failed to broadcast transaction: %v\n", err) - return - } - - // Waiting for transaction, just an example of it - fmt.Printf("Transaction has been broadcast. View it on Polygonscan here: %v\n", fmt.Sprintf("https://polygonscan.com/tx/%v", signedTx.Hash().Hex())) - for { - receipt, err := client.Wallet.TransactionReceipt(ctx, signedTx.Hash()) - if receipt != nil { - fmt.Println("Transaction complete!") - return - } - if err != nil { - fmt.Println("Waiting for transaction to be mined") - } - select { - case <-time.After(1000 * time.Millisecond): // check again after a delay - case <-ctx.Done(): - fmt.Println("Context cancelled") - return - } - } -} diff --git a/sdk-clients/orderbook/examples/get_all_orders/main.go b/sdk-clients/orderbook/examples/get_all_orders/main.go new file mode 100644 index 0000000..56e3aa8 --- /dev/null +++ b/sdk-clients/orderbook/examples/get_all_orders/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "os" + + "github.com/1inch/1inch-sdk-go/sdk-clients/orderbook" +) + +var ( + devPortalToken = os.Getenv("DEV_PORTAL_TOKEN") +) + +const ( + chainId = 137 +) + +func main() { + ctx := context.Background() + + config, err := orderbook.NewConfigurationAPI(chainId, "https://api.1inch.dev", devPortalToken) + if err != nil { + log.Fatal(err) + } + client, err := orderbook.NewClientOnlyAPI(config) + if err != nil { + log.Fatal(err) + } + + orders, err := client.GetAllOrders(ctx, orderbook.GetAllOrdersParams{ + LimitOrderV3SubscribedApiControllerGetAllLimitOrdersParams: orderbook.LimitOrderV3SubscribedApiControllerGetAllLimitOrdersParams{ + Page: 0, + Limit: 2, + Statuses: []float32{1}, + }, + }) + if err != nil { + log.Fatalf("Failed to get order count: %v", err) + } + + ordersIndented, err := json.MarshalIndent(orders, "", " ") + if err != nil { + log.Fatal(fmt.Errorf("Failed to marshal response: %v\n", err)) + } + + fmt.Printf("Orders: %s\n", ordersIndented) +} diff --git a/sdk-clients/orderbook/examples/get_order/main.go b/sdk-clients/orderbook/examples/get_order/main.go new file mode 100644 index 0000000..7ecde7b --- /dev/null +++ b/sdk-clients/orderbook/examples/get_order/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "os" + + "github.com/1inch/1inch-sdk-go/sdk-clients/orderbook" +) + +var ( + devPortalToken = os.Getenv("DEV_PORTAL_TOKEN") +) + +const ( + chainId = 137 +) + +func main() { + ctx := context.Background() + + config, err := orderbook.NewConfigurationAPI(chainId, "https://api.1inch.dev", devPortalToken) + if err != nil { + log.Fatal(err) + } + client, err := orderbook.NewClientOnlyAPI(config) + if err != nil { + log.Fatal(err) + } + + order, err := client.GetOrder(ctx, orderbook.GetOrderParams{ + OrderHash: "0x887b4e1b5ab0fd51884f25234fb725307056e0b2d33b881ea9013f9258fb444a", + }) + if err != nil { + log.Fatalf("Failed to get order count: %v", err) + } + + orderIndented, err := json.MarshalIndent(order, "", " ") + if err != nil { + log.Fatal(fmt.Errorf("Failed to marshal response: %v\n", err)) + } + + fmt.Printf("Order: %s\n", orderIndented) +} diff --git a/sdk-clients/orderbook/examples/get_order_count/main.go b/sdk-clients/orderbook/examples/get_order_count/main.go new file mode 100644 index 0000000..1fa0c84 --- /dev/null +++ b/sdk-clients/orderbook/examples/get_order_count/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + + "github.com/1inch/1inch-sdk-go/sdk-clients/orderbook" +) + +var ( + devPortalToken = os.Getenv("DEV_PORTAL_TOKEN") +) + +const ( + chainId = 8453 +) + +func main() { + ctx := context.Background() + + config, err := orderbook.NewConfigurationAPI(chainId, "https://api.1inch.dev", devPortalToken) + if err != nil { + log.Fatal(err) + } + client, err := orderbook.NewClientOnlyAPI(config) + if err != nil { + log.Fatal(err) + } + + orderCount, err := client.GetOrderCount(ctx, orderbook.GetOrderCountParams{ + Statuses: []orderbook.OrderStatus{orderbook.ValidOrders}, + MakerAsset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + TakerAsset: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", + }) + if err != nil { + log.Fatalf("Failed to get order count: %v", err) + } + + fmt.Printf("Order count: %v\n", orderCount.Count) +} diff --git a/sdk-clients/orderbook/makerTraits.go b/sdk-clients/orderbook/makerTraits.go index d3f7af9..cd16d6f 100644 --- a/sdk-clients/orderbook/makerTraits.go +++ b/sdk-clients/orderbook/makerTraits.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math/big" + "time" ) var ( @@ -59,6 +60,24 @@ type MakerTraits struct { AllowMultipleFills bool } +func NewMakerTraitsDefault() *MakerTraits { + return &MakerTraits{ + AllowedSender: "0x0000000000000000000000000000000000000000", + Expiry: time.Now().Add(time.Hour).Unix(), + Nonce: 0, + Series: 0, + NoPartialFills: false, + NeedPostinteraction: true, + NeedPreinteraction: false, + NeedEpochCheck: false, + HasExtension: true, + ShouldUsePermit2: false, + ShouldUnwrapWeth: false, + AllowPartialFills: true, + AllowMultipleFills: true, + } +} + func NewMakerTraits(params MakerTraitsParams) (*MakerTraits, error) { if params.AllowPartialFills && !params.AllowMultipleFills { @@ -69,6 +88,10 @@ func NewMakerTraits(params MakerTraitsParams) (*MakerTraits, error) { return nil, errors.New("AllowMultipleFills must be false if AllowPartialFills is false") } + if !params.HasPostInteraction { + return nil, errors.New("in the current iteration of Limit Order Protocol, HasPostInteraction must be true") + } + return &MakerTraits{ AllowedSender: params.AllowedSender, Expiry: params.Expiry, diff --git a/sdk-clients/orderbook/makerTraits_test.go b/sdk-clients/orderbook/makerTraits_test.go index 4cc8f4a..0629765 100644 --- a/sdk-clients/orderbook/makerTraits_test.go +++ b/sdk-clients/orderbook/makerTraits_test.go @@ -23,14 +23,14 @@ func TestMakerTraitsEncode(t *testing.T) { UnwrapWeth: false, HasExtension: true, HasPreInteraction: false, - HasPostInteraction: false, + HasPostInteraction: true, AllowPartialFills: true, AllowMultipleFills: true, Expiry: 1715201499, Nonce: 0, Series: 0, }, - expectedMakerTraits: "0x420000000000000000000000000000000000663be5db00000000000000000000", + expectedMakerTraits: "0x4a0000000000000000000000000000000000663be5db00000000000000000000", }, } diff --git a/sdk-clients/orderbook/orderbook_extension.go b/sdk-clients/orderbook/orderbook_extension.go new file mode 100644 index 0000000..9ce6b2e --- /dev/null +++ b/sdk-clients/orderbook/orderbook_extension.go @@ -0,0 +1,322 @@ +package orderbook + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + "math/big" + "sort" + "strings" +) + +const nullAddress = "0x0000000000000000000000000000000000000000" + +func packFeeParameter(integratorFee *IntegratorFee, resolverFee *ResolverFee) (uint64, error) { + var integratorFeeValue uint64 = 0 + var integratorShare uint64 = 0 + var resolverFeeValue uint64 = 0 + var resolverDiscount uint64 = 0 + + if integratorFee != nil { + integratorFeeValue = uint64(integratorFee.Fee * 10) // Convert to basis points * 10 + integratorShare = uint64(integratorFee.Share / 100) // Convert percentage to 0-100 range + } + + if resolverFee != nil { + resolverFeeValue = uint64(resolverFee.Fee * 10) // Convert to basis points * 10 + resolverDiscount = uint64(100 - resolverFee.WhitelistDiscount) // Invert discount (100 - discount%) + } + + // Range checks to ensure values fit in their allocated bit space + if integratorFeeValue > 0xffff { + return 0, fmt.Errorf("integrator fee value must be between 0 and 65535, got %d", integratorFeeValue) + } + if integratorShare > 0xff { + return 0, fmt.Errorf("integrator share must be between 0 and 255, got %d", integratorShare) + } + if resolverFeeValue > 0xffff { + return 0, fmt.Errorf("resolver fee value must be between 0 and 65535, got %d", resolverFeeValue) + } + if resolverDiscount > 0xff { + return 0, fmt.Errorf("resolver discount must be between 0 and 255, got %d", resolverDiscount) + } + + packed := (integratorFeeValue << 32) | // bits 47-32 (16 bits) + (integratorShare << 24) | // bits 31-24 (8 bits) + (resolverFeeValue << 8) | // bits 23-8 (16 bits) + resolverDiscount // bits 7-0 (8 bits) + + return packed, nil +} + +func encodeWhitelist(whitelist []string) (*big.Int, error) { + if len(whitelist) == 0 { + return big.NewInt(0), nil + } + + if len(whitelist) > 255 { + return nil, fmt.Errorf("whitelist can have at most 255 addresses, got %d", len(whitelist)) + } + + encoded := big.NewInt(int64(len(whitelist))) + + mask80 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 80), big.NewInt(1)) + + for _, address := range whitelist { + address = strings.TrimPrefix(address, "0x") + address = strings.TrimPrefix(address, "0X") + + addressInt := new(big.Int) + _, success := addressInt.SetString(address, 16) + if !success { + return nil, fmt.Errorf("invalid hex address: %s", address) + } + + // Mask to get only lower 80 bits + addressLower80 := new(big.Int).And(addressInt, mask80) + + // Shift encoded left by 80 bits and OR with the address + encoded.Lsh(encoded, 80) + encoded.Or(encoded, addressLower80) + } + + return encoded, nil +} + +// concatFeeAndWhitelist combines fee parameters and whitelist into a single encoded value +// Returns the combined value and the total bit length +func concatFeeAndWhitelist(whitelist []string, integratorFee *IntegratorFee, resolverFee *ResolverFee) (*big.Int, int, error) { + feeParameter, err := packFeeParameter(integratorFee, resolverFee) + if err != nil { + return nil, 0, err + } + + whitelistEncoded, err := encodeWhitelist(whitelist) + if err != nil { + return nil, 0, err + } + + // Calculate whitelist bit length + // If empty: 0 bits + // Otherwise: 8 bits (length) + (number of addresses * 80 bits each) + var whitelistBitLength int + if len(whitelist) == 0 { + whitelistBitLength = 0 + } else { + whitelistBitLength = 8 + (len(whitelist) * 80) + } + + // fee_parameter << whitelist_bit_length | whitelist_encoded + feeAndWhitelist := new(big.Int).SetUint64(feeParameter) + feeAndWhitelist.Lsh(feeAndWhitelist, uint(whitelistBitLength)) + feeAndWhitelist.Or(feeAndWhitelist, whitelistEncoded) + + totalBitLength := 48 + whitelistBitLength + + return feeAndWhitelist, totalBitLength, nil +} + +func buildFeePostInteractionData(params *buildFeePostInteractionDataParams) ([]byte, error) { + + if params.Whitelist == nil { + params.Whitelist = []string{} + } + + if params.CustomReceiverAddress == "" { + params.CustomReceiverAddress = nullAddress + } + + if params.ExtraInteractionTarget == "" { + params.ExtraInteractionTarget = nullAddress + } + + postInteraction := big.NewInt(0) + + // Add integrator address (20 bytes = 160 bits) + integratorAddress := nullAddress + if params.IntegratorFee != nil && params.IntegratorFee.Integrator != "" { + integratorAddress = params.IntegratorFee.Integrator + } + integratorInt := new(big.Int) + integratorInt.SetString(strings.TrimPrefix(integratorAddress, "0x"), 16) + postInteraction.Or(postInteraction, integratorInt) + + // Add resolver/protocol fee receiver address (20 bytes = 160 bits) + resolverAddress := nullAddress + if params.ResolverFee != nil && params.ResolverFee.Receiver != "" { + resolverAddress = params.ResolverFee.Receiver + } + resolverInt := new(big.Int) + resolverInt.SetString(strings.TrimPrefix(resolverAddress, "0x"), 16) + postInteraction.Lsh(postInteraction, 160) + postInteraction.Or(postInteraction, resolverInt) + + // Add custom receiver if present (20 bytes = 160 bits) + if params.CustomReceiver && params.CustomReceiverAddress != nullAddress { + receiverInt := new(big.Int) + receiverInt.SetString(strings.TrimPrefix(params.CustomReceiverAddress, "0x"), 16) + postInteraction.Lsh(postInteraction, 160) + postInteraction.Or(postInteraction, receiverInt) + } + + // Add fee and whitelist data + feeAndWhitelist, feeAndWhitelistLength, err := concatFeeAndWhitelist(params.Whitelist, params.IntegratorFee, params.ResolverFee) + if err != nil { + return nil, err + } + postInteraction.Lsh(postInteraction, uint(feeAndWhitelistLength)) + postInteraction.Or(postInteraction, feeAndWhitelist) + + // Add extra interaction if present + if params.ExtraInteractionTarget != nullAddress && len(params.ExtraInteractionData) > 0 { + targetInt := new(big.Int) + targetInt.SetString(strings.TrimPrefix(params.ExtraInteractionTarget, "0x"), 16) + postInteraction.Lsh(postInteraction, 160) + postInteraction.Or(postInteraction, targetInt) + + // Add extra interaction data (preserve all bits including leading zeros) + extraDataInt := new(big.Int).SetBytes(params.ExtraInteractionData) + postInteraction.Lsh(postInteraction, uint(len(params.ExtraInteractionData)*8)) + postInteraction.Or(postInteraction, extraDataInt) + } + + // Calculate expected byte length + expectedLength := 1 + 20 + 20 // flag + integrator + resolver + if params.CustomReceiver && params.CustomReceiverAddress != nullAddress { + expectedLength += 20 // custom receiver + } + if len(params.Whitelist) == 0 { + expectedLength += 6 // just fee_parameter (48 bits = 6 bytes) + } else { + expectedLength += (48 + 8 + len(params.Whitelist)*80) / 8 // fee + whitelist + } + if params.ExtraInteractionTarget != nullAddress && len(params.ExtraInteractionData) > 0 { + expectedLength += 20 + len(params.ExtraInteractionData) + } + + postInteractionBytes := postInteraction.Bytes() + + result := make([]byte, expectedLength) + if params.CustomReceiver { + result[0] = 0x01 + } else { + result[0] = 0x00 + } + + copy(result[expectedLength-len(postInteractionBytes):], postInteractionBytes) + + return result, nil +} + +// BuildOrderExtensionBytes builds the complete order extension +// Returns the encoded extension as a hex string +func BuildOrderExtensionBytes(params *BuildOrderExtensionBytesParams) (string, error) { + + var whiteListResolvers []string + for _, value := range params.Whitelist { + whiteListResolvers = append(whiteListResolvers, value) + sort.Strings(whiteListResolvers) // Sorting the final list to ensure a deterministic order + } + + feePostInteraction, err := buildFeePostInteractionData(&buildFeePostInteractionDataParams{ + CustomReceiver: params.CustomReceiver != "" && params.CustomReceiver != nullAddress, + CustomReceiverAddress: params.CustomReceiver, + IntegratorFee: params.IntegratorFee, + ResolverFee: params.ResolverFee, + Whitelist: whiteListResolvers, + ExtraInteractionTarget: params.ExtensionTarget, + ExtraInteractionData: params.ExtraInteraction, + }) + if err != nil { + return "", err + } + + makingTakingAmountData, makingTakingAmountDataLength, err := concatFeeAndWhitelist(whiteListResolvers, params.IntegratorFee, params.ResolverFee) + if err != nil { + return "", err + } + + extensionTargetBytes := make([]byte, 20) + targetInt := new(big.Int) + targetInt.SetString(strings.TrimPrefix(params.ExtensionTarget, "0x"), 16) + targetBytes := targetInt.Bytes() + copy(extensionTargetBytes[20-len(targetBytes):], targetBytes) + + makingTakingBytes := makingTakingAmountData.Bytes() + expectedMakingTakingLen := (makingTakingAmountDataLength + 7) / 8 // Round up to bytes + if len(makingTakingBytes) < expectedMakingTakingLen { + padded := make([]byte, expectedMakingTakingLen) + copy(padded[expectedMakingTakingLen-len(makingTakingBytes):], makingTakingBytes) + makingTakingBytes = padded + } + + makingTakingAmountDataBytes := append(extensionTargetBytes, makingTakingBytes...) + + // Prepend extension target to fee post-interaction + feePostInteractionWithTarget := append(extensionTargetBytes, feePostInteraction...) + + interactions := [][]byte{ + {}, // MakerAssetSuffix (empty) + {}, // TakerAssetSuffix (empty) + makingTakingAmountDataBytes, // MakingAmountData + makingTakingAmountDataBytes, // TakingAmountData (same as making) + {}, // Predicate (empty) + params.MakerPermit, // MakerPermit + {}, // PreInteractionData (empty) + feePostInteractionWithTarget, // PostInteractionData + } + + // Add customData if present + if len(params.CustomData) > 0 { + interactions = append(interactions, params.CustomData) + } + + extension := buildExtensionFromBytes(interactions) + + return extension, nil +} + +// buildExtensionFromBytes builds an extension hex string from byte slices +func buildExtensionFromBytes(interactions [][]byte) string { + // Calculate offsets for first 8 interactions only + var byteCounts []int + var dataBytes []byte + + // Process first 8 interactions (excluding customData) + mainInteractions := interactions + if len(interactions) > 8 { + mainInteractions = interactions[:8] + } + + for _, interaction := range mainInteractions { + byteCounts = append(byteCounts, len(interaction)) + dataBytes = append(dataBytes, interaction...) + } + + // Add customData if present (9th element) + if len(interactions) > 8 { + dataBytes = append(dataBytes, interactions[8]...) + } + + // Calculate cumulative offsets + cumulativeSum := 0 + var offsets []byte + for i := 0; i < len(byteCounts); i++ { + cumulativeSum += byteCounts[i] + offsetBytes := make([]byte, 4) + binary.BigEndian.PutUint32(offsetBytes, uint32(cumulativeSum)) + offsets = append(offsetBytes, offsets...) + } + + // If no data, return empty extension + if len(dataBytes) == 0 { + return "0x" + } + + // Encode offsets and data to hex + offsetsHex := hex.EncodeToString(offsets) + dataHex := hex.EncodeToString(dataBytes) + + // Concatenate with "0x" prefix + return "0x" + offsetsHex + dataHex +} diff --git a/sdk-clients/orderbook/orderbook_extension_test.go b/sdk-clients/orderbook/orderbook_extension_test.go new file mode 100644 index 0000000..bc8f5e9 --- /dev/null +++ b/sdk-clients/orderbook/orderbook_extension_test.go @@ -0,0 +1,924 @@ +package orderbook + +import ( + "fmt" + "math/big" + "testing" +) + +func TestPackFeeParameter(t *testing.T) { + tests := []struct { + name string + integratorFee *IntegratorFee + resolverFee *ResolverFee + expectedPacked uint64 + expectedHex string + expectError bool + }{ + { + name: "example from Python - zero integrator fee, resolver fee 50 bps with 50% discount", + integratorFee: &IntegratorFee{ + Integrator: "0x0000000000000000000000000000000000000000", + Protocol: "0x0000000000000000000000000000000000000000", + Fee: 0, + Share: 0, + }, + resolverFee: &ResolverFee{ + Receiver: "0x90cbe4bdd538d6e9b379bff5fe72c3d67a521de5", + Fee: 50, + WhitelistDiscount: 50, + }, + expectedPacked: 128050, // 0x1f432 + expectedHex: "0x1f432", + expectError: false, + }, + { + name: "nil fees - should return 0", + integratorFee: nil, + resolverFee: nil, + expectedPacked: 0, + expectedHex: "0x0", + expectError: false, + }, + { + name: "integrator fee only", + integratorFee: &IntegratorFee{ + Integrator: "0x1111111111111111111111111111111111111111", + Protocol: "0x2222222222222222222222222222222222222222", + Fee: 10, // 0.1% + Share: 500, // 5% + }, + resolverFee: nil, + expectedPacked: (100 << 32) | (5 << 24), // fee*10=100, share/100=5 + expectedHex: fmt.Sprintf("0x%x", (100<<32)|(5<<24)), + expectError: false, + }, + { + name: "resolver fee only", + integratorFee: nil, + resolverFee: &ResolverFee{ + Receiver: "0x3333333333333333333333333333333333333333", + Fee: 100, // 1% + WhitelistDiscount: 25, // 25% discount + }, + expectedPacked: (1000 << 8) | 75, // fee*10=1000, 100-25=75 + expectedHex: fmt.Sprintf("0x%x", (1000<<8)|75), + expectError: false, + }, + { + name: "both fees with various values", + integratorFee: &IntegratorFee{ + Fee: 1, // 0.01% + Share: 500, // 5% + }, + resolverFee: &ResolverFee{ + Fee: 50, // 0.5% + WhitelistDiscount: 50, // 50% discount + }, + expectedPacked: (10 << 32) | (5 << 24) | (500 << 8) | 50, + expectedHex: fmt.Sprintf("0x%x", (10<<32)|(5<<24)|(500<<8)|50), + expectError: false, + }, + { + name: "maximum valid values", + integratorFee: &IntegratorFee{ + Fee: 6553, // Maximum value that gives 65530 after *10 + Share: 25500, + }, + resolverFee: &ResolverFee{ + Fee: 6553, + WhitelistDiscount: 0, + }, + expectedPacked: (65530 << 32) | (255 << 24) | (65530 << 8) | 100, + expectedHex: fmt.Sprintf("0x%x", (65530<<32)|(255<<24)|(65530<<8)|100), + expectError: false, + }, + { + name: "integrator fee too large", + integratorFee: &IntegratorFee{ + Fee: 6554, // 6554 * 10 = 65540, exceeds 0xffff + Share: 0, + }, + resolverFee: nil, + expectError: true, + }, + { + name: "integrator share too large", + integratorFee: &IntegratorFee{ + Fee: 0, + Share: 25600, // 25600 / 100 = 256, exceeds 0xff + }, + resolverFee: nil, + expectError: true, + }, + { + name: "resolver fee too large", + integratorFee: nil, + resolverFee: &ResolverFee{ + Fee: 6554, // 6554 * 10 = 65540, exceeds 0xffff + WhitelistDiscount: 0, + }, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + packed, err := packFeeParameter(tt.integratorFee, tt.resolverFee) + + if tt.expectError { + if err == nil { + t.Errorf("expected an error but got none") + } + return + } + + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + if packed != tt.expectedPacked { + t.Errorf("packed value mismatch:\nexpected: %d (0x%x)\ngot: %d (0x%x)", + tt.expectedPacked, tt.expectedPacked, packed, packed) + } + + // Verify the hex representation matches + gotHex := fmt.Sprintf("0x%x", packed) + if gotHex != tt.expectedHex { + t.Errorf("hex representation mismatch:\nexpected: %s\ngot: %s", + tt.expectedHex, gotHex) + } + }) + } +} + +func TestPackFeeParameterBitLayout(t *testing.T) { + // Test to verify the bit layout is correct + integratorFee := &IntegratorFee{ + Fee: 1, // becomes 10 after *10 + Share: 200, // becomes 2 after /100 + } + resolverFee := &ResolverFee{ + Fee: 3, // becomes 30 after *10 + WhitelistDiscount: 25, // becomes 75 after (100-25) + } + + packed, err := packFeeParameter(integratorFee, resolverFee) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Extract each component and verify + extractedIntegratorFee := (packed >> 32) & 0xffff + extractedIntegratorShare := (packed >> 24) & 0xff + extractedResolverFee := (packed >> 8) & 0xffff + extractedResolverDiscount := packed & 0xff + + if extractedIntegratorFee != 10 { + t.Errorf("integrator fee: expected 10, got %d", extractedIntegratorFee) + } + if extractedIntegratorShare != 2 { + t.Errorf("integrator share: expected 2, got %d", extractedIntegratorShare) + } + if extractedResolverFee != 30 { + t.Errorf("resolver fee: expected 30, got %d", extractedResolverFee) + } + if extractedResolverDiscount != 75 { + t.Errorf("resolver discount: expected 75, got %d", extractedResolverDiscount) + } +} + +func TestEncodeWhitelist(t *testing.T) { + tests := []struct { + name string + whitelist []string + expectedHex string + expectedInt string + expectError bool + }{ + { + name: "empty whitelist", + whitelist: []string{}, + expectedHex: "0x0", + expectedInt: "0", + expectError: false, + }, + { + name: "single address", + whitelist: []string{ + "0x0b8a49d816cc709b6eadb09498030ae3416b66dc", + }, + // Length: 1 (0x01) + // Address: 0x0b8a49d816cc709b6eadb09498030ae3416b66dc + // Lower 80 bits of address: 0xb09498030ae3416b66dc (not 0x9498... - the 'b0' is included!) + // Result: 0x01 << 80 | 0xb09498030ae3416b66dc = 0x01b09498030ae3416b66dc + expectedHex: "0x1b09498030ae3416b66dc", + expectedInt: "2042803392333285612545756", + expectError: false, + }, + { + name: "two addresses", + whitelist: []string{ + "0x0b8a49d816cc709b6eadb09498030ae3416b66dc", + "0xad3b67bca8935cb510c8d18bd45f0b94f54a968f", + }, + // Length: 2 (0x02) + // Addr1: 0x0b8a49d816cc709b6eadb09498030ae3416b66dc -> lower 80 bits: 0xb09498030ae3416b66dc + // Addr2: 0xad3b67bca8935cb510c8d18bd45f0b94f54a968f -> lower 80 bits: 0xd18bd45f0b94f54a968f + // Result: 0x02 << 160 | addr1 << 80 | addr2 + expectedHex: "0x2b09498030ae3416b66dcd18bd45f0b94f54a968f", + expectedInt: "3931099402718965111428264566673874676661292275343", + expectError: false, + }, + { + name: "six addresses from Python example", + whitelist: []string{ + "0x0b8a49d816cc709b6eadb09498030ae3416b66dc", + "0xad3b67bca8935cb510c8d18bd45f0b94f54a968f", + "0xf81377c3f03996fde219c90ed87a54c23dc480b3", + "0xbeef02961503351625926ea9a11ae13b29f5c555", + "0x00000688768803bbd44095770895ad27ad6b0d95", + "0xf0a12fefa78181a3749474db31d09524fa87b1f7", + }, + expectedHex: "0x6b09498030ae3416b66dcd18bd45f0b94f54a968fc90ed87a54c23dc480b36ea9a11ae13b29f5c55595770895ad27ad6b0d9574db31d09524fa87b1f7", + expectedInt: "20883771562388111227015435143042649718086364789015763900152491422545023853078207873061945043557603652822441846333414299315624750241895600466932215", + expectError: false, + }, + { + name: "address without 0x prefix", + whitelist: []string{ + "0b8a49d816cc709b6eadb09498030ae3416b66dc", + }, + // Same address, just without 0x prefix + // Lower 80 bits: 0xb09498030ae3416b66dc + expectedHex: "0x1b09498030ae3416b66dc", + expectedInt: "2042803392333285612545756", + expectError: false, + }, + { + name: "too many addresses", + whitelist: func() []string { + addresses := make([]string, 256) + for i := 0; i < 256; i++ { + addresses[i] = "0x0b8a49d816cc709b6eadb09498030ae3416b66dc" + } + return addresses + }(), + expectError: true, + }, + { + name: "invalid hex address", + whitelist: []string{ + "0xGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG", + }, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + encoded, err := encodeWhitelist(tt.whitelist) + + if tt.expectError { + if err == nil { + t.Errorf("expected an error but got none") + } + return + } + + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + // Check hex representation + gotHex := "0x" + encoded.Text(16) + if gotHex != tt.expectedHex { + t.Errorf("hex mismatch:\nexpected: %s\ngot: %s", tt.expectedHex, gotHex) + } + + // Check integer representation + gotInt := encoded.String() + if gotInt != tt.expectedInt { + t.Errorf("int mismatch:\nexpected: %s\ngot: %s", tt.expectedInt, gotInt) + } + + // Check bit length for the Python example + if tt.name == "six addresses from Python example" { + bitLength := encoded.BitLen() + expectedBitLength := 483 + if bitLength != expectedBitLength { + t.Errorf("bit length mismatch:\nexpected: %d\ngot: %d", expectedBitLength, bitLength) + } + } + }) + } +} + +func TestEncodeWhitelistBitLayout(t *testing.T) { + // Test to verify addresses are properly masked to 80 bits + whitelist := []string{ + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", // Full address with all bits set + } + + encoded, err := encodeWhitelist(whitelist) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // The encoding should be: length (1) << 80 | (address & 80-bit mask) + // 80-bit mask of 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF = 0xFFFFFFFFFFFFFFFFFFFF + expected := new(big.Int) + expected.SetString("1ffffffffffffffffffff", 16) // 0x01 << 80 | 0xFFFFFFFFFFFFFFFFFFFF + + if encoded.Cmp(expected) != 0 { + t.Errorf("masking failed:\nexpected: 0x%x\ngot: 0x%x", expected, encoded) + } + + // Verify the address was properly truncated to 80 bits (10 bytes) + // Extract the address part (lower 80 bits) + addressPart := new(big.Int).Set(encoded) + mask80 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 80), big.NewInt(1)) + addressPart.And(addressPart, mask80) + + expectedAddr := new(big.Int) + expectedAddr.SetString("ffffffffffffffffffff", 16) + + if addressPart.Cmp(expectedAddr) != 0 { + t.Errorf("address masking failed:\nexpected: 0x%x\ngot: 0x%x", expectedAddr, addressPart) + } +} + +func TestEncodeWhitelistOrderPreservation(t *testing.T) { + // Test that the order of addresses is preserved in encoding + whitelist := []string{ + "0x1111111111111111111111111111111111111111", + "0x2222222222222222222222222222222222222222", + "0x3333333333333333333333333333333333333333", + } + + encoded, err := encodeWhitelist(whitelist) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Manually decode and verify order + // Expected format: [length=3][addr1_lower80][addr2_lower80][addr3_lower80] + + // Extract addresses by shifting + mask80 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 80), big.NewInt(1)) + + temp := new(big.Int).Set(encoded) + + // Extract third address (rightmost 80 bits) + addr3 := new(big.Int).And(temp, mask80) + temp.Rsh(temp, 80) + + // Extract second address + addr2 := new(big.Int).And(temp, mask80) + temp.Rsh(temp, 80) + + // Extract first address + addr1 := new(big.Int).And(temp, mask80) + + // Verify addresses match (lower 80 bits) + expected1 := new(big.Int) + expected1.SetString("1111111111111111111111111111111111111111", 16) + expected1.And(expected1, mask80) + + expected2 := new(big.Int) + expected2.SetString("2222222222222222222222222222222222222222", 16) + expected2.And(expected2, mask80) + + expected3 := new(big.Int) + expected3.SetString("3333333333333333333333333333333333333333", 16) + expected3.And(expected3, mask80) + + if addr1.Cmp(expected1) != 0 { + t.Errorf("first address mismatch:\nexpected: 0x%x\ngot: 0x%x", expected1, addr1) + } + if addr2.Cmp(expected2) != 0 { + t.Errorf("second address mismatch:\nexpected: 0x%x\ngot: 0x%x", expected2, addr2) + } + if addr3.Cmp(expected3) != 0 { + t.Errorf("third address mismatch:\nexpected: 0x%x\ngot: 0x%x", expected3, addr3) + } +} + +func TestEncodeWhitelistMaxLength(t *testing.T) { + // Test with exactly 255 addresses (maximum allowed) + whitelist := make([]string, 255) + for i := 0; i < 255; i++ { + whitelist[i] = "0x0b8a49d816cc709b6eadb09498030ae3416b66dc" + } + + encoded, err := encodeWhitelist(whitelist) + if err != nil { + t.Fatalf("unexpected error with 255 addresses: %v", err) + } + + // Verify length is 255 + // Extract the highest byte(s) which contain the length + temp := new(big.Int).Set(encoded) + temp.Rsh(temp, 255*80) // Shift past all addresses + + if temp.Int64() != 255 { + t.Errorf("length mismatch:\nexpected: 255\ngot: %d", temp.Int64()) + } +} + +func TestConcatFeeAndWhitelist(t *testing.T) { + tests := []struct { + name string + whitelist []string + integratorFee *IntegratorFee + resolverFee *ResolverFee + expectedHex string + expectedInt string + expectedBitLength int + expectError bool + }{ + { + name: "empty whitelist with fees", + whitelist: []string{}, + integratorFee: &IntegratorFee{ + Fee: 0, + Share: 0, + }, + resolverFee: &ResolverFee{ + Fee: 50, + WhitelistDiscount: 50, + }, + // fee_parameter = 0x1f432 (128050) + // whitelist is empty (0 bits) + // Result: 0x1f432 << 0 | 0 = 0x1f432 + expectedHex: "0x1f432", + expectedInt: "128050", + expectedBitLength: 48, + expectError: false, + }, + { + name: "six addresses from Python example", + whitelist: []string{ + "0x0b8a49d816cc709b6eadb09498030ae3416b66dc", + "0xad3b67bca8935cb510c8d18bd45f0b94f54a968f", + "0xf81377c3f03996fde219c90ed87a54c23dc480b3", + "0xbeef02961503351625926ea9a11ae13b29f5c555", + "0x00000688768803bbd44095770895ad27ad6b0d95", + "0xf0a12fefa78181a3749474db31d09524fa87b1f7", + }, + integratorFee: &IntegratorFee{ + Integrator: "0x0000000000000000000000000000000000000000", + Protocol: "0x0000000000000000000000000000000000000000", + Fee: 0, + Share: 0, + }, + resolverFee: &ResolverFee{ + Receiver: "0x90cbe4bdd538d6e9b379bff5fe72c3d67a521de5", + Fee: 50, + WhitelistDiscount: 50, + }, + // fee_parameter = 0x1f432 (128050) + // whitelist_encoded = 0x6b09498030ae3416b66dcd18bd45f0b94f54a968fc90ed87a54c23dc480b36ea9a11ae13b29f5c55595770895ad27ad6b0d9574db31d09524fa87b1f7 + // whitelist_bit_length = 8 + 6*80 = 488 bits + // Result: 0x1f432 << 488 | whitelist_encoded + expectedHex: "0x1f43206b09498030ae3416b66dcd18bd45f0b94f54a968fc90ed87a54c23dc480b36ea9a11ae13b29f5c55595770895ad27ad6b0d9574db31d09524fa87b1f7", + expectedInt: "102333435761970040526585089485838969078133364081436675317847752614578808440926825918247603365248761165799707486146452557225400316772363664864037468353015", + expectedBitLength: 536, + expectError: false, + }, + { + name: "single address", + whitelist: []string{ + "0x0b8a49d816cc709b6eadb09498030ae3416b66dc", + }, + integratorFee: &IntegratorFee{ + Fee: 10, + Share: 500, + }, + resolverFee: &ResolverFee{ + Fee: 100, + WhitelistDiscount: 25, + }, + // fee_parameter: integrator_fee=100, integrator_share=5, resolver_fee=1000, resolver_discount=75 + // = (100 << 32) | (5 << 24) | (1000 << 8) | 75 + // = 0x640503e84b + // whitelist_encoded = 0x01b09498030ae3416b66dc (note: 0x0b8a... truncated to lower 80 bits = 0xb09498...) + // whitelist_bit_length = 8 + 1*80 = 88 bits + // Result: 0x640503e84b << 88 | 0x01b09498030ae3416b66dc + expectedHex: "0x640503e84b01b09498030ae3416b66dc", + expectedInt: "132948840314160194232854165493602019036", + expectedBitLength: 136, + expectError: false, + }, + { + name: "nil fees with addresses", + whitelist: []string{ + "0x1111111111111111111111111111111111111111", + }, + integratorFee: nil, + resolverFee: nil, + // fee_parameter = 0 + // Address 0x1111111111111111111111111111111111111111 masked to lower 80 bits = 0x1111111111111111111111 + // whitelist_encoded = 0x011111111111111111111111 (length=1, then 80-bit address) + // whitelist_bit_length = 88 bits + // Result: 0 << 88 | 0x011111111111111111111111 + // Note: big.Int.Text(16) drops leading zeros, so 0x011... becomes 0x11... + expectedHex: "0x111111111111111111111", + expectedInt: "1289520874255604453019921", + expectedBitLength: 136, + expectError: false, + }, + { + name: "two addresses with fees", + whitelist: []string{ + "0x1111111111111111111111111111111111111111", + "0x2222222222222222222222222222222222222222", + }, + integratorFee: &IntegratorFee{ + Fee: 1, + Share: 1000, + }, + resolverFee: &ResolverFee{ + Fee: 1, + WhitelistDiscount: 0, + }, + // fee_parameter: (10 << 32) | (10 << 24) | (10 << 8) | 100 + // whitelist_bit_length = 8 + 2*80 = 168 bits + expectedBitLength: 216, + expectError: false, + }, + { + name: "invalid whitelist causes error", + whitelist: []string{ + "0xGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG", + }, + integratorFee: nil, + resolverFee: nil, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, bitLength, err := concatFeeAndWhitelist(tt.whitelist, tt.integratorFee, tt.resolverFee) + + if tt.expectError { + if err == nil { + t.Errorf("expected an error but got none") + } + return + } + + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + // Check bit length + if bitLength != tt.expectedBitLength { + t.Errorf("bit length mismatch:\nexpected: %d\ngot: %d", tt.expectedBitLength, bitLength) + } + + // Check hex representation if provided + if tt.expectedHex != "" { + gotHex := "0x" + result.Text(16) + if gotHex != tt.expectedHex { + t.Errorf("hex mismatch:\nexpected: %s\ngot: %s", tt.expectedHex, gotHex) + } + } + + // Check integer representation if provided + if tt.expectedInt != "" { + gotInt := result.String() + if gotInt != tt.expectedInt { + t.Errorf("int mismatch:\nexpected: %s\ngot: %s", tt.expectedInt, gotInt) + } + } + }) + } +} + +func TestConcatFeeAndWhitelistBitLayout(t *testing.T) { + // Test to verify the bit layout: [fee_parameter][whitelist_encoded] + whitelist := []string{ + "0x1111111111111111111111111111111111111111", + } + integratorFee := &IntegratorFee{ + Fee: 1, + Share: 100, + } + resolverFee := &ResolverFee{ + Fee: 2, + WhitelistDiscount: 50, + } + + result, bitLength, err := concatFeeAndWhitelist(whitelist, integratorFee, resolverFee) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Expected bit length: 48 (fee) + 88 (1 address with length byte) = 136 + expectedBitLength := 136 + if bitLength != expectedBitLength { + t.Errorf("bit length mismatch:\nexpected: %d\ngot: %d", expectedBitLength, bitLength) + } + + // Extract fee parameter (upper 48 bits) + temp := new(big.Int).Set(result) + temp.Rsh(temp, 88) // Shift right by whitelist bit length + feeParam := temp.Uint64() + + // Calculate expected fee parameter + expectedFeeParam, _ := packFeeParameter(integratorFee, resolverFee) + if feeParam != expectedFeeParam { + t.Errorf("fee parameter mismatch:\nexpected: 0x%x\ngot: 0x%x", expectedFeeParam, feeParam) + } + + // Extract whitelist (lower 88 bits) + mask88 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 88), big.NewInt(1)) + whitelistPart := new(big.Int).And(result, mask88) + + // Calculate expected whitelist + expectedWhitelist, _ := encodeWhitelist(whitelist) + if whitelistPart.Cmp(expectedWhitelist) != 0 { + t.Errorf("whitelist mismatch:\nexpected: 0x%x\ngot: 0x%x", expectedWhitelist, whitelistPart) + } +} + +func TestConcatFeeAndWhitelistEmptyWhitelist(t *testing.T) { + // Test that empty whitelist results in just the fee parameter + integratorFee := &IntegratorFee{ + Fee: 50, + Share: 2500, + } + resolverFee := &ResolverFee{ + Fee: 100, + WhitelistDiscount: 30, + } + + result, bitLength, err := concatFeeAndWhitelist([]string{}, integratorFee, resolverFee) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // With empty whitelist, result should just be the fee parameter + expectedFeeParam, _ := packFeeParameter(integratorFee, resolverFee) + + if result.Uint64() != expectedFeeParam { + t.Errorf("with empty whitelist, result should equal fee parameter:\nexpected: 0x%x\ngot: 0x%x", + expectedFeeParam, result.Uint64()) + } + + // Bit length should be exactly 48 + if bitLength != 48 { + t.Errorf("empty whitelist bit length should be 48, got: %d", bitLength) + } +} + +func TestBuildFeePostInteractionData(t *testing.T) { + tests := []struct { + name string + customReceiver bool + customReceiverAddress string + integratorFee *IntegratorFee + resolverFee *ResolverFee + whitelist []string + extraInteractionTarget string + extraInteractionData []byte + expectedHex string + expectedLength int + expectError bool + }{ + { + name: "Python example - no custom receiver, 6 addresses in whitelist", + customReceiver: false, + customReceiverAddress: "", + integratorFee: &IntegratorFee{ + Integrator: "0x0000000000000000000000000000000000000000", + Protocol: "0x0000000000000000000000000000000000000000", + Fee: 0, + Share: 0, + }, + resolverFee: &ResolverFee{ + Receiver: "0x90cbe4bdd538d6e9b379bff5fe72c3d67a521de5", + Fee: 50, + WhitelistDiscount: 50, + }, + whitelist: []string{ + "0x0b8a49d816cc709b6eadb09498030ae3416b66dc", + "0xad3b67bca8935cb510c8d18bd45f0b94f54a968f", + "0xf81377c3f03996fde219c90ed87a54c23dc480b3", + "0xbeef02961503351625926ea9a11ae13b29f5c555", + "0x00000688768803bbd44095770895ad27ad6b0d95", + "0xf0a12fefa78181a3749474db31d09524fa87b1f7", + }, + extraInteractionTarget: "0x0000000000000000000000000000000000000000", + extraInteractionData: []byte{}, + expectedHex: "00000000000000000000000000000000000000000090cbe4bdd538d6e9b379bff5fe72c3d67a521de500000001f43206b09498030ae3416b66dcd18bd45f0b94f54a968fc90ed87a54c23dc480b36ea9a11ae13b29f5c55595770895ad27ad6b0d9574db31d09524fa87b1f7", + expectedLength: 108, + expectError: false, + }, + { + name: "empty whitelist, no fees", + customReceiver: false, + customReceiverAddress: "", + integratorFee: nil, + resolverFee: nil, + whitelist: []string{}, + extraInteractionTarget: "", + extraInteractionData: nil, + // Structure: [1 byte flag: 0x00][20 bytes zeros][20 bytes zeros][6 bytes fee_parameter: 0x000000000000] + // Total: 1 + 20 + 20 + 6 = 47 bytes = 94 hex chars + expectedHex: "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedLength: 47, + expectError: false, + }, + { + name: "with custom receiver", + customReceiver: true, + customReceiverAddress: "0x1111111111111111111111111111111111111111", + integratorFee: &IntegratorFee{ + Integrator: "0x2222222222222222222222222222222222222222", + Protocol: "0x3333333333333333333333333333333333333333", + Fee: 10, + Share: 500, + }, + resolverFee: &ResolverFee{ + Receiver: "0x4444444444444444444444444444444444444444", + Fee: 20, + WhitelistDiscount: 30, + }, + whitelist: []string{}, + extraInteractionTarget: "", + extraInteractionData: nil, + // First byte should be 0x01 for custom receiver + expectedLength: 67, // 1 + 20 + 20 + 20 + 6 + expectError: false, + }, + { + name: "with extra interaction", + customReceiver: false, + integratorFee: nil, + resolverFee: nil, + whitelist: []string{}, + extraInteractionTarget: "0x5555555555555555555555555555555555555555", + extraInteractionData: []byte{0xde, 0xad, 0xbe, 0xef}, + expectedLength: 71, // 1 + 20 + 20 + 6 + 20 + 4 + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := buildFeePostInteractionData(&buildFeePostInteractionDataParams{ + CustomReceiver: tt.customReceiver, + CustomReceiverAddress: tt.customReceiverAddress, + IntegratorFee: tt.integratorFee, + ResolverFee: tt.resolverFee, + Whitelist: tt.whitelist, + ExtraInteractionTarget: tt.extraInteractionTarget, + ExtraInteractionData: tt.extraInteractionData, + }) + + if tt.expectError { + if err == nil { + t.Errorf("expected an error but got none") + } + return + } + + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + // Check length + if len(result) != tt.expectedLength { + t.Errorf("length mismatch:\nexpected: %d\ngot: %d", tt.expectedLength, len(result)) + } + + // Check hex if provided + if tt.expectedHex != "" { + gotHex := fmt.Sprintf("%x", result) + if gotHex != tt.expectedHex { + t.Errorf("hex mismatch:\nexpected: %s\ngot: %s", tt.expectedHex, gotHex) + } + } + + // Verify first byte (flag) + if tt.customReceiver { + if result[0] != 0x01 { + t.Errorf("expected first byte to be 0x01 for custom receiver, got: 0x%02x", result[0]) + } + } else { + if result[0] != 0x00 { + t.Errorf("expected first byte to be 0x00 for no custom receiver, got: 0x%02x", result[0]) + } + } + }) + } +} + +func TestBuildFeePostInteractionDataStructure(t *testing.T) { + // Test to verify the byte structure is correct + integratorFee := &IntegratorFee{ + Integrator: "0x1111111111111111111111111111111111111111", + Protocol: "0x2222222222222222222222222222222222222222", + Fee: 5, + Share: 1000, + } + resolverFee := &ResolverFee{ + Receiver: "0x3333333333333333333333333333333333333333", + Fee: 10, + WhitelistDiscount: 20, + } + + result, err := buildFeePostInteractionData(&buildFeePostInteractionDataParams{ + CustomReceiver: false, + CustomReceiverAddress: "", + IntegratorFee: integratorFee, + ResolverFee: resolverFee, + Whitelist: []string{}, + ExtraInteractionTarget: "", + ExtraInteractionData: nil, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Verify structure: + // [1 byte flag][20 bytes integrator][20 bytes resolver][6 bytes fee_parameter] + if len(result) != 47 { + t.Errorf("expected length 47, got %d", len(result)) + } + + // Check flag byte + if result[0] != 0x00 { + t.Errorf("expected flag byte 0x00, got 0x%02x", result[0]) + } + + // Check integrator address (bytes 1-20) + expectedIntegrator := "1111111111111111111111111111111111111111" + gotIntegrator := fmt.Sprintf("%x", result[1:21]) + if gotIntegrator != expectedIntegrator { + t.Errorf("integrator address mismatch:\nexpected: %s\ngot: %s", expectedIntegrator, gotIntegrator) + } + + // Check resolver address (bytes 21-40) + expectedResolver := "3333333333333333333333333333333333333333" + gotResolver := fmt.Sprintf("%x", result[21:41]) + if gotResolver != expectedResolver { + t.Errorf("resolver address mismatch:\nexpected: %s\ngot: %s", expectedResolver, gotResolver) + } +} + +func TestBuildOrderExtension(t *testing.T) { + extensionTarget := "0xc0dfdb9e7a392c3dbbe7c6fbe8fbc1789c9fe05e" + integratorFee := &IntegratorFee{ + Integrator: "0x0000000000000000000000000000000000000000", + Protocol: "0x0000000000000000000000000000000000000000", + Fee: 0, + Share: 0, + } + resolverFee := &ResolverFee{ + Receiver: "0x90cbe4bdd538d6e9b379bff5fe72c3d67a521de5", + Fee: 50, + WhitelistDiscount: 50, + } + whitelist := map[string]string{ + "0x9b8a49d816cc709b6eadb09498030ae3416b66dc": "0x0b8a49d816cc709b6eadb09498030ae3416b66dc", + "0x9d3b67bca8935cb510c8d18bd45f0b94f54a968f": "0xad3b67bca8935cb510c8d18bd45f0b94f54a968f", + "0x981377c3f03996fde219c90ed87a54c23dc480b3": "0xf81377c3f03996fde219c90ed87a54c23dc480b3", + "0x9eef02961503351625926ea9a11ae13b29f5c555": "0xbeef02961503351625926ea9a11ae13b29f5c555", + "0x90000688768803bbd44095770895ad27ad6b0d95": "0x00000688768803bbd44095770895ad27ad6b0d95", + "0x90a12fefa78181a3749474db31d09524fa87b1f7": "0xf0a12fefa78181a3749474db31d09524fa87b1f7", + } + + extension, err := BuildOrderExtensionBytes(&BuildOrderExtensionBytesParams{ + ExtensionTarget: extensionTarget, + IntegratorFee: integratorFee, + ResolverFee: resolverFee, + Whitelist: whitelist, + MakerPermit: nil, // makerPermit + CustomReceiver: "", // customReceiver + ExtraInteraction: []byte{}, // extraInteraction + CustomData: nil, // customData + }) + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expectedExtension := "0x0000012e000000ae000000ae000000ae000000ae000000570000000000000000c0dfdb9e7a392c3dbbe7c6fbe8fbc1789c9fe05e00000001f4320695770895ad27ad6b0d95b09498030ae3416b66dcd18bd45f0b94f54a968f6ea9a11ae13b29f5c55574db31d09524fa87b1f7c90ed87a54c23dc480b3c0dfdb9e7a392c3dbbe7c6fbe8fbc1789c9fe05e00000001f4320695770895ad27ad6b0d95b09498030ae3416b66dcd18bd45f0b94f54a968f6ea9a11ae13b29f5c55574db31d09524fa87b1f7c90ed87a54c23dc480b3c0dfdb9e7a392c3dbbe7c6fbe8fbc1789c9fe05e00000000000000000000000000000000000000000090cbe4bdd538d6e9b379bff5fe72c3d67a521de500000001f4320695770895ad27ad6b0d95b09498030ae3416b66dcd18bd45f0b94f54a968f6ea9a11ae13b29f5c55574db31d09524fa87b1f7c90ed87a54c23dc480b3" + + if extension != expectedExtension { + t.Errorf("extension mismatch:\nexpected: %s\ngot: %s", expectedExtension, extension) + } + + // Verify length (670 chars as per Python output) + if len(extension) != 670 { + t.Errorf("extension length mismatch:\nexpected: 670\ngot: %d", len(extension)) + } +} diff --git a/sdk-clients/orderbook/orderbook_types_manual.go b/sdk-clients/orderbook/orderbook_types_manual.go index 08fd9ed..6e7f9ec 100644 --- a/sdk-clients/orderbook/orderbook_types_manual.go +++ b/sdk-clients/orderbook/orderbook_types_manual.go @@ -70,7 +70,7 @@ type OrderData struct { TakingAmount string `json:"takingAmount"` Salt string `json:"salt"` Maker string `json:"maker"` - AllowedSender string `json:"allowedSender"` + AllowedSender string `json:"allowedSender,omitempty"` Receiver string `json:"receiver"` MakerTraits string `json:"makerTraits"` Extension string `json:"extension"` @@ -100,26 +100,19 @@ type OrderResponseExtended struct { } type GetOrderByHashResponse struct { - ID int `json:"id"` - OrderHash string `json:"orderHash"` - CreateDateTime time.Time `json:"createDateTime"` - LastChangedDateTime time.Time `json:"lastChangedDateTime"` - TakerAsset string `json:"takerAsset"` - MakerAsset string `json:"makerAsset"` - OrderMaker string `json:"orderMaker"` - OrderStatus int `json:"orderStatus"` - MakerAmount string `json:"makerAmount"` - RemainingMakerAmount string `json:"remainingMakerAmount"` - MakerBalance string `json:"makerBalance"` - MakerAllowance string `json:"makerAllowance"` - TakerAmount string `json:"takerAmount"` - Data OrderData `json:"data"` - MakerRate string `json:"makerRate"` - TakerRate string `json:"takerRate"` - TakerRateDoubled float64 `json:"takerRateDoubled"` - OrderHashSelector int `json:"orderHashSelector"` - OrderInvalidReason interface{} `json:"orderInvalidReason"` - IsMakerContract bool `json:"isMakerContract"` + OrderHash string `json:"orderHash"` + CreateDateTime time.Time `json:"createDateTime"` + Signature string `json:"signature"` + OrderStatus int `json:"orderStatus"` + RemainingMakerAmount string `json:"remainingMakerAmount"` + MakerBalance string `json:"makerBalance"` + MakerAllowance string `json:"makerAllowance"` + Data OrderData `json:"data"` + MakerRate string `json:"makerRate"` + TakerRate string `json:"takerRate"` + OrderInvalidReason string `json:"orderInvalidReason"` + IsMakerContract bool `json:"isMakerContract"` + Events string `json:"events"` } type CountResponse struct { @@ -146,6 +139,15 @@ type GetOrderByHashResponseExtended struct { LimitOrderDataNormalized NormalizedLimitOrderData } +type Orders struct { + Meta struct { + HasMore bool `json:"hasMore"` + NextCursor string `json:"nextCursor"` + Count int `json:"count"` + } `json:"meta"` + Items []GetOrderByHashResponse `json:"items"` +} + type OrderExtendedWithSignature struct { GetOrderByHashResponse LimitOrderDataNormalized NormalizedLimitOrderData @@ -177,3 +179,70 @@ type TakerTraitsCalldata struct { Trait *big.Int Args string } + +type GetFeeInfoParams struct { + MakerAsset string `url:"makerAsset"` + TakerAsset string `url:"takerAsset"` + MakerAmount string `url:"makerAmount"` + TakerAmount string `url:"takerAmount"` +} + +type FeeInfoResponse struct { + Whitelist map[string]string `json:"whitelist"` // Map of resolver addresses to their corresponding addresses + FeeBps int `json:"feeBps"` // Fee in basis points (e.g., 50 = 0.5%) + WhitelistDiscountPercent int `json:"whitelistDiscountPercent"` // Discount percentage for whitelisted resolvers (e.g., 50 = 50% off) + ProtocolFeeReceiver string `json:"protocolFeeReceiver"` // Address that receives protocol fees + ExtensionAddress string `json:"extensionAddress"` // Address of the fee extension contract +} + +type OrderStatus int + +const ( + ValidOrders OrderStatus = 1 + TemporarilyInvalidOrders OrderStatus = 2 + InvalidOrders OrderStatus = 3 +) + +type GetOrderCountParams struct { + Statuses []OrderStatus `url:"Statuses"` + TakerAsset string `url:"TakerAsset"` + MakerAsset string `url:"MakerAsset"` +} + +type GetOrderCountResponse struct { + Count int `json:"count"` +} + +type IntegratorFee struct { + Integrator string + Protocol string + Fee int // Fee in basis points (e.g., 1 = 0.01%, 100 = 1%) + Share int // Integrator's share in basis points (e.g., 1 = 0.01%, 100 = 1%) +} + +type ResolverFee struct { + Receiver string + Fee int // Fee in basis points (e.g., 1 = 0.01%, 100 = 1%) + WhitelistDiscount int // Discount percentage for whitelisted addresses (0-100) +} + +type buildFeePostInteractionDataParams struct { + CustomReceiver bool + CustomReceiverAddress string + IntegratorFee *IntegratorFee + ResolverFee *ResolverFee + Whitelist []string + ExtraInteractionTarget string + ExtraInteractionData []byte +} + +type BuildOrderExtensionBytesParams struct { + ExtensionTarget string + IntegratorFee *IntegratorFee + ResolverFee *ResolverFee + Whitelist map[string]string + MakerPermit []byte + CustomReceiver string + ExtraInteraction []byte + CustomData []byte +} diff --git a/sdk-clients/orderbook/salt.go b/sdk-clients/orderbook/salt.go new file mode 100644 index 0000000..25da3e3 --- /dev/null +++ b/sdk-clients/orderbook/salt.go @@ -0,0 +1,70 @@ +package orderbook + +import ( + "crypto/rand" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/crypto" +) + +type GetSaltParams struct { + Extension string + Source *string // Optional string for tracking code (defaults to "sdk" if nil) + UseRandom bool // If true, uses random bits for the middle section; otherwise uses timestamp +} + +// GenerateSaltNew generates a salt value with specific bit patterns +func GenerateSaltNew(params *GetSaltParams) (*big.Int, error) { + salt := big.NewInt(0) + + // Generate upper 32 bits (bits 224-255) - tracking code mask + trackingSource := "sdk" + if params.Source != nil { + trackingSource = *params.Source + } + trackingHash := crypto.Keccak256Hash([]byte(trackingSource)) + trackingCodeMask := new(big.Int).Lsh(new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 32), big.NewInt(1)), 224) // (2^32 - 1) << 224 + trackingBits := new(big.Int).SetBytes(trackingHash.Bytes()) + trackingBits.And(trackingBits, trackingCodeMask) + salt.Or(salt, trackingBits) + + // Generate middle 64 bits (bits 160-223) + var middleBits *big.Int + if params.UseRandom { + // Generate random 64 bits + randomBytes := make([]byte, 8) + _, err := rand.Read(randomBytes) + if err != nil { + return nil, fmt.Errorf("failed to generate random bytes: %w", err) + } + middleBits = new(big.Int).SetBytes(randomBytes) + } else { + middleBits = big.NewInt(time.Now().Unix()) + } + // Mask to 64 bits and shift to position 160-223 + mask64 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 64), big.NewInt(1)) + middleBits.And(middleBits, mask64) + middleBits.Lsh(middleBits, 160) + salt.Or(salt, middleBits) + + // Handle extension for lower 160 bits + if params.Extension == "0x" || params.Extension == "" { + // If there is no extension, salt can be anything for the lower bits + // (middle bits already set above, lower 160 bits remain 0 or can be left as-is) + } else { + // Lower 160 bits must be from keccak256 hash of the extension + extensionBytes, err := stringToHexBytes(params.Extension) + if err != nil { + return nil, fmt.Errorf("failed to convert extension to bytes: %w", err) + } + extensionHash := crypto.Keccak256Hash(extensionBytes) + mask160 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 160), big.NewInt(1)) + extensionBits := new(big.Int).SetBytes(extensionHash.Bytes()) + extensionBits.And(extensionBits, mask160) + salt.Or(salt, extensionBits) + } + + return salt, nil +} diff --git a/sdk-clients/orderbook/validate.go b/sdk-clients/orderbook/validate.go index c0c8603..9e5f583 100644 --- a/sdk-clients/orderbook/validate.go +++ b/sdk-clients/orderbook/validate.go @@ -81,3 +81,20 @@ func (params *GetActiveOrdersWithPermitParams) Validate() error { validationErrors = validate.Parameter(params.Token, "token", validate.CheckEthereumAddressRequired, validationErrors) return validate.ConsolidateValidationErorrs(validationErrors) } + +func (params *GetFeeInfoParams) Validate() error { + var validationErrors []error + validationErrors = validate.Parameter(params.MakerAmount, "makerAmount", validate.CheckBigIntRequired, validationErrors) + validationErrors = validate.Parameter(params.MakerAsset, "makerAsset", validate.CheckEthereumAddressRequired, validationErrors) + validationErrors = validate.Parameter(params.TakerAmount, "takerAmount", validate.CheckBigIntRequired, validationErrors) + validationErrors = validate.Parameter(params.TakerAsset, "takerAsset", validate.CheckEthereumAddressRequired, validationErrors) + return validate.ConsolidateValidationErorrs(validationErrors) +} + +func (params *GetOrderCountParams) Validate() error { + var validationErrors []error + //validationErrors = validate.Parameter(params.Statuses, "statuses", validate.CheckStatusesListRequired, validationErrors) + validationErrors = validate.Parameter(params.MakerAsset, "makerAsset", validate.CheckEthereumAddressRequired, validationErrors) + validationErrors = validate.Parameter(params.TakerAsset, "takerAsset", validate.CheckEthereumAddressRequired, validationErrors) + return validate.ConsolidateValidationErorrs(validationErrors) +} diff --git a/sdk-clients/orderbook/web3data.go b/sdk-clients/orderbook/web3data.go index 59b861a..a54d8ba 100644 --- a/sdk-clients/orderbook/web3data.go +++ b/sdk-clients/orderbook/web3data.go @@ -39,7 +39,7 @@ func (c *Client) GetSeriesNonce(ctx context.Context, publicAddress gethCommon.Ad return nonce, nil } -func (c *Client) GetFillOrderCalldata(order *OrderExtendedWithSignature, takerTraits *TakerTraits) ([]byte, error) { +func (c *Client) GetFillOrderCalldata(order *GetOrderByHashResponseExtended, takerTraits *TakerTraits) ([]byte, error) { var function string if order.Data.Extension == "0x" { From 8e43b5ba38b473c3e4b648af0115c7349007b2f1 Mon Sep 17 00:00:00 2001 From: Tanner Moore Date: Wed, 29 Oct 2025 17:40:05 -0600 Subject: [PATCH 3/3] simplifying/improving comments --- sdk-clients/orderbook/orderbook_extension.go | 27 +++++++++----------- sdk-clients/orderbook/salt.go | 2 +- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/sdk-clients/orderbook/orderbook_extension.go b/sdk-clients/orderbook/orderbook_extension.go index 9ce6b2e..6c410e9 100644 --- a/sdk-clients/orderbook/orderbook_extension.go +++ b/sdk-clients/orderbook/orderbook_extension.go @@ -11,6 +11,8 @@ import ( const nullAddress = "0x0000000000000000000000000000000000000000" +// packFeeParameter encodes integrator and resolver fee details into a single 48-bit value with error checks on input ranges. +// Returns the packed 48-bit value and an error if any input values exceed their allowed ranges. func packFeeParameter(integratorFee *IntegratorFee, resolverFee *ResolverFee) (uint64, error) { var integratorFeeValue uint64 = 0 var integratorShare uint64 = 0 @@ -49,6 +51,8 @@ func packFeeParameter(integratorFee *IntegratorFee, resolverFee *ResolverFee) (u return packed, nil } +// encodeWhitelist encodes a list of Ethereum addresses into a single *big.Int value with a specific bit layout. +// Returns an error if any address is invalid, has more than 255 addresses, or encounters issues during encoding. func encodeWhitelist(whitelist []string) (*big.Int, error) { if len(whitelist) == 0 { return big.NewInt(0), nil @@ -64,7 +68,7 @@ func encodeWhitelist(whitelist []string) (*big.Int, error) { for _, address := range whitelist { address = strings.TrimPrefix(address, "0x") - address = strings.TrimPrefix(address, "0X") + address = strings.TrimPrefix(address, "0X") // Check for capital x too addressInt := new(big.Int) _, success := addressInt.SetString(address, 16) @@ -116,6 +120,7 @@ func concatFeeAndWhitelist(whitelist []string, integratorFee *IntegratorFee, res return feeAndWhitelist, totalBitLength, nil } +// buildFeePostInteractionData encodes interaction data combining receiver, fee, whitelist, and optional interaction inputs into a byte array. func buildFeePostInteractionData(params *buildFeePostInteractionDataParams) ([]byte, error) { if params.Whitelist == nil { @@ -278,22 +283,16 @@ func BuildOrderExtensionBytes(params *BuildOrderExtensionBytesParams) (string, e // buildExtensionFromBytes builds an extension hex string from byte slices func buildExtensionFromBytes(interactions [][]byte) string { - // Calculate offsets for first 8 interactions only var byteCounts []int var dataBytes []byte - // Process first 8 interactions (excluding customData) - mainInteractions := interactions - if len(interactions) > 8 { - mainInteractions = interactions[:8] - } - - for _, interaction := range mainInteractions { - byteCounts = append(byteCounts, len(interaction)) - dataBytes = append(dataBytes, interaction...) + // Process first 8 interactions (these are used in the cumulative sum calculation) + for i := 0; i < len(interactions) && i < 8; i++ { + byteCounts = append(byteCounts, len(interactions[i])) + dataBytes = append(dataBytes, interactions[i]...) } - // Add customData if present (9th element) + // Add customData if present (no offset data) if len(interactions) > 8 { dataBytes = append(dataBytes, interactions[8]...) } @@ -308,15 +307,13 @@ func buildExtensionFromBytes(interactions [][]byte) string { offsets = append(offsetBytes, offsets...) } - // If no data, return empty extension + // If no data, return an empty extension if len(dataBytes) == 0 { return "0x" } - // Encode offsets and data to hex offsetsHex := hex.EncodeToString(offsets) dataHex := hex.EncodeToString(dataBytes) - // Concatenate with "0x" prefix return "0x" + offsetsHex + dataHex } diff --git a/sdk-clients/orderbook/salt.go b/sdk-clients/orderbook/salt.go index 25da3e3..2c6b681 100644 --- a/sdk-clients/orderbook/salt.go +++ b/sdk-clients/orderbook/salt.go @@ -11,7 +11,7 @@ import ( type GetSaltParams struct { Extension string - Source *string // Optional string for tracking code (defaults to "sdk" if nil) + Source *string // Optional string for tracking code UseRandom bool // If true, uses random bits for the middle section; otherwise uses timestamp }