@circle-fin/onramp-kit (英语)
嵌入 Arc Onramp 部件的官方圆形 SDK —— 服务器侧 会话创建和客户端部件正在安装.
目录
概览
onramp-kit 被拆分成两个互补的地表
同一个 npm 包 :
| 表面 | 导入路径 | 它运行的地方 | 目的 |
|---|---|---|---|
| 服务器包 | @circle-fin/onramp-kit/server | 节点 | 交换你的 apiKey 短暂的 sessionToken。 。 。 . |
| 客户包 | @circle-fin/onramp-kit | 浏览器 | 使用会话符记号挂载 . |
| 原始协议 | @circle-fin/onramp-kit/protocol | 两者 | 共享常数,事件信封,会话形状. |
| 模拟助手 | @circle-fin/onramp-kit/mocks | 测试 | 对服务器和客户端包进行模拟. |
分裂的存在是因为 apiKey 是一个长寿的证书,必须
永远不能到达浏览器。 服务器包创建一个每个用户 sessionToken
安全地交给客户.
安装
npm install @circle-fin/onramp-kit
# or
yarn add @circle-fin/onramp-kit
沙盒对生产
工具包的目标是默认的生产。 使用沙盒促进地方发展
以及测试,它不会移动真正的钱 并提供测试网的标志。 这个
只接受生产部件 https: 嵌入页面, 因此是一个本地 dev
打开服务器 http://localhost 必须使用沙盒( 也允许
http://localhost:*) (中文(简体) ).
| 设置 | 开始 | 沙盒 | 生产(违约) |
|---|---|---|---|
apiKey | 服务器 | 测试网 API 密钥 | 主网 API 密钥 |
baseUrl | 服务器 | https://api-test.circle.com | https://api.circle.com |
widgetBaseUrl | 服务器和客户端 | https://onramp-sandbox.arc.io | https://onramp.arc.io |
将 API 密钥和两个 URL 保存在同一环境中: 使用测试网
键有沙盒,主网键有生产。 服务器和
客户端 widgetBaseUrl 值必须匹配.
// server
const server = createOnrampServerKit({
apiKey: process.env.ONRAMP_API_KEY!,
baseUrl: process.env.ONRAMP_BASE_URL, // https://api-test.circle.com
widgetBaseUrl: process.env.ONRAMP_WIDGET_BASE_URL, // https://onramp-sandbox.arc.io
})
// browser
const onramp = createOnrampKit({
widgetBaseUrl: import.meta.env.VITE_ONRAMP_WIDGET_BASE_URL, // https://onramp-sandbox.arc.io
})
快开始
建议的流量是两个简短的代码: 您的服务器调用 SDK , 而浏览器中有一个调用 将客户端指向该路线.
1. 在服务器上显示会话创建路径
// app/api/onramp/sessions/route.ts (Next.js App Router)
import {
createOnrampServerKit,
createSessionRouteHandler,
} from '@circle-fin/onramp-kit/server'
const server = createOnrampServerKit({
apiKey: process.env.ONRAMP_API_KEY!,
})
export const POST = createSessionRouteHandler(server)
apiKey格式 : 精确使用圆形发布的值 操纵台 钥匙看起来像<ENV>_API_KEY:<keyId>:<keySecret>。工具包发送 它作为无记名的迹象Authorization: Bearer <value>页:1 脱去或重新添加前缀.
createSessionRouteHandler 是一个滴入 Request → Response 处理器
即:
- 仅接受
POST(回报)405否则, - 根据共享的会话请求计划验证天体
(单位:千美元)
400在形状不匹配的情况下, - 电话
server.createSession(body)并返回会话为 JSON, - 投出地图
KitErrors 到明智的 HTTP 状态代码 (单位:千美元)INPUT- 400个,RATE_LIMIT− 429个,NETWORK• 504,SERVICE编号 :RPC− 502,否则为500, - 套装
Cache-Control: no-store到处都是会议标志 寿命很短,绝不能缓存.
它在任何会讲 Facker API 标准的运行时间都起作用——Next.js
App路由器,Hono,Cloudflare工人,Bun,Deno,现代节点. 主机
在快递上 / 快递可以继续呼叫 server.createSession()
直接说.
插入您的自定义/ authz
import { auth } from '@/lib/auth'
export const POST = createSessionRouteHandler(server, {
authorize: async (request) => {
const session = await auth(request)
return session?.user != null
},
})
返回时 false 401. 丢出一个 KitError - 相应的状况.
返回时 true 或者说 undefined 继续.
2. 在浏览器中启动部件
客户端套件暴露出两个同步挂载助手—— mountIframe
(内在)和 openWindow (弹出)——和一个可选的REST取取取器
(单位:千美元)fetchOnrampSession) (中文(简体) ). 他们被故意分开 所以弹出模式
可以在同步用户手势框架(浏览器块)内运行
弹出,否则).
两种模式的手势规则不同.
mountIframe已经 没有 需要用户手势——它只是插入一个<iframe>输入 自己拥有的容器,所以在一页后调用页面负载是安全的await在反应中useEffect等 (中文(简体) ).openWindow必须 为: 从用户手势(点击处理框架)中同步调用或 浏览器屏蔽弹出——见 弹出模式 见下文.
import { createOnrampKit, fetchOnrampSession } from '@circle-fin/onramp-kit'
const onramp = createOnrampKit()
async function startOnramp() {
const session = await fetchOnrampSession({
url: '/api/onramp/sessions',
body: {
appUserId: 'user-123',
destinationAddress: '0xabc...',
},
})
const widget = onramp.mountIframe({
session,
container: document.getElementById('onramp-root')!,
onInitializationSuccess: () => console.log('ready'),
onDepositSettled: ({ payload }) => {
console.log('settled', payload.amount, payload.tokenSymbol)
},
})
return widget
}
定义部件提供的标志/链条
通过可选 assets 请求限制
部件中显示的符号/链对 。 每个领域都是可选的,它们
结合和语义;省略 assets 用于完整支持的目录 :
await fetchOnrampSession({
url: '/api/onramp/sessions',
body: {
appUserId: 'user-123',
destinationAddress: '0xabc...',
assets: { chains: ['arc'] }, // Arc only, all tokens
// assets: { tokens: ['USDC'] }, // USDC on every chain
// assets: { pairs: [{ token: 'USDC', chain: 'arc' }] }, // exact pairs
},
})
chains 接受链式标识或显示标签; tokens 一个符号。 范围界定
仅显示 —— 部件的目录和资格将保留为真相的来源,
因此从浏览器中设置是安全的.
授权您的嵌入域( N)referrerDomain(中文(简体) )
当您将部件嵌入您自己的网站时, 设置
referrerDomain 因此,您的页面被授权作为框架祖先
支付供应商的嵌入式iframe。 当部件运行时关闭它
顶级页面或弹出模式.
referrerDomain 是一个 服务器包上的构建选项,而不是一个
每会话参数——按设计。 它配置了帧祖先
允许列表( 安全边界) , 因此它必须来自您自己信任的
配置并永远不能被客户端设置. 把它放在包里(而不是
比过去 createSession)指客户端提交的会话机构不能
影响它,即使通过滴入 createSessionRouteHandler数字 :
// server entry — configured once from trusted config
const server = createOnrampServerKit({
apiKey: process.env.ONRAMP_API_KEY!,
referrerDomain: process.env.ONRAMP_REFERRER_DOMAIN, // e.g. 'portal.arc.io'
})
// the drop-in route stays a safe one-liner — `referrerDomain` is applied
// server-side to every mint, and nothing in the request body can override it
export const POST = createSessionRouteHandler(server)
值规则 :
- 仅主机名 ——没有计划,端口,路径,通配符.
- ✅
portal.arc.io - ❌
https://portal.arc.io• 妇女portal.arc.io:443• 妇女portal.arc.io/buy• 妇女*.arc.io
- ✅
- 对于一个子域,通过准确的主机,页面从
(单位:千美元)
buy.arc.io),而不是最高域.
这是与你网站分开的 社区支助方案:社区支助方案
让您的页面框架 onramp.arc.io· ; referrerDomain 让提供者
鸟巢Iframe接受你的一页作为祖先. 一个正确嵌入的 Iframe
两者都需要.
弹出模式
openWindow 必须援引 同步 从用户手势
(例如直接在点击处理器内)或浏览器会屏蔽
弹出. 返回值对 'opened' 财务报告和审定财务报表
'blocked' 成果 - 'blocked' 是正常流动,不是
非常特别的.
// Hint: create the session beforehand (e.g. when the form is rendered),
// then call openWindow synchronously on click.
const session = await fetchOnrampSession({ url: '/api/onramp/sessions', body })
button.addEventListener('click', () => {
const result = onramp.openWindow({
session,
onDepositSettled: ({ payload }) => …,
})
if (result.status === 'blocked') {
// Branch on `reason` to tailor recovery:
// - 'popup_blocked' → ask the user to retry (another sync click)
// - 'in_app_browser' → fall back to mountIframe(), or prompt the
// user to open the page in the system browser
// - 'pwa_standalone' → fall back to mountIframe()
if (result.reason === 'popup_blocked') {
showPopupRetryDialog(result.errorMessage)
} else {
onramp.mountIframe({ session, container })
}
return
}
result.widget.on('DEPOSIT_NOT_COMPLETED', ({ code }) => …)
})
弹出闭塞信号. 如果顾客之前关闭了弹出 提交押金,工具箱检测它并发射a
DEPOSIT_NOT_COMPLETED有代码的事件CANCELED_BY_CUSTOMER这样 onDepositNotCompleted操作员在无额外投票的情况下运行 在你身边.
替代品:自带交通工具
如果您的应用程序已经拥有自己的数据层( tRPC, GraphQL, 服务器)
组件,反应-平滑) fetchOnrampSession 手动
已决出会议 mountIframe 编号 : openWindow 直接说 这个
会话形状与您返回的服务器套件相同.
const session = await trpc.onramp.createSession.mutate({
appUserId,
destinationAddress,
})
const widget = onramp.mountIframe({ session, container })
倾听生命周期事件
每一艘发来的信封船 { event, code, payload? }编辑
event 描述主机可见效果 code 缩小
事业。 见 ONRAMP_EVENT_TYPES 财务报告和审定财务报表 ONRAMP_EVENT_CODES 内
@circle-fin/onramp-kit/protocol。 。 。 .
widget.on('INITIALIZATION_ERROR', ({ code, payload }) => {
if (code === 'INVALID_SESSION_TOKEN') return refreshSession()
if (code === 'PAGE_NOT_LOADED') return showLoadFailureToast()
})
widget.on('*', (envelope) => analytics.track('onramp', envelope))
由设计与前相兼容. 套装是 没有 验证 事件
{ event, code, payload }对付一个封闭的阴谋 一个部件 添加新事件、代码、支付方法或有效载荷字段的释放 交给您onAnyEvent编号 :on('*')逐字记录员 而不是被丢弃——所以SDK从来不会落后主机应用. 已知的事件仍然会获得自动完成的完整类型回调.
事件只到达启动部件的页面 . 部件 将事件张贴到嵌入它( 框架模式) 或打开它的页面 (弹出模式). 如果您打开部件 URL 作为顶级页面 - for 例如,来自系统浏览器中的本地应用程序(Safari View) Chrome Custom Tabs公司主计长——没有收到任何事件。 为了得到 它们,在您自己的页面上以
mountIframe或者说openWindow,然后打开页面.
确认购买
红外线 Kit 不向您的服务器发送 Webhoks, 没有
服务器端命令状态 API. 生命周期事件为浏览器
postMessage 信号:使用它们驱动您的用户界面,而不是作为证明一个
采购完成.
DEPOSIT_SUBMITTED指客户完成了部件流. 这个 不带transactionHash。 。 。 .- 卡,苹果支付,和谷歌支付:
amount即已支付的数额, 而不是交付的象征性金额. - 银行转账:当客户关闭银行详细信息时,它就会起火
屏幕,在任何钱移动之前.
amount是它们的数量 他们将永不派遣.
- 卡,苹果支付,和谷歌支付:
DEPOSIT_SETTLED是最佳和火灾 只为卡,苹果支付, 和谷歌付费购买。 通常会加入链上transactionHash,但只有在部件在 买下就解决了 一个客户谁先关闭标签 触发它,即使购买完成. 不要治疗 没有DEPOSIT_SETTLED作为"没有存款"
当您的应用程序需要可靠记录 购买完成时, 请检查 服务器上的链条 :
- 请检查access-date=中的日期值 (帮助)
destinationAddress你准备好了 会议闭幕。 使用transactionHash从DEPOSIT_SETTLED当你 拥有它. - 从链取出量. 交付的金额是扣除费用后的净额,
因此,它不等于虚伪
amount在事件有效载荷. - 每次转账一次,按其交易散列键.
银行转账需要额外照顾:
- 回来的客户可以选择他们开的银行转账账户
之前的会议。 押金就落在那个账户上
钱包,可能不同于会话
destinationAddress。 。 。 . - 银行转账账户保持开户,然后转入账户 链上没有部件事件.
orderId是客户的银行转账参考。 一样 所以它不能告诉存款 或账户分开.
不用发送地址识别交货。 送货来自 支付提供者的钱包,因环境和支付而异 方法,不能保证保持不变.
处理会话过期/ 闲置
当一个会话在部件打开时闲置出会话时,主机程序会释放
DEPOSIT_NOT_COMPLETED 有代码 SESSION_TIMEOUT。工具包也浮出水面
这个(和 INVALID_SESSION_TOKEN 输入错误)
onSessionExpired 调用后, 不需要手动分拆代码
这是你的"创建新的会话并重新发射"信号.
onramp.mountIframe({
session,
container,
onSessionExpired: async () => {
// The current session is dead — create a fresh one and re-mount.
const fresh = await fetchOnrampSession({
url: '/api/onramp/sessions',
body,
})
onramp.mountIframe({ session: fresh, container })
},
})
匹配 onDepositNotCompleted 编号 : onInitializationError 回调
仍然开火,所以现有的操作员继续工作.
总是在卸载时拆卸
部件控制器附加一个 message 监听器 window 财务报告和审定财务报表
保持一个超时计时器. 打电话 widget.close() 当你的视野
卸载 因此,这些被释放。 对于 Iframe 模式,工具包也安装了
(单位:千美元) MutationObserver 如果iframe脱离
dOM 没有 close() 你不应该依赖它作为
你的主要清理路径.
// React
useEffect(() => {
const widget = onramp.mountIframe({ session, container: ref.current! })
return () => widget.close()
}, [session])
处理错误
装备投出的所有东西都是 KitError (从
@circle-fin/onramp-kit),这样你就可以 instanceof- 检查它和树枝
它的结构化,稳定的字段而不是解析信件字符串 :
| 外地 | 分行请.. |
|---|---|
type | 对失败进行分类—— 'INPUT', (中文(简体) ), 'NETWORK', (中文(简体) ), 'SERVICE', (中文(简体) ), 'RATE_LIMIT', (中文(简体) ), 'RPC', (中文(简体) ), 'UNKNOWN'。 。 。 . |
recoverability | 决定是否重试 - 'RETRYABLE', (中文(简体) ), 'RESUMABLE',或 'FATAL'。 。 。 . |
name 编号 : code | 精确匹配、记录和遥测的稳定标识. |
import { KitError, fetchOnrampSession } from '@circle-fin/onramp-kit'
try {
const session = await fetchOnrampSession({ url, body })
onramp.mountIframe({ session, container })
} catch (err) {
if (err instanceof KitError) {
if (err.recoverability === 'RETRYABLE') return scheduleRetry()
if (err.type === 'INPUT') return showValidationError(err.message)
}
throw err
}
偏爱 type + 键 recoverability 为了控制流动,他们是稳定的,
远期合同。 使用 name 编号 : code 只有精确匹配,
伐木或遥测。 服务器路由处理器
(单位:千美元)createSessionRouteHandler)这些地图相同 types 到 HTTP 状态代码
自动( 见 步骤 1) (中文(简体) ).
浏览器支持和托管要求
横跨出处的自
https://onramp.arc.io (生产)或 https://onramp-sandbox.arc.io
(散装箱). 有一些东道方的先决条件.
内容安全政策
如果您的网站发送了 CSP, 它必须允许部件来源用于 你的目标环境。 没有这个指令, Iframe沉默地 无法渲染 —— 没有出错事件, 因为浏览器块 我们代码运行前的负载.
生产:
frame-src https://onramp.arc.io;
沙盒 :
frame-src https://onramp-sandbox.arc.io;
Iframe或弹出式中继部件本身的CSP后,
不是你的页面,所以没有 connect-src 他们需要进入。 装备箱
只从您的页面请求到您的会话路由 .
集装箱要求(框架模式)
呼叫时适用两个东道方先决条件 mountIframe数字 :
-
容器必须已经附在文件上 何时 你打电话
mountIframe。工具箱同步附加了框架 并安装MutationObserver如果容器自动处理 后分道扬镳. 在反应中,从一个useEffect(未在 因此,裁判员是人;在香草代码中,等待DOMContentLoaded或者自己在呼叫前插入容器. -
容器需要明确,非零高. 框架 于
width: 100%; height: 100%跨原生地框架 无法大小到内容, 所以没有解决的高度 iframe 崩溃到 0px 而用户看不到任何信息 .#onramp-root { height: 720px; } /* or min-height, or a flex child with a sized parent */
弹出模式限制
openWindow 是最佳效果并返回 a 'blocked' 结果(从未)
当无法打开可用弹出时:
reason | 原因 | 建议退后 |
|---|---|---|
popup_blocked | 封锁者,或 await 在电话之前跑掉了 | 从新同步点击重试 |
in_app_browser | Instagram / Facebook / TikTok / LinkedIn WebView等. 腾讯网. | mountIframe,或在系统浏览器中打开 |
pwa_standalone | 在独立显示模式下安装 PWA | mountIframe |
附加说明:
- 在移动浏览器上,
window.open打开一个 选项卡大小的 弹出——width编号 :height特性被忽略。 两者的设计. - 习惯
target联 合 国'_blank'禁用弹出特性; 通过 a 已命名目标(默认)'circle-onramp'一个真正的弹出. - 弹出故意保持
window.opener(我们省略)noopener编号 :noreferrer这样它就可以postMessage结果回来。 这个 信任边界是圆形本身的部件来源.
iOS Safari和第三方存储
iOS Safari的IPP可以限制嵌入式应用程序对自身的访问
存储在 Iframe 中。 如果您看到 KYC/ 会话问题被隔离到
iOS 萨法里,更喜欢 openWindow 模式在平台.
社区与支持
- 调和数字 : 加入我们的社区
许可证
这个项目根据Apache 2.0许可证获得许可. 联系人 支助 详细情况.