Gully Wallet SDK
One Gully ID account and one Gully Wallet in every app. The SDK gives your app a standard EIP-1193 provider backed by the user's Gully smart account (Kernel, passkey) on Gully Chain (485590), lists it in every EIP-6963 wallet picker as Gully Wallet, and does every approval in a Gully ID window. Your app never sees a key.
Install
Script tag (defines window.GullyWallet and window.gully, announces the wallet, registers <gully-wallet-pill>):
<script src="https://gullyid.xyz/wallet.js"></script> <gully-wallet-pill theme="auto"></gully-wallet-pill>
ES module (bundlers, Vite, Next.js client components):
import GullyWallet, { provider } from 'https://gullyid.xyz/wallet.mjs'
const gully = provider({ appName: 'My app' })
const [address] = await gully.request({ method: 'eth_requestAccounts' })
viem
import { createWalletClient, custom, defineChain } from 'viem'
const gullyChain = defineChain({ id: 485590, name: 'Gully Chain Testnet', nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, rpcUrls: { default: { http: ['https://rpc.gullychain.xyz'] } } })
const wallet = createWalletClient({ chain: gullyChain, transport: custom(window.gully) })
const [account] = await wallet.requestAddresses()
const hash = await wallet.sendTransaction({ account, to: '0x…', value: 1n })
// batches (EIP-5792), one approval, one user operation, gas covered when sponsored:
const { id } = await wallet.sendCalls({ account, calls: [{ to: token, data: approveData }, { to: router, data: swapData }] })
ethers v6
import { BrowserProvider } from 'ethers'
const provider = new BrowserProvider(window.gully)
const signer = await provider.getSigner()
const sig = await signer.signMessage('hello') // ERC-1271 (ERC-6492 before the first transaction)
wagmi
wagmi discovers EIP-6963 wallets by itself: load wallet.js and "Gully Wallet" shows up with its icon. To pin it as a connector:
import { createConfig, http } from 'wagmi'
import { injected } from 'wagmi/connectors'
export const config = createConfig({
chains: [gullyChain],
connectors: [injected({ target: { id: 'xyz.gullyid.wallet', name: 'Gully Wallet', provider: () => window.gully } })],
transports: { [gullyChain.id]: http('https://rpc.gullychain.xyz') },
})
Methods
| Method | What happens |
|---|---|
eth_requestAccounts, wallet_requestPermissions | Connect window (sign in with Gully if needed): app origin, requested permissions, optional one-tap daily limit. |
eth_accounts, eth_chainId, wallet_getPermissions | Local, no window. |
wallet_switchEthereumChain | Gully chains only (4902 otherwise). Today: 485590. |
personal_sign, eth_signTypedData_v4 | Sign window, human-readable. The result is an ERC-1271 signature of the smart account, wrapped in ERC-6492 until the account is deployed. Verify with viem verifyMessage / verifyTypedData (they handle both). |
eth_sendTransaction | Review window: decoded calls (ERC-20 transfer/approve, Uniswap and Gully Swap swaps), simulation, "Gas covered" when the Gully paymaster sponsors it. Resolves the transaction hash once included. |
wallet_sendCalls, wallet_getCallsStatus, wallet_showCallsStatus | EIP-5792 batches: atomic, one user operation. |
wallet_getCapabilities | { "0x768d6": { paymasterService: { supported: true }, atomicBatch: { supported: true }, atomic: { status: "supported" } } } |
read methods (eth_call, eth_getBalance, …) | Forwarded to the public Gully Chain RPC. |
eth_sign, eth_signTransaction | Refused (4200): a smart account has no raw key to sign with. |
Events: accountsChanged, chainChanged, connect, disconnect. Helpers on GullyWallet: connect(), disconnect(), state(), balances(), send({to, value} | {to, token, amount}), swap({from, to, amount}) (opens the wallet's Swap tab, https://gullyid.xyz/swap?from&to&amount), receive(), open(path).
One-tap limits
When connecting, a user can give your app a daily one-tap limit in USD. Transactions Gully Wallet can price (ETH, glUSD, USDC.e, WETH, gETH transfers, bounded approvals, swaps from those) that fit under the limit are approved with one tap, signed by a session key that lives only in the user's browser on https://gullyid.xyz. Everything else (over the limit, unknown contracts, unlimited approvals, unpriced tokens, every message signature) asks for the passkey. Users see and revoke connected apps and limits in Gully ID.
Security notes
- Approvals only happen on https://gullyid.xyz (the window's address bar shows it). The window cannot be framed, uses no inline script, and only answers the window that opened it, at that window's exact origin.
- Your app gets the address and a read-only connection token bound to your origin. It can never approve, sign, connect or change limits by itself: those need the user's Gully ID session cookie on https://gullyid.xyz plus a CSRF token.
- The server builds and checks every user operation, verifies each passkey signature before sending it, and refuses calls to the wallet contract itself.
- Session keys are Kernel permissions with an expiry and a daily operation cap, flagged so they can never produce an ERC-1271 signature (no permits, no off-chain orders).
- Ask only for what you need:
wallet_sendCallswith exact amounts, not unlimited approvals.