diff --git a/README.md b/README.md
index 888b4a94..3c716e31 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,8 @@ Jump To:
*Fusion API* - [[Docs](https://portal.1inch.dev/documentation/apis/swap/intent-swap/introduction) | [SDK Example](https://github.com/1inch/1inch-sdk-go/blob/main/sdk-clients/fusion/examples/place_order/main.go)] (now called Intent Swap)
+*Fusion Plus API* - [[Docs](https://portal.1inch.dev/documentation/apis/swap/fusion-plus/introduction) | [SDK Example](https://github.com/1inch/1inch-sdk-go/blob/main/sdk-clients/fusionplus/examples/place_order/main.go)]
+
*Orderbook API* - [[Docs](https://portal.1inch.dev/documentation/apis/orderbook/introduction) | [SDK Example](https://github.com/1inch/1inch-sdk-go/blob/main/sdk-clients/orderbook/examples/create_order/main.go)]
### Infrastructure
diff --git a/codegen/openapi/fusionplus_orders-openapi.json b/codegen/openapi/fusionplus_orders-openapi.json
new file mode 100644
index 00000000..838e7860
--- /dev/null
+++ b/codegen/openapi/fusionplus_orders-openapi.json
@@ -0,0 +1,1340 @@
+{
+ "openapi": "3.0.0",
+ "paths": {
+ "/v1.0/order/active": {
+ "get": {
+ "operationId": "OrderApiController_getActiveOrders",
+ "summary": "Get cross chain swap active orders",
+ "parameters": [
+ {
+ "name": "page",
+ "required": false,
+ "in": "query",
+ "description": "Pagination step, default: 1 (page = offset / limit)",
+ "example": 1,
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "limit",
+ "required": false,
+ "in": "query",
+ "description": "Number of active orders to receive (default: 100, max: 500)",
+ "example": 100,
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "srcChain",
+ "required": false,
+ "in": "query",
+ "description": "Source chain of cross chain",
+ "example": 1,
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "dstChain",
+ "required": false,
+ "in": "query",
+ "description": "Destination chain of cross chain",
+ "example": 137,
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Array of queried active orders"
+ },
+ "400": {
+ "description": "Input data is invalid"
+ },
+ "default": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GetActiveOrdersOutput"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Orders"
+ ]
+ }
+ },
+ "/v1.0/order/escrow": {
+ "get": {
+ "operationId": "OrderApiController_getSettlementContract",
+ "summary": "Get actual escrow factory contract address",
+ "parameters": [
+ {
+ "name": "chainId",
+ "required": false,
+ "in": "query",
+ "description": "Chain ID",
+ "example": 1,
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ }
+ ],
+ "responses": {
+ "default": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/EscrowFactory"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Orders"
+ ]
+ }
+ },
+ "/v1.0/order/maker/{address}": {
+ "get": {
+ "operationId": "OrderApiController_getOrdersByMaker",
+ "parameters": [
+ {
+ "name": "address",
+ "required": true,
+ "in": "path",
+ "description": "Maker's address",
+ "example": "0x1000000000000000000000000000000000000001",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "page",
+ "required": false,
+ "in": "query",
+ "description": "Pagination step, default: 1 (page = offset / limit)",
+ "example": 1,
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "limit",
+ "required": false,
+ "in": "query",
+ "description": "Number of active orders to receive (default: 100, max: 500)",
+ "example": 100,
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "timestampFrom",
+ "required": false,
+ "in": "query",
+ "description": "timestampFrom in milliseconds for interval [timestampFrom, timestampTo)",
+ "example": 1727173462451,
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "timestampTo",
+ "required": false,
+ "in": "query",
+ "description": "timestampTo in milliseconds for interval [timestampFrom, timestampTo)",
+ "example": 1727173462451,
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "srcToken",
+ "required": false,
+ "in": "query",
+ "description": "Find history by the given source token",
+ "example": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
+ "schema": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "dstToken",
+ "required": false,
+ "in": "query",
+ "description": "Find history by the given destination token",
+ "example": "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",
+ "schema": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "withToken",
+ "required": false,
+ "in": "query",
+ "description": "Find history items by source or destination token",
+ "example": "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",
+ "schema": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "dstChainId",
+ "required": false,
+ "in": "query",
+ "description": "Destination chain of cross chain",
+ "example": 137,
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "srcChainId",
+ "required": false,
+ "in": "query",
+ "description": "Source chain of cross chain",
+ "example": 1,
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "chainId",
+ "required": false,
+ "in": "query",
+ "description": "chainId for looking by dstChainId == chainId OR srcChainId == chainId",
+ "example": 56,
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ }
+ ],
+ "responses": {
+ "default": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GetOrderByMakerOutput"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Orders"
+ ]
+ }
+ },
+ "/v1.0/order/secrets/{orderHash}": {
+ "get": {
+ "operationId": "OrderApiController_getPublishedSecrets",
+ "summary": "Get all data to perform withdrawal and cancellation",
+ "parameters": [
+ {
+ "name": "orderHash",
+ "required": true,
+ "in": "path",
+ "example": "0xa0ea5bd12b2d04566e175de24c2df41a058bf16df4af3eb2fb9bff38a9da98e9",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Public secrets and all data related to withdrawal and cancellation"
+ },
+ "default": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ResolverDataOutput"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Orders"
+ ]
+ }
+ },
+ "/v1.0/order/ready-to-accept-secret-fills/{orderHash}": {
+ "get": {
+ "operationId": "OrderApiController_getReadyToAcceptSecretFills",
+ "summary": "Get idx of each secret that is ready for submission for specific order",
+ "parameters": [
+ {
+ "name": "orderHash",
+ "required": true,
+ "in": "path",
+ "example": "0xa0ea5bd12b2d04566e175de24c2df41a058bf16df4af3eb2fb9bff38a9da98e9",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "default": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ReadyToAcceptSecretFills"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Orders"
+ ]
+ }
+ },
+ "/v1.0/order/ready-to-accept-secret-fills": {
+ "get": {
+ "operationId": "OrderApiController_getReadyToAcceptSecretFillsForAllOrders",
+ "summary": "Get idx of each secret that is ready for submission for all orders",
+ "parameters": [],
+ "responses": {
+ "default": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ReadyToAcceptSecretFillsForAllOrders"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Orders"
+ ]
+ }
+ },
+ "/v1.0/order/ready-to-execute-public-actions": {
+ "get": {
+ "operationId": "OrderApiController_getEventsReadyForPublicAction",
+ "summary": "Get all data to perform a cancellation or withdrawal on public periods",
+ "parameters": [],
+ "responses": {
+ "default": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ReadyToExecutePublicActionsOutput"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Orders"
+ ]
+ }
+ },
+ "/v1.0/order/status/{orderHash}": {
+ "get": {
+ "operationId": "OrderApiController_getOrderByOrderHash",
+ "parameters": [
+ {
+ "name": "orderHash",
+ "required": true,
+ "in": "path",
+ "example": "0xa0ea5bd12b2d04566e175de24c2df41a058bf16df4af3eb2fb9bff38a9da98e9",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "default": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GetOrderFillsByHashOutput"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Orders"
+ ]
+ }
+ },
+ "/v1.0/order/status": {
+ "post": {
+ "operationId": "OrderApiController_getOrdersByOrderHashes",
+ "parameters": [],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OrdersByHashesInput"
+ }
+ }
+ }
+ },
+ "responses": {
+ "default": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GetOrderFillsByHashOutput"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Orders"
+ ]
+ }
+ }
+ },
+ "info": {
+ "title": "1inch Fusion+ Orders API",
+ "description": "Orders API provides access to active and historical orders",
+ "version": "1.0",
+ "contact": {}
+ },
+ "tags": [],
+ "servers": [
+ {
+ "url": "/orders"
+ }
+ ],
+ "components": {
+ "schemas": {
+ "Meta": {
+ "type": "object",
+ "properties": {
+ "totalItems": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "itemsPerPage": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "totalPages": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "currentPage": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "totalItems",
+ "itemsPerPage",
+ "totalPages",
+ "currentPage"
+ ]
+ },
+ "CrossChainOrderDto": {
+ "type": "object",
+ "properties": {
+ "salt": {
+ "type": "string",
+ "description": "Some unique value. It is necessary to be able to create cross chain orders with the same parameters (so that they have a different hash), Lowest 160 bits of the order salt must be equal to the lowest 160 bits of the extension hash",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "maker": {
+ "type": "string",
+ "description": "Address of the account creating the order (maker) in src chain.",
+ "example": "0x995BE1CA945174D5bA75410C1E658a41eB13a2FA",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "receiver": {
+ "type": "string",
+ "description": "Address of the account receiving the assets (receiver), if different from maker in dst chain.",
+ "example": "0x995BE1CA945174D5bA75410C1E658a41eB13a2FB",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "makerAsset": {
+ "type": "string",
+ "description": "Identifier of the asset being offered by the maker in src chain.",
+ "example": "0x995BE1CA945174D5bA75410C1E658a41eB13a2FC",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "takerAsset": {
+ "type": "string",
+ "description": "Identifier of the asset being requested by the maker in exchange in dst chain.",
+ "example": "0x995BE1CA945174D5bA75410C1E658a41eB13a2FD",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "makingAmount": {
+ "type": "string",
+ "description": "Amount of the makerAsset being offered by the maker in src chain.",
+ "example": "100000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "takingAmount": {
+ "type": "string",
+ "description": "Amount of the takerAsset being requested by the maker in dst chain.",
+ "example": "100000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "makerTraits": {
+ "type": "string",
+ "description": "Includes some flags like, allow multiple fills, is partial fill allowed or not, price improvement, nonce, deadline etc.",
+ "example": "0x",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "salt",
+ "maker",
+ "receiver",
+ "makerAsset",
+ "takerAsset",
+ "makingAmount",
+ "takingAmount",
+ "makerTraits"
+ ]
+ },
+ "ActiveOrdersOutput": {
+ "type": "object",
+ "properties": {
+ "orderHash": {
+ "type": "string",
+ "description": "Unique identifier of the order.",
+ "example": "0x496755a88564d8ded6759dff0252d3e6c3ef1fe42b4fa1bbc3f03bd2674f1078",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "signature": {
+ "type": "string",
+ "description": "Signature of the order.",
+ "example": "0xf7739f12038fa25a57b51b3fb26fd68bd4b80b534c9ff2a1fd396234b7a8f59308b573db71f7c9598746ecfe6f7f2a962a9d31a1229fc570044ebf0b4bc5db0c",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "deadline": {
+ "type": "number",
+ "description": "Deadline by which the order must be filled.",
+ "example": 1634025600000,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "auctionStartDate": {
+ "type": "number",
+ "description": "Start date of the auction for this order.",
+ "example": 1634025600000,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "auctionEndDate": {
+ "type": "number",
+ "description": "End date of the auction for this order.",
+ "example": 1634025600000,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "quoteId": {
+ "type": "string",
+ "description": "Identifier of the quote associated with this order.",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "remainingMakerAmount": {
+ "type": "string",
+ "description": "Remaining amount of the maker asset that can still be filled.",
+ "example": "100000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "makerBalance": {
+ "type": "string",
+ "description": "Amount of the maker asset balance.",
+ "example": "100000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "makerAllowance": {
+ "type": "string",
+ "description": "Amount of the maker asset allowance.",
+ "example": "100000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "isMakerContract": {
+ "type": "boolean",
+ "description": "True if order signed by contract (GnosisSafe, etc.)",
+ "example": true,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "extension": {
+ "type": "string",
+ "description": "An interaction call data. ABI encoded set of makerAssetSuffix, takerAssetSuffix, makingAmountGetter, takingAmountGetter, predicate, permit, preInteraction, postInteraction.If extension exists then lowest 160 bits of the order salt must be equal to the lowest 160 bits of the extension hash",
+ "example": "0x",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "srcChainId": {
+ "type": "number",
+ "description": "Identifier of the chain where the maker asset is located. ",
+ "example": 1,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "dstChainId": {
+ "type": "number",
+ "description": "Identifier of the chain where the taker asset is located.",
+ "example": 2,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "order": {
+ "$ref": "#/components/schemas/CrossChainOrderDto"
+ },
+ "secretHashes": {
+ "description": "Array of secret hashes.",
+ "example": [
+ "0x2048b38093dc53876b2bbd230ee8999791153db01de425112f449d018094e116",
+ "0x7972c1498893bb9b88baddc9decb78d8defdcc7a182a72edd8724498c75f088d",
+ "0x6d5b8f0b1f8a28564ff65e5f9c4d8a8a6babfb318bca6ecc9d872a3abe8a4ea0"
+ ],
+ "type": "array",
+ "items": {
+ "type": "array"
+ },
+ "x-go-type-skip-optional-pointer": true
+ },
+ "fills": {
+ "description": "Array of fills.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "orderHash",
+ "signature",
+ "deadline",
+ "auctionStartDate",
+ "auctionEndDate",
+ "quoteId",
+ "remainingMakerAmount",
+ "makerBalance",
+ "makerAllowance",
+ "isMakerContract",
+ "extension",
+ "srcChainId",
+ "dstChainId",
+ "order",
+ "fills"
+ ]
+ },
+ "GetActiveOrdersOutput": {
+ "type": "object",
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/Meta"
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ActiveOrdersOutput"
+ },
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "meta",
+ "items"
+ ]
+ },
+ "EscrowFactory": {
+ "type": "object",
+ "properties": {
+ "address": {
+ "type": "string",
+ "description": "actual escrow factory contract address",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "address"
+ ]
+ },
+ "GetOrderByMakerOutput": {
+ "type": "object",
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/Meta"
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ActiveOrdersOutput"
+ },
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "meta",
+ "items"
+ ]
+ },
+ "Immutables": {
+ "type": "object",
+ "properties": {
+ "orderHash": {
+ "type": "string",
+ "description": "Order's hash 32 bytes hex sting",
+ "example": "0x496755a88564d8ded6759dff0252d3e6c3ef1fe42b4fa1bbc3f03bd2674f1078",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "hashlock": {
+ "type": "string",
+ "description": "keccak256(secret(idx))",
+ "example": "0x03f9ebf9075dfaae76c43b7443d07399609ffe24a5d435045fe4d3bf82d9fce4",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "maker": {
+ "type": "string",
+ "description": "Maker's address which will receive tokens",
+ "example": "0xe75eD6F453c602Bd696cE27AF11565eDc9b46B0D",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "taker": {
+ "type": "string",
+ "description": "Escrow creation initiator address",
+ "example": "0x00000000009E50a7dDb7a7B0e2ee6604fd120E49",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "token": {
+ "type": "string",
+ "description": "Token to receive on specific chain",
+ "example": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "amount": {
+ "type": "string",
+ "description": "Amount of token to receive",
+ "example": "1000000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "safetyDeposit": {
+ "type": "string",
+ "description": "Security deposit in chain's native currency",
+ "example": "50000000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "timelocks": {
+ "type": "string",
+ "description": "Encoded timelocks. To decode use: https://github.com/1inch/cross-chain-sdk/blob/master/src/cross-chain-order/time-locks/time-locks.ts",
+ "example": "0x3000000020000000100000004000000030000000200000001",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "orderHash",
+ "hashlock",
+ "maker",
+ "taker",
+ "token",
+ "amount",
+ "safetyDeposit",
+ "timelocks"
+ ]
+ },
+ "PublicSecret": {
+ "type": "object",
+ "properties": {
+ "idx": {
+ "type": "number",
+ "description": "Sequence number of secrets",
+ "example": 1,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "secret": {
+ "type": "string",
+ "description": "Public secret to perform a withdrawal",
+ "example": "0xdb475911f2d1c5df6b1fb959777ddd01c89d881175a3b9693ec884f18dcb5734",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "srcImmutables": {
+ "$ref": "#/components/schemas/Immutables"
+ },
+ "dstImmutables": {
+ "$ref": "#/components/schemas/Immutables"
+ }
+ },
+ "required": [
+ "idx",
+ "secret",
+ "srcImmutables",
+ "dstImmutables"
+ ]
+ },
+ "ResolverDataOutput": {
+ "type": "object",
+ "properties": {
+ "orderType": {
+ "type": "string",
+ "enum": [
+ "SingleFill",
+ "MultipleFills"
+ ],
+ "description": "Type of the order: enabled or disabled partial fills",
+ "example": "SingleFill",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "secrets": {
+ "description": "The data required for order withdraw and cancel",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PublicSecret"
+ },
+ "x-go-type-skip-optional-pointer": true
+ },
+ "secretHashes": {
+ "description": "keccak256(secret(idx))[]",
+ "example": [
+ "0x2048b38093dc53876b2bbd230ee8999791153db01de425112f449d018094e116",
+ "0x7972c1498893bb9b88baddc9decb78d8defdcc7a182a72edd8724498c75f088d",
+ "0x6d5b8f0b1f8a28564ff65e5f9c4d8a8a6babfb318bca6ecc9d872a3abe8a4ea0"
+ ],
+ "type": "array",
+ "items": {
+ "type": "array"
+ },
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "orderType",
+ "secrets"
+ ]
+ },
+ "ReadyToAcceptSecretFill": {
+ "type": "object",
+ "properties": {
+ "idx": {
+ "type": "number",
+ "description": "Sequence number of secrets for submission",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "srcEscrowDeployTxHash": {
+ "type": "string",
+ "description": "Transaction hash where the source chain escrow was deployed",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "dstEscrowDeployTxHash": {
+ "type": "string",
+ "description": "Transaction hash where the destination chain escrow was deployed",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "idx",
+ "srcEscrowDeployTxHash",
+ "dstEscrowDeployTxHash"
+ ]
+ },
+ "ReadyToAcceptSecretFills": {
+ "type": "object",
+ "properties": {
+ "fills": {
+ "description": "Fills that are ready to accept secrets from the client",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ReadyToAcceptSecretFill"
+ },
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "fills"
+ ]
+ },
+ "ReadyToAcceptSecretFillsForOrder": {
+ "type": "object",
+ "properties": {
+ "orderHash": {
+ "type": "string",
+ "description": "Order hash",
+ "example": "0x496755a88564d8ded6759dff0252d3e6c3ef1fe42b4fa1bbc3f03bd2674f1078",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "makerAddress": {
+ "type": "string",
+ "description": "Maker address",
+ "example": "0x496755a88564d8ded6759dff0252d3e6c3ef1fe42b4fa1bbc3f03bd2674f1079",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "fills": {
+ "description": "Fills that are ready to accept secrets from the client",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ReadyToAcceptSecretFill"
+ },
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "orderHash",
+ "makerAddress",
+ "fills"
+ ]
+ },
+ "ReadyToAcceptSecretFillsForAllOrders": {
+ "type": "object",
+ "properties": {
+ "orders": {
+ "description": "Fills that are ready to accept secrets from the client for all orders",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ReadyToAcceptSecretFillsForOrder"
+ },
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "orders"
+ ]
+ },
+ "ReadyToExecutePublicAction": {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string",
+ "enum": [
+ "withdraw",
+ "cancel"
+ ],
+ "example": "withdraw",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "immutables": {
+ "$ref": "#/components/schemas/Immutables"
+ },
+ "chainId": {
+ "type": "number",
+ "description": "Execute action on this chain",
+ "example": 1,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "escrow": {
+ "type": "string",
+ "description": "Escrow's address to perform public action",
+ "example": "0xf1325353e081023520d44b7a24f72905ada3a080",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "secret": {
+ "type": "string",
+ "description": "Presented only for withdraw action",
+ "example": "0x496755a88564d8ded6759dff0252d3e6c3ef1fe42b4fa1bbc3f03bd2674f1078",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "action",
+ "immutables",
+ "chainId",
+ "escrow"
+ ]
+ },
+ "ReadyToExecutePublicActionsOutput": {
+ "type": "object",
+ "properties": {
+ "actions": {
+ "description": "Actions allowed to be performed on public timelock periods",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ReadyToExecutePublicAction"
+ },
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "actions"
+ ]
+ },
+ "LimitOrderV4StructOutput": {
+ "type": "object",
+ "properties": {
+ "salt": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "maker": {
+ "type": "string",
+ "description": "Maker address",
+ "example": "0x995BE1CA945174D5bA75410C1E658a41eB13a2FA",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "receiver": {
+ "type": "string",
+ "description": "Receiver address",
+ "example": "0x995BE1CA945174D5bA75410C1E658a41eB13a2FA",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "makerAsset": {
+ "type": "string",
+ "description": "Maker asset address",
+ "example": "0x995BE1CA945174D5bA75410C1E658a41eB13a2FA",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "takerAsset": {
+ "type": "string",
+ "description": "Taker asset address",
+ "example": "0x995BE1CA945174D5bA75410C1E658a41eB13a2FA",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "makingAmount": {
+ "type": "string",
+ "description": "Amount of the maker asset",
+ "example": "100000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "takingAmount": {
+ "type": "string",
+ "description": "Amount of the taker asset",
+ "example": "100000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "makerTraits": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "salt",
+ "maker",
+ "receiver",
+ "makerAsset",
+ "takerAsset",
+ "makingAmount",
+ "takingAmount",
+ "makerTraits"
+ ]
+ },
+ "AuctionPointOutput": {
+ "type": "object",
+ "properties": {
+ "delay": {
+ "type": "number",
+ "description": "The delay in seconds from the previous point or auction start time",
+ "example": 234,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "coefficient": {
+ "type": "number",
+ "description": "The rate bump from the order min taker amount",
+ "example": 200,
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "delay",
+ "coefficient"
+ ]
+ },
+ "EscrowEventDataOutput": {
+ "type": "object",
+ "properties": {
+ "transactionHash": {
+ "type": "string",
+ "description": "Transaction hash",
+ "example": "0x806039f5149065924ad52de616b50abff488c986716d052e9c160887bc09e559",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "side": {
+ "type": "string",
+ "description": "Side of the escrow event SRC or DST",
+ "example": "src",
+ "enum": [
+ "src",
+ "dst"
+ ],
+ "x-go-type-skip-optional-pointer": true
+ },
+ "action": {
+ "type": "string",
+ "description": "Action of the escrow event",
+ "example": "src_escrow_created",
+ "enum": [
+ "src_escrow_created",
+ "dst_escrow_created",
+ "withdrawn",
+ "funds_rescued",
+ "escrow_cancelled"
+ ],
+ "x-go-type-skip-optional-pointer": true
+ },
+ "blockTimestamp": {
+ "type": "number",
+ "description": "Unix timestamp in milliseconds",
+ "example": 123123123123,
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "transactionHash",
+ "side",
+ "action",
+ "blockTimestamp"
+ ]
+ },
+ "FillOutputDto": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "description": "Fill status",
+ "example": "pending",
+ "enum": [
+ "pending",
+ "executed",
+ "refunding",
+ "refunded"
+ ],
+ "x-go-type-skip-optional-pointer": true
+ },
+ "txHash": {
+ "type": "string",
+ "description": "Transaction hash",
+ "example": "0x806039f5149065924ad52de616b50abff488c986716d052e9c160887bc09e559",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "filledMakerAmount": {
+ "type": "string",
+ "description": "Amount of the makerAsset filled in src chain.",
+ "example": "100000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "filledAuctionTakerAmount": {
+ "type": "string",
+ "description": "Amount of the takerAsset filled in dst chain.",
+ "example": "100000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "escrowEvents": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/EscrowEventDataOutput"
+ },
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "status",
+ "txHash",
+ "filledMakerAmount",
+ "filledAuctionTakerAmount",
+ "escrowEvents"
+ ]
+ },
+ "GetOrderFillsByHashOutput": {
+ "type": "object",
+ "properties": {
+ "orderHash": {
+ "type": "string",
+ "description": "Order hash",
+ "example": "0x496755a88564d8ded6759dff0252d3e6c3ef1fe42b4fa1bbc3f03bd2674f1078",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "status": {
+ "type": "string",
+ "description": "Order status",
+ "example": "pending",
+ "enum": [
+ "pending",
+ "executed",
+ "expired",
+ "cancelled",
+ "refunding",
+ "refunded"
+ ],
+ "x-go-type-skip-optional-pointer": true
+ },
+ "validation": {
+ "type": "string",
+ "description": "Order validation status",
+ "example": "valid",
+ "enum": [
+ "valid",
+ "order-predicate-returned-false",
+ "not-enough-balance",
+ "not-enough-allowance",
+ "invalid-permit-signature",
+ "invalid-permit-spender",
+ "invalid-permit-signer",
+ "invalid-signature",
+ "failed-to-parse-permit-details",
+ "unknown-permit-version",
+ "wrong-epoch-manager-and-bit-invalidator",
+ "failed-to-decode-remaining",
+ "unknown-failure"
+ ],
+ "x-go-type-skip-optional-pointer": true
+ },
+ "order": {
+ "$ref": "#/components/schemas/LimitOrderV4StructOutput"
+ },
+ "extension": {
+ "type": "string",
+ "description": "An interaction call data. ABI encoded set of makerAssetSuffix, takerAssetSuffix, makingAmountGetter, takingAmountGetter, predicate, permit, preInteraction, postInteraction.If extension exists then lowest 160 bits of the order salt must be equal to the lowest 160 bits of the extension hash",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "points": {
+ "$ref": "#/components/schemas/AuctionPointOutput"
+ },
+ "approximateTakingAmount": {
+ "type": "string",
+ "description": "Approximate amount of the takerAsset being requested by the maker in dst chain.",
+ "example": "100000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "fills": {
+ "description": "Fills",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FillOutputDto"
+ },
+ "x-go-type-skip-optional-pointer": true
+ },
+ "auctionStartDate": {
+ "type": "number",
+ "description": "Unix timestamp in milliseconds",
+ "example": 123123123123,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "auctionDuration": {
+ "type": "number",
+ "description": "Unix timestamp in milliseconds",
+ "example": 123123123123,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "initialRateBump": {
+ "type": "number",
+ "description": "Initial rate bump",
+ "example": 1000,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "createdAt": {
+ "type": "number",
+ "description": "Unix timestamp in milliseconds",
+ "example": 123123123123,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "srcTokenPriceUsd": {
+ "type": "object",
+ "example": 100,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "dstTokenPriceUsd": {
+ "type": "object",
+ "example": 200,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "cancelTx": {
+ "type": "object",
+ "example": "0xa2768a9826b2c45a6937010ce21a91b1da9f8c7aa5194f68aa99306b22518b41",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "srcChainId": {
+ "type": "number",
+ "description": "Identifier of the chain where the maker asset is located. ",
+ "example": 1,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "dstChainId": {
+ "type": "number",
+ "description": "Identifier of the chain where the taker asset is located.",
+ "example": 2,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "cancelable": {
+ "type": "boolean",
+ "description": "Is order cancelable",
+ "example": true,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "takerAsset": {
+ "type": "string",
+ "description": "Identifier of the asset being requested by the maker in exchange in dst chain.",
+ "example": "0x995BE1CA945174D5bA75410C1E658a41eB13a2FD",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "timeLocks": {
+ "type": "string",
+ "description": "TimeLocks without deployedAt",
+ "example": "0xfc000000840000000c000001bc00000144000000a800000024",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "orderHash",
+ "status",
+ "validation",
+ "order",
+ "extension",
+ "points",
+ "approximateTakingAmount",
+ "fills",
+ "auctionStartDate",
+ "auctionDuration",
+ "initialRateBump",
+ "createdAt",
+ "srcTokenPriceUsd",
+ "dstTokenPriceUsd",
+ "cancelTx",
+ "srcChainId",
+ "dstChainId",
+ "cancelable",
+ "takerAsset",
+ "timeLocks"
+ ]
+ },
+ "OrdersByHashesInput": {
+ "type": "object",
+ "properties": {
+ "orderHashes": {
+ "example": [
+ "0x10ea5bd12b2d04566e175de24c2df41a058bf16df4af3eb2fb9bff38a9da98e9",
+ "0x20ea5bd12b2d04566e175de24c2df41a058bf16df4af3eb2fb9bff38a9da98e8",
+ "0x30ea5bd12b2d04566e175de24c2df41a058bf16df4af3eb2fb9bff38a9da98e7",
+ "0x40ea5bd12b2d04566e175de24c2df41a058bf16df4af3eb2fb9bff38a9da98e6"
+ ],
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "orderHashes"
+ ]
+ }
+ }
+ }
+}
diff --git a/codegen/openapi/fusionplus_quoter-openapi.json b/codegen/openapi/fusionplus_quoter-openapi.json
new file mode 100644
index 00000000..78a1f1d8
--- /dev/null
+++ b/codegen/openapi/fusionplus_quoter-openapi.json
@@ -0,0 +1,783 @@
+{
+ "openapi": "3.0.0",
+ "paths": {
+ "/v1.0/quote/receive": {
+ "get": {
+ "operationId": "QuoterController_getQuote",
+ "summary": "Get quote details based on input data",
+ "parameters": [
+ {
+ "name": "srcChain",
+ "required": true,
+ "in": "query",
+ "description": "Id of source chain",
+ "example": "1",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "dstChain",
+ "required": true,
+ "in": "query",
+ "description": "Id of destination chain",
+ "example": "137",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "srcTokenAddress",
+ "required": true,
+ "in": "query",
+ "description": "Address of \"SOURCE\" token in source chain",
+ "example": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "dstTokenAddress",
+ "required": true,
+ "in": "query",
+ "description": "Address of \"DESTINATION\" token in destination chain",
+ "example": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "amount",
+ "required": true,
+ "in": "query",
+ "description": "Amount to take from \"SOURCE\" token to get \"DESTINATION\" token",
+ "example": "100000000000000000",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "walletAddress",
+ "required": true,
+ "in": "query",
+ "description": "An address of the wallet or contract in source chain who will create Fusion order",
+ "example": "0x0000000000000000000000000000000000000000",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "enableEstimate",
+ "required": true,
+ "in": "query",
+ "description": "if enabled then get estimation from 1inch swap builder and generates quoteId, by default is false",
+ "example": "false",
+ "schema": {
+ "type": "boolean"
+ }
+ },
+ {
+ "name": "fee",
+ "required": false,
+ "in": "query",
+ "description": "fee in bps format, 1% is equal to 100bps",
+ "example": "0",
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "isPermit2",
+ "required": false,
+ "in": "query",
+ "description": "permit2 allowance transfer encoded call",
+ "schema": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "permit",
+ "required": false,
+ "in": "query",
+ "description": "permit, user approval sign",
+ "schema": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Returns quote"
+ },
+ "400": {
+ "description": "Input data is invalid"
+ },
+ "default": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GetQuoteOutput"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "operationId": "QuoterController_getQuoteWithCustomPresets",
+ "summary": "Get quote with custom preset details",
+ "parameters": [
+ {
+ "name": "srcChain",
+ "required": true,
+ "in": "query",
+ "description": "Id of source chain",
+ "example": "1",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "dstChain",
+ "required": true,
+ "in": "query",
+ "description": "Id of destination chain",
+ "example": "137",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "srcTokenAddress",
+ "required": true,
+ "in": "query",
+ "description": "Address of \"SOURCE\" token",
+ "example": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "dstTokenAddress",
+ "required": true,
+ "in": "query",
+ "description": "Address of \"DESTINATION\" token",
+ "example": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "amount",
+ "required": true,
+ "in": "query",
+ "description": "Amount to take from \"SOURCE\" token to get \"DESTINATION\" token",
+ "example": "100000000000000000",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "walletAddress",
+ "required": true,
+ "in": "query",
+ "description": "An address of the wallet or contract who will create Fusion order",
+ "example": "0x0000000000000000000000000000000000000000",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "enableEstimate",
+ "required": true,
+ "in": "query",
+ "description": "if enabled then get estimation from 1inch swap builder and generates quoteId, by default is false",
+ "example": "false",
+ "schema": {
+ "type": "boolean"
+ }
+ },
+ {
+ "name": "fee",
+ "required": false,
+ "in": "query",
+ "description": "fee in bps format, 1% is equal to 100bps",
+ "example": "0",
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "isPermit2",
+ "required": false,
+ "in": "query",
+ "description": "permit2 allowance transfer encoded call",
+ "schema": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "permit",
+ "required": false,
+ "in": "query",
+ "description": "permit, user approval sign",
+ "schema": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CustomPresetParams"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Returns slippage, quoteId and presets with custom preset details as well"
+ },
+ "400": {
+ "description": "Input data is invalid"
+ },
+ "default": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GetQuoteOutput"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/v1.0/quote/build": {
+ "post": {
+ "operationId": "QuoterController_buildQuoteTypedData",
+ "summary": "Build order by given quote",
+ "parameters": [
+ {
+ "name": "srcChain",
+ "required": true,
+ "in": "query",
+ "description": "Id of source chain",
+ "example": "1",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "dstChain",
+ "required": true,
+ "in": "query",
+ "description": "Id of destination chain",
+ "example": "137",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "srcTokenAddress",
+ "required": true,
+ "in": "query",
+ "description": "Address of \"SOURCE\" token",
+ "example": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "dstTokenAddress",
+ "required": true,
+ "in": "query",
+ "description": "Address of \"DESTINATION\" token",
+ "example": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "amount",
+ "required": true,
+ "in": "query",
+ "description": "Amount to take from \"SOURCE\" token to get \"DESTINATION\" token",
+ "example": "100000000000000000",
+ "schema": {
+ "type": "number"
+ }
+ },
+ {
+ "name": "walletAddress",
+ "required": true,
+ "in": "query",
+ "description": "An address of the wallet or contract who will create Fusion order",
+ "example": "0x0000000000000000000000000000000000000000",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "fee",
+ "required": false,
+ "in": "query",
+ "description": "fee in bps format, 1% is equal to 100bps",
+ "example": "0",
+ "schema": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "source",
+ "required": false,
+ "in": "query",
+ "description": "Frontend or some other source selector",
+ "example": "Frontend",
+ "schema": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "isPermit2",
+ "required": false,
+ "in": "query",
+ "description": "permit2 allowance transfer encoded call",
+ "schema": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "isMobile",
+ "required": false,
+ "in": "query",
+ "description": "Enabled flag allows to save quote for Mobile History",
+ "schema": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "feeReceiver",
+ "required": false,
+ "in": "query",
+ "description": "In case fee non zero -> the fee will be transferred to this address",
+ "schema": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "permit",
+ "required": false,
+ "in": "query",
+ "description": "permit, user approval sign",
+ "schema": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ {
+ "name": "preset",
+ "required": false,
+ "in": "query",
+ "description": "fast/medium/slow/custom",
+ "schema": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/BuildOrderBody"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Returns cross chain order details"
+ },
+ "400": {
+ "description": "Input data is invalid"
+ },
+ "default": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/BuildOrderOutput"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "info": {
+ "title": "1inch Fusion+ Quoter API",
+ "description": "Quoter API provides the access to Fusion+ price curves",
+ "version": "1.0",
+ "contact": {}
+ },
+ "tags": [],
+ "servers": [
+ {
+ "url": "/quoter"
+ }
+ ],
+ "components": {
+ "schemas": {
+ "AuctionPoint": {
+ "type": "object",
+ "properties": {
+ "delay": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "coefficient": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "delay",
+ "coefficient"
+ ]
+ },
+ "GasCostConfig": {
+ "type": "object",
+ "properties": {
+ "gasBumpEstimate": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "gasPriceEstimate": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "gasBumpEstimate",
+ "gasPriceEstimate"
+ ]
+ },
+ "Preset": {
+ "type": "object",
+ "properties": {
+ "auctionDuration": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "startAuctionIn": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "initialRateBump": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "auctionStartAmount": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "startAmount": {
+ "type": "string",
+ "description": "auction start amount taking into account gas bump",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "auctionEndAmount": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "exclusiveResolver": {
+ "type": "object",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "costInDstToken": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "points": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/AuctionPoint"
+ },
+ "x-go-type-skip-optional-pointer": true
+ },
+ "allowPartialFills": {
+ "type": "boolean",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "allowMultipleFills": {
+ "type": "boolean",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "gasCost": {
+ "$ref": "#/components/schemas/GasCostConfig"
+ },
+ "secretsCount": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "auctionDuration",
+ "startAuctionIn",
+ "initialRateBump",
+ "auctionStartAmount",
+ "startAmount",
+ "auctionEndAmount",
+ "exclusiveResolver",
+ "costInDstToken",
+ "points",
+ "allowPartialFills",
+ "allowMultipleFills",
+ "gasCost",
+ "secretsCount"
+ ]
+ },
+ "QuotePresets": {
+ "type": "object",
+ "properties": {
+ "fast": {
+ "$ref": "#/components/schemas/Preset"
+ },
+ "medium": {
+ "$ref": "#/components/schemas/Preset"
+ },
+ "slow": {
+ "$ref": "#/components/schemas/Preset"
+ },
+ "custom": {
+ "$ref": "#/components/schemas/Preset"
+ }
+ },
+ "required": [
+ "fast",
+ "medium",
+ "slow"
+ ]
+ },
+ "TimeLocks": {
+ "type": "object",
+ "properties": {
+ "srcWithdrawal": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "srcPublicWithdrawal": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "srcCancellation": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "srcPublicCancellation": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "dstWithdrawal": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "dstPublicWithdrawal": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "dstCancellation": {
+ "type": "number",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "srcWithdrawal",
+ "srcPublicWithdrawal",
+ "srcCancellation",
+ "srcPublicCancellation",
+ "dstWithdrawal",
+ "dstPublicWithdrawal",
+ "dstCancellation"
+ ]
+ },
+ "TokenPair": {
+ "type": "object",
+ "properties": {
+ "srcToken": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "dstToken": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "srcToken",
+ "dstToken"
+ ]
+ },
+ "PairCurrency": {
+ "type": "object",
+ "properties": {
+ "usd": {
+ "$ref": "#/components/schemas/TokenPair"
+ }
+ },
+ "required": [
+ "usd"
+ ]
+ },
+ "GetQuoteOutput": {
+ "type": "object",
+ "properties": {
+ "quoteId": {
+ "type": "object",
+ "description": "Current generated quote id, should be passed with order",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "srcTokenAmount": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "dstTokenAmount": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "presets": {
+ "$ref": "#/components/schemas/QuotePresets"
+ },
+ "srcEscrowFactory": {
+ "type": "string",
+ "description": "Escrow factory contract address at source chain",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "dstEscrowFactory": {
+ "type": "string",
+ "description": "Escrow factory contract address at destination chain",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "whitelist": {
+ "description": "current executors whitelist addresses",
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "x-go-type-skip-optional-pointer": true
+ },
+ "timeLocks": {
+ "$ref": "#/components/schemas/TimeLocks"
+ },
+ "srcSafetyDeposit": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "dstSafetyDeposit": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "recommendedPreset": {
+ "type": "string",
+ "description": "suggested preset",
+ "enum": [
+ "fast",
+ "slow",
+ "medium",
+ "custom"
+ ],
+ "x-go-type-skip-optional-pointer": true
+ },
+ "prices": {
+ "$ref": "#/components/schemas/PairCurrency"
+ },
+ "volume": {
+ "$ref": "#/components/schemas/PairCurrency"
+ }
+ },
+ "required": [
+ "quoteId",
+ "srcTokenAmount",
+ "dstTokenAmount",
+ "presets",
+ "srcEscrowFactory",
+ "dstEscrowFactory",
+ "whitelist",
+ "timeLocks",
+ "srcSafetyDeposit",
+ "dstSafetyDeposit",
+ "recommendedPreset",
+ "prices",
+ "volume"
+ ]
+ },
+ "CustomPresetParams": {
+ "type": "object",
+ "properties": {}
+ },
+ "BuildOrderBody": {
+ "type": "object",
+ "properties": {
+ "quote": {
+ "$ref": "#/components/schemas/GetQuoteOutput"
+ },
+ "secretsHashList": {
+ "type": "string",
+ "description": "keccak256(secret)[]",
+ "example": [
+ "0x315b47a8c3780434b153667588db4ca628526e20000000000000000000000000"
+ ],
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "quote",
+ "secretsHashList"
+ ]
+ },
+ "BuildOrderOutput": {
+ "type": "object",
+ "properties": {
+ "typedData": {
+ "type": "object",
+ "description": "EIP712 Typed Data",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "orderHash": {
+ "type": "string",
+ "description": "Hash of CrossChain order",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "extension": {
+ "type": "string",
+ "description": "CrossChain order extension",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "typedData",
+ "orderHash",
+ "extension"
+ ]
+ }
+ }
+ }
+}
diff --git a/codegen/openapi/fusionplus_relayer-openapi.json b/codegen/openapi/fusionplus_relayer-openapi.json
new file mode 100644
index 00000000..77d3a3f6
--- /dev/null
+++ b/codegen/openapi/fusionplus_relayer-openapi.json
@@ -0,0 +1,225 @@
+{
+ "openapi": "3.0.0",
+ "paths": {
+ "/v1.0/submit": {
+ "post": {
+ "operationId": "RelayerController_submit",
+ "summary": "Submit a cross chain order that resolvers will be able to fill",
+ "parameters": [],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SignedOrderInput"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "The order has been successfully saved"
+ }
+ },
+ "tags": [
+ "Relayer"
+ ]
+ }
+ },
+ "/v1.0/submit/many": {
+ "post": {
+ "operationId": "RelayerController_submitMany",
+ "summary": "Submit many cross chain orders that resolvers will be able to fill",
+ "parameters": [],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "x-go-type-skip-optional-pointer": true
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "The orders has been successfully saved"
+ }
+ },
+ "tags": [
+ "Relayer"
+ ]
+ }
+ },
+ "/v1.0/submit/secret": {
+ "post": {
+ "operationId": "RelayerController_submitSecrets",
+ "summary": "Submit a secret for order fill after SrcEscrow and DstEscrow deployed and DstChain finality lock passed",
+ "parameters": [],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SecretInput"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "The secret has been successfully saved"
+ }
+ },
+ "tags": [
+ "Relayer"
+ ]
+ }
+ }
+ },
+ "info": {
+ "title": "1inch Fusion+ Relayer API",
+ "description": "Relayer API accepts orders and distribute them across resolvers",
+ "version": "1.0",
+ "contact": {}
+ },
+ "tags": [],
+ "servers": [
+ {
+ "url": "/relayer"
+ }
+ ],
+ "components": {
+ "schemas": {
+ "OrderInput": {
+ "type": "object",
+ "properties": {
+ "salt": {
+ "type": "string",
+ "example": "42",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "makerAsset": {
+ "type": "string",
+ "description": "Source chain address of the maker asset",
+ "example": "0x0000000000000000000000000000000000000001",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "takerAsset": {
+ "type": "string",
+ "description": "Destination chain address of the taker asset",
+ "example": "0x0000000000000000000000000000000000000001",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "maker": {
+ "type": "string",
+ "description": "Source chain address of the maker (wallet or contract address)",
+ "example": "0x0000000000000000000000000000000000000001",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "receiver": {
+ "type": "string",
+ "description": "Destination chain address of the wallet or contract who will receive filled amount ",
+ "default": "0x0000000000000000000000000000000000000001",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "makingAmount": {
+ "type": "string",
+ "description": "Order maker's token amount",
+ "example": "100000000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "takingAmount": {
+ "type": "string",
+ "description": "Order taker's token amount",
+ "example": "100000000000000000000",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "makerTraits": {
+ "type": "string",
+ "description": "Includes some flags like: allow multiple fills, is partial fill allowed or not, price improvement, nonce, deadline etc. See maker-traits.ts",
+ "default": "0",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "salt",
+ "makerAsset",
+ "takerAsset",
+ "maker",
+ "receiver",
+ "makingAmount",
+ "takingAmount",
+ "makerTraits"
+ ]
+ },
+ "SignedOrderInput": {
+ "type": "object",
+ "properties": {
+ "order": {
+ "$ref": "#/components/schemas/OrderInput"
+ },
+ "srcChainId": {
+ "type": "number",
+ "description": "Source chain id",
+ "example": 1,
+ "x-go-type-skip-optional-pointer": true
+ },
+ "signature": {
+ "type": "string",
+ "description": "Signature of the cross chain order typed data (using signTypedData v4)",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "extension": {
+ "type": "string",
+ "description": "An interaction call data. ABI encoded a set of makerAssetSuffix, takerAssetSuffix, makingAmountGetter, takingAmountGetter, predicate, permit, preInteraction, postInteraction.Lowest 160 bits of the order salt must be equal to the lowest 160 bits of the extension hash. See escrow-extension.ts>",
+ "example": "0x",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "quoteId": {
+ "type": "string",
+ "description": "Quote id of the quote with presets",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "secretHashes": {
+ "description": "Secret Hashes, required for order with multiple fills allowed. keccak256(secret(idx))",
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "order",
+ "srcChainId",
+ "signature",
+ "extension",
+ "quoteId"
+ ]
+ },
+ "SecretInput": {
+ "type": "object",
+ "properties": {
+ "secret": {
+ "type": "string",
+ "description": "A secret for the fill hashlock",
+ "x-go-type-skip-optional-pointer": true
+ },
+ "orderHash": {
+ "type": "string",
+ "x-go-type-skip-optional-pointer": true
+ }
+ },
+ "required": [
+ "secret",
+ "orderHash"
+ ]
+ }
+ }
+ }
+}
diff --git a/go.mod b/go.mod
index ab32837c..da6f5d53 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/1inch/1inch-sdk-go
go 1.21.1
require (
+ github.com/FantasyJony/openzeppelin-merkle-tree-go v1.1.3
github.com/ethereum/go-ethereum v1.14.8
github.com/google/go-querystring v1.1.0
github.com/oapi-codegen/runtime v1.1.1
@@ -27,7 +28,11 @@ require (
github.com/google/uuid v1.5.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/holiman/uint256 v1.3.1 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/supranational/blst v0.3.11 // indirect
diff --git a/go.sum b/go.sum
index c9e64a84..2e784d9b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,7 @@
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
+github.com/FantasyJony/openzeppelin-merkle-tree-go v1.1.3 h1:KzMvCFet0baw6uJnxTE/His8YeRgaxlASd4/ISuTvzI=
+github.com/FantasyJony/openzeppelin-merkle-tree-go v1.1.3/go.mod h1:OiwyYqbtMkQH+VzA4b8lI+qHnExJy0fIdz+59/8nFes=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
@@ -81,6 +83,7 @@ github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
@@ -100,6 +103,8 @@ github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
@@ -126,6 +131,11 @@ github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8oh
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
diff --git a/internal/bigint/bigint.go b/internal/bigint/bigint.go
new file mode 100644
index 00000000..aa0a8dec
--- /dev/null
+++ b/internal/bigint/bigint.go
@@ -0,0 +1,14 @@
+package bigint
+
+import (
+ "fmt"
+ "math/big"
+)
+
+func FromString(s string) (*big.Int, error) {
+ bigInt, ok := new(big.Int).SetString(s, 10) // base 10 for decimal
+ if !ok {
+ return nil, fmt.Errorf("failed to convert string (%v) to big.Int", s)
+ }
+ return bigInt, nil
+}
diff --git a/internal/bigint/bigint_test.go b/internal/bigint/bigint_test.go
new file mode 100644
index 00000000..ac59c41b
--- /dev/null
+++ b/internal/bigint/bigint_test.go
@@ -0,0 +1,74 @@
+package bigint
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+var largeString, _ = big.NewInt(0).SetString("9999999999999999999999999999", 10)
+
+func TestFromString(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ want *big.Int
+ wantErr bool
+ }{
+ {
+ name: "Valid decimal (10)",
+ input: "10",
+ want: big.NewInt(10),
+ wantErr: false,
+ },
+ {
+ name: "Zero value",
+ input: "0",
+ want: big.NewInt(0),
+ wantErr: false,
+ },
+ {
+ name: "Negative value (-100)",
+ input: "-100",
+ want: big.NewInt(-100),
+ wantErr: false,
+ },
+ {
+ name: "Large number",
+ input: "9999999999999999999999999999",
+ want: largeString,
+ wantErr: false,
+ },
+ {
+ name: "Invalid string",
+ input: "hello",
+ want: nil,
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt // capture range variable
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := FromString(tt.input)
+ if tt.wantErr {
+ // We expect an error
+ require.Error(t, err, "Expected an error but got none")
+ return
+ }
+
+ // We do NOT expect an error
+ require.NoError(t, err, "Did not expect an error but got one: %v", err)
+
+ // Verify the returned *big.Int matches our expectation
+ // (a nil `tt.want` is unusual when wantErr=false, so ensure it's not nil)
+ require.NotNil(t, got, "Expected non-nil big.Int but got nil")
+
+ // Compare numerical values
+ if got.Cmp(tt.want) != 0 {
+ t.Errorf("FromString(%q) = %v, want %v", tt.input, got, tt.want)
+ }
+ })
+ }
+}
diff --git a/internal/bytesbuilder/bytesbuilder.go b/internal/bytesbuilder/bytesbuilder.go
new file mode 100644
index 00000000..5be48822
--- /dev/null
+++ b/internal/bytesbuilder/bytesbuilder.go
@@ -0,0 +1,69 @@
+package bytesbuilder
+
+import (
+ "encoding/hex"
+ "math/big"
+ "strings"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+type BytesBuilder struct {
+ data []byte
+}
+
+func New() *BytesBuilder {
+ return &BytesBuilder{data: []byte{}}
+}
+
+func (b *BytesBuilder) AddUint24(val *big.Int) {
+ bytes := val.Bytes()
+ switch {
+ case len(bytes) < 3:
+ // Pad on the left with zeros to make it 3 bytes
+ padded := make([]byte, 3-len(bytes))
+ bytes = append(padded, bytes...)
+ case len(bytes) > 3:
+ // Truncate any bytes above the 3 least significant
+ bytes = bytes[len(bytes)-3:]
+ }
+ b.data = append(b.data, bytes...)
+}
+
+func (b *BytesBuilder) AddUint32(val *big.Int) {
+ bytes := val.Bytes()
+ if len(bytes) < 4 {
+ padded := make([]byte, 4-len(bytes))
+ bytes = append(padded, bytes...)
+ }
+ b.data = append(b.data, bytes...)
+}
+
+func (b *BytesBuilder) AddUint16(val *big.Int) {
+ bytes := val.Bytes()
+ if len(bytes) < 2 {
+ padded := make([]byte, 2-len(bytes))
+ bytes = append(padded, bytes...)
+ }
+ b.data = append(b.data, bytes...)
+}
+
+func (b *BytesBuilder) AddUint8(val uint8) {
+ b.data = append(b.data, byte(val))
+}
+
+func (b *BytesBuilder) AddAddress(address common.Address) {
+ b.data = append(b.data, address.Bytes()...)
+}
+
+func (b *BytesBuilder) AddBytes(data string) {
+ bytes, err := hex.DecodeString(strings.TrimPrefix(data, "0x"))
+ if err != nil {
+ panic("invalid hex string")
+ }
+ b.data = append(b.data, bytes...)
+}
+
+func (b *BytesBuilder) AsHex() string {
+ return hex.EncodeToString(b.data)
+}
diff --git a/internal/bytesiterator/bytesiter.go b/internal/bytesiterator/bytesiter.go
new file mode 100644
index 00000000..f067321c
--- /dev/null
+++ b/internal/bytesiterator/bytesiter.go
@@ -0,0 +1,119 @@
+package bytesiterator
+
+import (
+ "errors"
+ "math/big"
+)
+
+// BytesIter facilitates sequential reading of bytes from a byte slice.
+type BytesIter struct {
+ data []byte
+ pos int
+}
+
+// New initializes a new BytesIter with the provided data.
+func New(data []byte) *BytesIter {
+ return &BytesIter{data: data, pos: 0}
+}
+
+// NextByte reads the next single byte.
+func (iter *BytesIter) NextByte() (byte, error) {
+ if iter.pos >= len(iter.data) {
+ return 0, errors.New("no more bytes to read")
+ }
+ val := iter.data[iter.pos]
+ iter.pos++
+ return val, nil
+}
+
+// NextUint16 reads the next 2 bytes and returns them as a *big.Int.
+func (iter *BytesIter) NextUint16() (*big.Int, error) {
+ if iter.pos+2 > len(iter.data) {
+ return nil, errors.New("insufficient bytes for uint16")
+ }
+ val := new(big.Int).SetBytes(iter.data[iter.pos : iter.pos+2])
+ iter.pos += 2
+ return val, nil
+}
+
+// NextUint24 reads the next 3 bytes and returns them as a uint32.
+func (iter *BytesIter) NextUint24() (uint32, error) {
+ if iter.pos+3 > len(iter.data) {
+ return 0, errors.New("insufficient bytes for uint24")
+ }
+ val := uint32(iter.data[iter.pos])<<16 | uint32(iter.data[iter.pos+1])<<8 | uint32(iter.data[iter.pos+2])
+ iter.pos += 3
+ return val, nil
+}
+
+// NextUint32 reads the next 4 bytes and returns them as a *big.Int.
+func (iter *BytesIter) NextUint32() (*big.Int, error) {
+ if iter.pos+4 > len(iter.data) {
+ return nil, errors.New("insufficient bytes for uint32")
+ }
+ val := new(big.Int).SetBytes(iter.data[iter.pos : iter.pos+4])
+ iter.pos += 4
+ return val, nil
+}
+
+// NextUint160 reads the next 20 bytes and returns them as a *big.Int.
+func (iter *BytesIter) NextUint160() (*big.Int, error) {
+ if iter.pos+20 > len(iter.data) {
+ return nil, errors.New("insufficient bytes for uint160")
+ }
+ val := new(big.Int).SetBytes(iter.data[iter.pos : iter.pos+20])
+ iter.pos += 20
+ return val, nil
+}
+
+// NextUint256 reads the next 32 bytes and returns them as a *big.Int.
+func (iter *BytesIter) NextUint256() (*big.Int, error) {
+ if iter.pos+32 > len(iter.data) {
+ return nil, errors.New("insufficient bytes for uint256")
+ }
+ val := new(big.Int).SetBytes(iter.data[iter.pos : iter.pos+32])
+ iter.pos += 32
+ return val, nil
+}
+
+// NextBytes reads the next n bytes and returns them as a byte slice.
+func (iter *BytesIter) NextBytes(n int) ([]byte, error) {
+ if n < 0 {
+ return nil, errors.New("negative byte count")
+ }
+ if iter.pos+n > len(iter.data) {
+ return nil, errors.New("insufficient bytes for NextBytes")
+ }
+ val := iter.data[iter.pos : iter.pos+n]
+ iter.pos += n
+ return val, nil
+}
+
+// NextString reads the next n bytes and returns them as a string.
+func (iter *BytesIter) NextString(n int) (string, error) {
+ bytes, err := iter.NextBytes(n)
+ if err != nil {
+ return "", err
+ }
+ return string(bytes), nil
+}
+
+// Rest returns the remaining bytes as a byte slice.
+func (iter *BytesIter) Rest() ([]byte, error) {
+ if iter.pos >= len(iter.data) {
+ return nil, nil
+ }
+ val := iter.data[iter.pos:]
+ iter.pos = len(iter.data)
+ return val, nil
+}
+
+// BytesLeft returns the number of bytes that are left unread.
+func (iter *BytesIter) BytesLeft() int {
+ return len(iter.data) - iter.pos
+}
+
+// IsEmpty checks if there are no more bytes to read.
+func (iter *BytesIter) IsEmpty() bool {
+ return iter.pos >= len(iter.data)
+}
diff --git a/internal/bytesiterator/bytesiter_test.go b/internal/bytesiterator/bytesiter_test.go
new file mode 100644
index 00000000..36397ba7
--- /dev/null
+++ b/internal/bytesiterator/bytesiter_test.go
@@ -0,0 +1,622 @@
+package bytesiterator
+
+import (
+ "encoding/hex"
+ "fmt"
+ "math/big"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func hexToBytes(s string) ([]byte, error) {
+ s = strings.TrimPrefix(s, "0x")
+ return hex.DecodeString(s)
+}
+
+func bigIntFromHex(hexStr string) (*big.Int, error) {
+ b, err := hexToBytes(hexStr)
+ if err != nil {
+ return nil, err
+ }
+ return new(big.Int).SetBytes(b), nil
+}
+
+func uint32FromHex(hexStr string) (uint32, error) {
+ b, err := hexToBytes(hexStr)
+ if err != nil {
+ return 0, err
+ }
+ if len(b) > 4 {
+ return 0, fmt.Errorf("more than 4 bytes: %v\n", hexStr)
+ }
+ var val uint32
+ for _, bb := range b {
+ val = (val << 8) | uint32(bb)
+ }
+ return val, nil
+}
+
+func TestUint32FromHex(t *testing.T) {
+ tests := []struct {
+ name string
+ hexStr string
+ expected uint32
+ expectError bool
+ }{
+ {
+ name: "Empty string",
+ hexStr: "0x",
+ expected: 0,
+ expectError: false,
+ },
+ {
+ name: "Single byte zero",
+ hexStr: "0x00",
+ expected: 0,
+ expectError: false,
+ },
+ {
+ name: "Two bytes 0x0001",
+ hexStr: "0x0001",
+ expected: 1,
+ expectError: false,
+ },
+ {
+ name: "Four bytes 0x01020304",
+ hexStr: "0x01020304",
+ expected: 16909060,
+ expectError: false,
+ },
+ {
+ name: "More than four bytes 0x01020304AA",
+ hexStr: "0x01020304AA",
+ expectError: true,
+ },
+ {
+ name: "Invalid hex characters",
+ hexStr: "0xGG",
+ expected: 0,
+ expectError: true,
+ },
+ {
+ name: "Odd length hex string",
+ hexStr: "0x123",
+ expected: 0,
+ expectError: true,
+ },
+ {
+ name: "Four bytes all zeros",
+ hexStr: "0x00000000",
+ expected: 0,
+ expectError: false,
+ },
+ {
+ name: "Four bytes all ones",
+ hexStr: "0xFFFFFFFF",
+ expected: 4294967295,
+ expectError: false,
+ },
+ {
+ name: "Three bytes 0x0000FF",
+ hexStr: "0x0000FF",
+ expected: 255,
+ expectError: false,
+ },
+ {
+ name: "Four bytes with leading zero 0x000000FF",
+ hexStr: "0x000000FF",
+ expected: 255,
+ expectError: false,
+ },
+ {
+ name: "Four bytes 0x01000000",
+ hexStr: "0x01000000",
+ expected: 16777216,
+ expectError: false,
+ },
+ {
+ name: "Four bytes 0x0A0B0C0D",
+ hexStr: "0x0A0B0C0D",
+ expected: 168496141,
+ expectError: false,
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ val, err := uint32FromHex(tt.hexStr)
+ if tt.expectError {
+ assert.Error(t, err, "Expected an error for input: %s", tt.hexStr)
+ } else {
+ require.NoError(t, err, "Did not expect an error for input: %s", tt.hexStr)
+ assert.Equal(t, tt.expected, val, "Mismatch for input: %s", tt.hexStr)
+ }
+ })
+ }
+}
+
+func TestBytesIter_NextByte(t *testing.T) {
+ tests := []struct {
+ name string
+ hexData string
+ readCount int
+ expectedHex string
+ expectError bool
+ }{
+ {
+ name: "Exact Bytes",
+ hexData: "0x010203",
+ readCount: 3,
+ expectedHex: "0x010203",
+ expectError: false,
+ },
+ {
+ name: "Read Beyond",
+ hexData: "0x0102",
+ readCount: 3,
+ expectError: true,
+ },
+ {
+ name: "Single Byte Exact",
+ hexData: "0xFF",
+ readCount: 1,
+ expectedHex: "0xFF",
+ expectError: false,
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ data, err := hexToBytes(tt.hexData)
+ require.NoError(t, err)
+ iter := New(data)
+
+ var result []byte
+ var readErr error
+ for i := 0; i < tt.readCount; i++ {
+ b, err := iter.NextByte()
+ if err != nil {
+ readErr = err
+ break
+ }
+ result = append(result, b)
+ }
+
+ if tt.expectError {
+ assert.Error(t, readErr)
+ } else {
+ expected, err := hexToBytes(tt.expectedHex)
+ require.NoError(t, err)
+ assert.NoError(t, readErr)
+ assert.Equal(t, expected, result)
+ }
+ })
+ }
+}
+
+func TestBytesIter_NextUint16(t *testing.T) {
+ tests := []struct {
+ name string
+ hexData string
+ expectedHex string
+ expectError bool
+ }{
+ {
+ name: "Valid",
+ hexData: "0x00FFAB",
+ expectedHex: "0x00FF",
+ expectError: false,
+ },
+ {
+ name: "Insufficient Bytes",
+ hexData: "0x00",
+ expectError: true,
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ data, err := hexToBytes(tt.hexData)
+ require.NoError(t, err)
+ iter := New(data)
+
+ val, err := iter.NextUint16()
+
+ if tt.expectError {
+ assert.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ expectedVal, err := bigIntFromHex(tt.expectedHex)
+ require.NoError(t, err)
+ assert.Equal(t, expectedVal, val)
+ }
+ })
+ }
+}
+
+func TestBytesIter_NextUint24(t *testing.T) {
+ tests := []struct {
+ name string
+ hexData string
+ expectedHex string
+ expectError bool
+ }{
+ {
+ name: "Valid",
+ hexData: "0x01020304",
+ expectedHex: "0x010203",
+ expectError: false,
+ },
+ {
+ name: "Insufficient Bytes",
+ hexData: "0x01",
+ expectError: true,
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ data, err := hexToBytes(tt.hexData)
+ require.NoError(t, err)
+ iter := New(data)
+
+ val, err := iter.NextUint24()
+
+ if tt.expectError {
+ assert.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ expectedVal, err := uint32FromHex(tt.expectedHex)
+ require.NoError(t, err)
+ assert.Equal(t, expectedVal, val)
+ }
+ })
+ }
+}
+
+func TestBytesIter_NextUint32(t *testing.T) {
+ tests := []struct {
+ name string
+ hexData string
+ expectedHex string
+ expectError bool
+ }{
+ {
+ name: "Valid",
+ hexData: "0xFFEEDDCCAB",
+ expectedHex: "0xFFEEDDCC",
+ expectError: false,
+ },
+ {
+ name: "Insufficient Bytes",
+ hexData: "0xFFEE",
+ expectError: true,
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ data, err := hexToBytes(tt.hexData)
+ require.NoError(t, err)
+ iter := New(data)
+
+ val, err := iter.NextUint32()
+
+ if tt.expectError {
+ assert.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ expectedVal, err := bigIntFromHex(tt.expectedHex)
+ require.NoError(t, err)
+ assert.Equal(t, expectedVal, val)
+ }
+ })
+ }
+}
+
+func TestBytesIter_NextUint160(t *testing.T) {
+ twentyBytes := strings.Repeat("FF", 20) // 20 bytes of 0xFF
+ tests := []struct {
+ name string
+ hexData string
+ expectError bool
+ }{
+ {
+ name: "Valid",
+ hexData: "0x" + twentyBytes,
+ },
+ {
+ name: "Insufficient Bytes",
+ hexData: "0xFFEE",
+ expectError: true,
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ data, err := hexToBytes(tt.hexData)
+ require.NoError(t, err)
+ iter := New(data)
+
+ val, err := iter.NextUint160()
+ if tt.expectError {
+ assert.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ expectedVal, err := bigIntFromHex(tt.hexData)
+ require.NoError(t, err)
+ assert.Equal(t, expectedVal, val)
+ }
+ })
+ }
+}
+
+func TestBytesIter_NextUint256(t *testing.T) {
+ thirtyTwoBytes := strings.Repeat("AA", 32) // 32 bytes of 0xAA
+ tests := []struct {
+ name string
+ hexData string
+ expectError bool
+ }{
+ {
+ name: "Valid",
+ hexData: "0x" + thirtyTwoBytes,
+ },
+ {
+ name: "Insufficient Bytes",
+ hexData: "0xAA",
+ expectError: true,
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ data, err := hexToBytes(tt.hexData)
+ require.NoError(t, err)
+ iter := New(data)
+
+ val, err := iter.NextUint256()
+ if tt.expectError {
+ assert.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ expectedVal, err := bigIntFromHex(tt.hexData)
+ require.NoError(t, err)
+ assert.Equal(t, expectedVal, val)
+ }
+ })
+ }
+}
+
+func TestBytesIter_NextBytes(t *testing.T) {
+ tests := []struct {
+ name string
+ hexData string
+ readSize int
+ expectedHex string
+ expectError bool
+ }{
+ {
+ name: "Valid Read",
+ hexData: "0x10203040",
+ readSize: 2,
+ expectedHex: "0x1020",
+ expectError: false,
+ },
+ {
+ name: "Insufficient Data",
+ hexData: "0x10",
+ readSize: 2,
+ expectError: true,
+ },
+ {
+ name: "Negative Length",
+ hexData: "0x1020",
+ readSize: -1,
+ expectError: true,
+ },
+ {
+ name: "Exact Length Bytes",
+ hexData: "0xABCDEE",
+ readSize: 3,
+ expectedHex: "0xABCDEE",
+ expectError: false,
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ data, err := hexToBytes(tt.hexData)
+ require.NoError(t, err)
+ iter := New(data)
+
+ val, err := iter.NextBytes(tt.readSize)
+
+ if tt.expectError {
+ assert.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ expected, err := hexToBytes(tt.expectedHex)
+ require.NoError(t, err)
+ assert.Equal(t, expected, val)
+ }
+ })
+ }
+}
+
+func TestBytesIter_NextString(t *testing.T) {
+ tests := []struct {
+ name string
+ hexData string
+ readSize int
+ expectedHex string
+ expectError bool
+ }{
+ {
+ name: "Valid String",
+ hexData: "0x48656C6C6F",
+ readSize: 5,
+ expectedHex: "0x48656C6C6F",
+ expectError: false,
+ },
+ {
+ name: "Insufficient Data",
+ hexData: "0x48",
+ readSize: 5,
+ expectError: true,
+ },
+ {
+ name: "Exact Length String",
+ hexData: "0x416263",
+ readSize: 3,
+ expectedHex: "0x416263",
+ expectError: false,
+ },
+ {
+ name: "One Byte String",
+ hexData: "0x41",
+ readSize: 1,
+ expectedHex: "0x41",
+ expectError: false,
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ data, err := hexToBytes(tt.hexData)
+ require.NoError(t, err)
+ iter := New(data)
+
+ strVal, err := iter.NextString(tt.readSize)
+
+ if tt.expectError {
+ assert.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ expectedBytes, err := hexToBytes(tt.expectedHex)
+ require.NoError(t, err)
+ expectedStr := string(expectedBytes)
+ assert.Equal(t, expectedStr, strVal)
+ }
+ })
+ }
+}
+
+func TestBytesIter_Rest(t *testing.T) {
+ tests := []struct {
+ name string
+ hexData string
+ readSize int
+ expectedHex string
+ }{
+ {
+ name: "Read Part, Then Rest",
+ hexData: "0x010203",
+ readSize: 1,
+ expectedHex: "0x0203",
+ },
+ {
+ name: "Read All, Then Rest",
+ hexData: "0x0102",
+ readSize: 2,
+ expectedHex: "0x", // no rest
+ },
+ {
+ name: "No Read, Return All",
+ hexData: "0x050607",
+ readSize: 0,
+ expectedHex: "0x050607",
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ data, err := hexToBytes(tt.hexData)
+ require.NoError(t, err)
+ iter := New(data)
+
+ if tt.readSize > 0 {
+ _, _ = iter.NextBytes(tt.readSize)
+ }
+
+ rest, err := iter.Rest()
+ require.NoError(t, err)
+
+ if tt.expectedHex == "0x" {
+ // Expect no remaining bytes; rest can be nil or empty
+ assert.Empty(t, rest, "Expected no remaining bytes, but some were found")
+ } else {
+ expected, err := hexToBytes(strings.TrimPrefix(tt.expectedHex, "0x"))
+ require.NoError(t, err)
+ assert.Equal(t, expected, rest)
+ }
+
+ // Subsequent calls should yield empty
+ rest, err = iter.Rest()
+ require.NoError(t, err)
+ assert.Empty(t, rest, "Expected no remaining bytes on subsequent Rest call")
+ })
+ }
+}
+
+func TestBytesIter_IsEmpty(t *testing.T) {
+ tests := []struct {
+ name string
+ hexData string
+ reads int
+ expectedEmpty bool
+ }{
+ {
+ name: "Initially Not Empty",
+ hexData: "0x0102",
+ reads: 0,
+ expectedEmpty: false,
+ },
+ {
+ name: "Partially Read",
+ hexData: "0x0102",
+ reads: 1,
+ expectedEmpty: false,
+ },
+ {
+ name: "Fully Read",
+ hexData: "0x0102",
+ reads: 2,
+ expectedEmpty: true,
+ },
+ {
+ name: "One Byte Only",
+ hexData: "0xFF",
+ reads: 1,
+ expectedEmpty: true,
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ data, err := hexToBytes(tt.hexData)
+ require.NoError(t, err)
+ iter := New(data)
+
+ for i := 0; i < tt.reads; i++ {
+ _, _ = iter.NextByte()
+ }
+
+ assert.Equal(t, tt.expectedEmpty, iter.IsEmpty())
+ })
+ }
+}
diff --git a/internal/keccak/keccak.go b/internal/keccak/keccak.go
new file mode 100644
index 00000000..6612b653
--- /dev/null
+++ b/internal/keccak/keccak.go
@@ -0,0 +1,13 @@
+package keccak
+
+import (
+ "fmt"
+
+ "golang.org/x/crypto/sha3"
+)
+
+func Keccak256Legacy(value []byte) string {
+ hash := sha3.NewLegacyKeccak256()
+ hash.Write(value)
+ return fmt.Sprintf("0x%x", hash.Sum(nil))
+}
diff --git a/internal/keccak/keccak_test.go b/internal/keccak/keccak_test.go
new file mode 100644
index 00000000..671e6892
--- /dev/null
+++ b/internal/keccak/keccak_test.go
@@ -0,0 +1,43 @@
+package keccak
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestKeccak256Legacy(t *testing.T) {
+ tests := []struct {
+ name string
+ input []byte
+ want string
+ }{
+ {
+ name: "Empty input",
+ input: []byte(""),
+ want: "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ },
+ {
+ name: "hello",
+ input: []byte("hello"),
+ want: "0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8",
+ },
+ {
+ name: "abc",
+ input: []byte("abc"),
+ want: "0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45",
+ },
+ {
+ name: "some random input",
+ input: []byte("some random input"),
+ want: "0xd9092dd563673ef538f6d6162a5d127ddbd3d220a3f1f0eff6f7bac0194acd19",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := Keccak256Legacy(tt.input)
+ require.Equal(t, tt.want, got, "hash should match expected result")
+ })
+ }
+}
diff --git a/internal/validate/general.go b/internal/validate/general.go
index 4c0ec8f6..8c1b09ef 100644
--- a/internal/validate/general.go
+++ b/internal/validate/general.go
@@ -1,18 +1,5 @@
package validate
-import (
- "fmt"
- "math/big"
-)
-
-func BigIntFromString(s string) (*big.Int, error) {
- bigInt, ok := new(big.Int).SetString(s, 10) // base 10 for decimal
- if !ok {
- return nil, fmt.Errorf("failed to convert string (%v) to big.Int", s)
- }
- return bigInt, nil
-}
-
// HasDuplicates checks if the provided slice contains any duplicate elements.
// It accepts a slice of any comparable type and returns true if there are duplicates, otherwise it returns false.
func HasDuplicates[T comparable](slice []T) bool {
diff --git a/internal/validate/validate.go b/internal/validate/validate.go
index 03c105f7..37786a3a 100644
--- a/internal/validate/validate.go
+++ b/internal/validate/validate.go
@@ -8,6 +8,7 @@ import (
"time"
"github.com/1inch/1inch-sdk-go/constants"
+ "github.com/1inch/1inch-sdk-go/internal/bigint"
"github.com/1inch/1inch-sdk-go/internal/slice_utils"
)
@@ -62,7 +63,7 @@ func CheckEthereumAddressListRequired(parameter interface{}, variableName string
return nil
}
-var bigIntMax, _ = BigIntFromString("115792089237316195423570985008687907853269984665640564039457584007913129639935")
+var bigIntMax, _ = bigint.FromString("115792089237316195423570985008687907853269984665640564039457584007913129639935")
var bigIntZero = big.NewInt(0)
func CheckBigIntRequired(parameter interface{}, variableName string) error {
@@ -87,7 +88,7 @@ func CheckBigInt(parameter interface{}, variableName string) error {
return nil
}
- parsedValue, err := BigIntFromString(value)
+ parsedValue, err := bigint.FromString(value)
if err != nil {
return NewParameterValidationError(variableName, "not a valid value")
}
@@ -100,23 +101,23 @@ func CheckBigInt(parameter interface{}, variableName string) error {
return nil
}
-func CheckChainIdRequired(parameter interface{}, variableName string) error {
+func CheckChainIdIntRequired(parameter interface{}, variableName string) error {
value, ok := parameter.(int)
if !ok {
- return fmt.Errorf("for parameter '%v' to be validated as '%v', it must be an int", variableName, "ChainId")
+ return fmt.Errorf("for parameter '%v' to be validated as '%v', it must be an int", variableName, "ChainIdInt")
}
if value == 0 {
return NewParameterMissingError(variableName)
}
- return CheckChainId(value, variableName)
+ return CheckChainIdInt(value, variableName)
}
-func CheckChainId(parameter interface{}, variableName string) error {
+func CheckChainIdInt(parameter interface{}, variableName string) error {
value, ok := parameter.(int)
if !ok {
- return fmt.Errorf("for parameter '%v' to be validated as '%v', it must be an int", variableName, "ChainId")
+ return fmt.Errorf("for parameter '%v' to be validated as '%v', it must be an int", variableName, "ChainIdInt")
}
if value == 0 {
return nil
@@ -128,6 +129,34 @@ func CheckChainId(parameter interface{}, variableName string) error {
return nil
}
+func CheckChainIdFloat32Required(parameter interface{}, variableName string) error {
+ value, ok := parameter.(float32)
+ if !ok {
+ return fmt.Errorf("for parameter '%v' to be validated as '%v', it must be a float32", variableName, "ChainIdFloat32")
+ }
+
+ if value == 0 {
+ return NewParameterMissingError(variableName)
+ }
+
+ return CheckChainIdFloat32(value, variableName)
+}
+
+func CheckChainIdFloat32(parameter interface{}, variableName string) error {
+ value, ok := parameter.(float32)
+ if !ok {
+ return fmt.Errorf("for parameter '%v' to be validated as '%v', it must be a float32", variableName, "ChainIdFloat32")
+ }
+ if value == 0 {
+ return nil
+ }
+
+ if !slice_utils.Contains(int(value), constants.ValidChainIds) {
+ return NewParameterValidationError(variableName, fmt.Sprintf("is invalid, valid chain ids are: %v", constants.ValidChainIds))
+ }
+ return nil
+}
+
func CheckPrivateKeyRequired(parameter interface{}, variableName string) error {
address, ok := parameter.(string)
if !ok {
diff --git a/internal/validate/validate_test.go b/internal/validate/validate_test.go
index 01536d09..204fb700 100644
--- a/internal/validate/validate_test.go
+++ b/internal/validate/validate_test.go
@@ -4,6 +4,7 @@ import (
"fmt"
"testing"
+ "github.com/1inch/1inch-sdk-go/internal/bigint"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -225,7 +226,7 @@ func TestChainIdRequired(t *testing.T) {
for _, tc := range testcases {
t.Run(tc.description, func(t *testing.T) {
- err := CheckChainIdRequired(tc.value, "testValue")
+ err := CheckChainIdIntRequired(tc.value, "testValue")
if tc.expectError {
require.Error(t, err, fmt.Sprintf("%s should have caused an error", tc.description))
} else {
@@ -262,7 +263,7 @@ func TestChainId(t *testing.T) {
for _, tc := range testcases {
t.Run(tc.description, func(t *testing.T) {
- err := CheckChainId(tc.value, "testValue")
+ err := CheckChainIdInt(tc.value, "testValue")
if tc.expectError {
require.Error(t, err, fmt.Sprintf("%s should have caused an error", tc.description))
} else {
@@ -1005,7 +1006,7 @@ func TestIsBigInt(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
- _, err := BigIntFromString(tc.input)
+ _, err := bigint.FromString(tc.input)
require.NoError(t, err)
})
}
diff --git a/sdk-clients/fusion/extension.go b/sdk-clients/fusion/extension.go
index 2b960f75..e2cd6dcb 100644
--- a/sdk-clients/fusion/extension.go
+++ b/sdk-clients/fusion/extension.go
@@ -3,9 +3,11 @@ package fusion
import (
"encoding/json"
"errors"
+ "fmt"
"math/big"
"strings"
+ geth_common "github.com/ethereum/go-ethereum/common"
"golang.org/x/crypto/sha3"
random_number_generation "github.com/1inch/1inch-sdk-go/internal/random-number-generation"
@@ -15,6 +17,14 @@ import (
// Extension represents the extension data for the Fusion order
// and should be only created using the NewExtension function
type Extension struct {
+ // Raw unencoded data
+ SettlementContract string
+ AuctionDetails *AuctionDetails
+ PostInteractionData *SettlementPostInteractionData
+ Asset string
+ Permit string
+
+ // Data formatted for Limit Order Extension
MakerAssetSuffix string
TakerAssetSuffix string
MakingAmountData string
@@ -27,14 +37,16 @@ type Extension struct {
}
type ExtensionParams struct {
+ SettlementContract string
+ AuctionDetails *AuctionDetails
+ PostInteractionData *SettlementPostInteractionData
+ Asset string
+ Permit string
+
MakerAssetSuffix string
TakerAssetSuffix string
- MakingAmountData string
- TakingAmountData string
Predicate string
- MakerPermit string
PreInteraction string
- PostInteraction string
CustomData string
}
@@ -45,18 +57,9 @@ func NewExtension(params ExtensionParams) (*Extension, error) {
if !isHexBytes(params.TakerAssetSuffix) {
return nil, errors.New("TakerAssetSuffix must be valid hex string")
}
- if !isHexBytes(params.MakingAmountData) {
- return nil, errors.New("MakingAmountData must be valid hex string")
- }
- if !isHexBytes(params.TakingAmountData) {
- return nil, errors.New("TakingAmountData must be valid hex string")
- }
if !isHexBytes(params.Predicate) {
return nil, errors.New("Predicate must be valid hex string")
}
- if !isHexBytes(params.MakerPermit) {
- return nil, errors.New("MakerPermit must be valid hex string")
- }
if params.CustomData != "" {
return nil, errors.New("CustomData is not currently supported")
}
@@ -64,21 +67,39 @@ func NewExtension(params ExtensionParams) (*Extension, error) {
return nil, errors.New("CustomData must be valid hex string")
}
- return &Extension{
+ settlementContractAddress := geth_common.HexToAddress(params.SettlementContract)
+ makingAndTakingAmountData := settlementContractAddress.String() + trim0x(params.AuctionDetails.Encode())
+
+ fusionExtension := &Extension{
+ SettlementContract: params.SettlementContract,
+ AuctionDetails: params.AuctionDetails,
+ PostInteractionData: params.PostInteractionData,
+ Asset: params.Asset,
+ Permit: params.Permit,
+
MakerAssetSuffix: params.MakerAssetSuffix,
TakerAssetSuffix: params.TakerAssetSuffix,
- MakingAmountData: params.MakingAmountData,
- TakingAmountData: params.TakingAmountData,
+ MakingAmountData: makingAndTakingAmountData,
+ TakingAmountData: makingAndTakingAmountData,
Predicate: params.Predicate,
- MakerPermit: params.MakerPermit,
PreInteraction: params.PreInteraction,
- PostInteraction: params.PostInteraction,
+ PostInteraction: NewInteraction(settlementContractAddress, params.PostInteractionData.Encode()).Encode(),
CustomData: params.CustomData,
- }, nil
+ }
+
+ if params.Permit != "" {
+ permitInteraction := &Interaction{
+ Target: geth_common.HexToAddress(params.Asset),
+ Data: params.Permit,
+ }
+ fusionExtension.MakerPermit = permitInteraction.Target.String() + trim0x(permitInteraction.Data)
+ }
+
+ return fusionExtension, nil
}
-// keccak256 calculates the Keccak256 hash of the extension data
-func (e *Extension) keccak256() *big.Int {
+// Keccak256 calculates the Keccak256 hash of the extension data
+func (e *Extension) Keccak256() *big.Int {
jsonData, err := json.Marshal(e)
if err != nil {
panic(err)
@@ -90,17 +111,15 @@ func (e *Extension) keccak256() *big.Int {
func (e *Extension) ConvertToOrderbookExtension() *orderbook.Extension {
return &orderbook.Extension{
- InteractionsArray: []string{
- strings.TrimPrefix(e.MakerAssetSuffix, "0x"),
- strings.TrimPrefix(e.TakerAssetSuffix, "0x"),
- strings.TrimPrefix(e.MakingAmountData, "0x"),
- strings.TrimPrefix(e.TakingAmountData, "0x"),
- strings.TrimPrefix(e.Predicate, "0x"),
- strings.TrimPrefix(e.MakerPermit, "0x"),
- e.PreInteraction,
- e.PostInteraction,
- //strings.TrimPrefix(e.CustomData, "0x"), // TODO Blocking custom data for now because it is breaking the cumsum method. The extension constructor will return with an error if the user provides this field.
- },
+ MakerAssetSuffix: e.MakerAssetSuffix,
+ TakerAssetSuffix: e.TakerAssetSuffix,
+ MakingAmountData: e.MakingAmountData,
+ TakingAmountData: e.TakingAmountData,
+ Predicate: e.Predicate,
+ MakerPermit: e.MakerPermit,
+ PreInteraction: e.PreInteraction,
+ PostInteraction: e.PostInteraction,
+ //strings.TrimPrefix(e.CustomData, "0x"), // TODO Blocking custom data for now because it is breaking the cumsum method. The extension constructor will return with an error if the user provides this field.
}
}
@@ -121,7 +140,7 @@ func (e *Extension) GenerateSalt() (*big.Int, error) {
uint160Max := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 160), big.NewInt(1))
- extensionHash := e.keccak256()
+ extensionHash := e.Keccak256()
salt := new(big.Int).Lsh(baseSalt, 160)
salt.Or(salt, new(big.Int).And(extensionHash, uint160Max))
@@ -136,3 +155,82 @@ func (e *Extension) isEmpty() bool {
func trim0x(s string) string {
return strings.TrimPrefix(s, "0x")
}
+
+func DecodeExtension(data []byte) (*Extension, error) {
+ orderbookExtension, err := orderbook.Decode(data)
+ if err != nil {
+ return &Extension{}, fmt.Errorf("error decoding extension: %v", err)
+ }
+
+ fusionExtension, err := FromLimitOrderExtension(orderbookExtension)
+ if err != nil {
+ return nil, fmt.Errorf("failed to convert orderbook extension to fusion extension: %v", err)
+ }
+
+ return &Extension{
+ SettlementContract: fusionExtension.SettlementContract,
+ AuctionDetails: fusionExtension.AuctionDetails,
+ PostInteractionData: fusionExtension.PostInteractionData,
+ Asset: fusionExtension.Asset,
+ Permit: fusionExtension.Permit,
+
+ MakerAssetSuffix: orderbookExtension.MakerAssetSuffix,
+ TakerAssetSuffix: orderbookExtension.TakerAssetSuffix,
+ MakingAmountData: orderbookExtension.MakingAmountData,
+ TakingAmountData: orderbookExtension.TakingAmountData,
+ Predicate: orderbookExtension.Predicate,
+ MakerPermit: orderbookExtension.MakerPermit,
+ PreInteraction: orderbookExtension.PreInteraction,
+ PostInteraction: orderbookExtension.PostInteraction,
+ }, nil
+}
+
+func FromLimitOrderExtension(extension *orderbook.Extension) (*Extension, error) {
+
+ settlementContractAddress := trim0x(extension.MakingAmountData)[:40]
+
+ if settlementContractAddress != trim0x(extension.TakingAmountData)[:40] {
+ return nil, fmt.Errorf("malfomed extension: settlement contract address should be the same in making and taking amount data")
+ }
+ if settlementContractAddress != trim0x(extension.PostInteraction)[:40] {
+ return nil, fmt.Errorf("malfomed extension: settlement contract address should be the same in making and post interaction")
+ }
+
+ auctionDetails, err := DecodeAuctionDetails(trim0x(extension.MakingAmountData)[40:])
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode auction details: %v", err)
+ }
+
+ postInteractionData, err := Decode(trim0x(extension.PostInteraction)[40:])
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode post interaction data: %v", err)
+ }
+
+ fusionExtension := &Extension{
+ SettlementContract: fmt.Sprintf("0x%s", settlementContractAddress),
+ AuctionDetails: auctionDetails,
+ PostInteractionData: &postInteractionData,
+
+ MakerAssetSuffix: extension.MakerAssetSuffix,
+ TakerAssetSuffix: extension.TakerAssetSuffix,
+ MakingAmountData: extension.MakingAmountData,
+ TakingAmountData: extension.TakingAmountData,
+ Predicate: extension.Predicate,
+ MakerPermit: extension.MakerPermit,
+ PreInteraction: extension.PreInteraction,
+ PostInteraction: extension.PostInteraction,
+ }
+
+ var permitInteraction *Interaction
+ if extension.MakerPermit != "" {
+ permitInteraction, err = DecodeInteraction(extension.MakerPermit)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode permit interaction: %v", err)
+ }
+
+ fusionExtension.Asset = permitInteraction.Target.String()
+ fusionExtension.Permit = permitInteraction.Data
+ }
+
+ return fusionExtension, nil
+}
diff --git a/sdk-clients/fusion/extension_test.go b/sdk-clients/fusion/extension_test.go
index 607421be..9ec2f43c 100644
--- a/sdk-clients/fusion/extension_test.go
+++ b/sdk-clients/fusion/extension_test.go
@@ -1,9 +1,17 @@
package fusion
import (
+ "bytes"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
"math/big"
+ "strings"
"testing"
+ "github.com/1inch/1inch-sdk-go/internal/bigint"
+ "github.com/1inch/1inch-sdk-go/sdk-clients/orderbook"
+ "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -43,14 +51,14 @@ func TestGenerateSalt(t *testing.T) {
PostInteraction: "post",
CustomData: "custom",
},
- expected: "180431658011416401710119735245975317914670388782711199",
+ expected: "180431178743033967347942937469468920088249224033532329",
expectErr: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
- expected, err := BigIntFromString(tc.expected)
+ expected, err := bigint.FromString(tc.expected)
require.NoError(t, err)
result, err := tc.extension.GenerateSalt()
@@ -66,22 +74,91 @@ func TestGenerateSalt(t *testing.T) {
func TestNewExtension(t *testing.T) {
tests := []struct {
- name string
- params ExtensionParams
- expectErr bool
- errMsg string
+ name string
+ params ExtensionParams
+ expectedExtension *Extension
+ expectErr bool
+ errMsg string
}{
{
name: "Valid parameters",
params: ExtensionParams{
+ SettlementContract: "0x5678",
+ AuctionDetails: &AuctionDetails{
+ StartTime: 0,
+ Duration: 0,
+ InitialRateBump: 0,
+ Points: nil,
+ GasCost: GasCostConfigClassFixed{},
+ },
+ PostInteractionData: &SettlementPostInteractionData{
+ Whitelist: []WhitelistItem{},
+ IntegratorFee: &IntegratorFee{
+ Ratio: big.NewInt(0),
+ Receiver: common.Address{},
+ },
+ BankFee: big.NewInt(0),
+ ResolvingStartTime: big.NewInt(0),
+ CustomReceiver: common.Address{},
+ },
+ Asset: "0x1234",
+ Permit: "0x3456",
+
MakerAssetSuffix: "0x1234",
TakerAssetSuffix: "0x1234",
- MakingAmountData: "0x1234",
- TakingAmountData: "0x1234",
Predicate: "0x1234",
- MakerPermit: "0x1234",
PreInteraction: "pre",
- PostInteraction: "post",
+ },
+ expectedExtension: &Extension{
+ MakerAssetSuffix: "0x1234",
+ TakerAssetSuffix: "0x1234",
+ MakingAmountData: "0x00000000000000000000000000000000000056780000000000000000000000000000000000",
+ TakingAmountData: "0x00000000000000000000000000000000000056780000000000000000000000000000000000",
+ Predicate: "0x1234",
+ MakerPermit: "0x00000000000000000000000000000000000012343456",
+ PreInteraction: "pre",
+ PostInteraction: "0x00000000000000000000000000000000000056780000000000",
+ },
+ expectErr: false,
+ },
+ {
+ name: "Valid parameters 2",
+ params: ExtensionParams{
+ SettlementContract: "0x0500000000000000000000000000000000000000",
+ AuctionDetails: &AuctionDetails{
+ StartTime: 0,
+ Duration: 0,
+ InitialRateBump: 0,
+ Points: nil,
+ GasCost: GasCostConfigClassFixed{},
+ },
+ PostInteractionData: &SettlementPostInteractionData{
+ Whitelist: []WhitelistItem{},
+ IntegratorFee: &IntegratorFee{
+ Ratio: big.NewInt(0),
+ Receiver: common.Address{},
+ },
+ BankFee: big.NewInt(0),
+ ResolvingStartTime: big.NewInt(0),
+ CustomReceiver: common.Address{},
+ },
+ Asset: "0x1234",
+ Permit: "0x03",
+
+ MakerAssetSuffix: "0x01",
+ TakerAssetSuffix: "0x02",
+ Predicate: "0x07",
+ PreInteraction: "0x09",
+ },
+ expectedExtension: &Extension{
+ MakerAssetSuffix: "0x01",
+ TakerAssetSuffix: "0x02",
+ MakingAmountData: "0x05000000000000000000000000000000000000000000000000000000000000000000000000",
+ TakingAmountData: "0x05000000000000000000000000000000000000000000000000000000000000000000000000",
+ Predicate: "0x07",
+ MakerPermit: "0x000000000000000000000000000000000000123403",
+ PreInteraction: "0x09",
+ PostInteraction: "0x05000000000000000000000000000000000000000000000000",
},
expectErr: false,
},
@@ -90,12 +167,8 @@ func TestNewExtension(t *testing.T) {
params: ExtensionParams{
MakerAssetSuffix: "invalid",
TakerAssetSuffix: "0x1234",
- MakingAmountData: "0x1234",
- TakingAmountData: "0x1234",
Predicate: "0x1234",
- MakerPermit: "0x1234",
PreInteraction: "pre",
- PostInteraction: "post",
},
expectErr: true,
errMsg: "MakerAssetSuffix must be valid hex string",
@@ -105,113 +178,297 @@ func TestNewExtension(t *testing.T) {
params: ExtensionParams{
MakerAssetSuffix: "0x1234",
TakerAssetSuffix: "invalid",
- MakingAmountData: "0x1234",
- TakingAmountData: "0x1234",
Predicate: "0x1234",
- MakerPermit: "0x1234",
PreInteraction: "pre",
- PostInteraction: "post",
},
expectErr: true,
errMsg: "TakerAssetSuffix must be valid hex string",
},
{
- name: "Invalid MakingAmountData",
+ name: "Invalid Predicate",
params: ExtensionParams{
MakerAssetSuffix: "0x1234",
TakerAssetSuffix: "0x1234",
- MakingAmountData: "invalid",
- TakingAmountData: "0x1234",
- Predicate: "0x1234",
- MakerPermit: "0x1234",
+ Predicate: "invalid",
PreInteraction: "pre",
- PostInteraction: "post",
},
expectErr: true,
- errMsg: "MakingAmountData must be valid hex string",
+ errMsg: "Predicate must be valid hex string",
},
{
- name: "Invalid TakingAmountData",
+ name: "CustomData not supported",
params: ExtensionParams{
MakerAssetSuffix: "0x1234",
TakerAssetSuffix: "0x1234",
- MakingAmountData: "0x1234",
- TakingAmountData: "invalid",
Predicate: "0x1234",
- MakerPermit: "0x1234",
PreInteraction: "pre",
- PostInteraction: "post",
+ CustomData: "0x1234",
},
expectErr: true,
- errMsg: "TakingAmountData must be valid hex string",
+ errMsg: "CustomData is not currently supported",
},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ ext, err := NewExtension(tc.params)
+ if tc.expectErr {
+ require.Error(t, err)
+ assert.Equal(t, tc.errMsg, err.Error())
+ } else {
+ require.NoError(t, err)
+ assert.NotNil(t, ext)
+ assert.Equal(t, tc.expectedExtension.MakerAssetSuffix, ext.MakerAssetSuffix)
+ assert.Equal(t, tc.expectedExtension.TakerAssetSuffix, ext.TakerAssetSuffix)
+ assert.Equal(t, tc.expectedExtension.MakingAmountData, ext.MakingAmountData)
+ assert.Equal(t, tc.expectedExtension.TakingAmountData, ext.TakingAmountData)
+ assert.Equal(t, tc.expectedExtension.Predicate, ext.Predicate)
+ assert.Equal(t, tc.expectedExtension.MakerPermit, ext.MakerPermit)
+ assert.Equal(t, tc.expectedExtension.PreInteraction, ext.PreInteraction)
+ assert.Equal(t, tc.expectedExtension.PostInteraction, ext.PostInteraction)
+ }
+ })
+ }
+}
+
+func TestDecodeExtension(t *testing.T) {
+ tests := []struct {
+ name string
+ hexInput string
+ expected *Extension
+ expectingErr bool
+ errorContains string
+ }{
{
- name: "Invalid Predicate",
- params: ExtensionParams{
+ name: "Successful Decoding",
+ hexInput: "0000007c00000063000000620000004d0000004c00000027000000020000000101020500000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000001234030905000000000000000000000000000000000000000000000000",
+ expected: &Extension{
+ MakerAssetSuffix: "0x01",
+ TakerAssetSuffix: "0x02",
+ MakingAmountData: "05000000000000000000000000000000000000000000000000000000000000000000000000",
+ TakingAmountData: "05000000000000000000000000000000000000000000000000000000000000000000000000",
+ Predicate: "0x07",
+ MakerPermit: "0x000000000000000000000000000000000000123403",
+ PreInteraction: "0x09",
+ PostInteraction: "0x05000000000000000000000000000000000000000000000000",
+ },
+ expectingErr: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Convert hex string to bytes
+ data, err := hexToBytes(tt.hexInput)
+ if err != nil {
+ t.Fatalf("Failed to convert hex to bytes: %v", err)
+ }
+
+ // Decode the data
+ decoded, err := DecodeExtension(data)
+ require.NoError(t, err)
+
+ if tt.expectingErr {
+ if err == nil {
+ t.Errorf("Expected error but got none")
+ } else if tt.errorContains != "" && !contains(err.Error(), tt.errorContains) {
+ t.Errorf("Expected error to contain '%s' but got '%s'", tt.errorContains, err.Error())
+ }
+ } else {
+ if err != nil {
+ t.Errorf("Unexpected error: %v", err)
+ }
+ if !extensionsEqual(decoded, tt.expected) {
+ t.Errorf("Decoded Extension does not match expected.\nGot: %+v\nExpected: %+v", printSelectedFields(decoded), printSelectedFields(tt.expected))
+ }
+ }
+ })
+ }
+}
+
+func printSelectedFields(ext *Extension) string {
+ selectedFields := map[string]string{
+ "MakerAssetSuffix": strings.TrimPrefix(ext.MakerAssetSuffix, "0x"),
+ "TakerAssetSuffix": strings.TrimPrefix(ext.TakerAssetSuffix, "0x"),
+ "MakingAmountData": strings.TrimPrefix(ext.MakingAmountData, "0x"),
+ "TakingAmountData": strings.TrimPrefix(ext.TakingAmountData, "0x"),
+ "Predicate": strings.TrimPrefix(ext.Predicate, "0x"),
+ "MakerPermit": strings.TrimPrefix(ext.MakerPermit, "0x"),
+ "PreInteraction": strings.TrimPrefix(ext.PreInteraction, "0x"),
+ "PostInteraction": strings.TrimPrefix(ext.PostInteraction, "0x"),
+ }
+
+ jsonData, err := json.MarshalIndent(selectedFields, "", " ")
+ if err != nil {
+ return fmt.Sprint("Error marshalling to JSON:", err)
+ }
+ return string(jsonData)
+}
+
+func TestConvertToOrderbookExtension(t *testing.T) {
+ tests := []struct {
+ name string
+ fusionExtension Extension
+ expectedOrderbookExtension *orderbook.Extension
+ expectErr bool
+ errMsg string
+ }{
+ {
+ name: "Valid parameters",
+ fusionExtension: Extension{
MakerAssetSuffix: "0x1234",
TakerAssetSuffix: "0x1234",
- MakingAmountData: "0x1234",
- TakingAmountData: "0x1234",
- Predicate: "invalid",
- MakerPermit: "0x1234",
+ MakingAmountData: "0x00000000000000000000000000000000000056780000000000000000000000000000000000",
+ TakingAmountData: "0x00000000000000000000000000000000000056780000000000000000000000000000000000",
+ Predicate: "0x1234",
+ MakerPermit: "0x00000000000000000000000000000000000012343456",
PreInteraction: "pre",
- PostInteraction: "post",
+ PostInteraction: "0x00000000000000000000000000000000000056780000000000",
},
- expectErr: true,
- errMsg: "Predicate must be valid hex string",
- },
- {
- name: "Invalid MakerPermit",
- params: ExtensionParams{
+ expectedOrderbookExtension: &orderbook.Extension{
MakerAssetSuffix: "0x1234",
TakerAssetSuffix: "0x1234",
- MakingAmountData: "0x1234",
- TakingAmountData: "0x1234",
+ MakingAmountData: "0x00000000000000000000000000000000000056780000000000000000000000000000000000",
+ TakingAmountData: "0x00000000000000000000000000000000000056780000000000000000000000000000000000",
Predicate: "0x1234",
- MakerPermit: "invalid",
+ MakerPermit: "0x00000000000000000000000000000000000012343456",
PreInteraction: "pre",
- PostInteraction: "post",
+ PostInteraction: "0x00000000000000000000000000000000000056780000000000",
},
- expectErr: true,
- errMsg: "MakerPermit must be valid hex string",
+ expectErr: false,
},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ ext := tc.fusionExtension.ConvertToOrderbookExtension()
+ assert.NotNil(t, ext)
+ assert.Equal(t, tc.expectedOrderbookExtension.MakerAssetSuffix, ext.MakerAssetSuffix)
+ assert.Equal(t, tc.expectedOrderbookExtension.TakerAssetSuffix, ext.TakerAssetSuffix)
+ assert.Equal(t, tc.expectedOrderbookExtension.MakingAmountData, ext.MakingAmountData)
+ assert.Equal(t, tc.expectedOrderbookExtension.TakingAmountData, ext.TakingAmountData)
+ assert.Equal(t, tc.expectedOrderbookExtension.Predicate, ext.Predicate)
+ assert.Equal(t, tc.expectedOrderbookExtension.MakerPermit, ext.MakerPermit)
+ assert.Equal(t, tc.expectedOrderbookExtension.PreInteraction, ext.PreInteraction)
+ assert.Equal(t, tc.expectedOrderbookExtension.PostInteraction, ext.PostInteraction)
+ })
+ }
+}
+
+var asset = "0xBAb2C3d4e5f67890123456789AbcDEf123456789"
+var permit = "9999999999999999999999"
+var fullAuctionDetails = &AuctionDetails{
+ StartTime: 1,
+ Duration: 2,
+ InitialRateBump: 3,
+ Points: []AuctionPointClassFixed{{Coefficient: 4, Delay: 5}},
+ GasCost: GasCostConfigClassFixed{GasBumpEstimate: 6, GasPriceEstimate: 7},
+}
+
+var fullPostInteractionData = &SettlementPostInteractionData{
+ Whitelist: []WhitelistItem{
{
- name: "CustomData not supported",
+ AddressHalf: "a1b2c3d4e5f678901234",
+ Delay: big.NewInt(8),
+ },
+ },
+ IntegratorFee: &IntegratorFee{
+ Ratio: big.NewInt(9),
+ Receiver: common.HexToAddress("0xB1B2C3D4E5F67890123456789ABCDEF123456789"),
+ },
+ BankFee: big.NewInt(10),
+ ResolvingStartTime: big.NewInt(11),
+ CustomReceiver: common.HexToAddress("0xC1B2C3D4E5F67890123456789ABCDEF123456789"),
+}
+
+func TestFromExtension(t *testing.T) {
+ tests := []struct {
+ name string
+ params ExtensionParams
+ expectedExtension *Extension
+ expectErr bool
+ errMsg string
+ }{
+ {
+ name: "Valid parameters",
params: ExtensionParams{
+ SettlementContract: "0xAAB2C3d4E5F67890123456789abcdef123456789",
+ AuctionDetails: fullAuctionDetails,
+ PostInteractionData: fullPostInteractionData,
+ Asset: asset,
+ Permit: permit,
+
MakerAssetSuffix: "0x1234",
TakerAssetSuffix: "0x1234",
- MakingAmountData: "0x1234",
- TakingAmountData: "0x1234",
Predicate: "0x1234",
- MakerPermit: "0x1234",
PreInteraction: "pre",
- PostInteraction: "post",
- CustomData: "0x1234",
},
- expectErr: true,
- errMsg: "CustomData is not currently supported",
+ expectedExtension: &Extension{
+ SettlementContract: "0xAAB2C3d4E5F67890123456789abcdef123456789",
+ AuctionDetails: fullAuctionDetails,
+ PostInteractionData: fullPostInteractionData,
+ Asset: asset,
+ Permit: permit,
+
+ MakerAssetSuffix: "0x1234",
+ TakerAssetSuffix: "0x1234",
+ MakingAmountData: "0xAAB2C3d4E5F67890123456789abcdef12345678900000600000007000000010000020000030000040005",
+ TakingAmountData: "0xAAB2C3d4E5F67890123456789abcdef12345678900000600000007000000010000020000030000040005",
+ Predicate: "0x1234",
+ MakerPermit: fmt.Sprintf("%s%s", asset, permit),
+ PreInteraction: "pre",
+ PostInteraction: "0xAAB2C3d4E5F67890123456789abcdef1234567890000000a0009b1b2c3d4e5f67890123456789abcdef123456789c1b2c3d4e5f67890123456789abcdef1234567890000000ba1b2c3d4e5f67890123400080f",
+ },
+ expectErr: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ext, err := NewExtension(tc.params)
- if tc.expectErr {
- require.Error(t, err)
- assert.Equal(t, tc.errMsg, err.Error())
- } else {
- require.NoError(t, err)
- assert.NotNil(t, ext)
- assert.Equal(t, tc.params.MakerAssetSuffix, ext.MakerAssetSuffix)
- assert.Equal(t, tc.params.TakerAssetSuffix, ext.TakerAssetSuffix)
- assert.Equal(t, tc.params.MakingAmountData, ext.MakingAmountData)
- assert.Equal(t, tc.params.TakingAmountData, ext.TakingAmountData)
- assert.Equal(t, tc.params.Predicate, ext.Predicate)
- assert.Equal(t, tc.params.MakerPermit, ext.MakerPermit)
- assert.Equal(t, tc.params.PreInteraction, ext.PreInteraction)
- assert.Equal(t, tc.params.PostInteraction, ext.PostInteraction)
- assert.Equal(t, tc.params.CustomData, ext.CustomData)
- }
+ require.NoError(t, err)
+
+ limitOrderExtensionPure := ext.ConvertToOrderbookExtension()
+ decodedExtension, err := FromLimitOrderExtension(limitOrderExtensionPure)
+ require.NoError(t, err)
+
+ assert.NotNil(t, ext)
+ assert.Equal(t, tc.expectedExtension.SettlementContract, decodedExtension.SettlementContract)
+ assert.Equal(t, tc.expectedExtension.AuctionDetails, decodedExtension.AuctionDetails)
+ assert.Equal(t, tc.expectedExtension.PostInteractionData, decodedExtension.PostInteractionData)
+ //assert.Equal(t, tc.expectedExtension.Asset, decodedExtension.Asset)
+ //assert.Equal(t, tc.expectedExtension.Permit, decodedExtension.Permit)
+
+ assert.Equal(t, tc.expectedExtension.MakerAssetSuffix, decodedExtension.MakerAssetSuffix)
+ assert.Equal(t, tc.expectedExtension.TakerAssetSuffix, decodedExtension.TakerAssetSuffix)
+ assert.Equal(t, tc.expectedExtension.MakingAmountData, decodedExtension.MakingAmountData)
+ assert.Equal(t, tc.expectedExtension.TakingAmountData, decodedExtension.TakingAmountData)
+ assert.Equal(t, tc.expectedExtension.Predicate, decodedExtension.Predicate)
+ assert.Equal(t, tc.expectedExtension.MakerPermit, decodedExtension.MakerPermit)
+ assert.Equal(t, tc.expectedExtension.PreInteraction, decodedExtension.PreInteraction)
+ assert.Equal(t, tc.expectedExtension.PostInteraction, decodedExtension.PostInteraction)
})
}
}
+
+func extensionsEqual(a, b *Extension) bool {
+ return strings.TrimPrefix(a.MakerAssetSuffix, "0x") == strings.TrimPrefix(b.MakerAssetSuffix, "0x") &&
+ strings.TrimPrefix(a.TakerAssetSuffix, "0x") == strings.TrimPrefix(b.TakerAssetSuffix, "0x") &&
+ strings.TrimPrefix(a.MakingAmountData, "0x") == strings.TrimPrefix(b.MakingAmountData, "0x") &&
+ strings.TrimPrefix(a.TakingAmountData, "0x") == strings.TrimPrefix(b.TakingAmountData, "0x") &&
+ strings.TrimPrefix(a.Predicate, "0x") == strings.TrimPrefix(b.Predicate, "0x") &&
+ strings.TrimPrefix(a.MakerPermit, "0x") == strings.TrimPrefix(b.MakerPermit, "0x") &&
+ strings.TrimPrefix(a.PreInteraction, "0x") == strings.TrimPrefix(b.PreInteraction, "0x") &&
+ strings.TrimPrefix(a.PostInteraction, "0x") == strings.TrimPrefix(b.PostInteraction, "0x")
+ // strings.TrimPrefix(a.CustomData, "0x") == strings.TrimPrefix(b.CustomData, "0x")
+}
+
+// hexToBytes converts a hexadecimal string to a byte slice.
+func hexToBytes(s string) ([]byte, error) {
+ return hex.DecodeString(s)
+}
+
+// contains checks if the substring is present in the string.
+func contains(s, substr string) bool {
+ return bytes.Contains([]byte(s), []byte(substr))
+}
diff --git a/sdk-clients/fusion/fusion_types_extended.go b/sdk-clients/fusion/fusion_types_extended.go
index 9d04669a..2774cad3 100644
--- a/sdk-clients/fusion/fusion_types_extended.go
+++ b/sdk-clients/fusion/fusion_types_extended.go
@@ -108,12 +108,12 @@ type GasCostConfigClassFixed struct {
}
type Preset struct {
- AuctionDuration *big.Int `json:"auctionDuration"`
- StartAuctionIn *big.Int `json:"startAuctionIn"`
+ AuctionDuration float32 `json:"auctionDuration"`
+ StartAuctionIn float32 `json:"startAuctionIn"`
BankFee *big.Int `json:"bankFee"`
- InitialRateBump *big.Int `json:"initialRateBump"`
- AuctionStartAmount *big.Int `json:"auctionStartAmount"`
- AuctionEndAmount *big.Int `json:"auctionEndAmount"`
+ InitialRateBump float32 `json:"initialRateBump"`
+ AuctionStartAmount string `json:"auctionStartAmount"`
+ AuctionEndAmount string `json:"auctionEndAmount"`
TokenFee *big.Int `json:"tokenFee"`
Points []AuctionPointClass `json:"points"`
GasCostInfo GasCostConfigClass `json:"gasCostInfo"`
diff --git a/sdk-clients/fusion/order.go b/sdk-clients/fusion/order.go
index 19110f55..70f06066 100644
--- a/sdk-clients/fusion/order.go
+++ b/sdk-clients/fusion/order.go
@@ -8,6 +8,7 @@ import (
"time"
"github.com/1inch/1inch-sdk-go/common"
+ "github.com/1inch/1inch-sdk-go/internal/bigint"
geth_common "github.com/ethereum/go-ethereum/common"
random_number_generation "github.com/1inch/1inch-sdk-go/internal/random-number-generation"
@@ -38,7 +39,7 @@ func CreateFusionOrderData(quote GetQuoteOutputFixed, orderParams OrderParams, w
}
//TODO this should be parsed as a big.int after the generated struct types are fixed
- bankFee, err := BigIntFromString(preset.BankFee)
+ bankFee, err := bigint.FromString(preset.BankFee)
if err != nil {
return nil, nil, fmt.Errorf("error parsing bank fee: %v", err)
}
@@ -106,25 +107,25 @@ func CreateFusionOrderData(quote GetQuoteOutputFixed, orderParams OrderParams, w
return nil, nil, fmt.Errorf("error creating post interaction data: %v", err)
}
- extension, err := CreateExtension(CreateExtensionParams{
- settlementAddress: quote.SettlementAddress,
- postInteractionData: postInteractionData,
- orderInfo: orderInfo,
- details: details,
- extraParams: extraParams,
+ extension, err := NewExtension(ExtensionParams{
+ SettlementContract: quote.SettlementAddress,
+ AuctionDetails: auctionDetails,
+ PostInteractionData: postInteractionData,
+ Asset: orderInfo.MakerAsset,
+ Permit: extraParams.Permit,
})
if err != nil {
return nil, nil, fmt.Errorf("error creating extension: %v", err)
}
fusionOrder, err := CreateOrder(CreateOrderDataParams{
- settlementAddress: quote.SettlementAddress,
- postInteractionData: postInteractionData,
- extension: extension,
+ SettlementAddress: quote.SettlementAddress,
+ PostInteractionData: postInteractionData,
+ Extension: extension,
orderInfo: orderInfo,
- details: details,
- extraParams: extraParams,
- makerTraits: makerTraits,
+ Details: details,
+ ExtraParams: extraParams,
+ MakerTraits: makerTraits,
})
if err != nil {
return nil, nil, fmt.Errorf("error creating fusion order: %v", err)
@@ -152,19 +153,11 @@ func CreateFusionOrderData(quote GetQuoteOutputFixed, orderParams OrderParams, w
}, limitOrder, nil
}
-func BigIntFromString(s string) (*big.Int, error) {
- bigInt, ok := new(big.Int).SetString(s, 10) // base 10 for decimal
- if !ok {
- return nil, fmt.Errorf("failed to convert string (%v) to big.Int", s)
- }
- return bigInt, nil
-}
-
func getPreset(presets QuotePresetsClassFixed, presetType GetQuoteOutputRecommendedPreset) (*PresetClassFixed, error) {
switch presetType {
case Custom:
if presets.Custom == nil {
- return nil, errors.New("custom preset is not available")
+ return nil, errors.New("custom preset is not available") // TODO support custom presets
}
return presets.Custom, nil
case Fast:
@@ -232,38 +225,6 @@ func CreateSettlementPostInteractionData(details Details, orderInfo FusionOrderV
})
}
-type CreateExtensionParams struct {
- settlementAddress string
- postInteractionData *SettlementPostInteractionData
- orderInfo FusionOrderV4
- details Details
- extraParams ExtraParams
-}
-
-func CreateExtension(params CreateExtensionParams) (*Extension, error) {
-
- var permitInteraction *Interaction
- if params.extraParams.Permit != "" {
- permitInteraction = &Interaction{
- Target: geth_common.HexToAddress(params.orderInfo.MakerAsset),
- Data: params.extraParams.Permit,
- }
- }
-
- settlementAddressContract := geth_common.HexToAddress(params.settlementAddress)
- makingAndTakingAmountData := settlementAddressContract.String() + trim0x(params.details.Auction.Encode())
- extensionParams := ExtensionParams{
- MakingAmountData: makingAndTakingAmountData,
- TakingAmountData: makingAndTakingAmountData,
- PostInteraction: NewInteraction(settlementAddressContract, params.postInteractionData.Encode()).Encode(),
- }
- if permitInteraction != nil {
- extensionParams.MakerPermit = permitInteraction.Target.String() + trim0x(permitInteraction.Data)
- }
-
- return NewExtension(extensionParams)
-}
-
func CreateMakerTraits(details Details, extraParams ExtraParams) (*orderbook.MakerTraits, error) {
deadline := details.Auction.StartTime + details.Auction.Duration + extraParams.OrderExpirationDelay
makerTraitParms := orderbook.MakerTraitsParams{
@@ -289,30 +250,30 @@ func CreateMakerTraits(details Details, extraParams ExtraParams) (*orderbook.Mak
}
type CreateOrderDataParams struct {
- settlementAddress string
- postInteractionData *SettlementPostInteractionData
- extension *Extension
+ SettlementAddress string
+ PostInteractionData *SettlementPostInteractionData
+ Extension *Extension
orderInfo FusionOrderV4
- details Details
- extraParams ExtraParams
- makerTraits *orderbook.MakerTraits
+ Details Details
+ ExtraParams ExtraParams
+ MakerTraits *orderbook.MakerTraits
}
func CreateOrder(params CreateOrderDataParams) (*Order, error) {
var receiver geth_common.Address
- if params.postInteractionData.IntegratorFee.Ratio != nil && params.postInteractionData.IntegratorFee.Ratio.Cmp(big.NewInt(0)) != 0 {
- receiver = geth_common.HexToAddress(params.settlementAddress)
+ if params.PostInteractionData.IntegratorFee.Ratio != nil && params.PostInteractionData.IntegratorFee.Ratio.Cmp(big.NewInt(0)) != 0 {
+ receiver = geth_common.HexToAddress(params.SettlementAddress)
} else {
receiver = geth_common.HexToAddress(params.orderInfo.Receiver)
}
- salt, err := params.extension.GenerateSalt()
+ salt, err := params.Extension.GenerateSalt()
if err != nil {
return nil, fmt.Errorf("error generating salt: %v", err)
}
return &Order{
- FusionExtension: params.extension,
+ FusionExtension: params.Extension,
Inner: orderbook.OrderData{
MakerAsset: params.orderInfo.MakerAsset,
TakerAsset: params.orderInfo.TakerAsset,
@@ -321,22 +282,22 @@ func CreateOrder(params CreateOrderDataParams) (*Order, error) {
Salt: fmt.Sprintf("%x", salt),
Maker: params.orderInfo.Maker,
Receiver: receiver.Hex(),
- MakerTraits: params.makerTraits.Encode(),
- Extension: fmt.Sprintf("%x", params.extension.keccak256()),
+ MakerTraits: params.MakerTraits.Encode(),
+ Extension: fmt.Sprintf("%x", params.Extension.Keccak256()),
},
- SettlementExtension: geth_common.HexToAddress(params.settlementAddress),
+ SettlementExtension: geth_common.HexToAddress(params.SettlementAddress),
OrderInfo: params.orderInfo,
- AuctionDetails: params.details.Auction,
- PostInteractionData: params.postInteractionData,
+ AuctionDetails: params.Details.Auction,
+ PostInteractionData: params.PostInteractionData,
Extra: ExtraData{
- UnwrapWETH: params.extraParams.unwrapWeth,
- Nonce: params.extraParams.Nonce,
- Permit: params.extraParams.Permit,
- AllowPartialFills: params.extraParams.AllowPartialFills,
- AllowMultipleFills: params.extraParams.AllowMultipleFills,
- OrderExpirationDelay: params.extraParams.OrderExpirationDelay,
- EnablePermit2: params.extraParams.EnablePermit2,
- Source: params.extraParams.Source,
+ UnwrapWETH: params.ExtraParams.unwrapWeth,
+ Nonce: params.ExtraParams.Nonce,
+ Permit: params.ExtraParams.Permit,
+ AllowPartialFills: params.ExtraParams.AllowPartialFills,
+ AllowMultipleFills: params.ExtraParams.AllowMultipleFills,
+ OrderExpirationDelay: params.ExtraParams.OrderExpirationDelay,
+ EnablePermit2: params.ExtraParams.EnablePermit2,
+ Source: params.ExtraParams.Source,
},
}, nil
}
diff --git a/sdk-clients/fusion/order_test.go b/sdk-clients/fusion/order_test.go
index 50100e36..62144170 100644
--- a/sdk-clients/fusion/order_test.go
+++ b/sdk-clients/fusion/order_test.go
@@ -1,133 +1,18 @@
package fusion
import (
- "encoding/json"
"errors"
"fmt"
"math/big"
"testing"
- web3_provider "github.com/1inch/1inch-sdk-go/internal/web3-provider"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- random_number_generation "github.com/1inch/1inch-sdk-go/internal/random-number-generation"
"github.com/1inch/1inch-sdk-go/sdk-clients/orderbook"
)
-var (
- publicAddress = "0x737baD27cF1374AE2af29C49Bb6D6007D5CD67EE"
- privateKey = "0f3edf983ac636a65a842ce7c78d9aa706d3b113b37e265ba6b02d758e70b3d0"
-)
-
-const (
- usdc = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
- wmatic = "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270"
- amount = "1000000000000000000"
- chainId = 137
-)
-
-func TestCreateFusionOrderData(t *testing.T) {
- tests := []struct {
- name string
- chainId uint64
- privateKey string
- orderParams OrderParams
- additionalParams AdditionalParams
- auctionStartTime uint32
- nonce *big.Int
- resolverStartTime int64
- baseSaltValue string
- serializedQuoteData string
- serializedPreparedOrderData string
- serializedLimitOrderData string
- data string
- }{
- {
- name: "Successful order creation",
- chainId: chainId,
- privateKey: privateKey,
- orderParams: OrderParams{
- WalletAddress: publicAddress,
- FromTokenAddress: wmatic,
- ToTokenAddress: usdc,
- Amount: amount,
- Receiver: "0x0000000000000000000000000000000000000000",
- Preset: "fast",
- },
- auctionStartTime: 1718671900,
- nonce: big.NewInt(887174712009),
- resolverStartTime: 1718671883,
- baseSaltValue: "35020243109857195061155306569",
- serializedQuoteData: `{"feeToken":"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359","fromTokenAmount":"1000000000000000000","presets":{"fast":{"allowMultipleFills":false,"allowPartialFills":false,"auctionDuration":180,"auctionEndAmount":"538946","auctionStartAmount":"557310","bankFee":"0","estP":100,"exclusiveResolver":null,"gasCost":{"gasBumpEstimate":0,"gasPriceEstimate":"0"},"initialRateBump":340757,"points":[],"startAuctionIn":17,"tokenFee":"18366"},"medium":{"allowMultipleFills":true,"allowPartialFills":true,"auctionDuration":360,"auctionEndAmount":"538946","auctionStartAmount":"576251","bankFee":"0","estP":100,"exclusiveResolver":null,"gasCost":{"gasBumpEstimate":0,"gasPriceEstimate":"0"},"initialRateBump":692202,"points":[{"coefficient":681533,"delay":6},{"coefficient":340757,"delay":6}],"startAuctionIn":17,"tokenFee":"18366"},"slow":{"allowMultipleFills":true,"allowPartialFills":true,"auctionDuration":600,"auctionEndAmount":"538946","auctionStartAmount":"581432","bankFee":"0","estP":100,"exclusiveResolver":null,"gasCost":{"gasBumpEstimate":0,"gasPriceEstimate":"0"},"initialRateBump":788335,"points":[{"coefficient":681533,"delay":81},{"coefficient":340757,"delay":6}],"startAuctionIn":17,"tokenFee":"18366"}},"prices":{"usd":{"fromToken":"0.57493897","toToken":"0.9995015368854032"}},"quoteId":"55c3f478-b176-448c-b968-656c19b9c04a","recommended_preset":"fast","settlementAddress":"0xfb2809a5314473e1165f6b58018e20ed8f07b840","suggested":true,"toTokenAmount":"575677","volume":{"usd":{"fromToken":"0.57493897","toToken":"0.57539"}},"whitelist":["0x46fd018b32a9315ef5b4c0866635457d36ab318d","0xc1b19a08c2798c6930b8f3a44b7b0d08f4e198b8","0x0000000000000000000000000000000000000000","0xad3b67bca8935cb510c8d18bd45f0b94f54a968f","0x0000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000","0x62f861201db5fdc04c48c976bf098c4dba0a061d","0x0000000000000000000000000000000000000000"]}`,
- serializedPreparedOrderData: `{"order":{"FusionExtension":{"MakerAssetSuffix":"","TakerAssetSuffix":"","MakingAmountData":"0xfb2809A5314473E1165f6B58018E20ed8F07B840000000000000006670da1c0000b4053315","TakingAmountData":"0xfb2809A5314473E1165f6B58018E20ed8F07B840000000000000006670da1c0000b4053315","Predicate":"","MakerPermit":"","PreInteraction":"","PostInteraction":"0xfb2809A5314473E1165f6B58018E20ed8F07B8406670da0bc0866635457d36ab318d0000f3a44b7b0d08f4e198b80000000000000000000000000000d18bd45f0b94f54a968f0000000000000000000000000000000000000000000000000000c976bf098c4dba0a061d000000000000000000000000000040","CustomData":""},"Inner":{"makerAsset":"0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270","takerAsset":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359","makingAmount":"1000000000000000000","takingAmount":"538946","salt":"712810ef08aca692b6d59c49fc131590b1edc52d382c2a9684cae76e49ca45bf","maker":"0x737baD27cF1374AE2af29C49Bb6D6007D5CD67EE","allowedSender":"","receiver":"0x0000000000000000000000000000000000000000","makerTraits":"0x8a0000000000000000000000ce8fbbcac9006670dad000000000000000000000","extension":"357969f7ed9a797c95a9da11fc131590b1edc52d382c2a9684cae76e49ca45bf"},"SettlementExtension":"0xfb2809a5314473e1165f6b58018e20ed8f07b840","OrderInfo":{"maker":"0x737baD27cF1374AE2af29C49Bb6D6007D5CD67EE","makerAsset":"0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270","makerTraits":"","makingAmount":"1000000000000000000","receiver":"0x0000000000000000000000000000000000000000","salt":"","takerAsset":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359","takingAmount":"538946"},"AuctionDetails":{"startTime":1718671900,"duration":180,"initialRateBump":340757,"points":[],"gasCost":{"gasBumpEstimate":0,"gasPriceEstimate":0}},"PostInteractionData":{"Whitelist":[{"AddressHalf":"c0866635457d36ab318d","Delay":0},{"AddressHalf":"f3a44b7b0d08f4e198b8","Delay":0},{"AddressHalf":"00000000000000000000","Delay":0},{"AddressHalf":"d18bd45f0b94f54a968f","Delay":0},{"AddressHalf":"00000000000000000000","Delay":0},{"AddressHalf":"00000000000000000000","Delay":0},{"AddressHalf":"c976bf098c4dba0a061d","Delay":0},{"AddressHalf":"00000000000000000000","Delay":0}],"IntegratorFee":{"Ratio":0,"Receiver":"0x0000000000000000000000000000000000000000"},"BankFee":0,"ResolvingStartTime":1718671883,"CustomReceiver":"0x0000000000000000000000000000000000000000"},"Extra":{"UnwrapWETH":false,"Nonce":887174712009,"Permit":"","AllowPartialFills":false,"AllowMultipleFills":false,"OrderExpirationDelay":0,"EnablePermit2":false,"Source":""}},"hash":"0xe635531055466f92fdf64222d3e6d5ff18cda08c1a87b28c6853095d50699574","quoteId":"55c3f478-b176-448c-b968-656c19b9c04a"}`,
- serializedLimitOrderData: `{"orderHash":"0xe635531055466f92fdf64222d3e6d5ff18cda08c1a87b28c6853095d50699574","signature":"0xa1cb6463f2e9126fe24e5b8f1f0bb3762ed588fc0e8c7186cfa81f19806127cd21a37b8c9ee812429a2449f926736d32b1e2108f7aae8f5e96802a2d35e242781b","data":{"makerAsset":"0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270","takerAsset":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359","makingAmount":"1000000000000000000","takingAmount":"538946","salt":"0x9a042bfb67cf14b0a1a98c4ae5d6295e2c08820","maker":"0x737baD27cF1374AE2af29C49Bb6D6007D5CD67EE","allowedSender":"0x0000000000000000000000000000000000000000","receiver":"0x0000000000000000000000000000000000000000","makerTraits":"0x8a0000000000000000000000ce8fbbcac9006670dad000000000000000000000","extension":"0x000000c30000004a0000004a0000004a0000004a000000250000000000000000fb2809A5314473E1165f6B58018E20ed8F07B840000000000000006670da1c0000b4053315fb2809A5314473E1165f6B58018E20ed8F07B840000000000000006670da1c0000b4053315fb2809A5314473E1165f6B58018E20ed8F07B8406670da0bc0866635457d36ab318d0000f3a44b7b0d08f4e198b80000000000000000000000000000d18bd45f0b94f54a968f0000000000000000000000000000000000000000000000000000c976bf098c4dba0a061d000000000000000000000000000040"}}`,
- },
- }
-
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- var quote GetQuoteOutputFixed
- err := json.Unmarshal([]byte(tc.serializedQuoteData), "e)
- require.NoError(t, err)
-
- var expectedOrdrebookOrder orderbook.Order
- err = json.Unmarshal([]byte(tc.serializedLimitOrderData), &expectedOrdrebookOrder)
- require.NoError(t, err)
-
- zero := big.NewInt(0)
- var expectedPreparedOrder PreparedOrder
- err = json.Unmarshal([]byte(tc.serializedPreparedOrderData), &expectedPreparedOrder)
- require.NoError(t, err)
- for _, whitelist := range expectedPreparedOrder.Order.PostInteractionData.Whitelist {
- if whitelist.Delay != nil && whitelist.Delay.Cmp(zero) == 0 {
- whitelist.Delay = zero
- }
- }
-
- baseSaltValue, err := BigIntFromString(tc.baseSaltValue)
- require.NoError(t, err)
-
- originalRandBigIntFunc := random_number_generation.BigIntMaxFunc
- first := true
- random_number_generation.BigIntMaxFunc = func(b *big.Int) (*big.Int, error) {
- if first {
- first = false
- return tc.nonce, nil
- } else {
- return baseSaltValue, nil
- }
- }
-
- // Monkey patch custom start time value
- originalTimeNowFunc := timeNow
- timeNow = func() int64 {
- return tc.resolverStartTime
- }
-
- // Monkey patch custom start time value
- originalCalcAuctionStartTimeFunc := CalcAuctionStartTimeFunc
- CalcAuctionStartTimeFunc = func(u uint32, u2 uint32) uint32 {
- return tc.auctionStartTime
- }
-
- wallet, err := web3_provider.DefaultWalletOnlyProvider(privateKey, tc.chainId)
- require.NoError(t, err)
-
- preparedOrder, orderbookOrder, err := CreateFusionOrderData(quote, tc.orderParams, wallet, tc.chainId)
- require.NoError(t, err)
- timeNow = originalTimeNowFunc
- CalcAuctionStartTimeFunc = originalCalcAuctionStartTimeFunc
- random_number_generation.BigIntMaxFunc = originalRandBigIntFunc
-
- assert.Equal(t, expectedOrdrebookOrder, *orderbookOrder)
- assert.Equal(t, expectedPreparedOrder, *preparedOrder)
-
- })
- }
-}
-
func TestGetPreset(t *testing.T) {
customPreset := &PresetClassFixed{
AllowMultipleFills: true,
@@ -694,344 +579,3 @@ func TestCreateSettlementPostInteractionData(t *testing.T) {
})
}
}
-
-func TestCreateExtension(t *testing.T) {
- tests := []struct {
- name string
- params CreateExtensionParams
- expected *Extension
- expectErr bool
- }{
- {
- name: "Valid Parameters with Permit",
- params: CreateExtensionParams{
- settlementAddress: "0x0000000000000000000000000000000000000001",
- postInteractionData: &SettlementPostInteractionData{
- Whitelist: []WhitelistItem{
- {
- AddressHalf: "abcdef",
- Delay: big.NewInt(1000),
- },
- },
- IntegratorFee: &IntegratorFee{
- Ratio: big.NewInt(100),
- Receiver: common.HexToAddress("0x0000000000000000000000000000000000000002"),
- },
- BankFee: big.NewInt(200),
- ResolvingStartTime: big.NewInt(1622548800),
- CustomReceiver: common.HexToAddress("0x0000000000000000000000000000000000000003"),
- },
- orderInfo: FusionOrderV4{
- MakerAsset: "0x0000000000000000000000000000000000000004",
- Receiver: "0x0000000000000000000000000000000000000005",
- },
- details: Details{
- Auction: &AuctionDetails{
- StartTime: 1000,
- Duration: 2000,
- },
- },
- extraParams: ExtraParams{
- Permit: "0xabcdef",
- },
- },
- expected: &Extension{
- MakingAmountData: "0x0000000000000000000000000000000000000001" + "00000000000000000003e80007d0000000",
- TakingAmountData: "0x0000000000000000000000000000000000000001" + "00000000000000000003e80007d0000000",
- PostInteraction: "0x0000000000000000000000000000000000000001000000c800640000000000000000000000000000000000000002000000000000000000000000000000000000000360b62140abcdef03e80f",
- MakerPermit: "0x0000000000000000000000000000000000000004abcdef",
- },
- expectErr: false,
- },
- {
- name: "Valid Parameters without Permit",
- params: CreateExtensionParams{
- settlementAddress: "0x0000000000000000000000000000000000000001",
- postInteractionData: &SettlementPostInteractionData{
- Whitelist: []WhitelistItem{
- {
- AddressHalf: "abcdef",
- Delay: big.NewInt(1000),
- },
- },
- IntegratorFee: &IntegratorFee{
- Ratio: big.NewInt(100),
- Receiver: common.HexToAddress("0x0000000000000000000000000000000000000002"),
- },
- BankFee: big.NewInt(200),
- ResolvingStartTime: big.NewInt(1622548800),
- CustomReceiver: common.HexToAddress("0x0000000000000000000000000000000000000003"),
- },
- orderInfo: FusionOrderV4{
- MakerAsset: "0x0000000000000000000000000000000000000004",
- Receiver: "0x0000000000000000000000000000000000000005",
- },
- details: Details{
- Auction: &AuctionDetails{
- StartTime: 1000,
- Duration: 2000,
- },
- },
- extraParams: ExtraParams{},
- },
- expected: &Extension{
- MakingAmountData: "0x0000000000000000000000000000000000000001" + "00000000000000000003e80007d0000000",
- TakingAmountData: "0x0000000000000000000000000000000000000001" + "00000000000000000003e80007d0000000",
- PostInteraction: "0x0000000000000000000000000000000000000001000000c800640000000000000000000000000000000000000002000000000000000000000000000000000000000360b62140abcdef03e80f",
- },
- expectErr: false,
- },
- }
-
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- result, err := CreateExtension(tc.params)
- if tc.expectErr {
- require.Error(t, err)
- } else {
- require.NoError(t, err)
- assert.Equal(t, tc.expected, result)
- }
- })
- }
-}
-
-func TestCreateOrder(t *testing.T) {
- tests := []struct {
- name string
- staticSalt string
- params CreateOrderDataParams
- expected *Order
- expectErr bool
- }{
- {
- name: "Valid Order with Integrator Fee",
- staticSalt: "180431658011416401710119735245975317914670388782711199",
- params: CreateOrderDataParams{
- settlementAddress: "0x0000000000000000000000000000000000000001",
- postInteractionData: &SettlementPostInteractionData{
- IntegratorFee: &IntegratorFee{
- Ratio: big.NewInt(100),
- Receiver: common.HexToAddress("0x0000000000000000000000000000000000000002"),
- },
- BankFee: big.NewInt(200),
- },
- extension: &Extension{
- MakerAssetSuffix: "suffix1",
- TakerAssetSuffix: "suffix2",
- MakingAmountData: "data1",
- TakingAmountData: "data2",
- Predicate: "predicate",
- MakerPermit: "permit",
- PreInteraction: "pre",
- PostInteraction: "post",
- CustomData: "custom",
- },
- orderInfo: FusionOrderV4{
- Maker: "0x0000000000000000000000000000000000000003",
- MakerAsset: "0x0000000000000000000000000000000000000004",
- TakerAsset: "0x0000000000000000000000000000000000000005",
- MakingAmount: "1000",
- TakingAmount: "2000",
- Receiver: "0x0000000000000000000000000000000000000006",
- },
- details: Details{
- Auction: &AuctionDetails{
- StartTime: 1000,
- Duration: 2000,
- },
- },
- extraParams: ExtraParams{
- Nonce: big.NewInt(1),
- },
- makerTraits: &orderbook.MakerTraits{
- AllowedSender: "0x0000000000000000000000000000000000000007",
- Expiry: 5000,
- Nonce: 1,
- AllowPartialFills: true,
- AllowMultipleFills: true,
- },
- },
- expected: &Order{
- FusionExtension: &Extension{
- MakerAssetSuffix: "suffix1",
- TakerAssetSuffix: "suffix2",
- MakingAmountData: "data1",
- TakingAmountData: "data2",
- Predicate: "predicate",
- MakerPermit: "permit",
- PreInteraction: "pre",
- PostInteraction: "post",
- CustomData: "custom",
- },
- Inner: orderbook.OrderData{
- Maker: "0x0000000000000000000000000000000000000003",
- MakerAsset: "0x0000000000000000000000000000000000000004",
- TakerAsset: "0x0000000000000000000000000000000000000005",
- MakingAmount: "1000",
- TakingAmount: "2000",
- Salt: "1e24059a92eed490b75f51b98fbf2e143c8e9712a419f59a92eed490b75f51b98fbf2e143c8e9712a419f",
- MakerTraits: "0x4000000000000000000000000000000001000000138800000000000000000007",
- Receiver: "0x0000000000000000000000000000000000000001", // Address of settlementAddress because Integrator Fee is set
- Extension: "343845d3ef4b5505456e95d059a92eed490b75f51b98fbf2e143c8e9712a419f",
- },
- SettlementExtension: common.HexToAddress("0x0000000000000000000000000000000000000001"),
- OrderInfo: FusionOrderV4{
- Maker: "0x0000000000000000000000000000000000000003",
- MakerAsset: "0x0000000000000000000000000000000000000004",
- TakerAsset: "0x0000000000000000000000000000000000000005",
- MakingAmount: "1000",
- TakingAmount: "2000",
- Receiver: "0x0000000000000000000000000000000000000006",
- },
- AuctionDetails: &AuctionDetails{
- StartTime: 1000,
- Duration: 2000,
- },
- PostInteractionData: &SettlementPostInteractionData{
- IntegratorFee: &IntegratorFee{
- Ratio: big.NewInt(100),
- Receiver: common.HexToAddress("0x0000000000000000000000000000000000000002"),
- },
- BankFee: big.NewInt(200),
- },
- Extra: ExtraData{
- UnwrapWETH: false,
- Nonce: big.NewInt(1),
- Permit: "",
- AllowPartialFills: false,
- AllowMultipleFills: false,
- OrderExpirationDelay: 0,
- EnablePermit2: false,
- Source: "",
- },
- },
- expectErr: false,
- },
- {
- name: "Valid Order without Integrator Fee",
- staticSalt: "180431658011416401710119735245975317914670388782711199",
- params: CreateOrderDataParams{
- settlementAddress: "0x0000000000000000000000000000000000000001",
- postInteractionData: &SettlementPostInteractionData{
- IntegratorFee: &IntegratorFee{
- Ratio: big.NewInt(0),
- Receiver: common.HexToAddress("0x0000000000000000000000000000000000000002"),
- },
- BankFee: big.NewInt(200),
- },
- extension: &Extension{
- MakerAssetSuffix: "suffix1",
- TakerAssetSuffix: "suffix2",
- MakingAmountData: "data1",
- TakingAmountData: "data2",
- Predicate: "predicate",
- MakerPermit: "permit",
- PreInteraction: "pre",
- PostInteraction: "post",
- CustomData: "custom",
- },
- orderInfo: FusionOrderV4{
- Maker: "0x0000000000000000000000000000000000000003",
- MakerAsset: "0x0000000000000000000000000000000000000004",
- TakerAsset: "0x0000000000000000000000000000000000000005",
- MakingAmount: "1000",
- TakingAmount: "2000",
- Receiver: "0x0000000000000000000000000000000000000006",
- },
- details: Details{
- Auction: &AuctionDetails{
- StartTime: 1000,
- Duration: 2000,
- },
- },
- extraParams: ExtraParams{
- Nonce: big.NewInt(1),
- },
- makerTraits: &orderbook.MakerTraits{
- AllowedSender: "0x0000000000000000000000000000000000000007",
- Expiry: 5000,
- Nonce: 1,
- AllowPartialFills: true,
- AllowMultipleFills: true,
- },
- },
- expected: &Order{
- FusionExtension: &Extension{
- MakerAssetSuffix: "suffix1",
- TakerAssetSuffix: "suffix2",
- MakingAmountData: "data1",
- TakingAmountData: "data2",
- Predicate: "predicate",
- MakerPermit: "permit",
- PreInteraction: "pre",
- PostInteraction: "post",
- CustomData: "custom",
- },
- Inner: orderbook.OrderData{
- Maker: "0x0000000000000000000000000000000000000003",
- MakerAsset: "0x0000000000000000000000000000000000000004",
- TakerAsset: "0x0000000000000000000000000000000000000005",
- MakingAmount: "1000",
- TakingAmount: "2000",
- Salt: "1e24059a92eed490b75f51b98fbf2e143c8e9712a419f59a92eed490b75f51b98fbf2e143c8e9712a419f",
- MakerTraits: "0x4000000000000000000000000000000001000000138800000000000000000007",
- Receiver: "0x0000000000000000000000000000000000000006", // Address of orderInfo.Receiver because Integrator Fee is not set
- Extension: "343845d3ef4b5505456e95d059a92eed490b75f51b98fbf2e143c8e9712a419f",
- },
- SettlementExtension: common.HexToAddress("0x0000000000000000000000000000000000000001"),
- OrderInfo: FusionOrderV4{
- Maker: "0x0000000000000000000000000000000000000003",
- MakerAsset: "0x0000000000000000000000000000000000000004",
- TakerAsset: "0x0000000000000000000000000000000000000005",
- MakingAmount: "1000",
- TakingAmount: "2000",
- Receiver: "0x0000000000000000000000000000000000000006",
- },
- AuctionDetails: &AuctionDetails{
- StartTime: 1000,
- Duration: 2000,
- },
- PostInteractionData: &SettlementPostInteractionData{
- IntegratorFee: &IntegratorFee{
- Ratio: big.NewInt(0),
- Receiver: common.HexToAddress("0x0000000000000000000000000000000000000002"),
- },
- BankFee: big.NewInt(200),
- },
- Extra: ExtraData{
- UnwrapWETH: false,
- Nonce: big.NewInt(1),
- Permit: "",
- AllowPartialFills: false,
- AllowMultipleFills: false,
- OrderExpirationDelay: 0,
- EnablePermit2: false,
- Source: "",
- },
- },
- expectErr: false,
- },
- }
-
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
-
- originalRandBigIntFunc := random_number_generation.BigIntMaxFunc
-
- staticSalt, err := BigIntFromString(tc.staticSalt)
- require.NoError(t, err)
- random_number_generation.BigIntMaxFunc = func(b *big.Int) (*big.Int, error) {
- return staticSalt, nil
- }
- result, err := CreateOrder(tc.params)
- random_number_generation.BigIntMaxFunc = originalRandBigIntFunc
- if tc.expectErr {
- require.Error(t, err)
- } else {
- require.NoError(t, err)
- assert.Equal(t, tc.expected, result)
- }
- })
- }
-}
diff --git a/sdk-clients/fusionplus/api.go b/sdk-clients/fusionplus/api.go
new file mode 100644
index 00000000..f5a507fa
--- /dev/null
+++ b/sdk-clients/fusionplus/api.go
@@ -0,0 +1,208 @@
+package fusionplus
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ "github.com/1inch/1inch-sdk-go/common"
+)
+
+func (api *api) GetOrderByOrderHash(ctx context.Context, params GetOrderByOrderHashParams) (*GetOrderFillsByHashOutputFixed, error) {
+ u := fmt.Sprintf("/fusion-plus/orders/v1.0/order/status/%s", params.Hash)
+
+ payload := common.RequestPayload{
+ Method: "GET",
+ Params: params,
+ U: u,
+ Body: nil,
+ }
+
+ var response GetOrderFillsByHashOutputFixed
+ err := api.httpExecutor.ExecuteRequest(ctx, payload, &response)
+ if err != nil {
+ return nil, err
+ }
+
+ return &response, nil
+}
+
+func (api *api) GetReadyToAcceptFills(ctx context.Context, params GetOrderByOrderHashParams) (*ReadyToAcceptSecretFills, error) {
+ u := fmt.Sprintf("/fusion-plus/orders/v1.0/order/ready-to-accept-secret-fills/%s", params.Hash)
+
+ payload := common.RequestPayload{
+ Method: "GET",
+ Params: params,
+ U: u,
+ Body: nil,
+ }
+
+ var response ReadyToAcceptSecretFills
+ err := api.httpExecutor.ExecuteRequest(ctx, payload, &response)
+ if err != nil {
+ return nil, err
+ }
+
+ return &response, nil
+}
+
+func (api *api) SubmitSecret(ctx context.Context, params SecretInput) error {
+ u := "/fusion-plus/relayer/v1.0/submit/secret"
+
+ body, err := json.Marshal(params)
+ if err != nil {
+ return err
+ }
+
+ payload := common.RequestPayload{
+ Method: "POST",
+ Params: params,
+ U: u,
+ Body: body,
+ }
+
+ err = api.httpExecutor.ExecuteRequest(ctx, payload, nil)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (api *api) GetActiveOrders(ctx context.Context, params OrderApiControllerGetActiveOrdersParams) (*GetActiveOrdersOutput, error) {
+ u := fmt.Sprintf("/fusion/orders/v2.0/%d/order/active", api.chainId)
+
+ payload := common.RequestPayload{
+ Method: "GET",
+ Params: params,
+ U: u,
+ Body: nil,
+ }
+
+ var response GetActiveOrdersOutput
+ err := api.httpExecutor.ExecuteRequest(ctx, payload, &response)
+ if err != nil {
+ return nil, err
+ }
+
+ return &response, nil
+}
+
+func (api *api) GetQuote(ctx context.Context, params QuoterControllerGetQuoteParamsFixed) (*GetQuoteOutputFixed, error) {
+ u := "/fusion-plus/quoter/v1.0/quote/receive"
+
+ err := params.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ payload := common.RequestPayload{
+ Method: "GET",
+ Params: params,
+ U: u,
+ Body: nil,
+ }
+
+ var response GetQuoteOutputFixed
+ err = api.httpExecutor.ExecuteRequest(ctx, payload, &response)
+ if err != nil {
+ return nil, err
+ }
+
+ // TODO must normalize response here
+
+ return &response, nil
+}
+
+func (api *api) GetQuoteWithCustomPreset(ctx context.Context, params QuoterControllerGetQuoteWithCustomPresetsParams, presetDetails QuoterControllerGetQuoteWithCustomPresetsJSONRequestBody) (*GetQuoteOutputFixed, error) {
+ u := fmt.Sprintf("/fusion/quoter/v2.0/%d/quote/receive", api.chainId)
+
+ body, err := json.Marshal(presetDetails)
+ if err != nil {
+ return nil, err
+ }
+
+ payload := common.RequestPayload{
+ Method: "GET",
+ Params: params,
+ U: u,
+ Body: body,
+ }
+
+ var response GetQuoteOutputFixed
+ err = api.httpExecutor.ExecuteRequest(ctx, payload, &response)
+ if err != nil {
+ return nil, err
+ }
+
+ return &response, nil
+}
+
+// PlaceOrder accepts a quote and submits it as a fusion plus order
+func (api *api) PlaceOrder(ctx context.Context, quoteParams QuoterControllerGetQuoteParamsFixed, quote *GetQuoteOutputFixed, orderParams OrderParams, wallet common.Wallet) (string, error) {
+ u := "/fusion-plus/relayer/v1.0/submit"
+
+ err := orderParams.Validate()
+ if err != nil {
+ return "", err
+ }
+
+ preset, err := GetPreset(quote.Presets, orderParams.Preset)
+ if err != nil {
+ return "", fmt.Errorf("failed to get preset: %v", err)
+ }
+
+ // TODO orders will now be allowed with multiple secret hashes for now
+ if len(orderParams.SecretHashes) > 1 {
+ return "", fmt.Errorf("Multiple secret hashes are not supported at this time. Please ")
+ }
+
+ if !preset.AllowMultipleFills && len(orderParams.SecretHashes) > 1 {
+ return "", fmt.Errorf("multiple secrets are required with multiple secret hashes")
+ }
+ //else {
+ // TODO support multiple secrets
+ //}
+
+ fusionPlusOrder, err := CreateFusionPlusOrderData(quoteParams, quote, orderParams, wallet, int(quoteParams.SrcChain))
+ if err != nil {
+ return "", fmt.Errorf("failed to create order: %v", err)
+ }
+
+ signedOrder := SignedOrderInput{
+ Extension: fusionPlusOrder.LimitOrder.Data.Extension,
+ Order: OrderInput{
+ Maker: fusionPlusOrder.LimitOrder.Data.Maker,
+ MakerAsset: fusionPlusOrder.LimitOrder.Data.MakerAsset,
+ MakerTraits: fusionPlusOrder.LimitOrder.Data.MakerTraits,
+ MakingAmount: fusionPlusOrder.LimitOrder.Data.MakingAmount,
+ Receiver: fusionPlusOrder.LimitOrder.Data.Receiver,
+ Salt: fusionPlusOrder.LimitOrder.Data.Salt,
+ TakerAsset: fusionPlusOrder.LimitOrder.Data.TakerAsset,
+ TakingAmount: fusionPlusOrder.LimitOrder.Data.TakingAmount,
+ },
+ QuoteId: quote.QuoteId,
+ //SecretHashes: orderParams.SecretHashes, // TODO this only should be submitted when there are multiple secrets
+ Signature: fusionPlusOrder.LimitOrder.Signature,
+ SrcChainId: quoteParams.SrcChain,
+ }
+
+ body, err := json.Marshal(signedOrder)
+ if err != nil {
+ return "", err
+ }
+
+ payload := common.RequestPayload{
+ Method: "POST",
+ Params: nil,
+ U: u,
+ Body: body,
+ }
+
+ err = api.httpExecutor.ExecuteRequest(ctx, payload, nil)
+ if err != nil {
+ return "", err
+ }
+
+ return fusionPlusOrder.Hash, nil
+}
diff --git a/sdk-clients/fusionplus/auctiondetails.go b/sdk-clients/fusionplus/auctiondetails.go
new file mode 100644
index 00000000..e61fddec
--- /dev/null
+++ b/sdk-clients/fusionplus/auctiondetails.go
@@ -0,0 +1,147 @@
+package fusionplus
+
+import (
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "math"
+ "math/big"
+
+ "github.com/1inch/1inch-sdk-go/internal/bytesbuilder"
+ "github.com/1inch/1inch-sdk-go/internal/bytesiterator"
+)
+
+const (
+ uint24Max = (1 << 24) - 1
+ uint32Max = math.MaxUint32
+)
+
+func NewAuctionDetails(startTime, duration, initialRateBump uint32, points []AuctionPointClassFixed, gasCost GasCostConfigClassFixed) (*AuctionDetails, error) {
+
+ if gasCost.GasBumpEstimate > uint24Max || gasCost.GasPriceEstimate > uint32Max ||
+ startTime > uint32Max || duration > uint24Max || initialRateBump > uint24Max {
+ return nil, errors.New("values exceed their respective limits")
+ }
+
+ return &AuctionDetails{
+ StartTime: startTime,
+ Duration: duration,
+ InitialRateBump: initialRateBump,
+ Points: points,
+ GasCost: gasCost,
+ }, nil
+}
+
+func DecodeAuctionDetails(data string) (*AuctionDetails, error) {
+ // Decode the hex string to bytes
+ rawBytes, err := hex.DecodeString(data)
+ if err != nil {
+ return nil, errors.New("invalid hex data")
+ }
+
+ // We expect at least 3 + 4 + 4 + 3 + 3 = 17 bytes for the mandatory fields
+ if len(rawBytes) < 17 {
+ return nil, errors.New("data too short for mandatory fields")
+ }
+
+ iter := bytesiterator.New(rawBytes)
+
+ // 1) GasBumpEstimate (3 bytes)
+ gasBumpEstimate, err := iter.NextUint24()
+ if err != nil {
+ return nil, fmt.Errorf("failed reading gasBumpEstimate: %w", err)
+ }
+
+ // 2) GasPriceEstimate (4 bytes)
+ gasPriceEstimateBI, err := iter.NextUint32()
+ if err != nil {
+ return nil, fmt.Errorf("failed reading gasPriceEstimate: %w", err)
+ }
+ gasPriceEstimate := uint32(gasPriceEstimateBI.Uint64())
+
+ // 3) StartTime (4 bytes)
+ startTimeBI, err := iter.NextUint32()
+ if err != nil {
+ return nil, fmt.Errorf("failed reading startTime: %w", err)
+ }
+ startTime := uint32(startTimeBI.Uint64())
+
+ // 4) Duration (3 bytes)
+ duration, err := iter.NextUint24()
+ if err != nil {
+ return nil, fmt.Errorf("failed reading duration: %w", err)
+ }
+
+ // 5) InitialRateBump (3 bytes)
+ initialRateBump, err := iter.NextUint24()
+ if err != nil {
+ return nil, fmt.Errorf("failed reading initialRateBump: %w", err)
+ }
+
+ // Now read points (each point is 5 bytes: 3 for Coefficient, 2 for Delay)
+ var points []AuctionPointClassFixed
+ for !iter.IsEmpty() {
+ // Ensure we have at least 5 bytes left for the next point
+ if iter.BytesLeft() < 5 {
+ return nil, errors.New("insufficient bytes to read next auction point")
+ }
+
+ // Coefficient (3 bytes => uint24)
+ coeff, err := iter.NextUint24()
+ if err != nil {
+ return nil, fmt.Errorf("failed reading Coefficient in points: %w", err)
+ }
+
+ // Delay (2 bytes => *big.Int => convert to uint16)
+ delayBI, err := iter.NextUint16()
+ if err != nil {
+ return nil, fmt.Errorf("failed reading Delay in points: %w", err)
+ }
+ delay := uint16(delayBI.Uint64())
+
+ points = append(points, AuctionPointClassFixed{
+ Coefficient: coeff,
+ Delay: delay,
+ })
+ }
+
+ return NewAuctionDetails(
+ startTime,
+ duration,
+ initialRateBump,
+ points,
+ GasCostConfigClassFixed{
+ GasBumpEstimate: gasBumpEstimate,
+ GasPriceEstimate: gasPriceEstimate,
+ },
+ )
+}
+
+func (ad AuctionDetails) Encode() string {
+ // Create a new bytes builder
+ bb := bytesbuilder.New()
+
+ // GasBumpEstimate -> 3 bytes
+ bb.AddUint24(big.NewInt(int64(ad.GasCost.GasBumpEstimate)))
+
+ // GasPriceEstimate -> 4 bytes
+ bb.AddUint32(big.NewInt(int64(ad.GasCost.GasPriceEstimate)))
+
+ // StartTime -> 4 bytes
+ bb.AddUint32(big.NewInt(int64(ad.StartTime)))
+
+ // Duration -> 3 bytes
+ bb.AddUint24(big.NewInt(int64(ad.Duration)))
+
+ // InitialRateBump -> 3 bytes
+ bb.AddUint24(big.NewInt(int64(ad.InitialRateBump)))
+
+ // Encode each point: 3 bytes for Coefficient, 2 bytes for Delay
+ for _, point := range ad.Points {
+ bb.AddUint24(big.NewInt(int64(point.Coefficient)))
+ bb.AddUint16(big.NewInt(int64(point.Delay)))
+ }
+
+ // Return the final hex string
+ return bb.AsHex()
+}
diff --git a/sdk-clients/fusionplus/auctiondetails_test.go b/sdk-clients/fusionplus/auctiondetails_test.go
new file mode 100644
index 00000000..89271411
--- /dev/null
+++ b/sdk-clients/fusionplus/auctiondetails_test.go
@@ -0,0 +1,43 @@
+package fusionplus
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestAuctionDetails(t *testing.T) {
+ tests := []struct {
+ name string
+ details AuctionDetails
+ }{
+ {
+ name: "Encode/Decode AuctionDetails",
+ details: AuctionDetails{
+ Duration: 180,
+ StartTime: 1673548149,
+ InitialRateBump: 50000,
+ Points: []AuctionPointClassFixed{
+ {
+ Delay: 10,
+ Coefficient: 10000,
+ },
+ {
+ Delay: 20,
+ Coefficient: 5000,
+ },
+ },
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ encoded := tc.details.Encode()
+ decoded, err := DecodeAuctionDetails(encoded)
+ require.NoError(t, err)
+ assert.Equal(t, tc.details, *decoded)
+ })
+ }
+}
diff --git a/sdk-clients/fusionplus/client.go b/sdk-clients/fusionplus/client.go
new file mode 100644
index 00000000..343a7f37
--- /dev/null
+++ b/sdk-clients/fusionplus/client.go
@@ -0,0 +1,23 @@
+package fusionplus
+
+import (
+ "github.com/1inch/1inch-sdk-go/common"
+)
+
+type Client struct {
+ api
+ Wallet common.Wallet
+}
+
+type api struct {
+ chainId uint64
+ httpExecutor common.HttpExecutor
+}
+
+func NewClient(cfg *Configuration) (*Client, error) {
+ c := Client{
+ api: cfg.APIConfiguration.API,
+ Wallet: cfg.WalletConfiguration.Wallet,
+ }
+ return &c, nil
+}
diff --git a/sdk-clients/fusionplus/configuration.go b/sdk-clients/fusionplus/configuration.go
new file mode 100644
index 00000000..5930c7db
--- /dev/null
+++ b/sdk-clients/fusionplus/configuration.go
@@ -0,0 +1,70 @@
+package fusionplus
+
+import (
+ "fmt"
+
+ "github.com/1inch/1inch-sdk-go/common"
+ http_executor "github.com/1inch/1inch-sdk-go/internal/http-executor"
+ web3_provider "github.com/1inch/1inch-sdk-go/internal/web3-provider"
+)
+
+type Configuration struct {
+ WalletConfiguration *ConfigurationWallet
+ APIConfiguration *ConfigurationAPI
+}
+
+type ConfigurationAPI struct {
+ ApiKey string
+ ApiURL string
+
+ API api
+}
+
+type ConfigurationWallet struct {
+ PrivateKey string
+ Wallet common.Wallet
+}
+
+type ConfigurationParams struct {
+ ApiUrl string
+ ApiKey string
+ PrivateKey string
+}
+
+func NewConfiguration(params ConfigurationParams) (*Configuration, error) {
+ executor, err := http_executor.DefaultHttpClient(params.ApiUrl, params.ApiKey)
+ if err != nil {
+ return nil, err
+ }
+
+ a := api{
+ httpExecutor: executor,
+ }
+
+ walletCfg, err := NewConfigurationWallet(params.PrivateKey)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Configuration{
+ WalletConfiguration: walletCfg,
+ APIConfiguration: &ConfigurationAPI{
+ ApiURL: params.ApiUrl,
+ ApiKey: params.ApiKey,
+ API: a,
+ },
+ }, nil
+}
+
+func NewConfigurationWallet(privateKey string) (*ConfigurationWallet, error) {
+ if privateKey == "" {
+ return nil, fmt.Errorf("private key cannot be empty")
+ }
+ w, err := web3_provider.DefaultWalletOnlyProvider(privateKey, 12345) // TODO Remove this later if possible
+ if err != nil {
+ return nil, err
+ }
+ return &ConfigurationWallet{
+ Wallet: w,
+ }, nil
+}
diff --git a/sdk-clients/fusionplus/configuration_test.go b/sdk-clients/fusionplus/configuration_test.go
new file mode 100644
index 00000000..1b272bba
--- /dev/null
+++ b/sdk-clients/fusionplus/configuration_test.go
@@ -0,0 +1,21 @@
+package fusionplus
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestNewConfigurationAPI(t *testing.T) {
+ configAPI, err := NewConfiguration(ConfigurationParams{
+ ApiUrl: "https://api.example.com",
+ ApiKey: "apikey123",
+ PrivateKey: "965e092fdfc08940d2bd05c7b5c7e1c51e283e92c7f52bbf1408973ae9a9acb7",
+ })
+
+ require.NoError(t, err)
+ assert.NotNil(t, configAPI)
+ assert.Equal(t, "https://api.example.com", configAPI.APIConfiguration.ApiURL)
+ assert.Equal(t, "apikey123", configAPI.APIConfiguration.ApiKey)
+}
diff --git a/sdk-clients/fusionplus/escrowextension.go b/sdk-clients/fusionplus/escrowextension.go
new file mode 100644
index 00000000..bf4fe865
--- /dev/null
+++ b/sdk-clients/fusionplus/escrowextension.go
@@ -0,0 +1,305 @@
+package fusionplus
+
+import (
+ "bytes"
+ "encoding/binary"
+ "encoding/hex"
+ "fmt"
+ "log"
+ "math/big"
+ "strings"
+
+ "github.com/1inch/1inch-sdk-go/internal/bytesiterator"
+ "github.com/1inch/1inch-sdk-go/sdk-clients/fusion"
+ "github.com/1inch/1inch-sdk-go/sdk-clients/orderbook"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+type EscrowExtension struct {
+ fusion.Extension
+ HashLock *HashLock
+ DstChainId float32
+ DstToken common.Address
+ SrcSafetyDeposit string
+ DstSafetyDeposit string
+ TimeLocks TimeLocks
+}
+
+func NewEscrowExtension(escrowParams EscrowExtensionParams) (*EscrowExtension, error) {
+
+ extension, err := fusion.NewExtension(escrowParams.ExtensionParams)
+ if err != nil {
+ return nil, err
+ }
+
+ escrowExtension := &EscrowExtension{
+ Extension: *extension,
+ HashLock: escrowParams.HashLock,
+ DstChainId: escrowParams.DstChainId,
+ DstToken: escrowParams.DstToken,
+ SrcSafetyDeposit: escrowParams.SrcSafetyDeposit,
+ DstSafetyDeposit: escrowParams.DstSafetyDeposit,
+ TimeLocks: escrowParams.TimeLocks,
+ }
+
+ return escrowExtension, nil
+}
+
+func (e *EscrowExtension) ConvertToOrderbookExtension() (*orderbook.Extension, error) {
+
+ srcSafetyDepositBig := new(big.Int)
+ _, ok := srcSafetyDepositBig.SetString(e.SrcSafetyDeposit, 10)
+ if !ok {
+ return nil, fmt.Errorf("invalid hexadecimal string for source safety deposit: %v", e.SrcSafetyDeposit)
+ }
+
+ dstSafetyDepositBig := new(big.Int)
+ _, ok = dstSafetyDepositBig.SetString(e.DstSafetyDeposit, 10)
+ if !ok {
+ return nil, fmt.Errorf("invalid hexadecimal string for destination safety deposit: %v", e.DstSafetyDeposit)
+ }
+
+ extraDataBytes, err := encodeExtraData(&EscrowExtraData{
+ HashLock: e.HashLock,
+ DstChainId: e.DstChainId,
+ DstToken: e.DstToken,
+ SrcSafetyDeposit: srcSafetyDepositBig,
+ DstSafetyDeposit: dstSafetyDepositBig,
+ TimeLocks: &e.TimeLocks,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to encode extra data: %v", err) // TODO handle
+ }
+
+ e.PostInteraction += trim0x(fmt.Sprintf("%x", extraDataBytes))
+
+ return &orderbook.Extension{
+ MakerAssetSuffix: e.MakerAssetSuffix,
+ TakerAssetSuffix: e.TakerAssetSuffix,
+ MakingAmountData: e.MakingAmountData,
+ TakingAmountData: e.TakingAmountData,
+ Predicate: e.Predicate,
+ MakerPermit: e.MakerPermit,
+ PreInteraction: e.PreInteraction,
+ PostInteraction: e.PostInteraction,
+ //strings.TrimPrefix(e.CustomData, "0x"), // TODO Blocking custom data for now because it is breaking the cumsum method. The extension constructor will return with an error if the user provides this field.
+ }, nil
+}
+
+// DecodeEscrowExtension decodes the input byte slice into an Extension struct using reflection.
+func DecodeEscrowExtension(data []byte) (*EscrowExtension, error) {
+
+ const extraDataCharacterLength = 320
+
+ // Create one extension that will be used for the Escrow extension data
+ orderbookExtensionTruncated, err := orderbook.Decode(data)
+ if err != nil {
+ return nil, fmt.Errorf("error decoding extension: %v", err)
+ }
+
+ // Remove the Fusion Plus Extension data before decoding
+ orderbookExtensionTruncated.PostInteraction = orderbookExtensionTruncated.PostInteraction[:len(orderbookExtensionTruncated.PostInteraction)-extraDataCharacterLength]
+ fusionExtension, err := fusion.FromLimitOrderExtension(orderbookExtensionTruncated)
+ if err != nil {
+ return &EscrowExtension{}, fmt.Errorf("error decoding escrow extension: %v", err)
+ }
+
+ // Create a second extension that will be used as a Fusion extension
+ orderbookExtension, err := orderbook.Decode(data)
+ if err != nil {
+ return nil, fmt.Errorf("error decoding extension: %v", err)
+ }
+ extraDataRaw := orderbookExtension.PostInteraction[len(orderbookExtension.PostInteraction)-extraDataCharacterLength:]
+ extraDataBytes, err := hex.DecodeString(extraDataRaw)
+ if err != nil {
+ return nil, fmt.Errorf("error decoding escrow extension extra data: %v", err)
+ }
+
+ // Send the final 160 bytes of the postInteraction to decodeExtraData
+ extraData, err := decodeExtraData(extraDataBytes)
+ if err != nil {
+ return nil, fmt.Errorf("error decoding escrow extension extra data: %v", err)
+ }
+
+ return &EscrowExtension{
+ Extension: *fusionExtension,
+ HashLock: extraData.HashLock,
+ DstChainId: extraData.DstChainId,
+ DstToken: extraData.DstToken,
+ SrcSafetyDeposit: fmt.Sprintf("%x", extraData.SrcSafetyDeposit),
+ DstSafetyDeposit: fmt.Sprintf("%x", extraData.DstSafetyDeposit),
+ TimeLocks: *extraData.TimeLocks,
+ }, nil
+}
+
+func decodeExtraData(data []byte) (*EscrowExtraData, error) {
+ iter := bytesiterator.New(data)
+ hashlockData, err := iter.NextUint256()
+ if err != nil {
+ log.Fatalf("Failed to read first uint256: %v", err)
+ }
+
+ dstChainIdData, err := iter.NextUint256()
+ if err != nil {
+ log.Fatalf("Failed to read second uint256: %v", err)
+ }
+
+ addressBig, err := iter.NextUint256()
+ if err != nil {
+ log.Fatalf("Failed to read address: %v", err)
+ }
+
+ addressHex := strings.ToLower(common.BigToAddress(addressBig).Hex())
+
+ safetyDepositData, err := iter.NextUint256()
+ if err != nil {
+ log.Fatalf("Failed to read third uint256: %v", err)
+ }
+
+ // Define a 128-bit mask (2^128 - 1)
+ mask := new(big.Int)
+ mask.Exp(big.NewInt(2), big.NewInt(128), nil).Sub(mask, big.NewInt(1))
+
+ srcSafetyDeposit := new(big.Int).And(safetyDepositData, mask)
+ dstSafetyDeposit := new(big.Int).Rsh(safetyDepositData, 128)
+
+ timelocksData, err := iter.NextUint256()
+ if err != nil {
+ log.Fatalf("Failed to read fourth uint256: %v", err)
+ }
+
+ timelocks, err := decodeTimeLocks(timelocksData)
+ if err != nil {
+ log.Fatalf("Failed to decode timelocks: %v", err)
+ }
+
+ return &EscrowExtraData{
+ HashLock: &HashLock{
+ hashlockData.String(),
+ },
+ DstChainId: float32(dstChainIdData.Uint64()),
+ DstToken: common.HexToAddress(addressHex),
+ SrcSafetyDeposit: srcSafetyDeposit,
+ DstSafetyDeposit: dstSafetyDeposit,
+ TimeLocks: timelocks,
+ }, nil
+}
+
+// decodeTimeLocks takes a *big.Int containing the raw hex data and returns a TimeLocks struct.
+func decodeTimeLocks(value *big.Int) (*TimeLocks, error) {
+ tl := &TimeLocks{}
+
+ // Convert big.Int to byte slice
+ data := value.Bytes()
+
+ if len(data) < 32 {
+ padded := make([]byte, 32)
+ copy(padded[32-len(data):], data)
+ data = padded
+ }
+
+ //TODO big.Int cannot preserve leading zeroes, so decoding the deploy time is impossible atm
+
+ // tl.DeployTime = float32(binary.BigEndian.Uint32((data[0:4])))
+ tl.DstCancellation = float32(binary.BigEndian.Uint32((data[4:8])))
+ tl.DstPublicWithdrawal = float32(binary.BigEndian.Uint32((data[8:12])))
+ tl.DstWithdrawal = float32(binary.BigEndian.Uint32((data[12:16])))
+ tl.SrcPublicCancellation = float32(binary.BigEndian.Uint32((data[16:20])))
+ tl.SrcCancellation = float32(binary.BigEndian.Uint32((data[20:24])))
+ tl.SrcPublicWithdrawal = float32(binary.BigEndian.Uint32((data[24:28])))
+ tl.SrcWithdrawal = float32(binary.BigEndian.Uint32((data[28:32])))
+
+ return tl, nil
+}
+
+type EscrowExtraData struct {
+ HashLock *HashLock
+ DstChainId float32
+ DstToken common.Address
+ SrcSafetyDeposit *big.Int
+ DstSafetyDeposit *big.Int
+ TimeLocks *TimeLocks
+}
+
+// encodeExtraData takes an EscrowExtraData struct and encodes it into a byte slice.
+func encodeExtraData(data *EscrowExtraData) ([]byte, error) {
+ var buffer bytes.Buffer
+
+ // 1. Encode HashLock.Value
+ hashlockData := new(big.Int)
+ _, ok := hashlockData.SetString(trim0x(data.HashLock.Value), 16)
+ if !ok {
+ return nil, fmt.Errorf("invalid HashLock value: %s", data.HashLock.Value)
+ }
+ err := writeBigIntAsUint256(&buffer, hashlockData)
+ if err != nil {
+ return nil, err
+ }
+
+ // 2. Encode DstChainId
+ dstChainIdBigInt := new(big.Int).SetUint64(uint64(data.DstChainId))
+ err = writeBigIntAsUint256(&buffer, dstChainIdBigInt)
+ if err != nil {
+ return nil, err
+ }
+
+ // 3. Encode DstToken
+ addressBig := new(big.Int).SetBytes(data.DstToken.Bytes())
+ err = writeBigIntAsUint256(&buffer, addressBig)
+ if err != nil {
+ return nil, err
+ }
+
+ // 4. Encode SafetyDeposits
+ safetyDepositData := new(big.Int)
+ srcShifted := new(big.Int).Lsh(data.SrcSafetyDeposit, 128)
+ safetyDepositData.Add(srcShifted, data.DstSafetyDeposit)
+ err = writeBigIntAsUint256(&buffer, safetyDepositData)
+ if err != nil {
+ return nil, err
+ }
+
+ // 5. Encode TimeLocks
+ timeLocksData, err := encodeTimeLocks(data.TimeLocks)
+ if err != nil {
+ return nil, err
+ }
+ err = writeBigIntAsUint256(&buffer, timeLocksData)
+ if err != nil {
+ return nil, err
+ }
+
+ return buffer.Bytes(), nil
+}
+
+// encodeTimeLocks packs a TimeLocks struct into a *big.Int.
+func encodeTimeLocks(tl *TimeLocks) (*big.Int, error) {
+ data := make([]byte, 32)
+
+ //TODO statically putting a timeDeployed value of 0 at the beginning of the encoded data for now. The data is missing from the generated struct.
+ // https://github.com/1inch/cross-chain-sdk/blob/532f6ae6dc401ddaf8fe3ad040305f2500156710/src/cross-chain-order/time-locks/time-locks.ts#L33-L33
+ // https://github.com/1inch/cross-chain-sdk/blob/532f6ae6dc401ddaf8fe3ad040305f2500156710/src/cross-chain-order/time-locks/time-locks.ts#L188-L188
+ binary.BigEndian.PutUint32(data[0:4], uint32(0))
+ binary.BigEndian.PutUint32(data[4:8], uint32(tl.DstCancellation))
+ binary.BigEndian.PutUint32(data[8:12], uint32(tl.DstPublicWithdrawal))
+ binary.BigEndian.PutUint32(data[12:16], uint32(tl.DstWithdrawal))
+ binary.BigEndian.PutUint32(data[16:20], uint32(tl.SrcPublicCancellation))
+ binary.BigEndian.PutUint32(data[20:24], uint32(tl.SrcCancellation))
+ binary.BigEndian.PutUint32(data[24:28], uint32(tl.SrcPublicWithdrawal))
+ binary.BigEndian.PutUint32(data[28:32], uint32(tl.SrcWithdrawal))
+ timeLocksData := new(big.Int).SetBytes(data)
+ return timeLocksData, nil
+}
+
+// writeBigIntAsUint256 writes a *big.Int as a 32-byte big-endian uint256 to a buffer.
+func writeBigIntAsUint256(buffer *bytes.Buffer, value *big.Int) error {
+ bytes := value.Bytes()
+ if len(bytes) > 32 {
+ return fmt.Errorf("value too large to fit in uint256")
+ }
+ // Pad with leading zeros to make it 32 bytes
+ padded := make([]byte, 32)
+ copy(padded[32-len(bytes):], bytes)
+ _, err := buffer.Write(padded)
+ return err
+}
diff --git a/sdk-clients/fusionplus/escrowextension_test.go b/sdk-clients/fusionplus/escrowextension_test.go
new file mode 100644
index 00000000..4c516a8c
--- /dev/null
+++ b/sdk-clients/fusionplus/escrowextension_test.go
@@ -0,0 +1,385 @@
+package fusionplus
+
+import (
+ "bytes"
+ "fmt"
+ "math/big"
+ "testing"
+
+ "github.com/1inch/1inch-sdk-go/internal/bigint"
+ random_number_generation "github.com/1inch/1inch-sdk-go/internal/random-number-generation"
+ "github.com/1inch/1inch-sdk-go/sdk-clients/fusion"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestGenerateSalt(t *testing.T) {
+ // Save the original function
+ originalBigIntMaxFunc := random_number_generation.BigIntMaxFunc
+
+ // Monkey patch the function
+ random_number_generation.BigIntMaxFunc = func(max *big.Int) (*big.Int, error) {
+ return big.NewInt(123456), nil
+ }
+
+ // Restore the original function after the test
+ defer func() {
+ random_number_generation.BigIntMaxFunc = originalBigIntMaxFunc
+ }()
+
+ tests := []struct {
+ name string
+ extension *EscrowExtension
+ expected string
+ expectErr bool
+ }{
+ {
+ name: "Generate salt when extension is not empty",
+ extension: &EscrowExtension{
+ Extension: fusion.Extension{
+ MakerAssetSuffix: "suffix1",
+ TakerAssetSuffix: "suffix2",
+ MakingAmountData: "data1",
+ TakingAmountData: "data2",
+ Predicate: "predicate",
+ MakerPermit: "permit",
+ PreInteraction: "pre",
+ PostInteraction: "post",
+ CustomData: "custom",
+ },
+ },
+ expected: "180431178743033967347942937469468920088249224033532329",
+ expectErr: false,
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ expected, err := bigint.FromString(tc.expected)
+ require.NoError(t, err)
+
+ result, err := tc.extension.GenerateSalt()
+ if tc.expectErr {
+ require.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ assert.Equal(t, expected, result)
+ }
+ })
+ }
+}
+
+func TestNewExtension(t *testing.T) {
+ tests := []struct {
+ name string
+ params EscrowExtensionParams
+ expected *EscrowExtension
+ expectErr bool
+ errMsg string
+ }{
+ {
+ name: "Valid parameters with Escrow",
+ params: EscrowExtensionParams{
+ ExtensionParams: fusion.ExtensionParams{
+ SettlementContract: "0x5678",
+ AuctionDetails: &fusion.AuctionDetails{
+ StartTime: 0,
+ Duration: 0,
+ InitialRateBump: 0,
+ Points: nil,
+ GasCost: fusion.GasCostConfigClassFixed{},
+ },
+ PostInteractionData: &fusion.SettlementPostInteractionData{
+ Whitelist: []fusion.WhitelistItem{},
+ IntegratorFee: &fusion.IntegratorFee{
+ Ratio: big.NewInt(0),
+ Receiver: common.Address{},
+ },
+ BankFee: big.NewInt(0),
+ ResolvingStartTime: big.NewInt(0),
+ CustomReceiver: common.Address{},
+ },
+ Asset: "0x1234",
+ Permit: "0x3456",
+
+ MakerAssetSuffix: "0x1234",
+ TakerAssetSuffix: "0x1234",
+ Predicate: "0x1234",
+ PreInteraction: "pre",
+ },
+ },
+ expected: &EscrowExtension{
+ Extension: fusion.Extension{
+ MakerAssetSuffix: "0x1234",
+ TakerAssetSuffix: "0x1234",
+ MakingAmountData: "0x00000000000000000000000000000000000056780000000000000000000000000000000000",
+ TakingAmountData: "0x00000000000000000000000000000000000056780000000000000000000000000000000000",
+ Predicate: "0x1234",
+ MakerPermit: "0x00000000000000000000000000000000000012343456",
+ PreInteraction: "pre",
+ PostInteraction: "0x00000000000000000000000000000000000056780000000000",
+ },
+ },
+ expectErr: false,
+ },
+ {
+ name: "Invalid MakerAssetSuffix",
+ params: EscrowExtensionParams{
+ ExtensionParams: fusion.ExtensionParams{
+ MakerAssetSuffix: "invalid",
+ TakerAssetSuffix: "0x1234",
+ Predicate: "0x1234",
+ PreInteraction: "pre",
+ },
+ },
+ expectErr: true,
+ errMsg: "MakerAssetSuffix must be valid hex string",
+ },
+ {
+ name: "Invalid TakerAssetSuffix",
+ params: EscrowExtensionParams{
+ ExtensionParams: fusion.ExtensionParams{
+ MakerAssetSuffix: "0x1234",
+ TakerAssetSuffix: "invalid",
+ Predicate: "0x1234",
+ PreInteraction: "pre",
+ },
+ },
+ expectErr: true,
+ errMsg: "TakerAssetSuffix must be valid hex string",
+ },
+ {
+ name: "Invalid Predicate",
+ params: EscrowExtensionParams{
+ ExtensionParams: fusion.ExtensionParams{
+ MakerAssetSuffix: "0x1234",
+ TakerAssetSuffix: "0x1234",
+ Predicate: "invalid",
+ PreInteraction: "pre",
+ },
+ },
+ expectErr: true,
+ errMsg: "Predicate must be valid hex string",
+ },
+ {
+ name: "CustomData not supported",
+ params: EscrowExtensionParams{
+ ExtensionParams: fusion.ExtensionParams{
+ MakerAssetSuffix: "0x1234",
+ TakerAssetSuffix: "0x1234",
+ Predicate: "0x1234",
+ PreInteraction: "pre",
+ CustomData: "0x1234",
+ },
+ },
+ expectErr: true,
+ errMsg: "CustomData is not currently supported",
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ ext, err := NewEscrowExtension(tc.params)
+ if tc.expectErr {
+ require.Error(t, err)
+ assert.Equal(t, tc.errMsg, err.Error())
+ } else {
+ require.NoError(t, err)
+ assert.NotNil(t, ext)
+ assert.Equal(t, tc.expected.MakerAssetSuffix, ext.MakerAssetSuffix)
+ assert.Equal(t, tc.expected.TakerAssetSuffix, ext.TakerAssetSuffix)
+ assert.Equal(t, tc.expected.Predicate, ext.Predicate)
+ assert.Equal(t, tc.expected.PreInteraction, ext.PreInteraction)
+ assert.Equal(t, tc.expected.PostInteraction, ext.PostInteraction)
+ assert.Equal(t, tc.expected.CustomData, ext.CustomData)
+ }
+ })
+ }
+}
+
+// TestDecodeEscrowExtension contains all unit tests for the DecodeEscrowExtension function.
+func TestDecodeEscrowExtension(t *testing.T) {
+ tests := []struct {
+ name string
+ hexInput string
+ expected *EscrowExtension
+ expectingErr bool
+ errorContains string
+ }{
+ {
+ name: "Full decode",
+ hexInput: "0x0000016b0000005e0000005e0000005e0000005e0000002f0000000000000000fb2809a5314473e1165f6b58018e20ed8f07b84000b8460000222c6656b88f0000b401e0da00ba01009000b8460024fb2809a5314473e1165f6b58018e20ed8f07b84000b8460000222c6656b88f0000b401e0da00ba01009000b8460024fb2809a5314473e1165f6b58018e20ed8f07b8406656b877b09498030ae3416b66dc0000db05a6a504f04d92e79d00000c989d73cf0bd5f83b660000d18bd45f0b94f54a968f0000d61b892b2ad6249011850000d0847e80c0b823a65ce70000901f8f650d76dcc657d1000038ad1723a873d05effcbdc57dcf7d00458d6a8c763558d5af7522bf6ad2d3e253d000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000064000000000000000000000000000000c80000000000000003000000020000000100000004000000030000000200000001",
+ expected: &EscrowExtension{
+ Extension: fusion.Extension{
+ SettlementContract: "0xfb2809a5314473e1165f6b58018e20ed8f07b840",
+ AuctionDetails: &fusion.AuctionDetails{
+ StartTime: 1716959375,
+ InitialRateBump: 123098,
+ Duration: 180,
+ Points: []fusion.AuctionPointClassFixed{
+ {
+ Coefficient: 47617,
+ Delay: 144,
+ },
+ {
+ Coefficient: 47174,
+ Delay: 36,
+ },
+ },
+ GasCost: fusion.GasCostConfigClassFixed{
+ GasBumpEstimate: 47174,
+ GasPriceEstimate: 8748,
+ },
+ },
+ PostInteractionData: &fusion.SettlementPostInteractionData{
+ Whitelist: []fusion.WhitelistItem{
+ {
+ AddressHalf: "b09498030ae3416b66dc",
+ Delay: big.NewInt(0),
+ },
+ {
+ AddressHalf: "db05a6a504f04d92e79d",
+ Delay: big.NewInt(0),
+ },
+ {
+ AddressHalf: "0c989d73cf0bd5f83b66",
+ Delay: big.NewInt(0),
+ },
+ {
+ AddressHalf: "d18bd45f0b94f54a968f",
+ Delay: big.NewInt(0),
+ },
+ {
+ AddressHalf: "d61b892b2ad624901185",
+ Delay: big.NewInt(0),
+ },
+ {
+ AddressHalf: "d0847e80c0b823a65ce7",
+ Delay: big.NewInt(0),
+ },
+ {
+ AddressHalf: "901f8f650d76dcc657d1",
+ Delay: big.NewInt(0),
+ },
+ },
+ BankFee: nil,
+ ResolvingStartTime: big.NewInt(1716959351),
+ },
+ MakerAssetSuffix: "",
+ TakerAssetSuffix: "",
+ MakingAmountData: "fb2809a5314473e1165f6b58018e20ed8f07b84000b8460000222c6656b88f0000b401e0da00ba01009000b8460024",
+ TakingAmountData: "fb2809a5314473e1165f6b58018e20ed8f07b84000b8460000222c6656b88f0000b401e0da00ba01009000b8460024",
+ Predicate: "",
+ MakerPermit: "",
+ PreInteraction: "",
+ PostInteraction: "fb2809a5314473e1165f6b58018e20ed8f07b8406656b877b09498030ae3416b66dc0000db05a6a504f04d92e79d00000c989d73cf0bd5f83b660000d18bd45f0b94f54a968f0000d61b892b2ad6249011850000d0847e80c0b823a65ce70000901f8f650d76dcc657d1000038",
+ CustomData: "",
+ },
+ HashLock: &HashLock{
+ Value: "0xed17b7cc09d7a0ba79bce96c0f0ec59d15e63bceeeae147ed230cff89689ce5c",
+ },
+ DstChainId: 42161,
+ DstToken: common.HexToAddress("0x0000000000000000000000000000000000000001"),
+ SrcSafetyDeposit: "100",
+ DstSafetyDeposit: "200",
+ TimeLocks: TimeLocks{
+ DstCancellation: 3,
+ DstPublicWithdrawal: 2,
+ DstWithdrawal: 1,
+ SrcPublicCancellation: 4,
+ SrcCancellation: 3,
+ SrcPublicWithdrawal: 2,
+ SrcWithdrawal: 1,
+ },
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Convert hex string to bytes
+ data := hexutil.MustDecode(tt.hexInput)
+
+ // Decode the data
+ decoded, err := DecodeEscrowExtension(data)
+ require.NoError(t, err)
+
+ if tt.expectingErr {
+ if err == nil {
+ t.Errorf("Expected error but got none")
+ } else if tt.errorContains != "" && !contains(err.Error(), tt.errorContains) {
+ t.Errorf("Expected error to contain '%s' but got '%s'", tt.errorContains, err.Error())
+ }
+ } else {
+ if err != nil {
+ t.Errorf("Unexpected error: %v", err)
+ }
+ assert.Equal(t, tt.expected.SettlementContract, decoded.SettlementContract)
+ assert.Equal(t, tt.expected.AuctionDetails, decoded.AuctionDetails)
+ assert.Equal(t, tt.expected.PostInteractionData, decoded.PostInteractionData)
+ assert.Equal(t, tt.expected.Asset, decoded.Asset)
+ assert.Equal(t, tt.expected.Permit, decoded.Permit)
+ assert.Equal(t, tt.expected.MakerAssetSuffix, decoded.MakerAssetSuffix)
+ assert.Equal(t, tt.expected.TakerAssetSuffix, decoded.TakerAssetSuffix)
+ assert.Equal(t, tt.expected.MakingAmountData, decoded.MakingAmountData)
+ assert.Equal(t, tt.expected.TakingAmountData, decoded.TakingAmountData)
+ assert.Equal(t, tt.expected.Predicate, decoded.Predicate)
+ assert.Equal(t, tt.expected.MakerPermit, decoded.MakerPermit)
+ assert.Equal(t, tt.expected.PreInteraction, decoded.PreInteraction)
+ assert.Equal(t, tt.expected.PostInteraction, decoded.PostInteraction)
+ assert.Equal(t, tt.expected.TimeLocks, decoded.TimeLocks)
+ }
+ })
+ }
+}
+
+func TestEncodeExtraData(t *testing.T) {
+ tests := []struct {
+ name string
+ expectedEncoded string
+ extraData *EscrowExtraData
+ expectingErr bool
+ errorContains string
+ }{
+ {
+ name: "Encode without any other data",
+ extraData: &EscrowExtraData{
+ HashLock: &HashLock{
+ Value: "ad1723a873d05effcbdc57dcf7d00458d6a8c763558d5af7522bf6ad2d3e253d",
+ },
+ DstChainId: 42161,
+ DstToken: common.HexToAddress("0x0000000000000000000000000000000000000001"),
+ SrcSafetyDeposit: big.NewInt(100),
+ DstSafetyDeposit: big.NewInt(200),
+ TimeLocks: &TimeLocks{
+ DstCancellation: 3,
+ DstPublicWithdrawal: 2,
+ DstWithdrawal: 1,
+ SrcPublicCancellation: 4,
+ SrcCancellation: 3,
+ SrcPublicWithdrawal: 2,
+ SrcWithdrawal: 1,
+ },
+ },
+ expectedEncoded: "ad1723a873d05effcbdc57dcf7d00458d6a8c763558d5af7522bf6ad2d3e253d000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000064000000000000000000000000000000c80000000000000003000000020000000100000004000000030000000200000001",
+ expectingErr: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+
+ encoded, err := encodeExtraData(tt.extraData)
+ require.NoError(t, err)
+
+ require.Equal(t, tt.expectedEncoded, fmt.Sprintf("%x", encoded))
+ })
+ }
+}
+
+// contains checks if the substring is present in the string.
+func contains(s, substr string) bool {
+ return bytes.Contains([]byte(s), []byte(substr))
+}
diff --git a/sdk-clients/fusionplus/examples/get_active_orders/main.go b/sdk-clients/fusionplus/examples/get_active_orders/main.go
new file mode 100644
index 00000000..1cfb0cfe
--- /dev/null
+++ b/sdk-clients/fusionplus/examples/get_active_orders/main.go
@@ -0,0 +1,47 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/1inch/1inch-sdk-go/sdk-clients/fusion"
+)
+
+var (
+ devPortalToken = os.Getenv("DEV_PORTAL_TOKEN")
+ privateKey = os.Getenv("WALLET_KEY")
+)
+
+func main() {
+ config, err := fusion.NewConfiguration(fusion.ConfigurationParams{
+ ApiUrl: "https://api.1inch.dev",
+ ApiKey: devPortalToken,
+ ChainId: 1,
+ PrivateKey: privateKey,
+ })
+ if err != nil {
+ log.Fatalf("failed to create configuration: %v", err)
+ }
+ client, err := fusion.NewClient(config)
+ if err != nil {
+ log.Fatalf("failed to create client: %v", err)
+ }
+ ctx := context.Background()
+
+ response, err := client.GetActiveOrders(ctx, fusion.OrderApiControllerGetActiveOrdersParams{
+ Page: 0,
+ Limit: 2,
+ })
+ if err != nil {
+ log.Fatalf("failed to request: %v", err)
+ }
+
+ output, err := json.MarshalIndent(response, "", " ")
+ if err != nil {
+ log.Fatalf("Failed to marshal response: %v\n", err)
+ }
+ fmt.Printf("Response: %s\n", string(output))
+}
diff --git a/sdk-clients/fusionplus/examples/get_order_by_hash/main.go b/sdk-clients/fusionplus/examples/get_order_by_hash/main.go
new file mode 100644
index 00000000..979219c2
--- /dev/null
+++ b/sdk-clients/fusionplus/examples/get_order_by_hash/main.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/1inch/1inch-sdk-go/sdk-clients/fusionplus"
+)
+
+var (
+ devPortalToken = os.Getenv("DEV_PORTAL_TOKEN")
+ privateKey = os.Getenv("WALLET_KEY")
+)
+
+func main() {
+ config, err := fusionplus.NewConfiguration(fusionplus.ConfigurationParams{
+ ApiUrl: "https://api.1inch.dev",
+ ApiKey: devPortalToken,
+ PrivateKey: privateKey,
+ })
+ if err != nil {
+ log.Fatalf("failed to create configuration: %v", err)
+ }
+ client, err := fusionplus.NewClient(config)
+ if err != nil {
+ log.Fatalf("failed to create client: %v", err)
+ }
+ ctx := context.Background()
+
+ response, err := client.GetReadyToAcceptFills(ctx, fusionplus.GetOrderByOrderHashParams{
+ Hash: "0x97729858044d3838c82f2ea5ca4764bd20bfdf1f99d3af05786e4a358b16fa91",
+ })
+ if err != nil {
+ log.Fatalf("failed to request: %v", err)
+ }
+
+ output, err := json.MarshalIndent(response, "", " ")
+ if err != nil {
+ log.Fatalf("Failed to marshal response: %v\n", err)
+ }
+ fmt.Printf("Response: %s\n", string(output))
+}
diff --git a/sdk-clients/fusionplus/examples/get_quote/main.go b/sdk-clients/fusionplus/examples/get_quote/main.go
new file mode 100644
index 00000000..969d9054
--- /dev/null
+++ b/sdk-clients/fusionplus/examples/get_quote/main.go
@@ -0,0 +1,53 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/1inch/1inch-sdk-go/sdk-clients/fusionplus"
+)
+
+var (
+ devPortalToken = os.Getenv("DEV_PORTAL_TOKEN")
+ publicAddress = os.Getenv("WALLET_ADDRESS_NEW")
+ privateKey = os.Getenv("WALLET_KEY_NEW")
+)
+
+func main() {
+ config, err := fusionplus.NewConfiguration(fusionplus.ConfigurationParams{
+ ApiUrl: "https://api.1inch.dev",
+ ApiKey: devPortalToken,
+ PrivateKey: privateKey,
+ })
+ if err != nil {
+ log.Fatalf("failed to create configuration: %v", err)
+ }
+ client, err := fusionplus.NewClient(config)
+ if err != nil {
+ log.Fatalf("failed to create client: %v", err)
+ }
+ ctx := context.Background()
+
+ response, err := client.GetQuote(ctx, fusionplus.QuoterControllerGetQuoteParamsFixed{
+ SrcChain: 42161,
+ DstChain: 8453,
+ SrcTokenAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
+ DstTokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ Amount: "10000000",
+ WalletAddress: publicAddress,
+ EnableEstimate: true,
+ })
+
+ if err != nil {
+ log.Fatalf("failed to request: %v", err)
+ }
+
+ output, err := json.MarshalIndent(response, "", " ")
+ if err != nil {
+ log.Fatalf("Failed to marshal response: %v\n", err)
+ }
+ fmt.Printf("Response: %s\n", string(output))
+}
diff --git a/sdk-clients/fusionplus/examples/get_ready_to_accept_secret_fills/main.go b/sdk-clients/fusionplus/examples/get_ready_to_accept_secret_fills/main.go
new file mode 100644
index 00000000..626d9f39
--- /dev/null
+++ b/sdk-clients/fusionplus/examples/get_ready_to_accept_secret_fills/main.go
@@ -0,0 +1,53 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/1inch/1inch-sdk-go/sdk-clients/fusionplus"
+)
+
+var (
+ devPortalToken = os.Getenv("DEV_PORTAL_TOKEN")
+ publicAddress = os.Getenv("WALLET_ADDRESS")
+ privateKey = os.Getenv("WALLET_KEY")
+)
+
+const (
+ usdc = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
+ wmatic = "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270"
+ amount = "100000000"
+ chainId = 137
+)
+
+func main() {
+ config, err := fusionplus.NewConfiguration(fusionplus.ConfigurationParams{
+ ApiUrl: "https://api.1inch.dev",
+ ApiKey: devPortalToken,
+ PrivateKey: privateKey,
+ })
+ if err != nil {
+ log.Fatalf("failed to create configuration: %v", err)
+ }
+ client, err := fusionplus.NewClient(config)
+ if err != nil {
+ log.Fatalf("failed to create client: %v", err)
+ }
+ ctx := context.Background()
+
+ response, err := client.GetOrderByOrderHash(ctx, fusionplus.GetOrderByOrderHashParams{
+ Hash: "0x97729858044d3838c82f2ea5ca4764bd20bfdf1f99d3af05786e4a358b16fa91",
+ })
+ if err != nil {
+ log.Fatalf("failed to request: %v", err)
+ }
+
+ output, err := json.MarshalIndent(response, "", " ")
+ if err != nil {
+ log.Fatalf("Failed to marshal response: %v\n", err)
+ }
+ fmt.Printf("Response: %s\n", string(output))
+}
diff --git a/sdk-clients/fusionplus/examples/get_settlement_contract/main.go b/sdk-clients/fusionplus/examples/get_settlement_contract/main.go
new file mode 100644
index 00000000..d7b0c8bb
--- /dev/null
+++ b/sdk-clients/fusionplus/examples/get_settlement_contract/main.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/1inch/1inch-sdk-go/sdk-clients/fusion"
+)
+
+var (
+ devPortalToken = os.Getenv("DEV_PORTAL_TOKEN")
+ privateKey = os.Getenv("WALLET_KEY")
+)
+
+func main() {
+ config, err := fusion.NewConfiguration(fusion.ConfigurationParams{
+ ApiUrl: "https://api.1inch.dev",
+ ApiKey: devPortalToken,
+ ChainId: 1,
+ PrivateKey: privateKey,
+ })
+ if err != nil {
+ log.Fatalf("failed to create configuration: %v", err)
+ }
+ client, err := fusion.NewClient(config)
+ if err != nil {
+ log.Fatalf("failed to create client: %v", err)
+ }
+ ctx := context.Background()
+
+ response, err := client.GetSettlementContract(ctx)
+ if err != nil {
+ log.Fatalf("failed to request: %v", err)
+ }
+
+ output, err := json.MarshalIndent(response, "", " ")
+ if err != nil {
+ log.Fatalf("Failed to marshal response: %v\n", err)
+ }
+ fmt.Printf("Response: %s\n", string(output))
+}
diff --git a/sdk-clients/fusionplus/examples/place_order/main.go b/sdk-clients/fusionplus/examples/place_order/main.go
new file mode 100644
index 00000000..fcdd9e16
--- /dev/null
+++ b/sdk-clients/fusionplus/examples/place_order/main.go
@@ -0,0 +1,190 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+ "time"
+
+ "github.com/1inch/1inch-sdk-go/sdk-clients/fusionplus"
+)
+
+var (
+ devPortalToken = os.Getenv("DEV_PORTAL_TOKEN")
+ publicAddress = os.Getenv("WALLET_ADDRESS_NEW")
+ privateKey = os.Getenv("WALLET_KEY_NEW")
+)
+
+func main() {
+ config, err := fusionplus.NewConfiguration(fusionplus.ConfigurationParams{
+ ApiUrl: "https://api.1inch.dev",
+ ApiKey: devPortalToken,
+ PrivateKey: privateKey,
+ })
+ if err != nil {
+ log.Fatalf("failed to create configuration: %v", err)
+ }
+ client, err := fusionplus.NewClient(config)
+ if err != nil {
+ log.Fatalf("failed to create client: %v", err)
+ }
+ ctx := context.Background()
+
+ srcChain := 42161
+ dstChain := 8453
+
+ srcToken := "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
+ dstToken := "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
+
+ invert := true
+ if invert {
+ srcChain, dstChain = dstChain, srcChain
+ srcToken, dstToken = dstToken, srcToken
+ }
+
+ quoteParams := fusionplus.QuoterControllerGetQuoteParamsFixed{
+ SrcChain: float32(srcChain),
+ DstChain: float32(dstChain),
+ SrcTokenAddress: srcToken,
+ DstTokenAddress: dstToken,
+ Amount: "1500000",
+ WalletAddress: publicAddress,
+ EnableEstimate: true,
+ }
+ quote, err := client.GetQuote(ctx, quoteParams)
+ if err != nil {
+ log.Fatalf("failed to get quote: %v", err)
+ }
+
+ preset, err := fusionplus.GetPreset(quote.Presets, quote.RecommendedPreset)
+ if err != nil {
+ log.Fatalf("Failed to get preset: %v", err)
+ }
+ secretsCount := preset.SecretsCount
+
+ secrets := make([]string, int(secretsCount))
+ for i := 0; i < int(secretsCount); i++ {
+ randomBytes, err := fusionplus.GetRandomBytes32()
+ if err != nil {
+ log.Fatalf("Failed to get random bytes: %v", err)
+ }
+ secrets[i] = randomBytes
+ }
+ var secretHashes []string
+ for _, secret := range secrets {
+ secretHash, err := fusionplus.HashSecret(secret)
+ if err != nil {
+ log.Fatalf("Failed to hash secret: %v", err)
+ }
+ secretHashes = append(secretHashes, secretHash)
+ }
+
+ var hashLock *fusionplus.HashLock
+
+ if secretsCount == 1 {
+ hashLock, err = fusionplus.ForSingleFill(secrets[0])
+ if err != nil {
+ log.Fatalf("Failed to create hashlock: %v", err)
+ }
+ } else {
+ hashLock, err = fusionplus.ForMultipleFills(secrets)
+ if err != nil {
+ log.Fatalf("Failed to create hashlock: %v", err)
+ }
+ }
+
+ orderParams := fusionplus.OrderParams{
+ HashLock: hashLock,
+ SecretHashes: secretHashes,
+ Receiver: "0x0000000000000000000000000000000000000000",
+ Preset: quote.RecommendedPreset,
+ }
+
+ orderHash, err := client.PlaceOrder(ctx, quoteParams, quote, orderParams, client.Wallet)
+ if err != nil {
+ log.Fatalf("Failed to create order data: %v", err)
+ }
+
+ // Get order by hash
+ order, err := client.GetOrderByOrderHash(ctx, fusionplus.GetOrderByOrderHashParams{
+ Hash: orderHash,
+ })
+ if err != nil {
+ log.Fatalf("Failed to get order by hash: %v", err)
+ }
+
+ orderQuickLookIndented, err := json.MarshalIndent(order, "", " ")
+ if err != nil {
+ log.Fatalf("Failed to marshal response: %v\n", err)
+ }
+ fmt.Printf("Order: %s\n", string(orderQuickLookIndented))
+
+ // Define loop parameters
+ delay := 1 * time.Second // Delay between retries
+ retryCount := 0 // Current retry count
+ orderStatus := "" // Current order status
+
+ // Loop until order status is "executed" or max retries reached
+ for {
+ // Get order by hash
+ order, err = client.GetOrderByOrderHash(ctx, fusionplus.GetOrderByOrderHashParams{
+ Hash: orderHash,
+ })
+ if err != nil {
+ log.Printf("Failed to get order by hash: %v", err)
+ } else {
+ // Assuming order.Status is a string. Adjust the field access as per actual response structure.
+ orderStatus = string(order.Status)
+ fmt.Printf("Attempt %d: Order Status: %s\n", retryCount+1, orderStatus)
+
+ // Check if status is "executed"
+ if orderStatus == "executed" {
+ fmt.Println("Order has been executed.")
+ break
+ }
+
+ // Check if status is "executed"
+ if orderStatus == "refunded" {
+ fmt.Println("Order has been refunded.")
+ break
+ }
+ }
+
+ // TODO fix params on this
+ fills, err := client.GetReadyToAcceptFills(ctx, fusionplus.GetOrderByOrderHashParams{
+ Hash: orderHash,
+ })
+ if err != nil {
+ log.Fatalf("failed to request: %v", err)
+ }
+
+ if len(fills.Fills) > 0 {
+ // TODO the secret index needs to match the index of the fill object, but I can ignore it for single-secre orders
+ err = client.SubmitSecret(ctx, fusionplus.SecretInput{
+ OrderHash: orderHash,
+ Secret: secrets[0],
+ })
+ if err != nil {
+ log.Fatalf("failed to submit secret: %v", err)
+ } else {
+ fmt.Println("Secret submitted!")
+ }
+ }
+
+ fmt.Printf("Fills: %v\n", fills)
+
+ // Increment retry count
+ retryCount++
+
+ // Wait before next retry
+ time.Sleep(delay)
+ }
+
+ orderIndented, err := json.MarshalIndent(order, "", " ")
+ if err != nil {
+ log.Fatalf("Failed to marshal response: %v\n", err)
+ }
+ fmt.Printf("Order: %s\n", string(orderIndented))
+}
diff --git a/sdk-clients/fusionplus/fusionplus_orders_types.gen.go b/sdk-clients/fusionplus/fusionplus_orders_types.gen.go
new file mode 100644
index 00000000..25d92104
--- /dev/null
+++ b/sdk-clients/fusionplus/fusionplus_orders_types.gen.go
@@ -0,0 +1,476 @@
+// Package fusionplus provides primitives to interact with the openapi HTTP API.
+//
+// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT.
+package fusionplus
+
+// Defines values for EscrowEventDataOutputAction.
+const (
+ DstEscrowCreated EscrowEventDataOutputAction = "dst_escrow_created"
+ EscrowCancelled EscrowEventDataOutputAction = "escrow_cancelled"
+ FundsRescued EscrowEventDataOutputAction = "funds_rescued"
+ SrcEscrowCreated EscrowEventDataOutputAction = "src_escrow_created"
+ Withdrawn EscrowEventDataOutputAction = "withdrawn"
+)
+
+// Defines values for EscrowEventDataOutputSide.
+const (
+ Dst EscrowEventDataOutputSide = "dst"
+ Src EscrowEventDataOutputSide = "src"
+)
+
+// Defines values for FillOutputDtoStatus.
+const (
+ FillOutputDtoStatusExecuted FillOutputDtoStatus = "executed"
+ FillOutputDtoStatusPending FillOutputDtoStatus = "pending"
+ FillOutputDtoStatusRefunded FillOutputDtoStatus = "refunded"
+ FillOutputDtoStatusRefunding FillOutputDtoStatus = "refunding"
+)
+
+// Defines values for GetOrderFillsByHashOutputStatus.
+const (
+ GetOrderFillsByHashOutputStatusCancelled GetOrderFillsByHashOutputStatus = "cancelled"
+ GetOrderFillsByHashOutputStatusExecuted GetOrderFillsByHashOutputStatus = "executed"
+ GetOrderFillsByHashOutputStatusExpired GetOrderFillsByHashOutputStatus = "expired"
+ GetOrderFillsByHashOutputStatusPending GetOrderFillsByHashOutputStatus = "pending"
+ GetOrderFillsByHashOutputStatusRefunded GetOrderFillsByHashOutputStatus = "refunded"
+ GetOrderFillsByHashOutputStatusRefunding GetOrderFillsByHashOutputStatus = "refunding"
+)
+
+// Defines values for GetOrderFillsByHashOutputValidation.
+const (
+ FailedToDecodeRemaining GetOrderFillsByHashOutputValidation = "failed-to-decode-remaining"
+ FailedToParsePermitDetails GetOrderFillsByHashOutputValidation = "failed-to-parse-permit-details"
+ InvalidPermitSignature GetOrderFillsByHashOutputValidation = "invalid-permit-signature"
+ InvalidPermitSigner GetOrderFillsByHashOutputValidation = "invalid-permit-signer"
+ InvalidPermitSpender GetOrderFillsByHashOutputValidation = "invalid-permit-spender"
+ InvalidSignature GetOrderFillsByHashOutputValidation = "invalid-signature"
+ NotEnoughAllowance GetOrderFillsByHashOutputValidation = "not-enough-allowance"
+ NotEnoughBalance GetOrderFillsByHashOutputValidation = "not-enough-balance"
+ OrderPredicateReturnedFalse GetOrderFillsByHashOutputValidation = "order-predicate-returned-false"
+ UnknownFailure GetOrderFillsByHashOutputValidation = "unknown-failure"
+ UnknownPermitVersion GetOrderFillsByHashOutputValidation = "unknown-permit-version"
+ Valid GetOrderFillsByHashOutputValidation = "valid"
+ WrongEpochManagerAndBitInvalidator GetOrderFillsByHashOutputValidation = "wrong-epoch-manager-and-bit-invalidator"
+)
+
+// Defines values for ReadyToExecutePublicActionAction.
+const (
+ Cancel ReadyToExecutePublicActionAction = "cancel"
+ Withdraw ReadyToExecutePublicActionAction = "withdraw"
+)
+
+// Defines values for ResolverDataOutputOrderType.
+const (
+ MultipleFills ResolverDataOutputOrderType = "MultipleFills"
+ SingleFill ResolverDataOutputOrderType = "SingleFill"
+)
+
+// ActiveOrdersOutput defines model for ActiveOrdersOutput.
+type ActiveOrdersOutput struct {
+ // AuctionEndDate End date of the auction for this order.
+ AuctionEndDate float32 `json:"auctionEndDate"`
+
+ // AuctionStartDate Start date of the auction for this order.
+ AuctionStartDate float32 `json:"auctionStartDate"`
+
+ // Deadline Deadline by which the order must be filled.
+ Deadline float32 `json:"deadline"`
+
+ // DstChainId Identifier of the chain where the taker asset is located.
+ DstChainId float32 `json:"dstChainId"`
+
+ // Extension An interaction call data. ABI encoded set of makerAssetSuffix, takerAssetSuffix, makingAmountGetter, takingAmountGetter, predicate, permit, preInteraction, postInteraction.If extension exists then lowest 160 bits of the order salt must be equal to the lowest 160 bits of the extension hash
+ Extension string `json:"extension"`
+
+ // Fills Array of fills.
+ Fills []string `json:"fills"`
+
+ // IsMakerContract True if order signed by contract (GnosisSafe, etc.)
+ IsMakerContract bool `json:"isMakerContract"`
+
+ // MakerAllowance Amount of the maker asset allowance.
+ MakerAllowance string `json:"makerAllowance"`
+
+ // MakerBalance Amount of the maker asset balance.
+ MakerBalance string `json:"makerBalance"`
+ Order CrossChainOrderDto `json:"order"`
+
+ // OrderHash Unique identifier of the order.
+ OrderHash string `json:"orderHash"`
+
+ // QuoteId Identifier of the quote associated with this order.
+ QuoteId string `json:"quoteId"`
+
+ // RemainingMakerAmount Remaining amount of the maker asset that can still be filled.
+ RemainingMakerAmount string `json:"remainingMakerAmount"`
+
+ // SecretHashes Array of secret hashes.
+ SecretHashes [][]interface{} `json:"secretHashes,omitempty"`
+
+ // Signature Signature of the order.
+ Signature string `json:"signature"`
+
+ // SrcChainId Identifier of the chain where the maker asset is located.
+ SrcChainId float32 `json:"srcChainId"`
+}
+
+// AuctionPointOutput defines model for AuctionPointOutput.
+type AuctionPointOutput struct {
+ // Coefficient The rate bump from the order min taker amount
+ Coefficient float32 `json:"coefficient"`
+
+ // Delay The delay in seconds from the previous point or auction start time
+ Delay float32 `json:"delay"`
+}
+
+// CrossChainOrderDto defines model for CrossChainOrderDto.
+type CrossChainOrderDto struct {
+ // Maker Address of the account creating the order (maker) in src chain.
+ Maker string `json:"maker"`
+
+ // MakerAsset Identifier of the asset being offered by the maker in src chain.
+ MakerAsset string `json:"makerAsset"`
+
+ // MakerTraits Includes some flags like, allow multiple fills, is partial fill allowed or not, price improvement, nonce, deadline etc.
+ MakerTraits string `json:"makerTraits"`
+
+ // MakingAmount Amount of the makerAsset being offered by the maker in src chain.
+ MakingAmount string `json:"makingAmount"`
+
+ // Receiver Address of the account receiving the assets (receiver), if different from maker in dst chain.
+ Receiver string `json:"receiver"`
+
+ // Salt Some unique value. It is necessary to be able to create cross chain orders with the same parameters (so that they have a different hash), Lowest 160 bits of the order salt must be equal to the lowest 160 bits of the extension hash
+ Salt string `json:"salt"`
+
+ // TakerAsset Identifier of the asset being requested by the maker in exchange in dst chain.
+ TakerAsset string `json:"takerAsset"`
+
+ // TakingAmount Amount of the takerAsset being requested by the maker in dst chain.
+ TakingAmount string `json:"takingAmount"`
+}
+
+// EscrowEventDataOutput defines model for EscrowEventDataOutput.
+type EscrowEventDataOutput struct {
+ // Action Action of the escrow event
+ Action EscrowEventDataOutputAction `json:"action"`
+
+ // BlockTimestamp Unix timestamp in milliseconds
+ BlockTimestamp float32 `json:"blockTimestamp"`
+
+ // Side Side of the escrow event SRC or DST
+ Side EscrowEventDataOutputSide `json:"side"`
+
+ // TransactionHash Transaction hash
+ TransactionHash string `json:"transactionHash"`
+}
+
+// EscrowEventDataOutputAction Action of the escrow event
+type EscrowEventDataOutputAction string
+
+// EscrowEventDataOutputSide Side of the escrow event SRC or DST
+type EscrowEventDataOutputSide string
+
+// EscrowFactory defines model for EscrowFactory.
+type EscrowFactory struct {
+ // Address actual escrow factory contract address
+ Address string `json:"address"`
+}
+
+// FillOutputDto defines model for FillOutputDto.
+type FillOutputDto struct {
+ EscrowEvents []EscrowEventDataOutput `json:"escrowEvents"`
+
+ // FilledAuctionTakerAmount Amount of the takerAsset filled in dst chain.
+ FilledAuctionTakerAmount string `json:"filledAuctionTakerAmount"`
+
+ // FilledMakerAmount Amount of the makerAsset filled in src chain.
+ FilledMakerAmount string `json:"filledMakerAmount"`
+
+ // Status Fill status
+ Status FillOutputDtoStatus `json:"status"`
+
+ // TxHash Transaction hash
+ TxHash string `json:"txHash"`
+}
+
+// FillOutputDtoStatus Fill status
+type FillOutputDtoStatus string
+
+// GetActiveOrdersOutput defines model for GetActiveOrdersOutput.
+type GetActiveOrdersOutput struct {
+ Items []ActiveOrdersOutput `json:"items"`
+ Meta Meta `json:"meta"`
+}
+
+// GetOrderByMakerOutput defines model for GetOrderByMakerOutput.
+type GetOrderByMakerOutput struct {
+ Items []ActiveOrdersOutput `json:"items"`
+ Meta Meta `json:"meta"`
+}
+
+// GetOrderFillsByHashOutput defines model for GetOrderFillsByHashOutput.
+type GetOrderFillsByHashOutput struct {
+ // ApproximateTakingAmount Approximate amount of the takerAsset being requested by the maker in dst chain.
+ ApproximateTakingAmount string `json:"approximateTakingAmount"`
+
+ // AuctionDuration Unix timestamp in milliseconds
+ AuctionDuration float32 `json:"auctionDuration"`
+
+ // AuctionStartDate Unix timestamp in milliseconds
+ AuctionStartDate float32 `json:"auctionStartDate"`
+ CancelTx map[string]interface{} `json:"cancelTx"`
+
+ // Cancelable Is order cancelable
+ Cancelable bool `json:"cancelable"`
+
+ // CreatedAt Unix timestamp in milliseconds
+ CreatedAt float32 `json:"createdAt"`
+
+ // DstChainId Identifier of the chain where the taker asset is located.
+ DstChainId float32 `json:"dstChainId"`
+ DstTokenPriceUsd map[string]interface{} `json:"dstTokenPriceUsd"`
+
+ // Extension An interaction call data. ABI encoded set of makerAssetSuffix, takerAssetSuffix, makingAmountGetter, takingAmountGetter, predicate, permit, preInteraction, postInteraction.If extension exists then lowest 160 bits of the order salt must be equal to the lowest 160 bits of the extension hash
+ Extension string `json:"extension"`
+
+ // Fills Fills
+ Fills []FillOutputDto `json:"fills"`
+
+ // InitialRateBump Initial rate bump
+ InitialRateBump float32 `json:"initialRateBump"`
+ Order LimitOrderV4StructOutput `json:"order"`
+
+ // OrderHash Order hash
+ OrderHash string `json:"orderHash"`
+ Points AuctionPointOutput `json:"points"`
+
+ // SrcChainId Identifier of the chain where the maker asset is located.
+ SrcChainId float32 `json:"srcChainId"`
+ SrcTokenPriceUsd map[string]interface{} `json:"srcTokenPriceUsd"`
+
+ // Status Order status
+ Status GetOrderFillsByHashOutputStatus `json:"status"`
+
+ // TakerAsset Identifier of the asset being requested by the maker in exchange in dst chain.
+ TakerAsset string `json:"takerAsset"`
+
+ // TimeLocks TimeLocks without deployedAt
+ TimeLocks string `json:"timeLocks"`
+
+ // Validation Order validation status
+ Validation GetOrderFillsByHashOutputValidation `json:"validation"`
+}
+
+// GetOrderFillsByHashOutputStatus Order status
+type GetOrderFillsByHashOutputStatus string
+
+// GetOrderFillsByHashOutputValidation Order validation status
+type GetOrderFillsByHashOutputValidation string
+
+// Immutables defines model for Immutables.
+type Immutables struct {
+ // Amount Amount of token to receive
+ Amount string `json:"amount"`
+
+ // Hashlock keccak256(secret(idx))
+ Hashlock string `json:"hashlock"`
+
+ // Maker Maker's address which will receive tokens
+ Maker string `json:"maker"`
+
+ // OrderHash Order's hash 32 bytes hex sting
+ OrderHash string `json:"orderHash"`
+
+ // SafetyDeposit Security deposit in chain's native currency
+ SafetyDeposit string `json:"safetyDeposit"`
+
+ // Taker Escrow creation initiator address
+ Taker string `json:"taker"`
+
+ // Timelocks Encoded timelocks. To decode use: https://github.com/1inch/cross-chain-sdk/blob/master/src/cross-chain-order/time-locks/time-locks.ts
+ Timelocks string `json:"timelocks"`
+
+ // Token Token to receive on specific chain
+ Token string `json:"token"`
+}
+
+// LimitOrderV4StructOutput defines model for LimitOrderV4StructOutput.
+type LimitOrderV4StructOutput struct {
+ // Maker Maker address
+ Maker string `json:"maker"`
+
+ // MakerAsset Maker asset address
+ MakerAsset string `json:"makerAsset"`
+ MakerTraits string `json:"makerTraits"`
+
+ // MakingAmount Amount of the maker asset
+ MakingAmount string `json:"makingAmount"`
+
+ // Receiver Receiver address
+ Receiver string `json:"receiver"`
+ Salt string `json:"salt"`
+
+ // TakerAsset Taker asset address
+ TakerAsset string `json:"takerAsset"`
+
+ // TakingAmount Amount of the taker asset
+ TakingAmount string `json:"takingAmount"`
+}
+
+// Meta defines model for Meta.
+type Meta struct {
+ CurrentPage float32 `json:"currentPage"`
+ ItemsPerPage float32 `json:"itemsPerPage"`
+ TotalItems float32 `json:"totalItems"`
+ TotalPages float32 `json:"totalPages"`
+}
+
+// OrdersByHashesInput defines model for OrdersByHashesInput.
+type OrdersByHashesInput struct {
+ OrderHashes []string `json:"orderHashes"`
+}
+
+// PublicSecret defines model for PublicSecret.
+type PublicSecret struct {
+ DstImmutables Immutables `json:"dstImmutables"`
+
+ // Idx Sequence number of secrets
+ Idx float32 `json:"idx"`
+
+ // Secret Public secret to perform a withdrawal
+ Secret string `json:"secret"`
+ SrcImmutables Immutables `json:"srcImmutables"`
+}
+
+// ReadyToAcceptSecretFill defines model for ReadyToAcceptSecretFill.
+type ReadyToAcceptSecretFill struct {
+ // DstEscrowDeployTxHash Transaction hash where the destination chain escrow was deployed
+ DstEscrowDeployTxHash string `json:"dstEscrowDeployTxHash"`
+
+ // Idx Sequence number of secrets for submission
+ Idx float32 `json:"idx"`
+
+ // SrcEscrowDeployTxHash Transaction hash where the source chain escrow was deployed
+ SrcEscrowDeployTxHash string `json:"srcEscrowDeployTxHash"`
+}
+
+// ReadyToAcceptSecretFills defines model for ReadyToAcceptSecretFills.
+type ReadyToAcceptSecretFills struct {
+ // Fills Fills that are ready to accept secrets from the client
+ Fills []ReadyToAcceptSecretFill `json:"fills"`
+}
+
+// ReadyToAcceptSecretFillsForAllOrders defines model for ReadyToAcceptSecretFillsForAllOrders.
+type ReadyToAcceptSecretFillsForAllOrders struct {
+ // Orders Fills that are ready to accept secrets from the client for all orders
+ Orders []ReadyToAcceptSecretFillsForOrder `json:"orders"`
+}
+
+// ReadyToAcceptSecretFillsForOrder defines model for ReadyToAcceptSecretFillsForOrder.
+type ReadyToAcceptSecretFillsForOrder struct {
+ // Fills Fills that are ready to accept secrets from the client
+ Fills []ReadyToAcceptSecretFill `json:"fills"`
+
+ // MakerAddress Maker address
+ MakerAddress string `json:"makerAddress"`
+
+ // OrderHash Order hash
+ OrderHash string `json:"orderHash"`
+}
+
+// ReadyToExecutePublicAction defines model for ReadyToExecutePublicAction.
+type ReadyToExecutePublicAction struct {
+ Action ReadyToExecutePublicActionAction `json:"action"`
+
+ // ChainId Execute action on this chain
+ ChainId float32 `json:"chainId"`
+
+ // Escrow Escrow's address to perform public action
+ Escrow string `json:"escrow"`
+ Immutables Immutables `json:"immutables"`
+
+ // Secret Presented only for withdraw action
+ Secret string `json:"secret,omitempty"`
+}
+
+// ReadyToExecutePublicActionAction defines model for ReadyToExecutePublicAction.Action.
+type ReadyToExecutePublicActionAction string
+
+// ReadyToExecutePublicActionsOutput defines model for ReadyToExecutePublicActionsOutput.
+type ReadyToExecutePublicActionsOutput struct {
+ // Actions Actions allowed to be performed on public timelock periods
+ Actions []ReadyToExecutePublicAction `json:"actions"`
+}
+
+// ResolverDataOutput defines model for ResolverDataOutput.
+type ResolverDataOutput struct {
+ // OrderType Type of the order: enabled or disabled partial fills
+ OrderType ResolverDataOutputOrderType `json:"orderType"`
+
+ // SecretHashes keccak256(secret(idx))[]
+ SecretHashes [][]interface{} `json:"secretHashes,omitempty"`
+
+ // Secrets The data required for order withdraw and cancel
+ Secrets []PublicSecret `json:"secrets"`
+}
+
+// ResolverDataOutputOrderType Type of the order: enabled or disabled partial fills
+type ResolverDataOutputOrderType string
+
+// OrderApiControllerGetActiveOrdersParams defines parameters for OrderApiControllerGetActiveOrders.
+type OrderApiControllerGetActiveOrdersParams struct {
+ // Page Pagination step, default: 1 (page = offset / limit)
+ Page float32 `url:"page,omitempty" json:"page,omitempty"`
+
+ // Limit Number of active orders to receive (default: 100, max: 500)
+ Limit float32 `url:"limit,omitempty" json:"limit,omitempty"`
+
+ // SrcChain Source chain of cross chain
+ SrcChain float32 `url:"srcChain,omitempty" json:"srcChain,omitempty"`
+
+ // DstChain Destination chain of cross chain
+ DstChain float32 `url:"dstChain,omitempty" json:"dstChain,omitempty"`
+}
+
+// OrderApiControllerGetSettlementContractParams defines parameters for OrderApiControllerGetSettlementContract.
+type OrderApiControllerGetSettlementContractParams struct {
+ // ChainId Chain ID
+ ChainId float32 `url:"chainId,omitempty" json:"chainId,omitempty"`
+}
+
+// OrderApiControllerGetOrdersByMakerParams defines parameters for OrderApiControllerGetOrdersByMaker.
+type OrderApiControllerGetOrdersByMakerParams struct {
+ // Page Pagination step, default: 1 (page = offset / limit)
+ Page float32 `url:"page,omitempty" json:"page,omitempty"`
+
+ // Limit Number of active orders to receive (default: 100, max: 500)
+ Limit float32 `url:"limit,omitempty" json:"limit,omitempty"`
+
+ // TimestampFrom timestampFrom in milliseconds for interval [timestampFrom, timestampTo)
+ TimestampFrom float32 `url:"timestampFrom,omitempty" json:"timestampFrom,omitempty"`
+
+ // TimestampTo timestampTo in milliseconds for interval [timestampFrom, timestampTo)
+ TimestampTo float32 `url:"timestampTo,omitempty" json:"timestampTo,omitempty"`
+
+ // SrcToken Find history by the given source token
+ SrcToken string `url:"srcToken,omitempty" json:"srcToken,omitempty"`
+
+ // DstToken Find history by the given destination token
+ DstToken string `url:"dstToken,omitempty" json:"dstToken,omitempty"`
+
+ // WithToken Find history items by source or destination token
+ WithToken string `url:"withToken,omitempty" json:"withToken,omitempty"`
+
+ // DstChainId Destination chain of cross chain
+ DstChainId float32 `url:"dstChainId,omitempty" json:"dstChainId,omitempty"`
+
+ // SrcChainId Source chain of cross chain
+ SrcChainId float32 `url:"srcChainId,omitempty" json:"srcChainId,omitempty"`
+
+ // ChainId chainId for looking by dstChainId == chainId OR srcChainId == chainId
+ ChainId float32 `url:"chainId,omitempty" json:"chainId,omitempty"`
+}
+
+// OrderApiControllerGetOrdersByOrderHashesJSONRequestBody defines body for OrderApiControllerGetOrdersByOrderHashes for application/json ContentType.
+type OrderApiControllerGetOrdersByOrderHashesJSONRequestBody = OrdersByHashesInput
diff --git a/sdk-clients/fusionplus/fusionplus_quoter_types.gen.go b/sdk-clients/fusionplus/fusionplus_quoter_types.gen.go
new file mode 100644
index 00000000..d24df908
--- /dev/null
+++ b/sdk-clients/fusionplus/fusionplus_quoter_types.gen.go
@@ -0,0 +1,239 @@
+// Package fusionplus provides primitives to interact with the openapi HTTP API.
+//
+// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT.
+package fusionplus
+
+// Defines values for GetQuoteOutputRecommendedPreset.
+const (
+ Custom GetQuoteOutputRecommendedPreset = "custom"
+ Fast GetQuoteOutputRecommendedPreset = "fast"
+ Medium GetQuoteOutputRecommendedPreset = "medium"
+ Slow GetQuoteOutputRecommendedPreset = "slow"
+)
+
+// AuctionPoint defines model for AuctionPoint.
+type AuctionPoint struct {
+ Coefficient float32 `json:"coefficient"`
+ Delay float32 `json:"delay"`
+}
+
+// BuildOrderBody defines model for BuildOrderBody.
+type BuildOrderBody struct {
+ Quote GetQuoteOutput `json:"quote"`
+
+ // SecretsHashList keccak256(secret)[]
+ SecretsHashList string `json:"secretsHashList"`
+}
+
+// BuildOrderOutput defines model for BuildOrderOutput.
+type BuildOrderOutput struct {
+ // Extension CrossChain order extension
+ Extension string `json:"extension"`
+
+ // OrderHash Hash of CrossChain order
+ OrderHash string `json:"orderHash"`
+
+ // TypedData EIP712 Typed Data
+ TypedData map[string]interface{} `json:"typedData"`
+}
+
+// CustomPresetParams defines model for CustomPresetParams.
+type CustomPresetParams = map[string]interface{}
+
+// GasCostConfig defines model for GasCostConfig.
+type GasCostConfig struct {
+ GasBumpEstimate float32 `json:"gasBumpEstimate"`
+ GasPriceEstimate string `json:"gasPriceEstimate"`
+}
+
+// GetQuoteOutput defines model for GetQuoteOutput.
+type GetQuoteOutput struct {
+ // DstEscrowFactory Escrow factory contract address at destination chain
+ DstEscrowFactory string `json:"dstEscrowFactory"`
+ DstSafetyDeposit string `json:"dstSafetyDeposit"`
+ DstTokenAmount string `json:"dstTokenAmount"`
+ Presets QuotePresets `json:"presets"`
+ Prices PairCurrency `json:"prices"`
+
+ // QuoteId Current generated quote id, should be passed with order
+ QuoteId map[string]interface{} `json:"quoteId"`
+
+ // RecommendedPreset suggested preset
+ RecommendedPreset GetQuoteOutputRecommendedPreset `json:"recommendedPreset"`
+
+ // SrcEscrowFactory Escrow factory contract address at source chain
+ SrcEscrowFactory string `json:"srcEscrowFactory"`
+ SrcSafetyDeposit string `json:"srcSafetyDeposit"`
+ SrcTokenAmount string `json:"srcTokenAmount"`
+ TimeLocks TimeLocks `json:"timeLocks"`
+ Volume PairCurrency `json:"volume"`
+
+ // Whitelist current executors whitelist addresses
+ Whitelist []string `json:"whitelist"`
+}
+
+// GetQuoteOutputRecommendedPreset suggested preset
+type GetQuoteOutputRecommendedPreset string
+
+// PairCurrency defines model for PairCurrency.
+type PairCurrency struct {
+ Usd TokenPair `json:"usd"`
+}
+
+// Preset defines model for Preset.
+type Preset struct {
+ AllowMultipleFills bool `json:"allowMultipleFills"`
+ AllowPartialFills bool `json:"allowPartialFills"`
+ AuctionDuration float32 `json:"auctionDuration"`
+ AuctionEndAmount string `json:"auctionEndAmount"`
+ AuctionStartAmount string `json:"auctionStartAmount"`
+ CostInDstToken string `json:"costInDstToken"`
+ ExclusiveResolver map[string]interface{} `json:"exclusiveResolver"`
+ GasCost GasCostConfig `json:"gasCost"`
+ InitialRateBump float32 `json:"initialRateBump"`
+ Points []AuctionPoint `json:"points"`
+ SecretsCount float32 `json:"secretsCount"`
+
+ // StartAmount auction start amount taking into account gas bump
+ StartAmount string `json:"startAmount"`
+ StartAuctionIn float32 `json:"startAuctionIn"`
+}
+
+// QuotePresets defines model for QuotePresets.
+type QuotePresets struct {
+ Custom *Preset `json:"custom,omitempty"`
+ Fast Preset `json:"fast"`
+ Medium Preset `json:"medium"`
+ Slow Preset `json:"slow"`
+}
+
+// TimeLocks defines model for TimeLocks.
+type TimeLocks struct {
+ DstCancellation float32 `json:"dstCancellation"`
+ DstPublicWithdrawal float32 `json:"dstPublicWithdrawal"`
+ DstWithdrawal float32 `json:"dstWithdrawal"`
+ SrcCancellation float32 `json:"srcCancellation"`
+ SrcPublicCancellation float32 `json:"srcPublicCancellation"`
+ SrcPublicWithdrawal float32 `json:"srcPublicWithdrawal"`
+ SrcWithdrawal float32 `json:"srcWithdrawal"`
+}
+
+// TokenPair defines model for TokenPair.
+type TokenPair struct {
+ DstToken string `json:"dstToken"`
+ SrcToken string `json:"srcToken"`
+}
+
+// QuoterControllerBuildQuoteTypedDataParams defines parameters for QuoterControllerBuildQuoteTypedData.
+type QuoterControllerBuildQuoteTypedDataParams struct {
+ // SrcChain Id of source chain
+ SrcChain float32 `url:"srcChain" json:"srcChain"`
+
+ // DstChain Id of destination chain
+ DstChain float32 `url:"dstChain" json:"dstChain"`
+
+ // SrcTokenAddress Address of "SOURCE" token
+ SrcTokenAddress string `url:"srcTokenAddress" json:"srcTokenAddress"`
+
+ // DstTokenAddress Address of "DESTINATION" token
+ DstTokenAddress string `url:"dstTokenAddress" json:"dstTokenAddress"`
+
+ // Amount Amount to take from "SOURCE" token to get "DESTINATION" token
+ Amount float32 `url:"amount" json:"amount"`
+
+ // WalletAddress An address of the wallet or contract who will create Fusion order
+ WalletAddress string `url:"walletAddress" json:"walletAddress"`
+
+ // Fee fee in bps format, 1% is equal to 100bps
+ Fee float32 `url:"fee,omitempty" json:"fee,omitempty"`
+
+ // Source Frontend or some other source selector
+ Source string `url:"source,omitempty" json:"source,omitempty"`
+
+ // IsPermit2 permit2 allowance transfer encoded call
+ IsPermit2 string `url:"isPermit2,omitempty" json:"isPermit2,omitempty"`
+
+ // IsMobile Enabled flag allows to save quote for Mobile History
+ IsMobile string `url:"isMobile,omitempty" json:"isMobile,omitempty"`
+
+ // FeeReceiver In case fee non zero -> the fee will be transferred to this address
+ FeeReceiver string `url:"feeReceiver,omitempty" json:"feeReceiver,omitempty"`
+
+ // Permit permit, user approval sign
+ Permit string `url:"permit,omitempty" json:"permit,omitempty"`
+
+ // Preset fast/medium/slow/custom
+ Preset string `url:"preset,omitempty" json:"preset,omitempty"`
+}
+
+// QuoterControllerGetQuoteParams defines parameters for QuoterControllerGetQuote.
+type QuoterControllerGetQuoteParams struct {
+ // SrcChain Id of source chain
+ SrcChain float32 `url:"srcChain" json:"srcChain"`
+
+ // DstChain Id of destination chain
+ DstChain float32 `url:"dstChain" json:"dstChain"`
+
+ // SrcTokenAddress Address of "SOURCE" token in source chain
+ SrcTokenAddress string `url:"srcTokenAddress" json:"srcTokenAddress"`
+
+ // DstTokenAddress Address of "DESTINATION" token in destination chain
+ DstTokenAddress string `url:"dstTokenAddress" json:"dstTokenAddress"`
+
+ // Amount Amount to take from "SOURCE" token to get "DESTINATION" token
+ Amount float32 `url:"amount" json:"amount"`
+
+ // WalletAddress An address of the wallet or contract in source chain who will create Fusion order
+ WalletAddress string `url:"walletAddress" json:"walletAddress"`
+
+ // EnableEstimate if enabled then get estimation from 1inch swap builder and generates quoteId, by default is false
+ EnableEstimate bool `url:"enableEstimate" json:"enableEstimate"`
+
+ // Fee fee in bps format, 1% is equal to 100bps
+ Fee float32 `url:"fee,omitempty" json:"fee,omitempty"`
+
+ // IsPermit2 permit2 allowance transfer encoded call
+ IsPermit2 string `url:"isPermit2,omitempty" json:"isPermit2,omitempty"`
+
+ // Permit permit, user approval sign
+ Permit string `url:"permit,omitempty" json:"permit,omitempty"`
+}
+
+// QuoterControllerGetQuoteWithCustomPresetsParams defines parameters for QuoterControllerGetQuoteWithCustomPresets.
+type QuoterControllerGetQuoteWithCustomPresetsParams struct {
+ // SrcChain Id of source chain
+ SrcChain float32 `url:"srcChain" json:"srcChain"`
+
+ // DstChain Id of destination chain
+ DstChain float32 `url:"dstChain" json:"dstChain"`
+
+ // SrcTokenAddress Address of "SOURCE" token
+ SrcTokenAddress string `url:"srcTokenAddress" json:"srcTokenAddress"`
+
+ // DstTokenAddress Address of "DESTINATION" token
+ DstTokenAddress string `url:"dstTokenAddress" json:"dstTokenAddress"`
+
+ // Amount Amount to take from "SOURCE" token to get "DESTINATION" token
+ Amount float32 `url:"amount" json:"amount"`
+
+ // WalletAddress An address of the wallet or contract who will create Fusion order
+ WalletAddress string `url:"walletAddress" json:"walletAddress"`
+
+ // EnableEstimate if enabled then get estimation from 1inch swap builder and generates quoteId, by default is false
+ EnableEstimate bool `url:"enableEstimate" json:"enableEstimate"`
+
+ // Fee fee in bps format, 1% is equal to 100bps
+ Fee float32 `url:"fee,omitempty" json:"fee,omitempty"`
+
+ // IsPermit2 permit2 allowance transfer encoded call
+ IsPermit2 string `url:"isPermit2,omitempty" json:"isPermit2,omitempty"`
+
+ // Permit permit, user approval sign
+ Permit string `url:"permit,omitempty" json:"permit,omitempty"`
+}
+
+// QuoterControllerBuildQuoteTypedDataJSONRequestBody defines body for QuoterControllerBuildQuoteTypedData for application/json ContentType.
+type QuoterControllerBuildQuoteTypedDataJSONRequestBody = BuildOrderBody
+
+// QuoterControllerGetQuoteWithCustomPresetsJSONRequestBody defines body for QuoterControllerGetQuoteWithCustomPresets for application/json ContentType.
+type QuoterControllerGetQuoteWithCustomPresetsJSONRequestBody = CustomPresetParams
diff --git a/sdk-clients/fusionplus/fusionplus_relayer_types.gen.go b/sdk-clients/fusionplus/fusionplus_relayer_types.gen.go
new file mode 100644
index 00000000..f7505423
--- /dev/null
+++ b/sdk-clients/fusionplus/fusionplus_relayer_types.gen.go
@@ -0,0 +1,68 @@
+// Package fusionplus provides primitives to interact with the openapi HTTP API.
+//
+// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT.
+package fusionplus
+
+// OrderInput defines model for OrderInput.
+type OrderInput struct {
+ // Maker Source chain address of the maker (wallet or contract address)
+ Maker string `json:"maker"`
+
+ // MakerAsset Source chain address of the maker asset
+ MakerAsset string `json:"makerAsset"`
+
+ // MakerTraits Includes some flags like: allow multiple fills, is partial fill allowed or not, price improvement, nonce, deadline etc. See maker-traits.ts
+ MakerTraits string `json:"makerTraits"`
+
+ // MakingAmount Order maker's token amount
+ MakingAmount string `json:"makingAmount"`
+
+ // Receiver Destination chain address of the wallet or contract who will receive filled amount
+ Receiver string `json:"receiver"`
+ Salt string `json:"salt"`
+
+ // TakerAsset Destination chain address of the taker asset
+ TakerAsset string `json:"takerAsset"`
+
+ // TakingAmount Order taker's token amount
+ TakingAmount string `json:"takingAmount"`
+}
+
+// SecretInput defines model for SecretInput.
+type SecretInput struct {
+ OrderHash string `json:"orderHash"`
+
+ // Secret A secret for the fill hashlock
+ Secret string `json:"secret"`
+}
+
+// SignedOrderInput defines model for SignedOrderInput.
+type SignedOrderInput struct {
+ // Extension An interaction call data. ABI encoded a set of makerAssetSuffix, takerAssetSuffix, makingAmountGetter, takingAmountGetter, predicate, permit, preInteraction, postInteraction.Lowest 160 bits of the order salt must be equal to the lowest 160 bits of the extension hash. See escrow-extension.ts>
+ Extension string `json:"extension"`
+ Order OrderInput `json:"order"`
+
+ // QuoteId Quote id of the quote with presets
+ QuoteId string `json:"quoteId"`
+
+ // SecretHashes Secret Hashes, required for order with multiple fills allowed. keccak256(secret(idx))
+ SecretHashes []string `json:"secretHashes,omitempty"`
+
+ // Signature Signature of the cross chain order typed data (using signTypedData v4)
+ Signature string `json:"signature"`
+
+ // SrcChainId Source chain id
+ SrcChainId float32 `json:"srcChainId"`
+}
+
+// RelayerControllerSubmitManyJSONBody defines parameters for RelayerControllerSubmitMany.
+type RelayerControllerSubmitManyJSONBody = []string
+
+// RelayerControllerSubmitJSONRequestBody defines body for RelayerControllerSubmit for application/json ContentType.
+type RelayerControllerSubmitJSONRequestBody = SignedOrderInput
+
+// RelayerControllerSubmitManyJSONRequestBody defines body for RelayerControllerSubmitMany for application/json ContentType.
+type RelayerControllerSubmitManyJSONRequestBody = RelayerControllerSubmitManyJSONBody
+
+// RelayerControllerSubmitSecretsJSONRequestBody defines body for RelayerControllerSubmitSecrets for application/json ContentType.
+type RelayerControllerSubmitSecretsJSONRequestBody = SecretInput
diff --git a/sdk-clients/fusionplus/fusionplus_types_extended.go b/sdk-clients/fusionplus/fusionplus_types_extended.go
new file mode 100644
index 00000000..2493d83e
--- /dev/null
+++ b/sdk-clients/fusionplus/fusionplus_types_extended.go
@@ -0,0 +1,290 @@
+package fusionplus
+
+import (
+ "math/big"
+
+ "github.com/1inch/1inch-sdk-go/sdk-clients/fusion"
+ "github.com/1inch/1inch-sdk-go/sdk-clients/orderbook"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+type GetOrderByOrderHashParams struct {
+ Hash string `url:"hash" json:"hash"`
+}
+
+// GetOrderFillsByHashOutputFixed replaces the DstTokenPriceUsd and SrcTokenPriceUsd fields with string and changes Points to be an array
+type GetOrderFillsByHashOutputFixed struct {
+ // ApproximateTakingAmount Approximate amount of the takerAsset being requested by the maker in dst chain.
+ ApproximateTakingAmount string `json:"approximateTakingAmount"`
+
+ // AuctionDuration Unix timestamp in milliseconds
+ AuctionDuration float32 `json:"auctionDuration"`
+
+ // AuctionStartDate Unix timestamp in milliseconds
+ AuctionStartDate float32 `json:"auctionStartDate"`
+ CancelTx map[string]interface{} `json:"cancelTx"`
+
+ // Cancelable Is order cancelable
+ Cancelable bool `json:"cancelable"`
+
+ // CreatedAt Unix timestamp in milliseconds
+ CreatedAt float32 `json:"createdAt"`
+
+ // DstChainId Identifier of the chain where the taker asset is located.
+ DstChainId float32 `json:"dstChainId"`
+ DstTokenPriceUsd string `json:"dstTokenPriceUsd"`
+
+ // Extension An interaction call data. ABI encoded set of makerAssetSuffix, takerAssetSuffix, makingAmountGetter, takingAmountGetter, predicate, permit, preInteraction, postInteraction.If extension exists then lowest 160 bits of the order salt must be equal to the lowest 160 bits of the extension hash
+ Extension string `json:"extension"`
+
+ // Fills Fills
+ Fills []FillOutputDto `json:"fills"`
+
+ // InitialRateBump Initial rate bump
+ InitialRateBump float32 `json:"initialRateBump"`
+ Order LimitOrderV4StructOutput `json:"order"`
+
+ // OrderHash Order hash
+ OrderHash string `json:"orderHash"`
+ Points []AuctionPointOutput `json:"points"`
+
+ // SrcChainId Identifier of the chain where the maker asset is located.
+ SrcChainId float32 `json:"srcChainId"`
+ SrcTokenPriceUsd string `json:"srcTokenPriceUsd"`
+
+ // Status Order status
+ Status GetOrderFillsByHashOutputStatus `json:"status"`
+
+ // TakerAsset Identifier of the asset being requested by the maker in exchange in dst chain.
+ TakerAsset string `json:"takerAsset"`
+
+ // TimeLocks TimeLocks without deployedAt
+ TimeLocks string `json:"timeLocks"`
+
+ // Validation Order validation status
+ Validation GetOrderFillsByHashOutputValidation `json:"validation"`
+}
+
+// QuoterControllerGetQuoteParamsFixed defines parameters for QuoterControllerGetQuote.
+type QuoterControllerGetQuoteParamsFixed struct {
+ // SrcChain Id of source chain
+ SrcChain float32 `url:"srcChain" json:"srcChain"`
+
+ // DstChain Id of destination chain
+ DstChain float32 `url:"dstChain" json:"dstChain"`
+
+ // SrcTokenAddress Address of "SOURCE" token in source chain
+ SrcTokenAddress string `url:"srcTokenAddress" json:"srcTokenAddress"`
+
+ // DstTokenAddress Address of "DESTINATION" token in destination chain
+ DstTokenAddress string `url:"dstTokenAddress" json:"dstTokenAddress"`
+
+ // Amount to take from "SOURCE" token to get "DESTINATION" token
+ Amount string `url:"amount" json:"amount"`
+
+ // WalletAddress An address of the wallet or contract in source chain who will create Fusion order
+ WalletAddress string `url:"walletAddress" json:"walletAddress"`
+
+ // EnableEstimate if enabled then get estimation from 1inch swap builder and generates quoteId, by default is false
+ EnableEstimate bool `url:"enableEstimate" json:"enableEstimate"`
+
+ // Fee in bps format, 1% is equal to 100bps
+ Fee *big.Int `url:"fee,omitempty" json:"fee,omitempty"` // This is changed from float32 to *big.Int
+
+ // IsPermit2 permit2 allowance transfer encoded call
+ IsPermit2 bool `url:"isPermit2,omitempty" json:"isPermit2,omitempty"` // This is changed from string to bool
+
+ // Permit permit, user approval sign
+ Permit string `url:"permit,omitempty" json:"permit,omitempty"`
+}
+
+// GetQuoteOutputFixed defines model for GetQuoteOutput. QuoteId, DstSafetyDeposit, and SrcSafetyDeposit have been fixed
+type GetQuoteOutputFixed struct {
+ // DstEscrowFactory Escrow factory contract address at destination chain
+ DstEscrowFactory string `json:"dstEscrowFactory"`
+ DstSafetyDeposit string `json:"dstSafetyDeposit"` // This is changed from string to *big.Int
+ DstTokenAmount string `json:"dstTokenAmount"`
+ Presets QuotePresets `json:"presets"`
+ Prices PairCurrency `json:"prices"`
+
+ // QuoteId Current generated quote id, should be passed with order
+ QuoteId string `json:"quoteId"` // This is changed from map[string]interface{} to string
+
+ // RecommendedPreset suggested preset
+ RecommendedPreset GetQuoteOutputRecommendedPreset `json:"recommendedPreset"`
+
+ // SrcEscrowFactory Escrow factory contract address at source chain
+ SrcEscrowFactory string `json:"srcEscrowFactory"`
+ SrcSafetyDeposit string `json:"srcSafetyDeposit"` // This is changed from string to *big.Int
+ SrcTokenAmount string `json:"srcTokenAmount"`
+ TimeLocks TimeLocks `json:"timeLocks"`
+ Volume PairCurrency `json:"volume"`
+
+ // Whitelist current executors whitelist addresses
+ Whitelist []string `json:"whitelist"`
+}
+
+type Order struct {
+ EscExtension *EscrowExtension
+ Inner orderbook.OrderData
+ SettlementExtension common.Address
+ OrderInfo CrossChainOrderDto
+ AuctionDetails *AuctionDetails
+ PostInteractionData *SettlementPostInteractionData
+ Extra ExtraData
+}
+
+type EscrowExtensionParams struct {
+ fusion.ExtensionParams
+ HashLock *HashLock
+ DstChainId float32
+ DstToken common.Address
+ SrcSafetyDeposit string
+ DstSafetyDeposit string
+ TimeLocks TimeLocks
+}
+
+type CrossChainOrderParams struct {
+ HashLock *HashLock
+ Preset GetQuoteOutputRecommendedPreset
+ Receiver string
+ Nonce *big.Int
+ Permit string
+ IsPermit2 bool
+ TakingFeeReceiver string
+ DelayAuctionStartTimeBy float32
+ /**
+ * Order will expire in `orderExpirationDelay` after auction ends
+ * Default 12s
+ */
+ OrderExpirationDelay uint32
+}
+
+type OrderParams struct {
+ HashLock *HashLock
+ SecretHashes []string
+ Permit string
+ Receiver string
+ Preset GetQuoteOutputRecommendedPreset
+ Nonce *big.Int
+ Fee TakingFeeInfo
+ Source string
+ IsPermit2 bool
+ TakingFeeReceiver string
+ CustomPreset CustomPreset
+}
+
+type TakingFeeInfo struct {
+ TakingFeeBps *big.Int // 100 == 1%
+ TakingFeeReceiver common.Address
+}
+
+type CustomPreset struct {
+ AuctionDuration int `json:"auctionDuration"`
+ AuctionStartAmount string `json:"auctionStartAmount"`
+ AuctionEndAmount string `json:"auctionEndAmount"`
+ Points []CustomPresetPoint `json:"points,omitempty"`
+}
+
+type CustomPresetPoint struct {
+ ToTokenAmount string `json:"toTokenAmount"`
+ Delay int `json:"delay"`
+}
+
+type AuctionDetails struct {
+ StartTime uint32 `json:"startTime"`
+ Duration uint32 `json:"duration"`
+ InitialRateBump uint32 `json:"initialRateBump"`
+ Points []AuctionPointClassFixed `json:"points"`
+ GasCost GasCostConfigClassFixed `json:"gasCost"`
+}
+
+type AuctionPointClassFixed struct {
+ Coefficient uint32 `json:"coefficient"`
+ Delay uint16 `json:"delay"`
+}
+
+type GasCostConfigClassFixed struct {
+ GasBumpEstimate uint32 `json:"gasBumpEstimate"`
+ GasPriceEstimate uint32 `json:"gasPriceEstimate"`
+}
+
+type PreparedOrder struct {
+ Order Order `json:"order"`
+ Hash string `json:"hash"`
+ QuoteId string `json:"quoteId"`
+ LimitOrder *orderbook.Order
+}
+
+type AdditionalParams struct {
+ NetworkId int
+ FromAddress string
+ PrivateKey string
+}
+
+type Details struct {
+ Auction *AuctionDetails `json:"auction"`
+ Fees Fees `json:"fees"`
+ Whitelist []AuctionWhitelistItem
+ ResolvingStartTime *big.Int
+}
+
+type Fees struct {
+ IntFee IntegratorFee
+ BankFee *big.Int
+}
+
+type IntegratorFee struct {
+ Ratio *big.Int
+ Receiver common.Address
+}
+
+type AuctionWhitelistItem struct {
+ Address common.Address
+ /**
+ * Timestamp in sec at which address can start resolving
+ */
+ AllowFrom *big.Int
+}
+
+type ExtraParams struct {
+ Nonce *big.Int
+ Permit string
+ AllowPartialFills bool
+ AllowMultipleFills bool
+ OrderExpirationDelay uint32
+ EnablePermit2 bool
+ Source string
+ unwrapWeth bool
+}
+
+type SettlementSuffixData struct {
+ Whitelist []AuctionWhitelistItem
+ IntegratorFee *IntegratorFee
+ BankFee *big.Int
+ ResolvingStartTime *big.Int
+ CustomReceiver common.Address
+}
+
+type WhitelistItem struct {
+ /**
+ * last 10 bytes of address, no 0x prefix
+ */
+ AddressHalf string
+ /**
+ * Delay from previous resolver in seconds
+ * For first resolver delay from `resolvingStartTime`
+ */
+ Delay *big.Int
+}
+
+type ExtraData struct {
+ UnwrapWETH bool
+ Nonce *big.Int
+ Permit string
+ AllowPartialFills bool
+ AllowMultipleFills bool
+ OrderExpirationDelay uint32
+ EnablePermit2 bool
+ Source string
+}
diff --git a/sdk-clients/fusionplus/hashlock.go b/sdk-clients/fusionplus/hashlock.go
new file mode 100644
index 00000000..bf0b1d32
--- /dev/null
+++ b/sdk-clients/fusionplus/hashlock.go
@@ -0,0 +1,240 @@
+package fusionplus
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "log"
+ "math/big"
+ "strings"
+
+ "github.com/1inch/1inch-sdk-go/internal/keccak"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/crypto"
+ "golang.org/x/crypto/sha3"
+)
+
+type HashLock struct {
+ Value string
+}
+
+func ForSingleFill(secret string) (*HashLock, error) {
+ hashlock, err := HashSecret(secret)
+ if err != nil {
+ return nil, err
+ }
+ return &HashLock{
+ hashlock,
+ }, nil
+}
+
+func ForMultipleFills(leaves []string) (*HashLock, error) {
+ // Assertion to check the number of leaves
+ if len(leaves) <= 2 {
+ return nil, errors.New("leaves array must be greater than 2. Or use HashLock.forSingleFill")
+ }
+
+ tree := MakeTree(leaves)
+
+ root := tree.tree[0]
+
+ // Convert root from []byte to big.Int
+ rootAsBytes := hexutil.MustDecode(root)
+ rootBig := new(big.Int).SetBytes(rootAsBytes)
+
+ // Specify the mask starting bit and length (for example, bits 240 to 256 for a 256-bit value)
+ maskStart := 240
+ maskLength := 16
+
+ countValue := big.NewInt(int64(len(leaves) - 1))
+
+ // Apply the mask using the SetMask function
+ rootWithCount := SetMask(rootBig, uint(maskStart), uint(maskLength), countValue)
+
+ // Convert the modified root back to []byte
+ rootWithCountBytes := rootWithCount.Bytes()
+
+ // Ensure the byte slice has the correct length (32 bytes)
+ if len(rootWithCountBytes) < 32 {
+ // Pad the byte slice to 32 bytes if needed
+ padding := make([]byte, 32-len(rootWithCountBytes))
+ rootWithCountBytes = append(padding, rootWithCountBytes...)
+ }
+
+ // Create and return the HashLock
+ return &HashLock{fmt.Sprintf("0x%x", rootWithCountBytes)}, nil
+}
+
+func GetMerkleLeaves(secrets []string) ([]string, error) {
+ secretHashes := make([]string, len(secrets))
+ for i, secret := range secrets {
+ hash, err := HashSecret(secret)
+ if err != nil {
+ return nil, err
+ }
+ secretHashes[i] = hash
+ }
+ return GetMerkleLeavesFromSecretHashes(secretHashes)
+}
+
+func GetMerkleLeavesFromSecretHashes(secretHashes []string) ([]string, error) {
+ var leaves []string
+ for idx, s := range secretHashes {
+ hash, err := solidityPackedKeccak256([]string{"uint64", "bytes32"}, []interface{}{idx, s})
+ if err != nil {
+ return nil, err
+ }
+ leaves = append(leaves, hash)
+ }
+ return leaves, nil
+}
+
+func GetRandomBytes32() (string, error) {
+ // Create a byte slice with a length of 32
+ bytes := make([]byte, 32)
+
+ // Read random bytes into the slice
+ _, err := rand.Read(bytes)
+ if err != nil {
+ return "", err
+ }
+
+ // Convert bytes to a hexadecimal string and prepend "0x"
+ return "0x" + hex.EncodeToString(bytes), nil
+}
+
+func SetMask(original *big.Int, maskStart, maskLength uint, value *big.Int) *big.Int {
+ // Create a mask that only affects the bits we want to modify
+ mask := new(big.Int).Lsh(big.NewInt(1), maskLength)
+ mask.Sub(mask, big.NewInt(1))
+ mask.Lsh(mask, maskStart)
+
+ // Clear the bits of the original value that are set in the mask
+ originalCleared := new(big.Int).AndNot(original, mask)
+
+ // Shift the value into the correct position
+ maskedValue := new(big.Int).Lsh(value, maskStart)
+
+ // Combine cleared original with the masked value
+ result := new(big.Int).Or(originalCleared, maskedValue)
+
+ return result
+}
+
+// Keccak256SortedHash takes two byte slices, sorts them lexicographically, concatenates them, and hashes the result.
+func Keccak256SortedHash(a, b []byte) []byte {
+ // Sort the inputs lexicographically
+ if bytes.Compare(a, b) > 0 {
+ a, b = b, a
+ }
+
+ // Concatenate the sorted byte slices
+ concatenated := append(a, b...)
+
+ // Apply the Keccak-256 hash on the concatenated result
+ return crypto.Keccak256(concatenated)
+}
+
+func leftChildIndex(i int) int {
+ return 2*i + 1
+}
+func rightChildIndex(i int) int {
+ return 2*i + 2
+}
+
+func getBytesCount(hex string) int {
+ return len(trim0x(hex)) / 2
+}
+
+func HashSecret(secret string) (string, error) {
+ if !isHexBytes(secret) || getBytesCount(secret) != 32 {
+ return "", fmt.Errorf("secret length must be 32 bytes hex encoded. Got %s", secret)
+ }
+
+ hexBytes, err := hex.DecodeString(secret[2:])
+ if err != nil {
+ log.Fatalf("Failed to decode hex string: %v", err)
+ }
+
+ return keccak.Keccak256Legacy(hexBytes), nil
+}
+
+func hexlify(data []byte) string {
+ return "0x" + hex.EncodeToString(data)
+}
+
+func concat(datas [][]byte) []byte {
+ result := []byte{}
+ for _, d := range datas {
+ result = append(result, d...)
+ }
+ return result
+}
+
+func solidityPacked(types []string, values []interface{}) ([]byte, error) {
+ if len(types) != len(values) {
+ return nil, fmt.Errorf("wrong number of values; expected %d", len(types))
+ }
+
+ var tight [][]byte
+ for i, t := range types {
+ packed, err := pack(t, values[i])
+ if err != nil {
+ return nil, err
+ }
+ tight = append(tight, packed)
+ }
+
+ return concat(tight), nil
+}
+
+func solidityPackedKeccak256(types []string, values []interface{}) (string, error) {
+ packed, err := solidityPacked(types, values)
+ if err != nil {
+ return "", err
+ }
+
+ hash := sha3.NewLegacyKeccak256()
+ hash.Write(packed)
+ hashed := hash.Sum(nil)
+
+ return hexlify(hashed), nil
+}
+
+func pack(typ string, value interface{}) ([]byte, error) {
+ switch typ {
+ case "uint64":
+ // Pack uint64 as big-endian 8-byte array
+ v, ok := value.(int)
+ if !ok {
+ return nil, fmt.Errorf("expected int for uint64 type, got %T", value)
+ }
+ bigInt := big.NewInt(int64(v))
+ packed := bigInt.FillBytes(make([]byte, 8))
+ return packed, nil
+
+ case "bytes32":
+ // Pack bytes32 as exactly 32 bytes
+ s, ok := value.(string)
+ if !ok {
+ return nil, fmt.Errorf("expected string for bytes32 type, got %T", value)
+ }
+ bytes, err := hex.DecodeString(s[2:]) // Remove "0x" prefix
+ if err != nil {
+ return nil, err
+ }
+ if len(bytes) != 32 {
+ return nil, fmt.Errorf("bytes32 value must be 32 bytes, got %d bytes", len(bytes))
+ }
+ return bytes, nil
+
+ default:
+ return nil, fmt.Errorf("unsupported type: %s", typ)
+ }
+}
+
+func trim0x(s string) string {
+ return strings.TrimPrefix(s, "0x")
+}
diff --git a/sdk-clients/fusionplus/hashlock_test.go b/sdk-clients/fusionplus/hashlock_test.go
new file mode 100644
index 00000000..48795f90
--- /dev/null
+++ b/sdk-clients/fusionplus/hashlock_test.go
@@ -0,0 +1,75 @@
+package fusionplus
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestHashLockForSingleFill(t *testing.T) {
+ result, err := ForSingleFill("0x531d1d2d7a594f1c7e413b074c7b693161486b5c495d457748144a01795c6a45")
+ require.NoError(t, err)
+ expected := "0x9f65fdcf781d4320c2dde70da02a1fe916d595dc1817149cc4758fd6a4bfd830"
+ assert.Equal(t, expected, result.Value, "HashLock for single fill is incorrect")
+}
+
+func TestHashLockForMultipleFills(t *testing.T) {
+ secrets := []string{
+ "0x531d1d2d7a594f1c7e413b074c7b693161486b5c495d457748144a01795c6a45",
+ "0x657812136b5000651d5e18516d764b5e661a681c760d3c3c4c15751020757823",
+ "0x62071a322351281f04756576270c362a6e5b395e3b0f68027f231141555c3d43",
+ }
+ leaves, err := GetMerkleLeaves(secrets)
+ require.NoError(t, err)
+ result, err := ForMultipleFills(leaves)
+ require.NoError(t, err)
+ expected := "0x000292766d9172e4b4983ee4d4b6d511cdbcbef175c7e3e1b1554d513e1ab724"
+ assert.Equal(t, expected, result.Value, "HashLock for multiple fills is incorrect")
+}
+
+func TestHashLockIsBytes32(t *testing.T) {
+ secrets := []string{
+ "0x6466643931343237333333313437633162386632316365646666323931643738",
+ "0x3131353932633266343034343466363562333230313837353438356463616130",
+ "0x6634376135663837653765303462346261616566383430303662303336386635",
+ }
+ leaves, err := GetMerkleLeaves(secrets)
+ require.NoError(t, err)
+ result, err := ForMultipleFills(leaves)
+ require.NoError(t, err)
+ bytes := getBytesCount(result.Value)
+ assert.Equal(t, 32, bytes, "HashLock result length is not 32 bytes")
+}
+
+func TestHashLockGetProof(t *testing.T) {
+ secrets := []string{
+ "0x6466643931343237333333313437633162386632316365646666323931643738",
+ "0x3131353932633266343034343466363562333230313837353438356463616130",
+ "0x6634376135663837653765303462346261616566383430303662303336386635",
+ }
+ leaves, err := GetMerkleLeaves(secrets)
+ require.NoError(t, err)
+ result, err := GetProof(leaves, 0)
+ require.NoError(t, err)
+ expected := []string{
+ "0x540daf363747246d40b31da95b3ef1c1497e22e9a56b70d117c835839822c95f",
+ }
+ assert.Equal(t, expected, result, "HashLock proof is incorrect")
+}
+
+func TestHashLockGetProof2(t *testing.T) {
+ secrets := []string{
+ "0x531d1d2d7a594f1c7e413b074c7b693161486b5c495d457748144a01795c6a45",
+ "0x657812136b5000651d5e18516d764b5e661a681c760d3c3c4c15751020757823",
+ "0x62071a322351281f04756576270c362a6e5b395e3b0f68027f231141555c3d43",
+ }
+ leaves, err := GetMerkleLeaves(secrets)
+ require.NoError(t, err)
+ result, err := GetProof(leaves, 0)
+ require.NoError(t, err)
+ expected := []string{
+ "0xb19c79aa34d58e459ce8119c301a24f7a01b8080ced7f3d608093e9e67624729",
+ }
+ assert.Equal(t, expected, result, "HashLock proof is incorrect")
+}
diff --git a/sdk-clients/fusionplus/interaction.go b/sdk-clients/fusionplus/interaction.go
new file mode 100644
index 00000000..c2bb5c26
--- /dev/null
+++ b/sdk-clients/fusionplus/interaction.go
@@ -0,0 +1,44 @@
+package fusionplus
+
+import (
+ "encoding/hex"
+ "fmt"
+ "strings"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+type Interaction struct {
+ Target common.Address
+ Data string
+}
+
+func NewInteraction(target common.Address, data string) *Interaction {
+ if !isHexBytes(data) {
+ panic("Interaction data must be valid hex bytes")
+ }
+ return &Interaction{
+ Target: target,
+ Data: data,
+ }
+}
+
+func (i *Interaction) Encode() string {
+ return i.Target.String() + trim0x(i.Data)
+}
+
+func DecodeInteraction(bytes string) (*Interaction, error) {
+ if !isHexBytes(bytes) {
+ return nil, fmt.Errorf("invalid hex bytes: %s", bytes)
+ }
+
+ return &Interaction{
+ Target: common.HexToAddress(bytes[:42]),
+ Data: fmt.Sprintf("0x%s", bytes[42:]),
+ }, nil
+}
+
+func isHexBytes(s string) bool {
+ _, err := hex.DecodeString(strings.TrimPrefix(s, "0x"))
+ return err == nil
+}
diff --git a/sdk-clients/fusionplus/interaction_test.go b/sdk-clients/fusionplus/interaction_test.go
new file mode 100644
index 00000000..73732c71
--- /dev/null
+++ b/sdk-clients/fusionplus/interaction_test.go
@@ -0,0 +1,35 @@
+package fusionplus
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestInteraction(t *testing.T) {
+ tests := []struct {
+ name string
+ target common.Address
+ data string
+ }{
+ {
+ name: "Encode/Decode Interaction",
+ target: common.BigToAddress(big.NewInt(1337)),
+ data: "0xdeadbeef",
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ interaction := NewInteraction(tc.target, tc.data)
+ encoded := interaction.Encode()
+ decoded, err := DecodeInteraction(encoded)
+ require.NoError(t, err)
+ assert.Equal(t, interaction.Target, decoded.Target)
+ assert.Equal(t, interaction.Data, decoded.Data)
+ })
+ }
+}
diff --git a/sdk-clients/fusionplus/merkletree.go b/sdk-clients/fusionplus/merkletree.go
new file mode 100644
index 00000000..b6d717cc
--- /dev/null
+++ b/sdk-clients/fusionplus/merkletree.go
@@ -0,0 +1,113 @@
+package fusionplus
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "sort"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+type MyMerkleTree struct {
+ tree []string
+ leaves []string
+}
+
+func MakeTree(leaves []string) *MyMerkleTree {
+ leavesUnsorted := make([]string, len(leaves))
+ copy(leavesUnsorted, leaves)
+ sort.Strings(leaves)
+
+ tree := make([][]byte, len(leaves)*2-1)
+ for i, leaf := range leaves {
+ tree[len(tree)-1-i] = hexutil.MustDecode(leaf)
+ }
+
+ for i := len(tree) - len(leaves) - 1; i >= 0; i-- {
+ left := tree[leftChildIndex(i)]
+ rightIndex := rightChildIndex(i)
+ var right []byte
+
+ // Check if the right child is out of bounds and skip if necessary
+ if rightIndex >= len(tree) {
+ right = []byte{}
+ } else {
+ right = tree[rightIndex]
+ }
+
+ tree[i] = Keccak256SortedHash(left, right)
+ }
+
+ finalTree := make([]string, len(tree))
+ for i, node := range tree {
+ nodeAsHex := fmt.Sprintf("0x%x", node)
+ finalTree[i] = nodeAsHex
+ }
+
+ return &MyMerkleTree{
+ tree: finalTree,
+ leaves: leavesUnsorted,
+ }
+}
+
+func GetProof(leaves []string, index int) ([]string, error) {
+ if index < 0 || index >= len(leaves) {
+ return nil, errors.New("index out of bounds")
+ }
+
+ tree := MakeTree(leaves)
+
+ leafToProve := tree.leaves[index]
+
+ var leafIndexInTree int
+ var foundLeaf bool
+ for i, leaf := range tree.tree {
+ if leaf == leafToProve {
+ foundLeaf = true
+ leafIndexInTree = i
+ break
+ }
+ }
+ if !foundLeaf {
+ panic("Leaf not found in tree")
+ }
+
+ currentIndex := leafIndexInTree
+ var proof []string
+
+ // Traverse up the tree to build the proof.
+ for currentIndex > 0 {
+ siblingIndex := getSiblingIndex(currentIndex)
+
+ // Add the sibling hash to the proof.
+ if siblingIndex < len(tree.tree) {
+ siblingHash := tree.tree[siblingIndex]
+ proof = append(proof, siblingHash)
+ }
+
+ // Move to the parent index.
+ var err error
+ currentIndex, err = ParentIndex(currentIndex)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return proof, nil
+}
+
+func getSiblingIndex(i int) int {
+ if i <= 0 {
+ panic("Root has no siblings")
+ }
+ return i - int(math.Pow(-1, float64(i%2)))
+}
+
+// ParentIndex returns the parent index of a given index in the tree.
+func ParentIndex(i int) (int, error) {
+ if i > 0 {
+ return (i - 1) / 2, nil
+ }
+ return 0, errors.New("root has no parent")
+}
diff --git a/sdk-clients/fusionplus/nativetokenwrappers.go b/sdk-clients/fusionplus/nativetokenwrappers.go
new file mode 100644
index 00000000..5f1ec175
--- /dev/null
+++ b/sdk-clients/fusionplus/nativetokenwrappers.go
@@ -0,0 +1,31 @@
+package fusionplus
+
+import "github.com/ethereum/go-ethereum/common"
+
+const NativeToken = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
+
+type NetworkEnum int
+
+const (
+ ETHEREUM NetworkEnum = 1
+ POLYGON NetworkEnum = 137
+ BINANCE NetworkEnum = 56
+ ARBITRUM NetworkEnum = 42161
+ AVALANCHE NetworkEnum = 43114
+ OPTIMISM NetworkEnum = 10
+ FANTOM NetworkEnum = 250
+ GNOSIS NetworkEnum = 100
+ COINBASE NetworkEnum = 8453
+)
+
+var chainToWrapper = map[NetworkEnum]common.Address{
+ ETHEREUM: common.HexToAddress("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"),
+ BINANCE: common.HexToAddress("0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c"),
+ POLYGON: common.HexToAddress("0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270"),
+ ARBITRUM: common.HexToAddress("0x82af49447d8a07e3bd95bd0d56f35241523fbab1"),
+ AVALANCHE: common.HexToAddress("0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7"),
+ GNOSIS: common.HexToAddress("0xe91d153e0b41518a2ce8dd3d7944fa863463a97d"),
+ COINBASE: common.HexToAddress("0x4200000000000000000000000000000000000006"),
+ OPTIMISM: common.HexToAddress("0x4200000000000000000000000000000000000006"),
+ FANTOM: common.HexToAddress("0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83"),
+}
diff --git a/sdk-clients/fusionplus/order.go b/sdk-clients/fusionplus/order.go
new file mode 100644
index 00000000..f63178b1
--- /dev/null
+++ b/sdk-clients/fusionplus/order.go
@@ -0,0 +1,393 @@
+package fusionplus
+
+import (
+ "errors"
+ "fmt"
+ "math/big"
+ "strconv"
+ "time"
+
+ "github.com/1inch/1inch-sdk-go/common"
+ random_number_generation "github.com/1inch/1inch-sdk-go/internal/random-number-generation"
+ "github.com/1inch/1inch-sdk-go/sdk-clients/fusion"
+ "github.com/1inch/1inch-sdk-go/sdk-clients/orderbook"
+ geth_common "github.com/ethereum/go-ethereum/common"
+)
+
+var uint40Max = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 40), big.NewInt(1))
+
+func CreateFusionPlusOrderData(quoteParams QuoterControllerGetQuoteParamsFixed, quote *GetQuoteOutputFixed, orderParams OrderParams, wallet common.Wallet, chainId int) (*PreparedOrder, error) {
+
+ // TODO preset is already gotten earlier for the secret count
+ preset, err := GetPreset(quote.Presets, orderParams.Preset)
+ if err != nil {
+ return nil, fmt.Errorf("error getting preset: %v", err)
+ }
+
+ auctionPointsFusion := make([]fusion.AuctionPointClass, 0)
+ for _, point := range preset.Points {
+ auctionPointsFusion = append(auctionPointsFusion, fusion.AuctionPointClass{
+ Coefficient: point.Coefficient,
+ Delay: point.Delay,
+ })
+ }
+
+ gasCostsFusion := fusion.GasCostConfigClass{
+ GasBumpEstimate: preset.GasCost.GasBumpEstimate,
+ GasPriceEstimate: preset.GasCost.GasPriceEstimate,
+ }
+ presetFusion := &fusion.PresetClassFixed{
+ AllowMultipleFills: preset.AllowMultipleFills,
+ //ExclusiveResolver: preset.ExclusiveResolver, // TODO This is not working for fusion at the moment
+ AllowPartialFills: preset.AllowPartialFills,
+ AuctionDuration: preset.AuctionDuration,
+ AuctionEndAmount: preset.AuctionEndAmount,
+ AuctionStartAmount: preset.AuctionStartAmount,
+ GasCost: gasCostsFusion,
+ InitialRateBump: preset.InitialRateBump,
+ Points: auctionPointsFusion,
+ StartAuctionIn: preset.StartAuctionIn,
+ }
+
+ auctionDetails, err := CreateAuctionDetails(preset, 0) // No extra delay for now
+ if err != nil {
+ return nil, fmt.Errorf("error creating auction details: %v", err)
+ }
+
+ auctionDetailsFusion, err := fusion.CreateAuctionDetails(presetFusion, 0)
+ if err != nil {
+ return nil, fmt.Errorf("error creating auction details: %v", err)
+ }
+
+ takerAsset := quoteParams.DstTokenAddress
+ if takerAsset == NativeToken {
+ takerAssetWrapped, ok := chainToWrapper[NetworkEnum(chainId)]
+ if !ok {
+ return nil, fmt.Errorf("unable to get address for taker asset's wrapped token. unrecognized network: %v", chainId)
+ }
+ takerAsset = takerAssetWrapped.Hex()
+ }
+
+ var takingFreeReceiver geth_common.Address
+ if orderParams.TakingFeeReceiver == "" {
+ takingFreeReceiver = geth_common.HexToAddress("0x0000000000000000000000000000000000000000")
+ } else {
+ takingFreeReceiver = geth_common.HexToAddress(orderParams.TakingFeeReceiver)
+ }
+
+ fees := Fees{
+ IntFee: IntegratorFee{
+ Ratio: bpsToRatioFormat(quoteParams.Fee),
+ Receiver: takingFreeReceiver,
+ },
+ BankFee: big.NewInt(0),
+ }
+ feesFusion := fusion.Fees{
+ IntFee: fusion.IntegratorFee{
+ Ratio: bpsToRatioFormat(quoteParams.Fee),
+ Receiver: takingFreeReceiver,
+ },
+ BankFee: big.NewInt(0),
+ }
+
+ whitelistAddresses := make([]AuctionWhitelistItem, 0)
+ for _, address := range quote.Whitelist {
+ whitelistAddresses = append(whitelistAddresses, AuctionWhitelistItem{
+ Address: geth_common.HexToAddress(address),
+ AllowFrom: big.NewInt(0), // TODO generating the correct list here requires checking for an exclusive resolver. This needs to be checked for later. The generated object does not see exclusive resolver correctly
+ })
+ }
+ whitelistAddressesFusion := make([]fusion.AuctionWhitelistItem, 0)
+ for _, address := range quote.Whitelist {
+ whitelistAddressesFusion = append(whitelistAddressesFusion, fusion.AuctionWhitelistItem{
+ Address: geth_common.HexToAddress(address),
+ AllowFrom: big.NewInt(0), // TODO generating the correct list here requires checking for an exclusive resolver. This needs to be checked for later. The generated object does not see exclusive resolver correctly
+ })
+ }
+
+ var nonce *big.Int
+ if isNonceRequired(preset.AllowPartialFills, preset.AllowMultipleFills) {
+ if orderParams.Nonce != nil {
+ nonce = orderParams.Nonce
+ } else {
+ nonce, err = random_number_generation.BigIntMaxFunc(uint40Max)
+ if err != nil {
+ return nil, fmt.Errorf("error generating nonce: %v\n", err)
+ }
+ }
+ } else {
+ nonce = orderParams.Nonce
+ }
+
+ details := Details{
+ Auction: auctionDetails,
+ Fees: fees,
+ Whitelist: whitelistAddresses,
+ }
+ detailsFusion := fusion.Details{
+ Auction: auctionDetailsFusion,
+ Fees: feesFusion,
+ Whitelist: whitelistAddressesFusion,
+ }
+
+ extraParams := ExtraParams{
+ Nonce: nonce,
+ Permit: orderParams.Permit,
+ AllowPartialFills: preset.AllowPartialFills,
+ AllowMultipleFills: preset.AllowMultipleFills,
+ OrderExpirationDelay: 0,
+ Source: "",
+ }
+ extraParamsFusion := fusion.ExtraParams{
+ Nonce: nonce,
+ Permit: orderParams.Permit,
+ AllowPartialFills: preset.AllowPartialFills,
+ AllowMultipleFills: preset.AllowMultipleFills,
+ OrderExpirationDelay: 0,
+ Source: "",
+ }
+
+ makerTraitsFusion, err := fusion.CreateMakerTraits(detailsFusion, extraParamsFusion)
+ if err != nil {
+ return nil, fmt.Errorf("error creating maker traits: %v", err)
+ }
+
+ orderInfo := CrossChainOrderDto{
+ Maker: quoteParams.WalletAddress,
+ MakerAsset: quoteParams.SrcTokenAddress,
+ MakingAmount: quoteParams.Amount,
+ Receiver: orderParams.Receiver,
+ TakerAsset: takerAsset,
+ TakingAmount: preset.AuctionEndAmount,
+ }
+ orderInfoFusion := fusion.FusionOrderV4{
+ Maker: quoteParams.WalletAddress,
+ MakerAsset: quoteParams.SrcTokenAddress,
+ MakingAmount: quoteParams.Amount,
+ Receiver: orderParams.Receiver,
+ TakerAsset: takerAsset,
+ TakingAmount: preset.AuctionEndAmount,
+ }
+
+ escrowParams := EscrowExtensionParams{
+ HashLock: orderParams.HashLock,
+ DstChainId: quoteParams.DstChain,
+ SrcSafetyDeposit: quote.SrcSafetyDeposit,
+ DstSafetyDeposit: quote.DstSafetyDeposit,
+ TimeLocks: TimeLocks{
+ DstCancellation: quote.TimeLocks.DstCancellation,
+ DstPublicWithdrawal: quote.TimeLocks.DstPublicWithdrawal,
+ DstWithdrawal: quote.TimeLocks.DstWithdrawal,
+ SrcCancellation: quote.TimeLocks.SrcCancellation,
+ SrcPublicCancellation: quote.TimeLocks.SrcPublicCancellation,
+ SrcPublicWithdrawal: quote.TimeLocks.SrcPublicWithdrawal,
+ SrcWithdrawal: quote.TimeLocks.SrcWithdrawal,
+ }, // TODO timelocks have many safety checks
+ }
+
+ postInteractionData, err := CreateSettlementPostInteractionData(details, orderInfo)
+ if err != nil {
+ return nil, fmt.Errorf("error creating post interaction data: %v", err)
+ }
+ postInteractionDataFusion, err := fusion.CreateSettlementPostInteractionData(detailsFusion, orderInfoFusion)
+ if err != nil {
+ return nil, fmt.Errorf("error creating post interaction data: %v", err)
+ }
+
+ extension, err := NewEscrowExtension(EscrowExtensionParams{
+ ExtensionParams: fusion.ExtensionParams{
+ SettlementContract: quote.SrcEscrowFactory,
+ PostInteractionData: postInteractionDataFusion,
+ AuctionDetails: auctionDetailsFusion,
+ Asset: quoteParams.SrcTokenAddress,
+ Permit: orderParams.Permit, // TODO unsure about this permit value
+ },
+ HashLock: orderParams.HashLock,
+ DstChainId: quoteParams.DstChain,
+ DstToken: geth_common.HexToAddress(takerAsset),
+ SrcSafetyDeposit: quote.SrcSafetyDeposit,
+ DstSafetyDeposit: quote.DstSafetyDeposit,
+ TimeLocks: escrowParams.TimeLocks,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("error creating extension: %v", err)
+ }
+
+ fusionPlusOrder, err := CreateOrder(CreateOrderDataParams{
+ srcEscrowFactory: quote.SrcEscrowFactory,
+ orderInfo: orderInfo,
+ escrowParams: escrowParams,
+ details: details,
+ extraParams: extraParams,
+ extension: extension,
+ makerTraits: makerTraitsFusion,
+ postInteractionData: postInteractionData,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("error creating fusion order: %v", err)
+ }
+
+ extensionOrderbook, err := extension.ConvertToOrderbookExtension()
+ if err != nil {
+ return nil, fmt.Errorf("error converting extension to orderbook extension: %v", err)
+ }
+
+ limitOrder, err := orderbook.CreateLimitOrderMessage(orderbook.CreateOrderParams{
+ Wallet: wallet,
+ MakerTraits: makerTraitsFusion,
+ Extension: *extensionOrderbook,
+ Maker: orderInfo.Maker,
+ MakerAsset: orderInfo.MakerAsset,
+ TakerAsset: orderInfo.TakerAsset,
+ TakingAmount: orderInfo.TakingAmount,
+ MakingAmount: orderInfo.MakingAmount,
+ Taker: orderInfo.Receiver,
+ }, chainId)
+ if err != nil {
+ return nil, fmt.Errorf("error creating limit order message: %v", err)
+ }
+
+ return &PreparedOrder{
+ Order: *fusionPlusOrder,
+ Hash: limitOrder.OrderHash,
+ QuoteId: quote.QuoteId,
+ LimitOrder: limitOrder,
+ }, nil
+}
+
+func GetPreset(presets QuotePresets, presetType GetQuoteOutputRecommendedPreset) (*Preset, error) {
+ switch presetType {
+ case Custom:
+ if presets.Custom == nil {
+ return nil, errors.New("custom preset is not available")
+ }
+ return presets.Custom, nil
+ case Fast:
+ return &presets.Fast, nil
+ case Medium:
+ return &presets.Medium, nil
+ case Slow:
+ return &presets.Slow, nil
+ }
+ return nil, fmt.Errorf("unknown preset type: %v", presetType)
+}
+
+var CalcAuctionStartTimeFunc func(uint32, uint32) uint32 = CalcAuctionStartTime
+
+func CalcAuctionStartTime(startAuctionIn uint32, additionalWaitPeriod uint32) uint32 {
+ currentTime := time.Now().Unix()
+ return uint32(currentTime) + additionalWaitPeriod + startAuctionIn
+}
+
+func CreateAuctionDetails(preset *Preset, additionalWaitPeriod float32) (*AuctionDetails, error) {
+ pointsFixed := make([]AuctionPointClassFixed, 0)
+ for _, point := range preset.Points {
+ pointsFixed = append(pointsFixed, AuctionPointClassFixed{
+ Coefficient: uint32(point.Coefficient),
+ Delay: uint16(point.Delay),
+ })
+ }
+
+ gasPriceEstimateFixed, err := strconv.ParseUint(preset.GasCost.GasPriceEstimate, 10, 32)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing gas price estimate: %v", err)
+ }
+
+ gasCostFixed := GasCostConfigClassFixed{
+ GasBumpEstimate: uint32(preset.GasCost.GasBumpEstimate),
+ GasPriceEstimate: uint32(gasPriceEstimateFixed),
+ }
+
+ return &AuctionDetails{
+ StartTime: CalcAuctionStartTimeFunc(uint32(preset.StartAuctionIn), uint32(additionalWaitPeriod)),
+ Duration: uint32(preset.AuctionDuration),
+ InitialRateBump: uint32(preset.InitialRateBump),
+ Points: pointsFixed,
+ GasCost: gasCostFixed,
+ }, nil
+}
+
+var timeNow func() int64 = GetCurrentTime
+
+func GetCurrentTime() int64 {
+ return time.Now().Unix()
+}
+
+func CreateSettlementPostInteractionData(details Details, orderInfo CrossChainOrderDto) (*SettlementPostInteractionData, error) {
+ resolverStartTime := details.ResolvingStartTime
+ if details.ResolvingStartTime == nil || details.ResolvingStartTime.Cmp(big.NewInt(0)) == 0 {
+ resolverStartTime = big.NewInt(timeNow())
+ }
+ return NewSettlementPostInteractionData(SettlementSuffixData{
+ Whitelist: details.Whitelist,
+ IntegratorFee: &details.Fees.IntFee,
+ BankFee: details.Fees.BankFee,
+ ResolvingStartTime: resolverStartTime,
+ CustomReceiver: geth_common.HexToAddress(orderInfo.Receiver),
+ })
+}
+
+type CreateOrderDataParams struct {
+ srcEscrowFactory string
+ orderInfo CrossChainOrderDto
+ escrowParams EscrowExtensionParams
+ details Details
+ extraParams ExtraParams
+ extension *EscrowExtension
+ makerTraits *orderbook.MakerTraits
+ postInteractionData *SettlementPostInteractionData
+}
+
+func CreateOrder(params CreateOrderDataParams) (*Order, error) {
+
+ salt, err := params.extension.GenerateSalt()
+ if err != nil {
+ return nil, fmt.Errorf("error generating salt: %v", err)
+ }
+
+ return &Order{
+ EscExtension: params.extension,
+ Inner: orderbook.OrderData{
+ MakerAsset: params.orderInfo.MakerAsset,
+ TakerAsset: params.orderInfo.TakerAsset,
+ MakingAmount: params.orderInfo.MakingAmount,
+ TakingAmount: params.orderInfo.TakingAmount,
+ Salt: fmt.Sprintf("%x", salt),
+ Maker: params.orderInfo.Maker,
+ Receiver: params.orderInfo.Receiver,
+ MakerTraits: params.makerTraits.Encode(),
+ Extension: fmt.Sprintf("%x", params.extension.Keccak256()),
+ },
+ OrderInfo: params.orderInfo,
+ AuctionDetails: params.details.Auction,
+ PostInteractionData: params.postInteractionData,
+ Extra: ExtraData{
+ UnwrapWETH: params.extraParams.unwrapWeth,
+ Nonce: params.extraParams.Nonce,
+ Permit: params.extraParams.Permit,
+ AllowPartialFills: params.extraParams.AllowPartialFills,
+ AllowMultipleFills: params.extraParams.AllowMultipleFills,
+ OrderExpirationDelay: params.extraParams.OrderExpirationDelay,
+ EnablePermit2: params.extraParams.EnablePermit2,
+ Source: params.extraParams.Source,
+ },
+ }, nil
+}
+
+func isNonceRequired(allowPartialFills, allowMultipleFills bool) bool {
+ return !allowPartialFills || !allowMultipleFills
+}
+
+var (
+ feeBase = big.NewInt(100_000)
+ bpsBase = big.NewInt(10_000)
+ bpsToRatioNumber = new(big.Int).Div(feeBase, bpsBase)
+)
+
+func bpsToRatioFormat(bps *big.Int) *big.Int {
+ if bps == nil || bps.Cmp(big.NewInt(0)) == 0 {
+ return big.NewInt(0)
+ }
+
+ return bps.Mul(bps, bpsToRatioNumber)
+}
diff --git a/sdk-clients/fusionplus/order_test.go b/sdk-clients/fusionplus/order_test.go
new file mode 100644
index 00000000..4948d24d
--- /dev/null
+++ b/sdk-clients/fusionplus/order_test.go
@@ -0,0 +1,318 @@
+package fusionplus
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetPreset(t *testing.T) {
+ // Define test presets
+ customPreset := &Preset{
+ AllowMultipleFills: true,
+ AllowPartialFills: true,
+ AuctionDuration: 10,
+ AuctionEndAmount: "1000",
+ AuctionStartAmount: "500",
+ InitialRateBump: 0.1,
+ Points: []AuctionPoint{},
+ StartAuctionIn: 1,
+ }
+
+ fastPreset := &Preset{
+ AllowMultipleFills: false,
+ AllowPartialFills: false,
+ AuctionDuration: 20,
+ AuctionEndAmount: "2000",
+ AuctionStartAmount: "1000",
+ InitialRateBump: 0.2,
+ Points: []AuctionPoint{},
+ StartAuctionIn: 2,
+ }
+
+ mediumPreset := &Preset{
+ AllowMultipleFills: true,
+ AllowPartialFills: false,
+ AuctionDuration: 30,
+ AuctionEndAmount: "3000",
+ AuctionStartAmount: "1500",
+ InitialRateBump: 0.3,
+ Points: []AuctionPoint{},
+ StartAuctionIn: 3,
+ }
+
+ slowPreset := &Preset{
+ AllowMultipleFills: false,
+ AllowPartialFills: true,
+ AuctionDuration: 40,
+ AuctionEndAmount: "4000",
+ AuctionStartAmount: "2000",
+ InitialRateBump: 0.4,
+ Points: []AuctionPoint{},
+ StartAuctionIn: 4,
+ }
+
+ // Define test cases
+ tests := []struct {
+ name string
+ presets QuotePresets
+ presetType GetQuoteOutputRecommendedPreset
+ expected *Preset
+ expectErr bool
+ }{
+ {
+ name: "Get Custom Preset",
+ presets: QuotePresets{
+ Custom: customPreset,
+ },
+ presetType: Custom,
+ expected: customPreset,
+ expectErr: false,
+ },
+ {
+ name: "Get Fast Preset",
+ presets: QuotePresets{
+ Fast: *fastPreset,
+ },
+ presetType: Fast,
+ expected: fastPreset,
+ expectErr: false,
+ },
+ {
+ name: "Get Medium Preset",
+ presets: QuotePresets{
+ Medium: *mediumPreset,
+ },
+ presetType: Medium,
+ expected: mediumPreset,
+ expectErr: false,
+ },
+ {
+ name: "Get Slow Preset",
+ presets: QuotePresets{
+ Slow: *slowPreset,
+ },
+ presetType: Slow,
+ expected: slowPreset,
+ expectErr: false,
+ },
+ {
+ name: "Unknown Preset Type",
+ presets: QuotePresets{
+ Custom: customPreset,
+ Fast: *fastPreset,
+ Medium: *mediumPreset,
+ Slow: *slowPreset,
+ },
+ presetType: GetQuoteOutputRecommendedPreset("unknown"),
+ expected: nil,
+ expectErr: true,
+ },
+ {
+ name: "Nil Presets",
+ presets: QuotePresets{
+ Custom: nil,
+ Fast: Preset{},
+ Medium: Preset{},
+ Slow: Preset{},
+ },
+ presetType: Custom,
+ expected: nil,
+ expectErr: true,
+ },
+ }
+
+ // Run test cases
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ result, err := GetPreset(tc.presets, tc.presetType)
+ if tc.expectErr {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, tc.expected, result)
+ }
+ })
+ }
+}
+
+func TestCreateAuctionDetails(t *testing.T) {
+ tests := []struct {
+ name string
+ preset *Preset
+ additionalWaitPeriod float32
+ expected *AuctionDetails
+ expectErr bool
+ }{
+ {
+ name: "Valid preset with points",
+ preset: &Preset{
+ Points: []AuctionPoint{
+ {Coefficient: 100, Delay: 10},
+ {Coefficient: 200, Delay: 20},
+ },
+ GasCost: GasCostConfig{
+ GasBumpEstimate: 1,
+ GasPriceEstimate: "100",
+ },
+ AuctionDuration: 300,
+ AuctionEndAmount: "1000",
+ AuctionStartAmount: "500",
+ InitialRateBump: 1,
+ StartAuctionIn: 5,
+ },
+ additionalWaitPeriod: 2,
+ expected: &AuctionDetails{
+ StartTime: CalcAuctionStartTimeFunc(5, 2),
+ Duration: 300,
+ InitialRateBump: 1,
+ Points: []AuctionPointClassFixed{
+ {Coefficient: 100, Delay: 10},
+ {Coefficient: 200, Delay: 20},
+ },
+ GasCost: GasCostConfigClassFixed{
+ GasBumpEstimate: 1,
+ GasPriceEstimate: 100,
+ },
+ },
+ expectErr: false,
+ },
+ {
+ name: "Invalid gas price estimate",
+ preset: &Preset{
+ Points: []AuctionPoint{
+ {Coefficient: 100, Delay: 10},
+ },
+ GasCost: GasCostConfig{
+ GasBumpEstimate: 1,
+ GasPriceEstimate: "invalid",
+ },
+ AuctionDuration: 300,
+ AuctionEndAmount: "1000",
+ AuctionStartAmount: "500",
+ InitialRateBump: 0.1,
+ StartAuctionIn: 5,
+ },
+ additionalWaitPeriod: 2,
+ expected: nil,
+ expectErr: true,
+ },
+ {
+ name: "Empty points",
+ preset: &Preset{
+ Points: []AuctionPoint{},
+ GasCost: GasCostConfig{
+ GasBumpEstimate: 1,
+ GasPriceEstimate: "100",
+ },
+ AuctionDuration: 300,
+ AuctionEndAmount: "1000",
+ AuctionStartAmount: "500",
+ InitialRateBump: 1,
+ StartAuctionIn: 5,
+ },
+ additionalWaitPeriod: 2,
+ expected: &AuctionDetails{
+ StartTime: CalcAuctionStartTimeFunc(5, 2),
+ Duration: 300,
+ InitialRateBump: 1,
+ Points: []AuctionPointClassFixed{},
+ GasCost: GasCostConfigClassFixed{
+ GasBumpEstimate: 1,
+ GasPriceEstimate: 100,
+ },
+ },
+ expectErr: false,
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ result, err := CreateAuctionDetails(tc.preset, tc.additionalWaitPeriod)
+ if tc.expectErr {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, tc.expected, result)
+ }
+ })
+ }
+}
+
+func TestIsNonceRequired(t *testing.T) {
+ tests := []struct {
+ name string
+ allowPartialFills bool
+ allowMultipleFills bool
+ expected bool
+ }{
+ {
+ name: "Both allowPartialFills and allowMultipleFills are true",
+ allowPartialFills: true,
+ allowMultipleFills: true,
+ expected: false,
+ },
+ {
+ name: "allowPartialFills is true, allowMultipleFills is false",
+ allowPartialFills: true,
+ allowMultipleFills: false,
+ expected: true,
+ },
+ {
+ name: "allowPartialFills is false, allowMultipleFills is true",
+ allowPartialFills: false,
+ allowMultipleFills: true,
+ expected: true,
+ },
+ {
+ name: "Both allowPartialFills and allowMultipleFills are false",
+ allowPartialFills: false,
+ allowMultipleFills: false,
+ expected: true,
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ result := isNonceRequired(tc.allowPartialFills, tc.allowMultipleFills)
+ assert.Equal(t, tc.expected, result)
+ })
+ }
+}
+
+func TestBpsToRatioFormat(t *testing.T) {
+ tests := []struct {
+ name string
+ bps *big.Int
+ expected *big.Int
+ }{
+ {
+ name: "Nil bps",
+ bps: nil,
+ expected: big.NewInt(0),
+ },
+ {
+ name: "Zero bps",
+ bps: big.NewInt(0),
+ expected: big.NewInt(0),
+ },
+ {
+ name: "Positive bps",
+ bps: big.NewInt(500),
+ expected: big.NewInt(5000),
+ },
+ {
+ name: "Large bps",
+ bps: big.NewInt(10000),
+ expected: big.NewInt(100000),
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ result := bpsToRatioFormat(tc.bps)
+ assert.Equal(t, tc.expected, result)
+ })
+ }
+}
diff --git a/sdk-clients/fusionplus/settlementpostinteractiondata.go b/sdk-clients/fusionplus/settlementpostinteractiondata.go
new file mode 100644
index 00000000..eb161e58
--- /dev/null
+++ b/sdk-clients/fusionplus/settlementpostinteractiondata.go
@@ -0,0 +1,219 @@
+package fusionplus
+
+import (
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "math/big"
+ "sort"
+ "strings"
+
+ "github.com/1inch/1inch-sdk-go/internal/bytesbuilder"
+ "github.com/1inch/1inch-sdk-go/internal/bytesiterator"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+type SettlementPostInteractionData struct {
+ Whitelist []WhitelistItem
+ IntegratorFee *IntegratorFee
+ BankFee *big.Int
+ ResolvingStartTime *big.Int
+ CustomReceiver common.Address
+}
+
+var uint16Max = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 16), big.NewInt(1))
+
+func NewSettlementPostInteractionData(data SettlementSuffixData) (*SettlementPostInteractionData, error) {
+ if len(data.Whitelist) == 0 {
+ return nil, errors.New("whitelist cannot be empty")
+ }
+
+ sumDelay := big.NewInt(0)
+ whitelist := make([]WhitelistItem, len(data.Whitelist))
+
+ // Transform timestamps to cumulative delays
+ sort.Slice(data.Whitelist, func(i, j int) bool {
+ return data.Whitelist[i].AllowFrom.Cmp(data.Whitelist[j].AllowFrom) < 0
+ })
+
+ for i, d := range data.Whitelist {
+ allowFrom := d.AllowFrom
+ if d.AllowFrom.Cmp(data.ResolvingStartTime) < 0 {
+ allowFrom = data.ResolvingStartTime
+ }
+
+ zero := big.NewInt(0)
+ delay := new(big.Int).Sub(allowFrom, data.ResolvingStartTime)
+ delay.Sub(delay, sumDelay)
+ // If the resulting value of delay is zero, set it to a fresh big.Int of value zero (for comparisons in tests)
+ if delay.Cmp(zero) == 0 {
+ delay = zero
+ }
+ whitelist[i] = WhitelistItem{
+ AddressHalf: strings.ToLower(d.Address.Hex())[len(d.Address.Hex())-20:],
+ Delay: delay,
+ }
+
+ sumDelay.Add(sumDelay, whitelist[i].Delay)
+
+ if whitelist[i].Delay.Cmp(uint16Max) >= 0 {
+ return nil, fmt.Errorf("delay too big - %d must be less than %d", whitelist[i].Delay, uint16Max)
+ }
+ }
+
+ return &SettlementPostInteractionData{
+ Whitelist: whitelist,
+ IntegratorFee: data.IntegratorFee,
+ BankFee: data.BankFee,
+ ResolvingStartTime: data.ResolvingStartTime,
+ CustomReceiver: data.CustomReceiver,
+ }, nil
+}
+
+func Decode(data string) (SettlementPostInteractionData, error) {
+ bytes, err := hex.DecodeString(strings.TrimPrefix(data, "0x"))
+ if err != nil {
+ return SettlementPostInteractionData{}, errors.New("invalid hex string")
+ }
+
+ flags := big.NewInt(int64(bytes[len(bytes)-1]))
+ bytesWithoutFlags := bytes[:len(bytes)-1]
+
+ iter := bytesiterator.New(bytesWithoutFlags)
+ var bankFee *big.Int
+ var integratorFee *IntegratorFee
+ var customReceiver common.Address
+
+ if flags.Bit(0) == 1 {
+ bankFee, err = iter.NextUint32()
+ if err != nil {
+ return SettlementPostInteractionData{}, err
+ }
+ }
+
+ if flags.Bit(1) == 1 {
+
+ ratio, err := iter.NextUint16()
+ if err != nil {
+ return SettlementPostInteractionData{}, err
+ }
+
+ receiver, err := iter.NextUint160()
+ if err != nil {
+ return SettlementPostInteractionData{}, err
+ }
+
+ integratorFee = &IntegratorFee{
+ Ratio: ratio,
+ Receiver: common.HexToAddress(receiver.Text(16)),
+ }
+
+ if flags.Bit(2) == 1 {
+
+ customReceiverRaw, err := iter.NextUint160()
+ if err != nil {
+ return SettlementPostInteractionData{}, err
+ }
+
+ customReceiver = common.HexToAddress(customReceiverRaw.Text(16))
+ }
+ }
+
+ resolvingStartTime, err := iter.NextUint32()
+ if err != nil {
+ return SettlementPostInteractionData{}, err
+ }
+ var whitelist []WhitelistItem
+
+ for !iter.IsEmpty() {
+ addressHalfRaw, err := iter.NextBytes(10)
+ if err != nil {
+ return SettlementPostInteractionData{}, err
+ }
+ addressHalf := hex.EncodeToString(addressHalfRaw)
+ delay, err := iter.NextUint16()
+ if err != nil {
+ return SettlementPostInteractionData{}, err
+ }
+ whitelist = append(whitelist, WhitelistItem{
+ AddressHalf: addressHalf,
+ Delay: delay,
+ })
+ }
+
+ return SettlementPostInteractionData{
+ IntegratorFee: integratorFee,
+ BankFee: bankFee,
+ ResolvingStartTime: resolvingStartTime,
+ Whitelist: whitelist,
+ CustomReceiver: customReceiver,
+ }, nil
+}
+
+func (spid SettlementPostInteractionData) Encode() string {
+ bitMask := big.NewInt(0)
+ bytes := bytesbuilder.New()
+
+ if spid.BankFee != nil && spid.BankFee.Cmp(big.NewInt(0)) != 0 {
+ bitMask.SetBit(bitMask, 0, 1)
+ bytes.AddUint32(spid.BankFee)
+ }
+
+ if spid.IntegratorFee != nil && spid.IntegratorFee.Ratio.Cmp(big.NewInt(0)) != 0 {
+ bitMask.SetBit(bitMask, 1, 1)
+ bytes.AddUint16(spid.IntegratorFee.Ratio)
+ bytes.AddAddress(spid.IntegratorFee.Receiver)
+
+ // TODO this check is probably not good enough
+ if spid.CustomReceiver.Hex() != "0x0000000000000000000000000000000000000000" {
+ bitMask.SetBit(bitMask, 2, 1)
+ bytes.AddAddress(spid.CustomReceiver)
+ }
+ }
+
+ bytes.AddUint32(spid.ResolvingStartTime)
+
+ for _, wl := range spid.Whitelist {
+ bytes.AddBytes(wl.AddressHalf)
+ bytes.AddUint16(wl.Delay)
+ }
+
+ bitMask.Or(bitMask, big.NewInt(int64(len(spid.Whitelist)<<3)))
+ bytes.AddUint8(uint8(bitMask.Int64()))
+
+ output := fmt.Sprintf("0x%s", bytes.AsHex())
+
+ return output
+}
+
+func (spid SettlementPostInteractionData) CanExecuteAt(executor common.Address, executionTime *big.Int) bool {
+ addressHalf := executor.Hex()[len(executor.Hex())-20:]
+
+ allowedFrom := spid.ResolvingStartTime
+
+ for _, whitelist := range spid.Whitelist {
+ allowedFrom.Add(allowedFrom, whitelist.Delay)
+
+ if addressHalf == whitelist.AddressHalf {
+ return executionTime.Cmp(allowedFrom) >= 0
+ } else if executionTime.Cmp(allowedFrom) < 0 {
+ return false
+ }
+ }
+
+ return false
+}
+
+func (spid SettlementPostInteractionData) IsExclusiveResolver(wallet common.Address) bool {
+ addressHalf := wallet.Hex()[len(wallet.Hex())-20:]
+
+ if len(spid.Whitelist) == 1 {
+ return addressHalf == spid.Whitelist[0].AddressHalf
+ }
+
+ if spid.Whitelist[0].Delay.Cmp(spid.Whitelist[1].Delay) == 0 {
+ return false
+ }
+
+ return addressHalf == spid.Whitelist[0].AddressHalf
+}
diff --git a/sdk-clients/fusionplus/settlementpostinteractiondata_test.go b/sdk-clients/fusionplus/settlementpostinteractiondata_test.go
new file mode 100644
index 00000000..41bd1760
--- /dev/null
+++ b/sdk-clients/fusionplus/settlementpostinteractiondata_test.go
@@ -0,0 +1,130 @@
+package fusionplus
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestSettlementPostInteractionData(t *testing.T) {
+ tests := []struct {
+ name string
+ data SettlementSuffixData
+ expectedBytes int
+ }{
+ {
+ name: "Should encode/decode with bank fee and whitelist",
+ data: SettlementSuffixData{
+ BankFee: big.NewInt(1),
+ ResolvingStartTime: big.NewInt(1708117482),
+ Whitelist: []AuctionWhitelistItem{
+ {
+ Address: common.Address{},
+ AllowFrom: big.NewInt(0),
+ },
+ },
+ },
+ expectedBytes: 21,
+ },
+ {
+ name: "Should encode/decode with bank fee and whitelist with multiple entries",
+ data: SettlementSuffixData{
+ BankFee: big.NewInt(1),
+ ResolvingStartTime: big.NewInt(1708117482),
+ Whitelist: []AuctionWhitelistItem{
+ {
+ Address: common.HexToAddress("0x7a28c1b1478581b9e1293fc1c20449e2ed3efec9"),
+ AllowFrom: big.NewInt(1),
+ },
+ {
+ Address: common.HexToAddress("0x7a28c1b1478581b9e1293fc1c20449e2ed3efec9"),
+ AllowFrom: big.NewInt(2),
+ },
+ },
+ },
+ },
+ {
+ name: "Should encode/decode with no fees and whitelist",
+ data: SettlementSuffixData{
+ ResolvingStartTime: big.NewInt(1708117482),
+ Whitelist: []AuctionWhitelistItem{
+ {
+ Address: common.Address{},
+ AllowFrom: big.NewInt(0),
+ },
+ },
+ },
+ expectedBytes: 17,
+ },
+ {
+ name: "Should encode/decode with fees and whitelist",
+ data: SettlementSuffixData{
+ ResolvingStartTime: big.NewInt(1708117482),
+ Whitelist: []AuctionWhitelistItem{
+ {
+ Address: common.Address{},
+ AllowFrom: big.NewInt(0),
+ },
+ },
+ IntegratorFee: &IntegratorFee{
+ Receiver: common.Address{1},
+ Ratio: big.NewInt(10),
+ },
+ },
+ },
+ {
+ name: "Should encode/decode with fees, custom receiver and whitelist",
+ data: SettlementSuffixData{
+ ResolvingStartTime: big.NewInt(1708117482),
+ Whitelist: []AuctionWhitelistItem{
+ {
+ Address: common.Address{},
+ AllowFrom: big.NewInt(0),
+ },
+ },
+ IntegratorFee: &IntegratorFee{
+ Receiver: common.Address{1},
+ Ratio: big.NewInt(10),
+ },
+ CustomReceiver: common.Address{123},
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ data, err := NewSettlementPostInteractionData(tc.data)
+ require.NoError(t, err)
+
+ encoded := data.Encode()
+ if tc.expectedBytes != 0 {
+ assert.Equal(t, tc.expectedBytes, len(encoded[2:])/2)
+ }
+ decoded, err := Decode(encoded)
+
+ if decoded.BankFee != nil {
+ decoded.BankFee = big.NewInt(decoded.BankFee.Int64())
+ }
+ if data.BankFee != nil {
+ data.BankFee = big.NewInt(data.BankFee.Int64())
+ }
+
+ assert.NoError(t, err)
+ assert.Equal(t, data.ResolvingStartTime.Cmp(decoded.ResolvingStartTime), 0)
+ assert.Equal(t, data.BankFee.Cmp(decoded.BankFee), 0)
+
+ for i, expectedItem := range data.Whitelist {
+ assert.Equal(t, expectedItem.AddressHalf, decoded.Whitelist[i].AddressHalf)
+ assert.Equal(t, expectedItem.Delay.Cmp(decoded.Whitelist[i].Delay), 0)
+ }
+
+ assert.Equal(t, data.IntegratorFee, decoded.IntegratorFee)
+ assert.Equal(t, data.CustomReceiver, decoded.CustomReceiver)
+ assert.Equal(t, data.IntegratorFee, decoded.IntegratorFee)
+ assert.Equal(t, data.CustomReceiver, decoded.CustomReceiver)
+ })
+ }
+}
diff --git a/sdk-clients/fusionplus/validation.go b/sdk-clients/fusionplus/validation.go
new file mode 100644
index 00000000..61220634
--- /dev/null
+++ b/sdk-clients/fusionplus/validation.go
@@ -0,0 +1,50 @@
+package fusionplus
+
+import (
+ "fmt"
+
+ "github.com/1inch/1inch-sdk-go/constants"
+ "github.com/1inch/1inch-sdk-go/internal/validate"
+)
+
+func (params *OrderApiControllerGetActiveOrdersParams) Validate() error {
+ var validationErrors []error
+ validationErrors = validate.Parameter(params.Page, "Page", validate.CheckPage, validationErrors)
+ validationErrors = validate.Parameter(params.Limit, "Limit", validate.CheckLimit, validationErrors)
+ return validate.ConsolidateValidationErorrs(validationErrors)
+}
+
+func (params *QuoterControllerGetQuoteParamsFixed) Validate() error {
+ var validationErrors []error
+ validationErrors = validate.Parameter(params.SrcTokenAddress, "SrcTokenAddress", validate.CheckEthereumAddressRequired, validationErrors)
+ validationErrors = validate.Parameter(params.DstTokenAddress, "SrcTokenAddress", validate.CheckEthereumAddressRequired, validationErrors)
+ validationErrors = validate.Parameter(params.WalletAddress, "WalletAddress", validate.CheckEthereumAddressRequired, validationErrors)
+ validationErrors = validate.Parameter(params.SrcChain, "SrcChain", validate.CheckChainIdFloat32Required, validationErrors)
+ validationErrors = validate.Parameter(params.DstChain, "DstChain", validate.CheckChainIdFloat32Required, validationErrors)
+ validationErrors = validate.Parameter(params.Amount, "Amount", validate.CheckBigIntRequired, validationErrors)
+ validationErrors = validate.Parameter(params.Permit, "Permit", validate.CheckPermitHash, validationErrors)
+ return validate.ConsolidateValidationErorrs(validationErrors)
+}
+
+func (params *QuoterControllerGetQuoteWithCustomPresetsParams) Validate() error {
+ var validationErrors []error
+ validationErrors = validate.Parameter(params.SrcTokenAddress, "SrcTokenAddress", validate.CheckEthereumAddressRequired, validationErrors)
+ validationErrors = validate.Parameter(params.DstTokenAddress, "SrcTokenAddress", validate.CheckEthereumAddressRequired, validationErrors)
+ validationErrors = validate.Parameter(params.WalletAddress, "WalletAddress", validate.CheckEthereumAddressRequired, validationErrors)
+ validationErrors = validate.Parameter(params.Amount, "Amount", validate.CheckBigIntRequired, validationErrors)
+ validationErrors = validate.Parameter(params.SrcChain, "SrcChain", validate.CheckChainIdFloat32Required, validationErrors)
+ validationErrors = validate.Parameter(params.DstChain, "DstChain", validate.CheckChainIdFloat32Required, validationErrors)
+ validationErrors = validate.Parameter(params.Amount, "Amount", validate.CheckBigIntRequired, validationErrors)
+ validationErrors = validate.Parameter(params.Permit, "Permit", validate.CheckPermitHash, validationErrors)
+ return validate.ConsolidateValidationErorrs(validationErrors)
+}
+
+func (body *OrderParams) Validate() error {
+ var validationErrors []error
+ validationErrors = validate.Parameter(body.Receiver, "Receiver", validate.CheckEthereumAddressRequired, validationErrors)
+ validationErrors = validate.Parameter(body.Permit, "Permit", validate.CheckPermitHash, validationErrors)
+ if body.Preset == "" {
+ validationErrors = append(validationErrors, validate.NewParameterCustomError(fmt.Sprintf("Preset is required. Pass in one of the Fusion library constants: %v", constants.ValidFusionPresets)))
+ }
+ return validate.ConsolidateValidationErorrs(validationErrors)
+}
diff --git a/sdk-clients/history/validation.go b/sdk-clients/history/validation.go
index ef9bf606..8a46220b 100644
--- a/sdk-clients/history/validation.go
+++ b/sdk-clients/history/validation.go
@@ -8,7 +8,7 @@ func (params *EventsByAddressParams) Validate() error {
var validationErrors []error
validationErrors = validate.Parameter(params.TokenAddress, "TokenAddress", validate.CheckEthereumAddress, validationErrors)
validationErrors = validate.Parameter(params.Address, "Address", validate.CheckEthereumAddressRequired, validationErrors)
- validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainId, validationErrors)
+ validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainIdInt, validationErrors)
//validationErrors = validate.Parameter(params.FromTimestampMs, "FromTimestampMs", validate.CheckFloat32NonNegativeWhole, validationErrors)
//validationErrors = validate.Parameter(params.ToTimestampMs, "ToTimestampMs", validate.CheckFloat32NonNegativeWhole, validationErrors)
return validate.ConsolidateValidationErorrs(validationErrors)
diff --git a/sdk-clients/nft/validation.go b/sdk-clients/nft/validation.go
index ca45debc..8388ec60 100644
--- a/sdk-clients/nft/validation.go
+++ b/sdk-clients/nft/validation.go
@@ -8,7 +8,7 @@ func (params *GetNftsByAddressParams) Validate() error {
var validationErrors []error
validationErrors = validate.Parameter(params.Address, "Address", validate.CheckEthereumAddressRequired, validationErrors)
for _, v := range params.ChainIds {
- validationErrors = validate.Parameter(int(v), "ChainId", validate.CheckChainIdRequired, validationErrors)
+ validationErrors = validate.Parameter(int(v), "ChainId", validate.CheckChainIdIntRequired, validationErrors)
}
return validate.ConsolidateValidationErorrs(validationErrors)
}
diff --git a/sdk-clients/orderbook/examples/create_order_permit/main.go b/sdk-clients/orderbook/examples/create_order_permit/main.go
index df26c7d8..c352aab0 100644
--- a/sdk-clients/orderbook/examples/create_order_permit/main.go
+++ b/sdk-clients/orderbook/examples/create_order_permit/main.go
@@ -111,7 +111,7 @@ func main() {
Wallet: client.Wallet,
SeriesNonce: seriesNonce,
MakerTraits: makerTraits,
- Extension: extension,
+ Extension: *extension,
ExpireAfterUnix: expireAfter,
Maker: publicAddress.Hex(),
MakerAsset: PolygonFRAX,
diff --git a/sdk-clients/orderbook/examples/fill_order/main.go b/sdk-clients/orderbook/examples/fill_order/main.go
index 4ed9f20b..84c2d81d 100644
--- a/sdk-clients/orderbook/examples/fill_order/main.go
+++ b/sdk-clients/orderbook/examples/fill_order/main.go
@@ -20,7 +20,7 @@ var (
)
const (
- limitOrderHash = "0xc883127b38c6965a55adfbf0d55e734ed96169f8471dbf3af63f79f3718839d2"
+ limitOrderHash = "0x585e2e2488d9654926a80356ce135e0ef890cbb6ecec156cad347e091635029e"
chainId = 137
)
diff --git a/sdk-clients/orderbook/extension.go b/sdk-clients/orderbook/extension.go
index 2744602b..417964bd 100644
--- a/sdk-clients/orderbook/extension.go
+++ b/sdk-clients/orderbook/extension.go
@@ -1,14 +1,18 @@
package orderbook
import (
+ "bytes"
+ "encoding/binary"
+ "encoding/hex"
+ "errors"
"fmt"
"math/big"
+ "reflect"
"strings"
-)
-type Extension struct {
- InteractionsArray []string
-}
+ "github.com/1inch/1inch-sdk-go/internal/bytesiterator"
+ "github.com/ethereum/go-ethereum/common/math"
+)
type ExtensionParams struct {
MakerAsset string
@@ -22,73 +26,178 @@ type ExtensionParams struct {
PostInteraction string
}
-func NewExtension(params ExtensionParams) (Extension, error) {
+func NewExtension(params ExtensionParams) (*Extension, error) {
if params.Permit != "" {
if params.MakerAsset == "" {
- return Extension{}, fmt.Errorf("when Permit is present, a maker asset must also be defined requires MakerAsset")
+ return nil, fmt.Errorf("when Permit is present, a maker asset must also be defined requires MakerAsset")
}
}
if params.MakerAsset != "" {
if params.Permit == "" {
- return Extension{}, fmt.Errorf("when MakerAsset is present, a maker asset must also be defined requires Permit")
+ return nil, fmt.Errorf("when MakerAsset is present, a maker asset must also be defined requires Permit")
}
}
- makerAssetData := params.MakerAssetData
- takerAssetData := params.TakerAssetData
- getMakingAmount := params.GetMakingAmount
- getTakingAmount := params.GetTakingAmount
- predicate := params.Predicate
- permit := params.MakerAsset + strings.TrimPrefix(params.Permit, "0x")
- preInteraction := params.PreInteraction
- postInteraction := params.PostInteraction
-
- interactions := []string{makerAssetData, takerAssetData, getMakingAmount, getTakingAmount, predicate, permit, preInteraction, postInteraction}
-
- return Extension{
- InteractionsArray: interactions,
+ return &Extension{
+ MakerAssetSuffix: params.MakerAssetData,
+ TakerAssetSuffix: params.TakerAssetData,
+ MakingAmountData: params.GetMakingAmount,
+ TakingAmountData: params.GetTakingAmount,
+ Predicate: params.Predicate,
+ MakerPermit: params.MakerAsset + strings.TrimPrefix(params.Permit, "0x"),
+ PreInteraction: params.PreInteraction,
+ PostInteraction: params.PostInteraction,
}, nil
}
-func (i *Extension) Encode() string {
- interactionsConcatednated := i.getConcatenatedInteractions()
- if interactionsConcatednated == "" {
- return "0x"
+type Extension struct {
+ MakerAssetSuffix string
+ TakerAssetSuffix string
+ MakingAmountData string
+ TakingAmountData string
+ Predicate string
+ MakerPermit string
+ PreInteraction string
+ PostInteraction string
+}
+
+// Decode decodes the input byte slice into an Extension struct using reflection.
+func Decode(data []byte) (*Extension, error) {
+ // TODO Handle the special case where data equals ZX.
+ //if string(data) == ZX {
+ // return DefaultExtension(), nil
+ //}
+
+ iter := bytesiterator.New(data)
+
+ // Read the first 32 bytes as offsets.
+ offsets, err := iter.NextUint256()
+ if err != nil {
+ return &Extension{}, errors.New("failed to read offsets: " + err.Error())
}
- offsetsBytes := i.getOffsets()
- paddedOffsetHex := fmt.Sprintf("%064x", offsetsBytes)
- return "0x" + paddedOffsetHex + interactionsConcatednated
-}
+ consumed := 0
+
+ // Initialize the Extension struct
+ var ext Extension
+
+ // Use reflection to iterate over the struct fields in order.
+ val := reflect.ValueOf(&ext).Elem() // Get the reflect.Value of the struct
+ typ := val.Type() // Get the reflect.Type of the struct
+
+ numFields := typ.NumField()
+
+ // Iterate through all fields except the last one (CustomData)
+ for i := 0; i < numFields; i++ {
+ field := typ.Field(i)
+ fieldVal := val.Field(i)
-func (i *Extension) getConcatenatedInteractions() string {
- var builder strings.Builder
- for _, interaction := range i.InteractionsArray {
- interaction = strings.TrimPrefix(interaction, "0x")
- builder.WriteString(interaction)
+ // Skip CustomData for now
+ if field.Name == "CustomData" {
+ continue
+ }
+
+ const uint32Max = math.MaxUint32
+
+ // Extract the lowest 32 bits for the current field's offset.
+ offset := new(big.Int).And(offsets, big.NewInt(uint32Max)).Uint64()
+ bytesCount := int(offset) - consumed
+
+ if bytesCount < 0 {
+ return &Extension{}, errors.New("invalid offset leading to negative bytesCount for field: " + field.Name)
+ }
+
+ // Read the next bytesCount bytes for the current field.
+ fieldBytes, err := iter.NextBytes(bytesCount)
+ if err != nil {
+ return &Extension{}, errors.New("failed to read field " + field.Name + ": " + err.Error())
+ }
+ if len(fieldBytes) < bytesCount {
+ return &Extension{}, errors.New("insufficient bytes for field " + field.Name)
+ }
+
+ // Set the field value using reflection.
+ if field.Type.Kind() == reflect.String {
+ fieldVal.SetString(fmt.Sprintf("%x", fieldBytes))
+ } else {
+ return &Extension{}, errors.New("unsupported field type for field: " + field.Name)
+ }
+
+ // Update the consumed bytes and shift the offsets for the next field.
+ consumed += bytesCount
+ offsets = new(big.Int).Rsh(offsets, 32)
}
- return builder.String()
+
+ // TODO The remaining bytes are considered as CustomData, but it is not supported yet.
+ //customDataBytes, err := iter.Rest()
+ //if err != nil {
+ // return &Extension{}, errors.New("failed to read CustomData: " + err.Error())
+ //}
+ //ext.CustomData = string(customDataBytes)
+
+ return &ext, nil
+}
+
+// hexToBytes converts a hexadecimal string to a byte slice.
+func hexToBytes(s string) ([]byte, error) {
+ return hex.DecodeString(s)
}
-func (i *Extension) getOffsets() *big.Int {
- var lengthMap []int
- for _, interaction := range i.InteractionsArray {
- lengthMap = append(lengthMap, len(strings.TrimPrefix(interaction, "0x"))/2)
+// contains checks if the substring is present in the string.
+func contains(s, substr string) bool {
+ return bytes.Contains([]byte(s), []byte(substr))
+}
+
+// Encode encodes the Extension struct into a hex string with offsets.
+func (ext *Extension) Encode() (string, error) {
+ fields := []string{
+ ext.MakerAssetSuffix,
+ ext.TakerAssetSuffix,
+ ext.MakingAmountData,
+ ext.TakingAmountData,
+ ext.Predicate,
+ ext.MakerPermit,
+ ext.PreInteraction,
+ ext.PostInteraction,
+ }
+
+ var byteCounts []int
+ var dataBytes []byte
+
+ // Decode each field and collect byte counts
+ for _, field := range fields {
+ fieldStr := strings.TrimPrefix(field, "0x")
+ fieldStr = strings.TrimPrefix(fieldStr, "0X")
+
+ // Ensure even length for hex decoding
+ if len(fieldStr)%2 != 0 {
+ fieldStr = "0" + fieldStr
+ }
+
+ fieldData, err := hex.DecodeString(fieldStr)
+ if err != nil {
+ return "", fmt.Errorf("failed to decode field '%s': %v", field, err)
+ }
+ byteCounts = append(byteCounts, len(fieldData))
+ dataBytes = append(dataBytes, fieldData...)
}
+ // Calculate cumulative offsets
cumulativeSum := 0
- bytesAccumulator := big.NewInt(0)
- var index uint64
-
- for _, length := range lengthMap {
- cumulativeSum += length
- shiftVal := big.NewInt(int64(cumulativeSum))
- shiftVal.Lsh(shiftVal, uint(32*index)) // Shift left
- bytesAccumulator.Add(bytesAccumulator, shiftVal) // Add to accumulator
- index++
+ 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...)
}
- return bytesAccumulator
+ // Encode offsets and data to hex
+ offsetsHex := hex.EncodeToString(offsets)
+ dataHex := hex.EncodeToString(dataBytes)
+
+ // Concatenate with "0x" prefix
+ return "0x" + offsetsHex + dataHex, nil
}
diff --git a/sdk-clients/orderbook/extension_test.go b/sdk-clients/orderbook/extension_test.go
index 8900736d..22a85ff5 100644
--- a/sdk-clients/orderbook/extension_test.go
+++ b/sdk-clients/orderbook/extension_test.go
@@ -1,175 +1,149 @@
package orderbook
import (
- "fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
-func TestGetConcatenatedInteractions(t *testing.T) {
-
+func TestEncode(t *testing.T) {
tests := []struct {
- name string
- extension Extension
- expectedConcatenatedInteractions string
+ name string
+ extension Extension
+ expectedEncoding string
}{
{
- name: "Single hex value",
+ name: "Simple Limit Order 1",
extension: Extension{
- InteractionsArray: []string{
- "",
- "",
- "",
- "",
- "",
- "0x45c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3",
- "",
- "",
- },
+ MakerAssetSuffix: "0x01",
+ TakerAssetSuffix: "0x02",
+ MakingAmountData: "0x03",
+ TakingAmountData: "0x04",
+ Predicate: "0x05",
+ MakerPermit: "0x06",
+ PreInteraction: "0x07",
+ PostInteraction: "0x08",
},
- expectedConcatenatedInteractions: "45c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3",
+ expectedEncoding: "0x00000008000000070000000600000005000000040000000300000002000000010102030405060708",
},
{
- name: "Multiple hex value",
+ name: "Realistic Order 1",
extension: Extension{
- InteractionsArray: []string{
- "",
- "",
- "0x12345",
- "",
- "",
- "0x45c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3",
- "",
- "",
- },
+ MakerAssetSuffix: "0x",
+ TakerAssetSuffix: "0x",
+ MakingAmountData: "fb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666cdf850000b400c45c00688b007e",
+ TakingAmountData: "fb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666cdf850000b400c45c00688b007e",
+ Predicate: "0x",
+ MakerPermit: "0x",
+ PreInteraction: "0x",
+ PostInteraction: "0xfb2809A5314473E1165f6B58018E20ed8F07B840666cdf74c0866635457d36ab318d0000f3a44b7b0d08f4e198b80000000000000000000000000000d18bd45f0b94f54a968f0000000000000000000000000000000000000000000000000000c976bf098c4dba0a061d000000000000000000000000000040",
},
- expectedConcatenatedInteractions: "1234545c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3",
- },
- {
- name: "Hex and non-hex values",
- extension: Extension{
- InteractionsArray: []string{
- "",
- "",
- "0x12345",
- "nonhex",
- "",
- "0x45c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3",
- "",
- "",
- },
- },
- expectedConcatenatedInteractions: "12345nonhex45c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3",
+ expectedEncoding: "0x000000cd000000540000005400000054000000540000002a0000000000000000fb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666cdf850000b400c45c00688b007efb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666cdf850000b400c45c00688b007efb2809A5314473E1165f6B58018E20ed8F07B840666cdf74c0866635457d36ab318d0000f3a44b7b0d08f4e198b80000000000000000000000000000d18bd45f0b94f54a968f0000000000000000000000000000000000000000000000000000c976bf098c4dba0a061d000000000000000000000000000040",
},
- }
-
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- assert.Equal(t, tc.expectedConcatenatedInteractions, tc.extension.getConcatenatedInteractions())
- })
- }
-}
-
-func TestGetOffsets(t *testing.T) {
-
- tests := []struct {
- name string
- extension Extension
- expectedOffsets string
- }{
{
- name: "Single hex value",
+ name: "Realistic Order 2",
extension: Extension{
- InteractionsArray: []string{
- "",
- "",
- "",
- "",
- "",
- "0x45c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3",
- "",
- "",
- },
+ MakerAssetSuffix: "0x",
+ TakerAssetSuffix: "0x",
+ MakingAmountData: "fb2809A5314473E1165f6B58018E20ed8F07B8400000000000000067217a910000b401a70b",
+ TakingAmountData: "fb2809A5314473E1165f6B58018E20ed8F07B8400000000000000067217a910000b401a70b",
+ Predicate: "0x",
+ MakerPermit: "0x",
+ PreInteraction: "0x",
+ PostInteraction: "0xfb2809A5314473E1165f6B58018E20ed8F07B84067217a80c0866635457d36ab318d00002385c09fca8e96142deb0000000000000000000000000000d18bd45f0b94f54a968f0000000000000000000000000000ade19567bb538035ed360000fff14e3bbfd616d555650000000000000000000000000000000000000000000000000000f3a44b7b0d08f4e198b80000c976bf098c4dba0a061d000000000000000000000000000060",
},
- expectedOffsets: "F4000000F4000000F40000000000000000000000000000000000000000",
+ expectedEncoding: "0x000000f30000004a0000004a0000004a0000004a000000250000000000000000fb2809A5314473E1165f6B58018E20ed8F07B8400000000000000067217a910000b401a70bfb2809A5314473E1165f6B58018E20ed8F07B8400000000000000067217a910000b401a70bfb2809A5314473E1165f6B58018E20ed8F07B84067217a80c0866635457d36ab318d00002385c09fca8e96142deb0000000000000000000000000000d18bd45f0b94f54a968f0000000000000000000000000000ade19567bb538035ed360000fff14e3bbfd616d555650000000000000000000000000000000000000000000000000000f3a44b7b0d08f4e198b80000c976bf098c4dba0a061d000000000000000000000000000060",
},
{
- name: "Fusion Order",
+ name: "Simple Limit Order 2",
extension: Extension{
- InteractionsArray: []string{
- "0x",
- "0x",
- "fb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666cdf850000b400c45c00688b007e",
- "fb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666cdf850000b400c45c00688b007e",
- "0x",
- "0x",
- "0x",
- "0xfb2809A5314473E1165f6B58018E20ed8F07B840666cdf74c0866635457d36ab318d0000f3a44b7b0d08f4e198b80000000000000000000000000000d18bd45f0b94f54a968f0000000000000000000000000000000000000000000000000000c976bf098c4dba0a061d000000000000000000000000000040",
- },
+ MakerAssetSuffix: "0x01",
+ TakerAssetSuffix: "0x0222",
+ MakingAmountData: "0x033344",
+ TakingAmountData: "0x04",
+ Predicate: "0x05",
+ MakerPermit: "0x06",
+ PreInteraction: "0x075533",
+ PostInteraction: "0x4A7F9C3B2D8E1F5A6B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F8A7B6C5D4E3F2A1B0C9D8E7F6A5B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F8A7B6C5D4E3F2A1B0C9D8E7F6A5B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F8A7B6C5D4E3F2A1B0C9D8E7F6A5B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F8A7B6C5D4E3F2A1B0C9D8E7F6A5B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F8A7B6C5D4E3F2A1B0C9D8E7F6A5B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F8A7B6C5D4E3F2A1B0C9D8E7F6A5B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F87",
},
- expectedOffsets: "000000cd000000540000005400000054000000540000002a0000000000000000",
+ expectedEncoding: "0x000000da0000000c0000000900000008000000070000000600000003000000010102220333440405060755334A7F9C3B2D8E1F5A6B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F8A7B6C5D4E3F2A1B0C9D8E7F6A5B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F8A7B6C5D4E3F2A1B0C9D8E7F6A5B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F8A7B6C5D4E3F2A1B0C9D8E7F6A5B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F8A7B6C5D4E3F2A1B0C9D8E7F6A5B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F8A7B6C5D4E3F2A1B0C9D8E7F6A5B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F8A7B6C5D4E3F2A1B0C9D8E7F6A5B4C3D2E1F0A9B8C7D6E5F4A3B2C1D0E9F87",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
- expectedOffsets := strings.ToLower(fmt.Sprintf("%064s", tc.expectedOffsets))
-
- offsets := tc.extension.getOffsets()
- paddedOffsetHex := strings.ToLower(fmt.Sprintf("%064x", offsets))
- assert.Equal(t, expectedOffsets, paddedOffsetHex)
+ result, err := tc.extension.Encode()
+ require.NoError(t, err)
+ assert.Equal(t, strings.ToLower(tc.expectedEncoding), strings.ToLower(result))
})
}
}
-func TestEncode(t *testing.T) {
-
+// TestDecodeExtension contains all unit tests for the DecodeEscrowExtension function.
+func TestDecodeExtension(t *testing.T) {
tests := []struct {
- name string
- extension Extension
- expectedEncoding string
+ name string
+ hexInput string
+ expected *Extension
+ expectingErr bool
+ errorContains string
}{
{
- name: "Another Fusion Order",
- extension: Extension{
- InteractionsArray: []string{
- "0x",
- "0x",
- "fb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666cdf850000b400c45c00688b007e",
- "fb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666cdf850000b400c45c00688b007e",
- "0x",
- "0x",
- "0x",
- "0xfb2809A5314473E1165f6B58018E20ed8F07B840666cdf74c0866635457d36ab318d0000f3a44b7b0d08f4e198b80000000000000000000000000000d18bd45f0b94f54a968f0000000000000000000000000000000000000000000000000000c976bf098c4dba0a061d000000000000000000000000000040",
- },
- },
- expectedEncoding: "0x000000cd000000540000005400000054000000540000002a0000000000000000fb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666cdf850000b400c45c00688b007efb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666cdf850000b400c45c00688b007efb2809A5314473E1165f6B58018E20ed8F07B840666cdf74c0866635457d36ab318d0000f3a44b7b0d08f4e198b80000000000000000000000000000d18bd45f0b94f54a968f0000000000000000000000000000000000000000000000000000c976bf098c4dba0a061d000000000000000000000000000040",
- },
- {
- name: "Anotherer Fusion Order",
- extension: Extension{
- InteractionsArray: []string{
- "",
- "",
- "fb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666ce2ca0000b400c45c007db8007e",
- "fb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666ce2ca0000b400c45c007db8007e",
- "",
- "",
- "",
- "0xfb2809A5314473E1165f6B58018E20ed8F07B840666ce2b9c0866635457d36ab318d0000f3a44b7b0d08f4e198b80000000000000000000000000000d18bd45f0b94f54a968f0000000000000000000000000000000000000000000000000000c976bf098c4dba0a061d000000000000000000000000000040",
- "",
- },
+ name: "Successful Decoding",
+ hexInput: "00000008000000070000000600000005000000040000000300000002000000010102050604030708",
+ expected: &Extension{
+ MakerAssetSuffix: "0x01",
+ TakerAssetSuffix: "0x02",
+ MakingAmountData: "0x05",
+ TakingAmountData: "0x06",
+ Predicate: "0x04",
+ MakerPermit: "0x03",
+ PreInteraction: "0x07",
+ PostInteraction: "0x08",
},
- expectedEncoding: "0xcd000000cd000000540000005400000054000000540000002a0000000000000000fb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666ce2ca0000b400c45c007db8007efb2809A5314473E1165f6B58018E20ed8F07B84000000000000000666ce2ca0000b400c45c007db8007efb2809A5314473E1165f6B58018E20ed8F07B840666ce2b9c0866635457d36ab318d0000f3a44b7b0d08f4e198b80000000000000000000000000000d18bd45f0b94f54a968f0000000000000000000000000000000000000000000000000000c976bf098c4dba0a061d000000000000000000000000000040",
+ expectingErr: false,
},
}
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- result := tc.extension.Encode()
- assert.Equal(t, strings.ToLower(tc.expectedEncoding), strings.ToLower(result))
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Convert hex string to bytes
+ data, err := hexToBytes(tt.hexInput)
+ if err != nil {
+ t.Fatalf("Failed to convert hex to bytes: %v", err)
+ }
+
+ // Decode the data
+ decoded, err := Decode(data)
+
+ if tt.expectingErr {
+ if err == nil {
+ t.Errorf("Expected error but got none")
+ } else if tt.errorContains != "" && !contains(err.Error(), tt.errorContains) {
+ t.Errorf("Expected error to contain '%s' but got '%s'", tt.errorContains, err.Error())
+ }
+ } else {
+ if err != nil {
+ t.Errorf("Unexpected error: %v", err)
+ }
+ if !extensionsEqual(decoded, tt.expected) {
+ t.Errorf("Decoded Extension does not match expected.\nGot: %+v\nExpected: %+v", decoded, tt.expected)
+ }
+ }
})
}
}
+
+func extensionsEqual(a, b *Extension) bool {
+ return strings.TrimPrefix(a.MakerAssetSuffix, "0x") == strings.TrimPrefix(b.MakerAssetSuffix, "0x") &&
+ strings.TrimPrefix(a.TakerAssetSuffix, "0x") == strings.TrimPrefix(b.TakerAssetSuffix, "0x") &&
+ strings.TrimPrefix(a.MakingAmountData, "0x") == strings.TrimPrefix(b.MakingAmountData, "0x") &&
+ strings.TrimPrefix(a.TakingAmountData, "0x") == strings.TrimPrefix(b.TakingAmountData, "0x") &&
+ strings.TrimPrefix(a.Predicate, "0x") == strings.TrimPrefix(b.Predicate, "0x") &&
+ strings.TrimPrefix(a.MakerPermit, "0x") == strings.TrimPrefix(b.MakerPermit, "0x") &&
+ strings.TrimPrefix(a.PreInteraction, "0x") == strings.TrimPrefix(b.PreInteraction, "0x") &&
+ strings.TrimPrefix(a.PostInteraction, "0x") == strings.TrimPrefix(b.PostInteraction, "0x")
+ // strings.TrimPrefix(a.CustomData, "0x") == strings.TrimPrefix(b.CustomData, "0x")
+}
diff --git a/sdk-clients/orderbook/limitorder.go b/sdk-clients/orderbook/limitorder.go
index 2bf8d9ea..30e82561 100644
--- a/sdk-clients/orderbook/limitorder.go
+++ b/sdk-clients/orderbook/limitorder.go
@@ -16,17 +16,27 @@ import (
func CreateLimitOrderMessage(orderRequest CreateOrderParams, chainId int) (*Order, error) {
+ encodedExtension, err := orderRequest.Extension.Encode()
+ if err != nil {
+ return nil, fmt.Errorf("error encoding extension: %v", err)
+ }
+
+ // TODO this is a temporary fix to simulate the same extension data after a refactor
+ if encodedExtension == "0x0000000000000000000000000000000000000000000000000000000000000000" {
+ encodedExtension = "0x"
+ }
+
orderData := OrderData{
MakerAsset: orderRequest.MakerAsset,
TakerAsset: orderRequest.TakerAsset,
MakingAmount: orderRequest.MakingAmount,
TakingAmount: orderRequest.TakingAmount,
- Salt: GenerateSalt(orderRequest.Extension.Encode()),
+ Salt: GenerateSalt(encodedExtension),
Maker: orderRequest.Maker,
AllowedSender: "0x0000000000000000000000000000000000000000",
Receiver: orderRequest.Taker,
MakerTraits: orderRequest.MakerTraits.Encode(),
- Extension: orderRequest.Extension.Encode(),
+ Extension: encodedExtension,
}
aggregationRouter, err := constants.Get1inchRouterFromChainId(chainId)
@@ -116,9 +126,13 @@ func CreateLimitOrderMessage(orderRequest CreateOrderParams, chainId int) (*Orde
}, err
}
+var timeNow = func() int64 {
+ return time.Now().UnixNano()
+}
+
func GenerateSalt(extension string) string {
if extension == "0x" {
- return fmt.Sprintf("%d", time.Now().UnixNano()/int64(time.Millisecond))
+ return fmt.Sprintf("%d", timeNow()/int64(time.Millisecond))
}
byteConverted, err := stringToHexBytes(extension)
diff --git a/sdk-clients/orderbook/orderbook_types_manual.go b/sdk-clients/orderbook/orderbook_types_manual.go
index 700e6587..1d952160 100644
--- a/sdk-clients/orderbook/orderbook_types_manual.go
+++ b/sdk-clients/orderbook/orderbook_types_manual.go
@@ -13,6 +13,7 @@ type CreateOrderParams struct {
SeriesNonce *big.Int
MakerTraits *MakerTraits
Extension Extension
+ ExtensionEncoded string
ExpireAfterUnix int64
Maker string
MakerAsset string
diff --git a/sdk-clients/orderbook/takerTraits_test.go b/sdk-clients/orderbook/takerTraits_test.go
index b805532c..ad364850 100644
--- a/sdk-clients/orderbook/takerTraits_test.go
+++ b/sdk-clients/orderbook/takerTraits_test.go
@@ -4,11 +4,10 @@ import (
"fmt"
"testing"
+ "github.com/1inch/1inch-sdk-go/internal/bigint"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
-
- "github.com/1inch/1inch-sdk-go/internal/validate"
)
func TestTakerTraitsEncode(t *testing.T) {
@@ -32,7 +31,7 @@ func TestTakerTraitsEncode(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
- expectedTakerTraitsBig, err := validate.BigIntFromString(tc.expectedTakerTraits)
+ expectedTakerTraitsBig, err := bigint.FromString(tc.expectedTakerTraits)
require.NoError(t, err)
takerTraits := NewTakerTraits(tc.takerTraitParams)
diff --git a/sdk-clients/portfolio/validation.go b/sdk-clients/portfolio/validation.go
index 89e416f1..5de8174f 100644
--- a/sdk-clients/portfolio/validation.go
+++ b/sdk-clients/portfolio/validation.go
@@ -7,28 +7,28 @@ import (
func (params *GetCurrentValuePortfolioV4OverviewProtocolsCurrentValueGetParams) Validate() error {
var validationErrors []error
validationErrors = validate.Parameter(params.Addresses, "Addresses", validate.CheckEthereumAddressListRequired, validationErrors)
- validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainId, validationErrors)
+ validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainIdInt, validationErrors)
return validate.ConsolidateValidationErorrs(validationErrors)
}
func (params *GetProfitAndLossPortfolioV4OverviewProtocolsProfitAndLossGetParams) Validate() error {
var validationErrors []error
validationErrors = validate.Parameter(params.Addresses, "Addresses", validate.CheckEthereumAddressListRequired, validationErrors)
- validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainId, validationErrors)
+ validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainIdInt, validationErrors)
return validate.ConsolidateValidationErorrs(validationErrors)
}
func (params *GetCurrentValuePortfolioV4GeneralCurrentValueGetParams) Validate() error {
var validationErrors []error
validationErrors = validate.Parameter(params.Addresses, "Addresses", validate.CheckEthereumAddressListRequired, validationErrors)
- validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainId, validationErrors)
+ validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainIdInt, validationErrors)
return validate.ConsolidateValidationErorrs(validationErrors)
}
func (params *GetProfitAndLossPortfolioV4GeneralProfitAndLossGetParams) Validate() error {
var validationErrors []error
validationErrors = validate.Parameter(params.Addresses, "Addresses", validate.CheckEthereumAddressListRequired, validationErrors)
- validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainId, validationErrors)
+ validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainIdInt, validationErrors)
//validationErrors = validate.Parameter(params.Timerange, "Timerange", validate.CheckTimerange, validationErrors) // TODO "x-go-type-skip-optional-pointer": true does not work as expected for parameters of type schema. Need to research this
return validate.ConsolidateValidationErorrs(validationErrors)
}
@@ -36,28 +36,28 @@ func (params *GetProfitAndLossPortfolioV4GeneralProfitAndLossGetParams) Validate
func (params *GetDetailsPortfolioV4OverviewProtocolsDetailsGetParams) Validate() error {
var validationErrors []error
validationErrors = validate.Parameter(params.Addresses, "Addresses", validate.CheckEthereumAddressListRequired, validationErrors)
- validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainId, validationErrors)
+ validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainIdInt, validationErrors)
return validate.ConsolidateValidationErorrs(validationErrors)
}
func (params *GetCurrentValuePortfolioV4OverviewErc20CurrentValueGetParams) Validate() error {
var validationErrors []error
validationErrors = validate.Parameter(params.Addresses, "Addresses", validate.CheckEthereumAddressListRequired, validationErrors)
- validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainId, validationErrors)
+ validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainIdInt, validationErrors)
return validate.ConsolidateValidationErorrs(validationErrors)
}
func (params *GetProfitAndLossPortfolioV4OverviewErc20ProfitAndLossGetParams) Validate() error {
var validationErrors []error
validationErrors = validate.Parameter(params.Addresses, "Addresses", validate.CheckEthereumAddressListRequired, validationErrors)
- validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainId, validationErrors)
+ validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainIdInt, validationErrors)
return validate.ConsolidateValidationErorrs(validationErrors)
}
func (params *GetDetailsPortfolioV4OverviewErc20DetailsGetParams) Validate() error {
var validationErrors []error
validationErrors = validate.Parameter(params.Addresses, "Addresses", validate.CheckEthereumAddressListRequired, validationErrors)
- validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainId, validationErrors)
+ validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainIdInt, validationErrors)
//validationErrors = validate.Parameter(params.Timerange, "Timerange", validate.CheckTimerange, validationErrors) // TODO "x-go-type-skip-optional-pointer": true does not work as expected for parameters of type schema. Need to research this
return validate.ConsolidateValidationErorrs(validationErrors)
}
@@ -65,7 +65,7 @@ func (params *GetDetailsPortfolioV4OverviewErc20DetailsGetParams) Validate() err
func (params *GetValueChartPortfolioV4GeneralValueChartGetParams) Validate() error {
var validationErrors []error
validationErrors = validate.Parameter(params.Addresses, "Addresses", validate.CheckEthereumAddressListRequired, validationErrors)
- validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainId, validationErrors)
+ validationErrors = validate.Parameter(params.ChainId, "ChainId", validate.CheckChainIdInt, validationErrors)
//validationErrors = validate.Parameter(params.Timerange, "Timerange", validate.CheckTimerange, validationErrors) // TODO "x-go-type-skip-optional-pointer": true does not work as expected for parameters of type schema. Need to research this
return validate.ConsolidateValidationErorrs(validationErrors)
}