Orion Protocol
  • Building on Orion
    • Architecture
    • SDK (Javascript)
      • Integration Guide
      • High level methods
      • Low level methods
    • APIs
      • Aggregator API
        • Order API
        • Exchange API
        • Swap API
          • DEX liquidity
          • How to Use Swap API
        • Orderbook API
        • Paths API
        • Pairs API
        • Pools API
        • Atomic Swap API
          • How to Use Atomic Swap
        • WebSocket API
    • Smart Contracts
      • Oracle
    • Public Repositories
Powered by GitBook
On this page
  • 1. Set a Token Allowance
  • TIP
  • 2. Perform initial lock transaction.
  • 2.1 Payload preparation (secret)
  • 2.2 Payload preparation (lockOrder)
  • 3. Order placement using Atomic Swap API
  • 4. Calling redeemAtomic method
  1. Building on Orion
  2. APIs
  3. Aggregator API
  4. Atomic Swap API

How to Use Atomic Swap

PreviousAtomic Swap APINextWebSocket API

Last updated 1 year ago

1. Set a Token Allowance

A is required if you want a third-party to move funds on your behalf. In short, you are allowing them to move your tokens.

In our case, we would like the Orion Exchange contract to trade our ERC20 tokens for us, so we will need to approve an allowance (a certain amount) for this contract to move a certain amount of our ERC20 tokens on our behalf.

When setting the token allowance, make sure to provide enough allowance for the buy or sell amount as well as the gas.

TIP

When setting the token allowance, make sure to provide enough allowance for the buy or sell amount as well as the gas; otherwise, you may receive a 'Gas estimation failed' error.

2. Perform initial lock transaction.

In order to carry out a cross-chain transaction, the first step is to lock your assets on the origin chain. This is achieved by invoking the lockAtomic method on the Exchange contract. The process involves two main steps described in following sections.

2.1 Payload preparation (secret)

Here is the code snippet to correctly generate secretHash & secret for lockOrder.

This part of the process involves generating a secret hash and secret for the lock order. Here's a Go code snippet that accomplishes this task. This code utilizes the crypto/rand package from Go's standard library to generate random bytes, and the crypto package from go-ethereum to hash the secret.

You can check this code here:

package main

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"log"
	"strconv"
	"github.com/ethereum/go-ethereum/crypto"
)

func main() {
	const randomBits = 256
	randomBytes := make([]byte, randomBits/8)

	if _, err := rand.Read(randomBytes); err != nil {
		log.Fatal(err)
	}

	random := hex.EncodeToString(randomBytes)

	// user address, amount, asset name, chain id, salt
	data := [][]byte{
		[]byte("0x61Eed69c0d112C690fD6f44bB621357B89fBE67F"),
		[]byte(strconv.FormatInt(12400000000, 10)),
		[]byte("ORN"),
		[]byte(strconv.FormatInt(0x61, 10)),
		[]byte(random),
	}

	hash := crypto.Keccak256Hash(data...)
	fmt.Printf("Hash: %s\n", hash.Hex())
	// will output this
	// Hash: 0xa1c0f31c75a94bf6beb9587c26da286a4dea9b83936eaac4648851a3f0e7fd46

	// Then we can form LockOrder
}

2.2 Payload preparation (lockOrder)

Here is the code snipper to perform lockAtomic call.

This step involves preparing the lock order payload and then invoking the lockAtomic function on the smart contract. Here's a Go code snippet that uses the go-ethereum package to interact with the EVM network. This code snippet essentially constructs a transaction calling the lockAtomic method on the contract, then signs it with the private key, and finally sends it to the EVM network of your choice.

package atomicswap

import (
	"context"
	"crypto/ecdsa"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"log"
	"math/big"
	"strconv"
	"strings"
	"testing"

	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
	"time"
)

// solidity-compatible struct
type LockOrder struct {
	Sender        common.Address
	Expiration    uint64
	Asset         common.Address
	Amount        uint64
	TargetChainId *big.Int
	SecretHash    [32]byte
}

// RedeemOrder represents the input parameters to the redeemAtomic function.
type RedeemOrder struct {
	Sender        common.Address
	Receiver      common.Address
	ClaimReceiver common.Address
	Asset         common.Address
	Amount        uint64
	Expiration    uint64
	SecretHash    [32]byte
	Signature     []byte
}


const ExchangeWithAtomicABI = `[{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint64","name":"expiration","type":"uint64"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint64","name":"amount","type":"uint64"},{"internalType":"uint24","name":"targetChainId","type":"uint24"},{"internalType":"bytes32","name":"secretHash","type":"bytes32"}],"internalType":"struct LibAtomic.LockOrder","name":"swap","type":"tuple"}],"name":"lockAtomic","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"claimReceiver","type":"address"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint64","name":"amount","type":"uint64"},{"internalType":"uint64","name":"expiration","type":"uint64"},{"internalType":"bytes32","name":"secretHash","type":"bytes32"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct LibAtomic.RedeemOrder","name":"order","type":"tuple"},{"internalType":"bytes","name":"secret","type":"bytes"}],"name":"redeemAtomic","outputs":[],"stateMutability":"nonpayable","type":"function"}]`

// cl65:~ $ cast wallet new
// Address:     0xfb77f4bF210eb58D3966eE6C8fB23E802F8Cf394
// Private key: 0x435200deac28cf6acb411238abf556c48f74afc2e5d6bea4d7a2cd754c78d564

func main() {
	client, err := ethclient.Dial("https://rpc.mevblocker.io")
	if err != nil {
		log.Fatalf("Failed to connect to the Ethereum client: %v", err)
	}
	
	privateKey, err := crypto.HexToECDSA("435200deac28cf6acb411238abf556c48f74afc2e5d6bea4d7a2cd754c78d564")
	if err != nil {
		log.Fatal(err)
	}

	publicKey := privateKey.Public()
	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
	if !ok {
		log.Fatal("error casting public key to ECDSA")
	}

	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
	nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
	if err != nil {
		log.Fatal(err)
	}

	gasPrice, err := client.SuggestGasPrice(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	parsedABI, err := abi.JSON(strings.NewReader(ExchangeWithAtomicABI)) // replace with your contract ABI
	if err != nil {
		log.Fatalf("Failed to parse contract ABI: %v", err)
	}

	targetChain := big.NewInt(56)

	swap := &LockOrder{
		Sender:        common.HexToAddress("0xSenderAddress"), // replace with your sender address
		Expiration:    time.Now().Unix(),
		Asset:         common.HexToAddress("0xAssetAddress"), // replace with your asset address
		Amount:        1000,
		TargetChainId: targetChain,
		SecretHash:    crypto.Keccak256Hash([]byte("secret")),
	}

	data, err := parsedABI.Pack("lockAtomic", swap)
	if err != nil {
		log.Fatalf("Failed to pack data for lockAtomic: %v", err)
	}

	gasLimit := uint64(450_000)

	toAddress := common.HexToAddress("0xYourContractAddress") // replace with your contract address
	tx := types.NewTransaction(nonce, toAddress, big.NewInt(0), gasLimit, gasPrice, data)

	chainID, err := client.NetworkID(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("signedTx: %+v", signedTx)

	// 
	// err = client.SendTransaction(context.Background(), signedTx)
	// if err != nil {
	// 	log.Fatal(err)
	// }
}

It's necessary to replace "ExchangeWithAtomicABI" with the ABI of Exchange contract. Lastly, remember to uncomment the line that sends the transaction if you wish to perform the actual lock operation.

3. Order placement using Atomic Swap API

Next step is let Aggregator (Atomic Swap API) know that you've sent lock transaction. This step is crucial to make redeem possible on target chain.

In our example, origin chain is BSC, destination chain is Fantom.

package main

import (
	"bytes"
	"log"
	"net/http"
)

func main() {
	// you should use destination chain API
	url := "https://trade-ftm.orionprotocol.io/backend/api/v1/atomic-swap"
	data := `{"secretHash":"0xa61f19d6a28c191ccfe32d41754e5ab2545cc1b72b2731f872e0275a1b27520a","sourceNetworkCode":"BSC"}`

	req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data)))
	if err != nil {
		log.Fatal("Error creating request: ", err)
	}

	req.Header.Set("authority", "trade-ftm.orionprotocol.io")
	req.Header.Set("accept", "application/json")
	req.Header.Set("accept-language", "en-US,en;q=0.9,ru;q=0.8")
	req.Header.Set("content-type", "application/json")
	req.Header.Set("origin", "https://trade.orionprotocol.io")
	req.Header.Set("referer", "https://trade.orionprotocol.io/")
	req.Header.Set("sec-ch-ua-mobile", "?0")
	req.Header.Set("sec-ch-ua-platform", `"macOS"`)
	req.Header.Set("sec-fetch-dest", "empty")
	req.Header.Set("sec-fetch-mode", "cors")
	req.Header.Set("sec-fetch-site", "same-site")
	
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal("Error sending request: ", err)
	}
	defer resp.Body.Close()

	// Handle response here
}
#!/bin/bash


curl 'https://trade-ftm.orionprotocol.io/backend/api/v1/atomic-swap' \
  -H 'authority: trade-ftm.orionprotocol.io' \
  -H 'accept: application/json' \
  -H 'accept-language: en-US,en;q=0.9,ru;q=0.8' \
  -H 'content-type: application/json' \
  -H 'origin: https://trade.orionprotocol.io' \
  -H 'referer: https://trade.orionprotocol.io/' \
  -H 'sec-ch-ua: "Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"' \
  -H 'sec-fetch-dest: empty' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-site: same-site' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' \
  --data-raw '{"secretHash":"0xa61f19d6a28c191ccfe32d41754e5ab2545cc1b72b2731f872e0275a1b27520a","sourceNetworkCode":"BSC"}' \
  --compressed

4. Calling redeemAtomic method

package atomicswap

import (
	"context"
	"crypto/ecdsa"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"log"
	"math/big"
	"strconv"
	"strings"
	"testing"

	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
)



// cl65:~ $ cast wallet new
// Address:     0xfb77f4bF210eb58D3966eE6C8fB23E802F8Cf394
// Private key: 0x435200deac28cf6acb411238abf556c48f74afc2e5d6bea4d7a2cd754c78d564

func main() {
	client, err := ethclient.Dial("https://rpc.mevblocker.io")
	if err != nil {
		log.Fatalf("Failed to connect to the Ethereum client: %v", err)
	}
	
	privateKey, err := crypto.HexToECDSA("435200deac28cf6acb411238abf556c48f74afc2e5d6bea4d7a2cd754c78d564")
	if err != nil {
		log.Fatal(err)
	}

	publicKey := privateKey.Public()
	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
	if !ok {
		log.Fatal("error casting public key to ECDSA")
	}

	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
	nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
	if err != nil {
		log.Fatal(err)
	}

	gasPrice, err := client.SuggestGasPrice(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	parsedABI, err := abi.JSON(strings.NewReader(ExchangeWithAtomicABI)) // replace with your contract ABI
	if err != nil {
		log.Fatalf("Failed to parse contract ABI: %v", err)
	}

	order := RedeemOrder{
		Sender:        common.HexToAddress("0xSenderAddress"), // replace with your sender address
		Receiver:      common.HexToAddress("0xReceiverAddress"), // replace with your receiver address
		ClaimReceiver: common.HexToAddress("0xClaimReceiver"), // replace with your claim receiver address
		Asset:         common.HexToAddress("0xAssetAddress"), // replace with your asset address
		Amount:        uint64(1000),
		Expiration:    uint64(123456789),
		SecretHash:    crypto.Keccak256Hash([]byte("secretHash")),
		Signature:     []byte("signature"), // replace with your signature
	}

	// parsedABI, err := abi.JSON(strings.NewReader(contractABI))
	if err != nil {
		log.Fatalf("Failed to parse contract ABI: %v", err)
	}

	data, err := parsedABI.Pack("redeemAtomic", order, []byte("secret"))
	if err != nil {
		log.Fatalf("Failed to pack data for redeemAtomic: %v", err)
	}

	gasLimit := uint64(450_000)

	toAddress := common.HexToAddress("0xYourContractAddress") // replace with your contract address
	tx := types.NewTransaction(nonce, toAddress, big.NewInt(0), gasLimit, gasPrice, data)

	chainID, err := client.NetworkID(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("signedTx: %+v", signedTx)

	// 
	// err = client.SendTransaction(context.Background(), signedTx)
	// if err != nil {
	// 	log.Fatal(err)
	// }
}

In the given RedeemOrder structure, you also need to replace []byte("signature") with the actual byte slice signature you received when the redeem order was created. Please remember that the process of generating a proper signature might be quite complex and can require specific cryptographic libraries or tools.

token allowance
https://go.dev/play/p/OkyX1dqJorS