coinvoyage-payments — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited coinvoyage-payments (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
CoinVoyage is a non-custodial, multi-chain crypto payment gateway. Merchants accept any supported token on any supported chain; funds settle directly to the merchant's wallet (minus a 1.5% platform fee). Buyers can pay from any chain — CoinVoyage handles cross-chain routing via Uniswap, Jupiter, Cetus, CCTP, ChangeNow, or direct transfer.
https://api.coinvoyage.ioBitcoin, Solana, Ethereum, Optimism, Arbitrum, Base, BSC, Polygon, Sui — plus any ERC-20 / SPL / native token on these chains. Stable testnet available for dev.
The user is building or adding a payment feature and any of these is true:
The merchant signs up at https://dashboard.coinvoyage.io, creates an organization, and in Developers generates:
NEXT_PUBLIC_COIN_VOYAGE_API_KEY.npm i @coin-voyage/paykit @tanstack/react-query@^5.90.6Peer deps: react@^18 || ^19 and react-dom in the same range.
Always create PayOrders on the server so the API secret stays private. Return only the payOrderId to the client.
// app/api/pay/route.ts — Next.js App Router
import { ApiClient } from '@coin-voyage/paykit/server';
const client = new ApiClient({
apiKey: process.env.COIN_VOYAGE_API_KEY!,
apiSecret: process.env.COIN_VOYAGE_API_SECRET!,
});
export async function POST(req: Request) {
const { amountUSD, sku } = await req.json();
const order = await client.createDepositPayOrder({
amount: { currency: 'USD', value: amountUSD },
metadata: { sku },
settlement: { chainId: 8453, token: 'USDC' }, // settle into Base USDC
});
return Response.json({ payOrderId: order.id });
}PayOrder types:
'use client';
import { CoinVoyageProvider, PaymentWidget } from '@coin-voyage/paykit';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const qc = new QueryClient();
export function Checkout({ payOrderId }: { payOrderId: string }) {
return (
<QueryClientProvider client={qc}>
<CoinVoyageProvider apiKey={process.env.NEXT_PUBLIC_COIN_VOYAGE_API_KEY!}>
<PaymentWidget
payOrderId={payOrderId}
onComplete={() => {
// redirect to /thank-you, clear cart, etc.
}}
/>
</CoinVoyageProvider>
</QueryClientProvider>
);
}Never trust payment status from the client. Create a webhook in the dashboard (or via API with the API secret) pointing to an HTTPS endpoint, and subscribe to at minimum:
ORDER_COMPLETED — payment finalized; fulfill the orderORDER_ERROR — processing failedORDER_EXPIRED — buyer didn't pay in timeORDER_REFUNDED — refund issuedVerify the CoinVoyage-Webhook-Signature header (HMAC-SHA256 of the raw request body, using the webhook secret, base64-encoded):
import crypto from 'node:crypto';
function verifyWebhook(rawBody: string, signatureHeader: string, webhookSecret: string) {
const expected = crypto
.createHmac('sha256', webhookSecret)
.update(rawBody)
.digest('base64');
const expectedBuf = Buffer.from(expected);
const gotBuf = Buffer.from(signatureHeader);
if (expectedBuf.length !== gotBuf.length) return false;
return crypto.timingSafeEqual(expectedBuf, gotBuf);
}Full event list: ORDER_CREATED, ORDER_AWAITING_PAYMENT, ORDER_CONFIRMING, ORDER_EXECUTING, ORDER_COMPLETED, ORDER_ERROR, ORDER_REFUNDED, ORDER_EXPIRED.
Base URL: https://api.coinvoyage.io
| Method | Path | Purpose |
|---|---|---|
| POST | /pay-orders | Create PayOrder (DEPOSIT / SALE / REFUND) |
| GET | /pay-orders/{id} | Retrieve a PayOrder |
| GET | /pay-orders | List PayOrders |
| POST | /pay-orders/{id}/quote | Generate payment route quote |
| GET | /pay-orders/{id}/payment-methods | Supported payment options |
| POST | /pay-orders/{id}/payment-details | Wallet address / tx data for buyer |
| GET / POST / PUT / DELETE | /webhooks[/{id}] | Manage webhooks |
| POST | /swap/quote | Swap quote |
| POST | /swap/data | Swap tx data |
| GET | /fee-balances | Merchant's claimable fees |
| POST | /fees/claim | Claim accrued fees |
Unauthenticated (public) requests and DEPOSIT PayOrder reads/creates:
Authorization: APIKey=<key>Authenticated endpoints (SALE, REFUND, webhook management, fee claims):
Authorization: APIKey=<key>,signature=<sig>,timestamp=<unixSeconds>Where <sig> = HMAC-SHA256(method + path + timestamp, apiSecret), hex-encoded. Timestamp is unix seconds. Request body is NOT included in the signature.
This plugin bundles an MCP server so Claude can directly create and inspect PayOrders, manage webhooks, and get swap quotes inside any chat once the plugin is installed. Tools:
create_pay_order, get_pay_order, list_pay_orderslist_payment_methods, generate_quote, get_payment_detailslist_webhooks, create_webhook, update_webhook, delete_webhookget_swap_quote, get_swap_dataget_fee_balances, claim_feessupported_chains (local reference, no API call)Use these for: scaffolding a working integration, smoke-testing an API key, running a dry-run transaction, exploring the API conversationally, or generating webhook handlers wired to real event shapes.
[email protected]payOrderId is passed to the client.env.local, deployment secrets), not in code@coin-voyage/paykit pinned to a specific version in production dependenciesPaymentWidget with the returned IDPOST /api/pay creates order, returns ID; POST /api/webhooks/coinvoyage verifies signature and fulfillsORDER_COMPLETED, extend the subscription~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.