DEV Community

Den
Den

Posted on

The Smoothest Cross-Chain Integration I’ve Ever Built

I used to think cross-chain swaps were one of the most complicated things in crypto.

Every time I looked into them, I’d feel that familiar hesitation — too many moving parts, too many edge cases, and way too much waiting around.

I believe that integrating them was always a pain in the ass. From juggling wallets and gas on multiple chains to writing spaghetti code to handle every possible failure, it felt like duct-taping over a fragmented system.

Then, about a year ago, I came across an idea that completely changed how I looked at cross-chain swaps.

  • What if the swap didn’t have to leave the chain?
  • What if you could make it happen from one place, atomically and as easily as updating a field in a smart contract?

And recently, I finally found a system that makes it real. It’s called NEAR Intents — and on top of that, they’ve just released something called the 1Click API, which blew me away.

What are Near Intents?

NEAR Intents is a multi-chain transaction protocol designed for peer-to-peer exchanges of any kind of resource, whether that’s tokens, NFTs, or even access rights to services or data. In other words, it enables people to trade whatever holds value to them.

Image description

The protocol is made up of three main components:

  1. Solvers (or Market Makers)
    These are automated agents that participate in the exchange process. Their job is to fulfill user requests by offering the resources the user wants, in return for whatever the user is offering. Solvers compete with one another to provide the best terms. If one solver offers a worse deal, another one can step in with a better one, effectively outbidding them. This competition ensures users always get the most favorable rate. Solvers earn a small fee from each successful trade, which motivates them to stay competitive and keep the system running efficiently.

  2. Verifier Smart Contract
    This contract runs on the NEAR Protocol and plays a key role in the system. It ensures that each trade is executed atomically, meaning it either happens in full or not at all, and it verifies the integrity of the transaction. We’ll dig into how exactly this verification works a bit later when we walk through an example.

  3. Applications (or Distribution Channels)
    These are the frontends or interfaces that users interact with (wallets, dApps, exchanges, or any other service that connects to the protocol). While technically optional, since it's peer-to-peer and users could interact with the protocol directly, in practice, most people use apps or wallets to make the experience smoother and more intuitive.

How does the Cross-Chain part work?

Right now, the main use case for Near Intents is peer-to-peer money transfers, swapping tokens across different networks. But how does Near make cross-chain swaps possible?

That’s where Omni Bridge comes in. It’s a core service within the Near ecosystem that allows assets to move between blockchains. Here’s how it works: when a user wants to bridge tokens to Near, they burn the tokens on the source chain and generate a proof of that burn. This proof is then submitted to Near, where it’s verified by the Near Light Client. And once the proof checks out, the Near side mints the equivalent amount of corresponding tokens (NEP‑141) and credits them to the user’s account.

Walkthrough with Alice

Let’s walk through how someone, say, Alice, would use Near Intents to swap 100 USDT on Ethereum for BTC on the Bitcoin network.

You can find another TypeScript example here.

Step 1: Deposit to Near

As described earlier, Alice starts by burning her USDT tokens on Ethereum. She then sends the proof of that burn to Near. Once the proof is verified, she receives a bridged version of USDT, minted directly to her address on the Near Protocol.

Step 2: Get a Quote

Alice queries a JSON RPC endpoint for swap quotes, specifying that she wants to swap 100 USDT and receive BTC in return.

Under the hood, this triggers a short bidding window, about 1 second, where all available solvers receive Alice’s request. Each solver checks if they can fulfill the trade and, if so, responds with a quote. These responses include how much BTC they can offer and until what time.

Here’s what the request body for querying a quote looks like:

{
    "id": "dontcare",
    "jsonrpc": "2.0",
    "method": "quote",
    "params": [
        {
            "defuse_asset_identifier_in": "nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near", // bridged USDT
            "defuse_asset_identifier_out": "nep141:btc.omft.near", // bridged BTC
            "exact_amount_in": "100000000" // 100 USDT
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

And below is an example of the response you’ll get back:

{
    "jsonrpc": "2.0",
    "id": "dontcare",
    "result": [
        {
            "defuse_asset_identifier_in": "nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near",
            "defuse_asset_identifier_out": "nep141:btc.omft.near",
            "amount_in": "100000000", // 100 USDT
            "amount_out": "99999", // 0.00099999 BTC
            "expiration_time": "2025-06-15T09:42:42.294Z",
            "quote_hash": "2EKjNj2fYe73tbRJegHoeetNCc2oAb2kiFPpVs6VK54P"
        },
        {
            "defuse_asset_identifier_in": "nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near",
            "defuse_asset_identifier_out": "nep141:btc.omft.near",
            "amount_in": "100000000", // 100 USDT
            "amount_out": "100000", // 0.001 BTC
            "expiration_time": "2025-06-15T09:42:42.424Z",
            "quote_hash": "JAvGrCio97STdSX5P52Xo1WpBur3S8LKmoDRzQtJijEw"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

From the list of available quotes, she picks the one she likes best.

Step 3: Create and Sign an Intent

Now that Alice is happy with the quote, she creates an intent - a message saying, "I’m willing to give 100 USDT to receive 0.001 BTC".

Here’s what the intent message looks like:

const message = {
    signer_id: "alice.near",
    deadline: "2025-06-15T09:42:42.424Z",
    intents: [
        {
            intent: "token_diff",
            diff: {
                "nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near": "-100000000",
                "nep141:btc.omft.near": "1000000",
            },
        },
    ],
};
Enter fullscreen mode Exit fullscreen mode

Notice that the USDT amount is negative, which indicates the amount being sent in the swap.

After the intent is created, we also need to generate a random 32-byte nonce, serialize all the data, and produce an ED25519 signature. It's worth noting here that the intent must be signed with a key that has already been registered on the Verifier Smart Contract. This allows the contract to verify both the validity of the signature and that the key belongs to your account.

Step 4: Submit the Intent

Alice submits the signed intent back to the RPC server. Solvers also sign their part, confirming that they’ll deliver the requested asset.

The request should follow this specific format:

{
    "jsonrpc": "2.0",
    "id": "dontcare",
    "method": "publish_intent",
    "params": [
        {
            "quote_hashes": ["JAvGrCio97STdSX5P52Xo1WpBur3S8LKmoDRzQtJijEw"],
            "signed_data": {
                "standard": "nep413",
                "payload": {
                    "message": "{\"deadline\":\"2025-06-15T09:42:42.424Z\",\"intents\":[{\"intent\":\"token_diff\",\"diff\":{\"nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near\":\"-100000000\",\"nep141:btc.omft.near\":\"1000000\"},\"signer_id\":\"alice.near\"}", // serialized intent message
                    "nonce": "D4DFuiyOSjNmYcmu/Rqkx/Q624mBnzk9WhnYdqUz0ek=", // random 32 bytes represented as base64 string
                    "recipient": "intents.near" // verifier smart contract
                },
                "signature": "ed25519:24nL3P37o27uCdsfyFqtCBEPuEgiu90AfaJSJSPpEE8vCAyYZTtgbBEyF7nkZ69nJwqReimDaU4n33zrSwziXiG1",
                "public_key": "ed25519:33fZjXn7DmM2XrAxZWK5XE6EnZugAusMtkKM9Zbb3P4J"
            }
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

In response, we’ll receive a JSON object that includes an intent_hash, which can later be used to check the status of the execution.

{
    "jsonrpc": "2.0",
    "id": "dontcare",
    "result": {
        "status": "OK",
        "intent_hash": "9TBktE2kKuN0XHueyHwsJNQWY4EW73KMDkx9FizWJcsB"
    }
}
Enter fullscreen mode Exit fullscreen mode

Once an intent is published to the RPC, the system builds a composite object that includes all the intents involved in the deal (it doesn't necessarily have to be just two parties, any number of participants are supported in a single agreement).

After all intents are signed by their respective participants, they’re bundled and sent to the Verifier Smart Contract. That’s where the magic happens. The Verifier runs a method called execute_intent, which does:

  1. It checks that each intent is signed with a public key that matches the one declared in the intent itself and that it's already registered with the Verifier contract (this ensures the intent was genuinely signed by the account it claims to come from)

  2. Next, it verifies that the net asset flow across all intents equals zero.
    For example, if Alice offers 100 USDT, then the combined intents of the other participants must result in someone receiving exactly 100 USDT. Same with BTC: if someone gives up 0.001 BTC, someone else must receive exactly that amount.

  3. Once all checks pass, it simply updates token balances. Alice’s USDT balance decreases by 100, but she gains 0.001 BTC. Her counterparty’s balances shift in the opposite direction.

Step 5: Withdraw

Finally, Alice burns her bridged BTC tokens on Near and submits the proof. Just like during the deposit flow, once verified, she receives native BTC on the Bitcoin network.

Making It Seamless with 1Click API

Now, let’s be real. That was a lot of steps. Even if Near Intents is cleaner than most cross-chain protocols, it still requires a solid grasp of knowledge about bridges, contracts, and signatures.

This is exactly why 1Click API exists.

What is 1Click API?

1Click API wraps everything, from quoting to execution, into a single API call. You just tell it what you want to send, what you want to receive, and where it should go. Done.

Image description

The magic of 1Click lies in how little you have to do to make this happen. You simply have to make a POST request to the /quote endpoint. In that request, you describe what asset they are sending and of what amount, what asset they want to receive, and where it should go and how refunds should be handled if anything fails.

Walkthrough with Alice again

To show how seamless this is, let’s go back to the example we explored earlier, of Alice swapping 100 USDT on Ethereum for BTC on Bitcoin, and walk through how it works using the 1Click API.

You can find more TypeScript examples for various 1Click API use cases here.

Step 1: Get a Quote

Alice makes a POST /quote request with the details.

Let’s walk through each important field to understand what it means and why we’re using those specific values.

The complete and latest 1Click API spec is available here.

  • depositType is set to ORIGIN_CHAIN, because the funds originate outside of NEAR, we’re telling the API to expect the deposit on the origin chain

  • originAsset is set to nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near, which is the address of the NEAR-bridged token that mirrors USDT on Ethereum

  • destinationAsset is set to nep141:btc.omft.near, same logic as above

  • amount is the number of USDT tokens Alice wants to send. It's passed over as units, for USDT with 6 decimals, it equals 100000000

  • recipientType is set to ORIGIN_CHAIN and recipient is Alice's Bitcoin wallet address

  • refundType is set to ORIGIN_CHAIN and refundTo is Alice's Ethereum wallet address. These parameters define where the funds should go if something goes wrong. It’s important to provide an address you own. If the refund address is incorrect, Alice could permanently lose access to the funds.

Here’s what the request would look like:

{
    "dry": false,
    "swapType": "EXACT_INPUT",
    "slippageTolerance": 10, // 0.1%
    "depositType": "ORIGIN_CHAIN", // expects a deposit on Ethereum
    "originAsset": "nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near", // bridged USDT
    "destinationAsset": "nep141:btc.omft.near", // bridged BTC
    "amount": "100000000", // 100 USDT
    "recipient": "bc1q3pqz07f0t48lrxunyqqx4yk3f9gr65nfxncc0r", // your Bitcoin wallet
    "recipientType": "ORIGIN_CHAIN", // withdraws on Bitcoin
    "refundTo": "0x427F9620Be0fe8Db2d840E2b6145D1CF2975bcaD",
    "refundType": "ORIGIN_CHAIN",
    "deadline": "2025-06-15T17:21:37.194Z"
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Make the Deposit

Now that we have the deposit address, Alice can send her 100 USDT to it on Ethereum. This transaction is what actually kicks off the swap.

Let’s use viem to send USDT:

import { erc20Abi } from "viem";
import { createWalletClient, http } from "viem";
import { mainnet } from "viem/chains";

const walletClient = createWalletClient({
    chain: mainnet,
    transport: http(), // use injected transport in a browser wallet
});

const depositAddress = response.quote.depositAddress;

const txHash = await walletClient.writeContract({
    account: "0x427F9620Be0fe8Db2d840E2b6145D1CF2975bcaD", // your address
    address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT token address
    abi: erc20Abi,
    functionName: "transfer",
    args: [
        depositAddress,
        100000000, // 100 USDT
    ],
});
Enter fullscreen mode Exit fullscreen mode

After that, the 1Click begins monitoring for the transaction’s finalization.


And that’s it! Once the transaction is fully finalized on the Bitcoin network, Alice will see her updated BTC balance in her wallet.

Was that simple enough? Feel free to share your thoughts in the comments. And if you have any technical questions about the process, don’t hesitate to ask — I’ll be happy to help.

Thanks for reading and have a great day!

Top comments (0)