@paygate/client-cosmos
x402 client for Cosmos SDK chains (Osmosis, CosmosHub, Noble, and any IBC-connected chain). Uses CosmJS for transaction signing and supports Keplr, Leap, and local mnemonic wallets.
Installation
npm install @paygate/client-cosmos
# or
pnpm add @paygate/client-cosmosThis package depends on @paygate/core (installed automatically) and CosmJS libraries.
How Cosmos Payments Work
The payer signs a MsgSend transaction but does not broadcast it. The signed transaction bytes are base64-encoded and sent to the resource server via the X-PAYMENT header. The facilitator broadcasts the signed transaction on-chain.
Payer signs MsgSend (offline, no fees paid yet)
↓
Sends signed tx bytes to resource server via X-PAYMENT
↓
Resource server forwards to facilitator
↓
Facilitator broadcasts the pre-signed transaction
↓
Tokens move from payer → merchantCosmosX402Client
import { CosmosX402Client } from '@paygate/client-cosmos'
const client = new CosmosX402Client(config: CosmosX402ClientConfig)CosmosX402ClientConfig
interface CosmosX402ClientConfig {
walletProvider: CosmosWalletProvider // wallet implementation (see below)
rpcEndpoint: string // Cosmos RPC URL (Tendermint)
chainId: string // e.g. "osmosis-1", "cosmoshub-4"
denom?: string // default token denom, e.g. "uatom"
facilitatorUrl?: string // for discoverResources()
}Methods
| Method | Returns | Description |
|---|---|---|
fetchWithPayment(url, options?) | Promise<Response> | Fetch a URL, auto-paying any 402. |
getAccount() | Promise<{ address: string }> | Returns the connected wallet address. |
discoverResources(facilitatorUrl?, filters?) | Promise<any> | Lists resources from the facilitator registry. |
Wallet Providers
KeplrWalletProvider - Browser (Keplr Extension)
import { CosmosX402Client, KeplrWalletProvider } from '@paygate/client-cosmos'
const provider = new KeplrWalletProvider('osmosis-1')
// Enable the chain in Keplr before connecting
await provider.enable()
const client = new CosmosX402Client({
walletProvider: provider,
rpcEndpoint: 'https://rpc.osmosis.zone',
chainId: 'osmosis-1',
})
const response = await client.fetchWithPayment('https://api.example.com/premium')LeapWalletProvider - Browser (Leap Extension)
import { CosmosX402Client, LeapWalletProvider } from '@paygate/client-cosmos'
const provider = new LeapWalletProvider('osmosis-1')
await provider.enable()
const client = new CosmosX402Client({
walletProvider: provider,
rpcEndpoint: 'https://rpc.osmosis.zone',
chainId: 'osmosis-1',
})LocalCosmosWalletProvider - Mnemonic (Node.js / Server)
For server-side scripts and AI agents. Derives a key from a BIP-39 mnemonic.
import { CosmosX402Client, LocalCosmosWalletProvider } from '@paygate/client-cosmos'
const provider = await LocalCosmosWalletProvider.fromMnemonic(
'word1 word2 ... word12',
'osmo' // bech32 prefix
)
const client = new CosmosX402Client({
walletProvider: provider,
rpcEndpoint: 'https://rpc.osmosis.zone',
chainId: 'osmosis-1',
denom: 'uosmo',
})
const response = await client.fetchWithPayment('https://api.example.com/data')
const data = await response.json()CosmosWalletProvider Interface
Implement this to support any other Cosmos wallet:
interface CosmosWalletProvider {
getAccounts(): Promise<readonly { address: string; pubkey: Uint8Array }[]>
signDirect?(signerAddress: string, signDoc: any): Promise<any>
getOfflineSigner(): any // compatible with CosmJS SigningStargateClient
signArbitrary(message: string): Promise<any>
}Cosmos Payment Payload
Here is what gets sent in the X-PAYMENT header (base64-encoded JSON):
{
x402Version: 1,
paymentRequirements: { ... },
paymentPayload: {
x402Version: 1,
scheme: "exact",
network: "osmosis-1",
payload: "<base64-encoded CosmosPaymentPayload>",
signature: "<base64-encoded signature>"
}
}The inner CosmosPaymentPayload:
{
version: 1,
chainId: "osmosis-1",
payment: {
amount: "1000000", // atomic units
denom: "ibc/...", // IBC denom or native denom
payer: "osmo1...",
recipient: "osmo1...",
txBase64: "<base64-encoded signed TxRaw bytes>"
}
}The facilitator receives the pre-signed TxRaw bytes and broadcasts them directly to the chain - no re-signing required.
Full Example - React App with Keplr
import { useState } from 'react'
import { CosmosX402Client, KeplrWalletProvider } from '@paygate/client-cosmos'
export function PremiumDataButton() {
const [result, setResult] = useState(null)
const [status, setStatus] = useState('')
async function fetchData() {
const provider = new KeplrWalletProvider('osmosis-1')
const client = new CosmosX402Client({
walletProvider: provider,
rpcEndpoint: 'https://rpc.osmosis.zone',
chainId: 'osmosis-1',
})
const response = await client.fetchWithPayment(
'https://api.example.com/premium',
{
onPaymentRequired: async (req) => {
const amount = (Number(req.maxAmountRequired) / 1e6).toFixed(2)
setStatus(`Paying ${amount} USDC on Osmosis...`)
return true
},
onSigningRequired: () => {
setStatus('Approve in Keplr...')
},
onPaymentComplete: (s) => {
setStatus(`Done! Tx: ${s?.transaction?.slice(0, 10)}...`)
},
}
)
setResult(await response.json())
}
return (
<div>
<button onClick={fetchData}>Access Premium Data</button>
{status && <p>{status}</p>}
{result && <pre>{JSON.stringify(result, null, 2)}</pre>}
</div>
)
}Full Example - Node.js Script
import { CosmosX402Client, LocalCosmosWalletProvider } from '@paygate/client-cosmos'
async function main() {
const provider = await LocalCosmosWalletProvider.fromMnemonic(
process.env.COSMOS_MNEMONIC!,
'osmo'
)
const client = new CosmosX402Client({
walletProvider: provider,
rpcEndpoint: 'https://rpc.osmosis.zone',
chainId: 'osmosis-1',
denom: 'ibc/498A0751C798A0D9A389AA3691123DADA57DAA4FE165D5C75894505B876BA84',
})
const response = await client.fetchWithPayment('https://api.example.com/data')
console.log(await response.json())
}
main()Supported Networks
| Network | Chain ID | Asset |
|---|---|---|
| Osmosis | osmosis-1 | IBC USDC |
| CosmosHub | cosmoshub-4 | IBC USDC |
| Noble | noble-1 | Native USDC |
| Osmosis Testnet | osmo-test-5 | IBC USDC |