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
- The app asks the service for a quote: source chain and asset, amount, the coin, the recipient.
- 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).
- The buyer signs. The funds arrive on the deposit address on Gully Chain.
- 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. - 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
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.
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 | refundedOr 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 |
#Share links
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-opsl3/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'sxchainmodule). - The intent format, adapters and client: gullychain-sdk
src/xchain.