Live#…
Reading the chain…
Network now · GullyChain Testnet
Block
—
Txs · 30 blocks
—
Gas, gwei
—
Launches
—

Buy from any chain

On this page

A buyer on another chain pays in ETH there. Behind the scenes the funds are bridged to Gully Chain, swapped into the coin on Gully Core and delivered to the buyer's Gully ID smart account. One signature, no network switch. Every coin page on kre8.fun offers it; this page is for builders who want the same flow in their own app.

Live

Testnet: Arbitrum Sepolia -> Gully Chain Testnet over the Gully Bridge fast lane (about a minute). Contract GullyXChainBuy 0x8b6CD68B31d6a8d74828F5ddE747134e5D68D54F, service https://xchain.gullychain.xyz. Mainnet: Base, BNB Chain, Ethereum, Solana and Arbitrum through Relay and LI.FI once they list Gully Chain.

#How it works

  1. The app asks the service for a quote: source chain and asset, amount, the coin, the recipient.
  2. The service answers with an intent (coin, minimum out, recipient, refund address, deadline, nonce), its deposit address on Gully Chain and the one transaction to sign on the source chain, which sends the funds to that deposit address through the fastest lane (fast lane today; Relay or LI.FI at mainnet).
  3. The buyer signs. The funds arrive on the deposit address on Gully Chain.
  4. The Gully executor calls GullyXChainBuy.execute(intent, routerCalldata): the contract pulls the funds from the deposit address, swaps them on Gully Core (UniversalRouter) and sends the coin to the recipient, in one transaction. Anything above the intent's amount goes back to the refund address.
  5. If the swap is impossible before the deadline, refund(intent) returns the funds. After the deadline anyone can call it; the executor can call it any time.

The timeline the buyer sees: sent -> bridging to Gully Chain -> arrived -> buying on Gully Core -> delivered.

#Why a deposit address per intent

Neither the warp route nor the canonical bridge can carry calldata to a contract, and a signature on Gully Chain would only prove what the deposit already proves. The deposit address is CREATE2(GullyXChainBuy, intentHash, XChainVault): sending to it is the buyer's commitment to exactly that intent. The contract can only do what the intent says, funds are attributed by address on chain, and the executor can never pool one buyer's funds into another buyer's swap.

Custody. The contract holds nothing between transactions. The executor key is a gas-only key: it chooses the route and the moment, the contract enforces the coin, minAmountOut, the deadline, the per-intent cap and the recipient. A bad or compromised executor can at worst delay a buy until the deadline, after which anyone can refund. The owner is the governance Safe (caps, executors, pause); the ops key is the guardian (pause only, the kill switch). Pausing stops execute; refund always works. No fee is taken by the contract: the Gully Core swap already pays the pool and launch fees, and there is no bridge fee on testnet.

#The intent

Solidity
struct Intent {
    uint64  srcChainId;    // where the buyer paid (421614 on testnet)
    address buyer;         // the buyer on the source chain
    address tokenIn;       // what arrives on Gully Chain: address(0) = ETH (USDC.e once it has a Gully Core pool)
    address tokenOut;      // the coin
    uint256 amountIn;      // what arrives after bridge fees; swapped in full, the rest is refunded
    uint256 minAmountOut;  // least tokenOut the recipient must get
    address recipient;     // the buyer's Gully ID smart account
    address refundTo;      // where refunds go
    uint64  deadline;      // unix seconds
    bytes32 nonce;         // random: one deposit address per intent
}
bytes32 id    = keccak256(abi.encode(INTENT_TYPEHASH, intent));   // INTENT_TYPEHASH = keccak256("Intent(uint64 srcChainId,...,bytes32 nonce)")
address vault = Create2.computeAddress(id, VAULT_INIT_CODE_HASH, address(GullyXChainBuy));

Test vector: the intent srcChainId 421614, buyer/recipient/refundTo 0x12Fa…7a93, tokenIn 0x0, tokenOut 0x3De9…2358, amountIn 1e14, minAmountOut 1, deadline 1800000000, nonce 0x0101…01 hashes to 0xc49047444c2a7376b8ae7fd45310722470da7cdfc49b9427d67e31ada8593ec5 and its deposit address under the testnet contract is 0x8d2fe80adEedaC35C1CA04105ec3059b394da880. VAULT_INIT_CODE_HASH = 0xeaec77b2300a3cac2e3505740596573a37746bd0ec1604b0f583a0138bae9b3b.

#Integrate from your app

The shared code is the xchain module of the GullyChain SDK (opens in a new tab) (@gullylabs/gullychain-sdk/xchain): the intent format, hashIntent / vaultAddress, the QuoteProvider interface with the Relay, LI.FI and Gully fast-lane adapters, the per-chain config and a client of the service.

TypeScript
import { xchain } from '@gullylabs/gullychain-sdk'

const api = xchain.createXChainBuyClient('https://xchain.gullychain.xyz')
const q = await api.quote({ srcChainId: 421614, asset: 'ETH', amountIn, buyer, tokenOut: coin, recipient })
// q.best.tx: the one transaction to sign on the source chain; q.best.etaSeconds; q.best.fees
// q.swap: expected coin out and the minimum; q.intent + q.vault: what you are committing to
await api.create(xchain.parseIntent(q.intent))          // register (the service starts watching the deposit address)
const hash = await wallet.sendTransaction(q.best.tx)     // the buyer signs once, on the source chain
await api.reportSourceTx(q.id, hash)                     // optional: faster timeline
const s = await api.status(q.id)                         // created -> sent -> arrived -> swapping -> delivered | refunded

Or quote the lanes yourself with xchain.pickQuote([relay, lifi, warp], request) and only use the service to register and watch the intent. Keep the deposit address as the bridge recipient: that is what binds the funds to the intent.

#Service API

Method What
GET /config network, contract, caps, source chains with their providers and assets
POST /quote { srcChainId, asset, amountIn, buyer, tokenOut, recipient, refundTo?, slippageBps?, deadlineSeconds? } -> bridge quotes (fastest first), the Gully Core swap quote, the intent and its deposit address
POST /intents { intent, provider? } registers an intent
POST /intents/:id/source-tx { tx } the buyer's source-chain transaction
GET /intents/:id the record and its stage, with explorer links
GET /stats executed / refunded / pending counts, volume, 24 h hourly buckets, recent buys

https://kre8.fun/token/<coin>?from=<chainId> opens the coin page with the cross-chain buy ready for that chain (xchain.buyFromChainLink() builds it).

#Source chains and providers

Chain Providers Status
Arbitrum Sepolia (testnet) Gully Bridge fast lane (Hyperlane warp route) live
Base Relay, LI.FI mainnet, after listing
BNB Chain Relay, LI.FI mainnet, after listing
Ethereum Relay, LI.FI mainnet, after listing
Solana LI.FI (Relay once listed) mainnet, after listing
Arbitrum Relay, LI.FI, Gully fast lane mainnet, after listing and the mainnet warp route

Everything the buyer sees is branded Gully Chain; the provider only appears as a small "route" detail line. Before mainnet the owner applies to Relay to list Gully Chain and registers gully-labs as a LI.FI integrator; the executor key needs gas on Gully Chain only (the relayer never holds funds on the source chains).

#Contracts and code

  • GullyXChainBuy + XChainVault: gully-ops l3/xchain-buy/contracts (MIT; forge unit tests, fork test against the live Gully Core, CreateX deploy script with a pinned address).
  • Executor service: gully-ops l3/xchain-buy/service (Node, viem, the SDK's xchain module).
  • The intent format, adapters and client: gullychain-sdk src/xchain.