Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) starting with the *v1.0.0-beta.1* release.

## [v2.0.0] - 2025-11-05
[v2.0.0 release page](https://github.com/1inch/1inch-sdk-go/releases/tag/v2.0.0)

### Changed
- Fusion Plus updated to use v1.1 API

## [v2.0.0-preview.2] - 2025-10-30
[v2.0.0-preview.2 release page](https://github.com/1inch/1inch-sdk-go/releases/tag/v2.0.0-preview.2)

### Breaking Changes
- Limit Orders have been refactored. Order creation now uses a different flow. See the examples for more details.

### Changed
- Limit Order SDK updated to support v4.1 API

## [v2.0.0-preview] - 2025-1-22
[v2.0.0-preview release page](https://github.com/1inch/1inch-sdk-go/releases/tag/v2.0.0-preview)

Expand Down
66 changes: 10 additions & 56 deletions sdk-clients/fusionplus/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ package fusionplus
import (
"context"
"encoding/json"
"errors"
"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)
u := fmt.Sprintf("/fusion-plus/orders/v1.1/order/status/%s", params.Hash)

payload := common.RequestPayload{
Method: "GET",
Expand All @@ -28,8 +27,8 @@ func (api *api) GetOrderByOrderHash(ctx context.Context, params GetOrderByOrderH
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)
func (api *api) GetReadyToAcceptFills(ctx context.Context, params GetReadyToAcceptFillsParams) (*ReadyToAcceptSecretFills, error) {
u := fmt.Sprintf("/fusion-plus/orders/v1.1/order/ready-to-accept-secret-fills/%s", params.Hash)

payload := common.RequestPayload{
Method: "GET",
Expand All @@ -48,7 +47,7 @@ func (api *api) GetReadyToAcceptFills(ctx context.Context, params GetOrderByOrde
}

func (api *api) SubmitSecret(ctx context.Context, params SecretInput) error {
u := "/fusion-plus/relayer/v1.0/submit/secret"
u := "/fusion-plus/relayer/v1.1/submit/secret"

body, err := json.Marshal(params)
if err != nil {
Expand All @@ -70,57 +69,10 @@ func (api *api) SubmitSecret(ctx context.Context, params SecretInput) error {
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) {
return nil, errors.New("fusion Plus API currently not supported")

//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)
u := "/fusion-plus/quoter/v1.1/quote/receive"

body, err := json.Marshal(presetDetails)
err := params.Validate()
if err != nil {
return nil, err
}
Expand All @@ -129,7 +81,7 @@ func (api *api) GetQuoteWithCustomPreset(ctx context.Context, params QuoterContr
Method: "GET",
Params: params,
U: u,
Body: body,
Body: nil,
}

var response GetQuoteOutputFixed
Expand All @@ -138,12 +90,14 @@ func (api *api) GetQuoteWithCustomPreset(ctx context.Context, params QuoterContr
return nil, err
}

// TODO must normalize response here

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"
u := "/fusion-plus/relayer/v1.1/submit"

err := orderParams.Validate()
if err != nil {
Expand Down
1 change: 0 additions & 1 deletion sdk-clients/fusionplus/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ type Client struct {
}

type api struct {
chainId uint64
httpExecutor common.HttpExecutor
}

Expand Down
137 changes: 133 additions & 4 deletions sdk-clients/fusionplus/escrowextension.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ 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/internal/hexadecimal"
"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
ExtensionFusion
HashLock *HashLock
DstChainId float32
DstToken common.Address
Expand All @@ -24,13 +27,13 @@ type EscrowExtension struct {

func NewEscrowExtension(escrowParams EscrowExtensionParams) (*EscrowExtension, error) {

extension, err := fusion.NewExtension(escrowParams.ExtensionParams)
extension, err := NewExtensionFusion(escrowParams.ExtensionParamsFusion)
if err != nil {
return nil, err
}

escrowExtension := &EscrowExtension{
Extension: *extension,
ExtensionFusion: *extension,
HashLock: escrowParams.HashLock,
DstChainId: escrowParams.DstChainId,
DstToken: escrowParams.DstToken,
Expand Down Expand Up @@ -83,6 +86,132 @@ func (e *EscrowExtension) ConvertToOrderbookExtension() (*orderbook.Extension, e
}, 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 := 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{
ExtensionFusion: *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
Expand Down
Loading