diff --git a/docs/pages/cn/explanation/enterprise-sdk.mdx b/docs/pages/cn/explanation/enterprise-sdk.mdx new file mode 100644 index 0000000..7ab51a8 --- /dev/null +++ b/docs/pages/cn/explanation/enterprise-sdk.mdx @@ -0,0 +1,70 @@ +--- +source_path: explanation/enterprise-sdk.mdx +source_sha: a632f3be5fbff966d9da230ec302b4ce4e6773f2 +title: "Stable Enterprise SDK" +description: "使用类型化的 @stablechain/enterprise SDK 从后端中继免 gas 和保证区块空间的交易。" +diataxis: "explanation" +--- + +# Stable Enterprise SDK + +`@stablechain/enterprise` 是 Stable 企业交易通道的服务器端 TypeScript 客户端。它会签署并中继两种特权交易:免 gas 交易(由您来支付用户的 gas)和保证区块空间交易(在保留的企业通道中处理)。您可以在一个客户端上启用其中一个或两个通道。 + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { account: privateKeyToAccount("0xYOUR_WAIVER_KEY") }, +}); + +const { txHash } = await enterprise.gasWaiver.send(user, { to: token, data }); +``` + +```text +txHash: 0x8f3a...2d41 +``` + +此调用返回 `H_inner`,即用户交易的哈希,而不是承载它的包装器交易的哈希。 + +## SDK 的作用 + +SDK 暴露了三个功能,每个模块一个。每个功能都是可选的、独立配置的,并且都带有自己的签名账户,以便您可以使用单独的密钥。 + +- **免 gas 中继** (`gasWaiver`):构建、签署并中继免 gas 交易。一个白名单中的免 gas 账户会封装用户的零 gas 交易,这样用户就可以在不持有任何 USDT0 的情况下进行交易。请参阅[免 gas](/cn/explanation/gas-waiver)。 +- **保证区块空间** (`guaranteedBlock`):通过企业 RPC 网关中继交易,使其落在保留的企业通道区块空间中。与免 gas 不同,签名者支付自己的 gas。请参阅[保证区块空间](/cn/explanation/guaranteed-blockspace)。 +- **保证中继交易** (`guaranteedWaiver`):两者结合。一种通过保证区块空间路由的免 gas 交易,这样用户就不需要余额,并且交易仍然落在企业通道中。 + +每个功能都提供相同的四种方法:`send` 和 `sendBatch` 用于 SDK 签名的托管路径,以及 `relay` 和 `relayBatch` 用于用户在其他地方签名并只向您提供签名十六进制的非托管路径。 + +## 访问权限 + +企业 SDK 目前仅在 Stable Testnet 上可用。 + +这两个通道都是受限的。免 gas 需要治理注册的免 gas 密钥,而保证区块空间需要企业 RPC 网关 API 密钥。要进行集成,请[联系 Stable](https://discord.gg/stablexyz) 以获取访问权限。Stable 会为您提供所需的免 gas 密钥和网关端点。 + +## 仅限服务器端 + +:::warning +企业 SDK 是一个无状态的服务器端库,使用私钥进行签名。切勿在浏览器中运行它。请将免 gas 密钥和企业 RPC 网关 URL 保留在您的后端。 +::: + +免 gas 密钥是一个列入白名单的、经过治理注册的账户:任何持有它的人都可以根据您的策略支付 gas。企业 RPC 网关 URL 嵌入了您的 API 密钥。请将两者都视为秘密。为了使免 gas 密钥不被泄露,请使用 AWS KMS 等托管签名服务对其进行备份。请参阅[签名密钥和托管](/cn/reference/enterprise-sdk#signing-keys-and-custody)。 + +## 何时使用 + +当您运营后端并希望为用户支付 gas 或为自己的流量保证交易包含时,请使用企业 SDK。对于后端或浏览器中的日常转账、桥接、兑换和金库收益,请改用通用[Stable SDK](/cn/explanation/sdk-overview)。 + +## 从这里开始 + +- [**免 gas 中继**](/cn/how-to/relay-with-gas-waiver):为用户支付 gas,这样他们就可以在不持有 USDT0 的情况下进行交易。 +- [**发送保证交易**](/cn/how-to/send-guaranteed-transactions):将交易落在保留的企业通道区块空间中。 +- [**保证中继交易**](/cn/how-to/guaranteed-relayed-transactions):结合这两个通道:企业通道中的免 gas 交易。 +- [**企业 SDK 参考**](/cn/reference/enterprise-sdk):每个模块、方法、配置选项和错误类。 + +## 下一步建议 + +- [**免 gas 协议**](/cn/reference/gas-waiver-api):交易格式、标记路由和治理控制。 +- [**Stable SDK**](/cn/explanation/sdk-overview):用于转账、桥接、兑换和收益的通用客户端。 +- [**连接 Stable**](/cn/reference/connect):测试网的链 ID、RPC 端点和浏览器。 diff --git a/docs/pages/cn/how-to/guaranteed-relayed-transactions.mdx b/docs/pages/cn/how-to/guaranteed-relayed-transactions.mdx new file mode 100644 index 0000000..efd32f8 --- /dev/null +++ b/docs/pages/cn/how-to/guaranteed-relayed-transactions.mdx @@ -0,0 +1,141 @@ +--- +source_path: how-to/guaranteed-relayed-transactions.mdx +source_sha: 007be0e546cc1251771678ee74523b6a79a2d76d +title: "保证中继交易" +description: "通过 Enterprise SDK 经由保证的区块空间中继免 gas 交易,这样用户无需余额也能进入 Enterprise 通道。" +diataxis: "how-to" +--- + +# 保证中继交易 + +将 Stable 企业级网络与 `@stablechain/enterprise` 结合使用。保证中继交易是[免 gas 交易](/cn/how-to/relay-with-gas-waiver),其通过[保证的区块空间](/cn/how-to/send-guaranteed-transactions)进行路由:免除交易费的功能支付 gas 费,因此用户无需余额和费用字段,交易仍会进入预留的 Enterprise 通道。 + +`guaranteedWaiver` 模块同时处理这两个方面。用户签署内部的 `0x3F` CustomTx,白名单中的免除交易费账户将其包装在一个共享相同 Enterprise `nonceKey` 的外部 `0x3F` CustomTx 中,并通过 Enterprise RPC 网关广播。每个方法都返回 `H_inner`,即用户的交易哈希。 + +## 前提条件 + +- Node.js 20 或更高版本,并安装 `@stablechain/enterprise` 和 `viem`。请参阅 [Enterprise SDK 参考](/cn/reference/enterprise-sdk#install)。 +- 一个在治理层注册的免除交易费密钥和一个 Enterprise RPC 网关 API 密钥。Enterprise SDK 目前仅支持测试网,并且需要申请访问权限:[联系 Stable](https://discord.gg/stablexyz) 以获取这两项。 + +:::warning +这是一个服务器端库。请将免除交易费密钥和 Enterprise RPC 网关 URL 保留在您的后端:网关 URL 嵌入了您的 API 密钥。 +::: + +## 1. 使用保证免除交易费模块创建客户端 + +将 `guaranteedWaiver` 和 `enterpriseRpcEndpoints` 传递给 `createStableEnterprise`。该模块接收白名单中的免除交易费密钥 (用于签署外部包装器) 和 Enterprise 通道。它还接受与 `gasWaiver` 相同的 `allowedTargets`、`maxGasLimit` 和 `maxDataLength` 策略限制。 + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL], // the gateway URL Stable provisions + guaranteedWaiver: { + account: privateKeyToAccount(process.env.WAIVER_PRIVATE_KEY as `0x${string}`), + laneId: 0n, // Enterprise lane + }, +}); + +const gw = enterprise.guaranteedWaiver; +``` + +```text +StableEnterpriseClient { gasWaiver: undefined, guaranteedBlock: undefined, guaranteedWaiver } +``` + +:::tip +要将免除交易费密钥保留在托管后端中,请传递 `signer` 而不是 `account`。`@stablechain/enterprise/aws-kms` 中的 `awsKmsSigner` 支持 AWS KMS。请参阅[签名密钥和托管](/cn/reference/enterprise-sdk#signing-keys-and-custody)。 +::: + +## 2. 中继单个交易 + +使用用户账户和变化的字段调用 `send`。由于免除了 gas 费,用户不需要余额和费用字段。内部的 `0x3F` CustomTx、其 Enterprise `nonceKey` 和 2D nonce (从网关发现) 都会为您处理。 + +```ts +const user = privateKeyToAccount(process.env.USER_PRIVATE_KEY as `0x${string}`); + +const { txHash } = await gw.send(user, { to: recipient }); +console.log("H_inner:", txHash); +``` + +```text +H_inner: 0x8f3a...2d41 +``` + +`gas` 默认为共享的内部 gas 默认值;仅在覆盖时传递。允许价值转移,因此您也可以传递 `value`。 + +## 3. 中继批次交易 + +调用 `sendBatch` 中继来自一个用户的多笔交易。内部 nonce 会从发现的基本 nonce 自动排序,并且您会按照输入顺序获得每个输入的单个结果。失败会导致其后续交易无法进行。 + +```ts +const results = await gw.sendBatch(user, [{ to: a }, { to: b }]); + +for (const r of results) { + console.log(r.success ? `[${r.index}] ✔ ${r.txHash}` : `[${r.index}] ✖ ${r.error?.code}`); +} +``` + +```text +[0] ✔ 0x8f3a...2d41 +[1] ✔ 0x2b7c...9e04 +``` + +## 4. 中继预签名交易 + +对于非托管流程,用户在其自己的环境中签署内部 `0x3F` CustomTx,并只向您提供签名的十六进制数据。使用 `buildGuaranteedTx` 构建它,其中 Enterprise nonce 密钥使用 `nonceKeyForLane`,费用为零 (gas 已免除)。您的后端会使用免除交易费密钥对其进行封装,并使用 `relay` 进行中继。 + +```ts +import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise"; + +// on the user's side — buildGuaranteedTx signs through a Signer; toSigner adapts a viem account +const signedInner = await buildGuaranteedTx(toSigner(user), stableTestnet.id, { + to: recipient, + gas: 100_000n, + gasFeeCap: 0n, // waived + gasTipCap: 0n, + nonce, // the user's current 2D-lane nonce + nonceKey: nonceKeyForLane(0n), // Enterprise lane 0 +}); + +// on your backend +const { txHash } = await gw.relay(signedInner); // waiver wraps + broadcasts → H_inner +console.log("H_inner:", txHash); +``` + +```text +H_inner: 0x8f3a...2d41 +``` + +对于多个预签名交易,请使用 `relayBatch`。 + +## 处理拒绝 + +`send` 和 `relay` 在拒绝时会抛出 `StableEnterpriseRelayError`。由于此流程通过网关路由,因此网关代码 (如 `GATEWAY_UNAUTHORIZED` 和 `QUOTA_EXCEEDED`) 以及免除交易费策略代码 (如 `TARGET_NOT_ALLOWED`) 都适用。 + +```ts +import { StableEnterpriseRelayError } from "@stablechain/enterprise"; + +try { + await gw.send(user, { to: recipient }); +} catch (err) { + if (err instanceof StableEnterpriseRelayError && err.code === "GATEWAY_UNAUTHORIZED") { + // the Enterprise RPC gateway rejected the API key + } + throw err; +} +``` + +```text +StableEnterpriseRelayError: relay failed [GATEWAY_UNAUTHORIZED]: invalid api key +``` + +请参阅完整的 [`ErrorCode`](/cn/reference/enterprise-sdk#errorcode) 表,了解所有拒绝原因。 + +## 接下来去哪里 + +- [**使用免 gas 功能中继**](/cn/how-to/relay-with-gas-waiver):为用户支付 gas 费,无需保证通道。 +- [**发送保证交易**](/cn/how-to/send-guaranteed-transactions):单独使用保证区块空间,由签署者支付 gas 费。 +- [**Enterprise SDK 参考**](/cn/reference/enterprise-sdk#guaranteed-relay-transactions):所有方法、配置选项和错误类的完整说明。 diff --git a/docs/pages/cn/how-to/relay-with-gas-waiver.mdx b/docs/pages/cn/how-to/relay-with-gas-waiver.mdx new file mode 100644 index 0000000..a68208c --- /dev/null +++ b/docs/pages/cn/how-to/relay-with-gas-waiver.mdx @@ -0,0 +1,157 @@ +--- +source_path: how-to/relay-with-gas-waiver.mdx +source_sha: c1b34aab292d8e9623b59810cd6b668468e834ce +title: "使用 Gas 代付中继交易" +description: "使用 Enterprise SDK 赞助用户的 gas:中继零 gas 交易、批量中继、强制执行策略限制以及中继预签名交易。" +diataxis: "how-to" +--- + +# 使用 Gas 代付中继交易 + +使用 `@stablechain/enterprise` 赞助用户的 gas 费用。白名单代付账户会包装用户的零 gas 交易 (InnerTx) 并广播它,因此用户无需持有任何 USDT0 即可进行交易。`gasWaiver` 模块在一个调用中构建、签名和中继,因此您每个操作只需调用一个方法。 + +每个方法都返回 `H_inner`,即用户交易的哈希,而不是承载它的包装器。 + +## 先决条件 + +- Node.js 20 或更高版本,并安装了 `@stablechain/enterprise` 和 `viem`。请参阅 [Enterprise SDK 参考](/cn/reference/enterprise-sdk#install)。 +- 治理注册的代付密钥。Enterprise SDK 目前仅限测试网,并且访问受限:[联系 Stable](https://discord.gg/stablexyz) 获取白名单代付密钥。 + +:::warning +这是一个使用私钥进行签名的服务器端库。切勿在浏览器中运行它。将代付密钥保存在您的后端。 +::: + +## 1. 使用 gas 代付模块创建客户端 + +将 `gasWaiver: { account }` 传递给 `createStableEnterprise`,其中 `account` 是您的白名单代付密钥。仅在您配置后,模块才会在客户端上出现。 + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { account: privateKeyToAccount(process.env.WAIVER_PRIVATE_KEY as `0x${string}`) }, +}); + +const gw = enterprise.gasWaiver; +``` + +```text +StableEnterpriseClient { gasWaiver, guaranteedBlock: undefined, guaranteedWaiver: undefined } +``` + +`rpcEndpoints` 选项是可选的。如果未设置,客户端将使用链的内置 RPC。 + +:::tip +要将代付密钥保存在托管后端中,请传递 `signer` 而不是 `account`。`@stablechain/enterprise/aws-kms` 中的 `awsKmsSigner` 支持 AWS KMS。请参阅[签名密钥和托管](/cn/reference/enterprise-sdk#signing-keys-and-custody)。 +::: + +## 2. 中继单个交易 + +使用用户帐户和变化的字段调用 `send`。`gasPrice: 0`、旧版类型、`chainId` 和待处理 Nonce 都已为您处理,因此您只需传递 `to`,以及可选的 `data`、`value` 和 `gas`。 + +```ts +const user = privateKeyToAccount(process.env.USER_PRIVATE_KEY as `0x${string}`); + +const { txHash } = await gw.send(user, { to: token, data, gas: 150_000n }); +console.log("H_inner:", txHash); +``` + +```text +H_inner: 0x8f3a...2d41 +``` + +用户不需要 USDT0:代付账户会赞助 gas。`gas` 默认为 `150_000n` (`DEFAULT_INNER_GAS`),足以覆盖代币转账或批准。对于更复杂的调用,请传递显式的 `gas`。 + +## 3. 批量中继 + +调用 `sendBatch` 从一个账户中继多笔交易。Nonce 会根据账户的待处理 Nonce 自动排序,并且您会按输入顺序获得每个输入的结果。 + +```ts +const results = await gw.sendBatch(user, [ + { to: token, data: dataA, gas: 150_000n }, + { to: token, data: dataB, gas: 150_000n }, +]); + +for (const r of results) { + console.log(r.success ? `[${r.index}] ✔ ${r.txHash}` : `[${r.index}] ✖ ${r.error?.code}`); +} +``` + +```text +[0] ✔ 0x8f3a...2d41 +[1] ✔ 0x2b7c...9e04 +``` + +批量处理报告每个项目的失败 `result.error` 而不是抛出异常,因此一个糟糕的交易不会影响其余交易。 + +## 4. 执行每个合作伙伴的策略限制 + +通过向配置添加策略限制来限制代付密钥可以赞助的内容。违反限制的 InnerTx 在广播前将被拒绝。 + +```ts +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { + account: privateKeyToAccount(process.env.WAIVER_PRIVATE_KEY as `0x${string}`), + maxGasLimit: 500_000n, + maxDataLength: 4_096, + allowedTargets: [ + { address: token, selectors: ["0xa9059cbb"] }, // ERC-20 transfer only + ], + }, +}); +``` + +针对 `allowedTargets` 之外的合约的交易将失败并显示 `TARGET_NOT_ALLOWED`;超过 `maxGasLimit` 的交易将失败并显示 `GAS_LIMIT_EXCEEDED`。使用 `"*"` 作为 `address` 允许任何合约,并省略 `selectors` 允许任何方法。 + +## 5. 中继预签名交易 + +对于非托管流程,用户在其自己的环境中签署 InnerTx 并仅将签名十六进制传递给您,因此您永远不会看到他们的密钥。使用 `buildWaiverInnerTx` 构建它,然后使用 `relay` 中继它。 + +```ts +import { buildWaiverInnerTx, toSigner } from "@stablechain/enterprise"; + +// 在用户端 — buildWaiverInnerTx 通过 Signer 签名;toSigner 适配 viem 账户 +const signed = await buildWaiverInnerTx(toSigner(user), stableTestnet.id, { to: token, data, gas: 150_000n, nonce }); + +// 在您的后端 +const { txHash } = await gw.relay(signed); +console.log("H_inner:", txHash); +``` + +```text +H_inner: 0x8f3a...2d41 +``` + +对于多个预签名交易,请使用 `relayBatch`,它像 `sendBatch` 一样为每个输入返回一个结果。 + +## 处理拒绝 + +`send` 和 `relay` 在拒绝时会抛出 `StableEnterpriseRelayError`,并带有您可以分支的 `code`。 + +```ts +import { StableEnterpriseRelayError } from "@stablechain/enterprise"; + +try { + await gw.send(user, { to: token, data }); +} catch (err) { + if (err instanceof StableEnterpriseRelayError && err.code === "TARGET_NOT_ALLOWED") { + // InnerTx 目标不在配置的白名单中 + } + throw err; +} +``` + +```text +StableEnterpriseRelayError: relay failed [TARGET_NOT_ALLOWED]: target 0x... not allowed +``` + +请参阅完整的 [`ErrorCode`](/cn/reference/enterprise-sdk#errorcode) 表格以了解所有拒绝原因。 + +## 下一步 + +- [**发送保证交易**](/cn/how-to/send-guaranteed-transactions):在保留的 Enterprise 通道区块空间中进行交易,并将其与 gas 代付结合使用。 +- [**Enterprise SDK 参考**](/cn/reference/enterprise-sdk#relay-with-gas-waiver):每个方法、配置选项和错误类的完整说明。 +- [**Gas 代付**](/cn/explanation/gas-waiver):协议级别的零 gas 交易工作原理。 diff --git a/docs/pages/cn/how-to/send-guaranteed-transactions.mdx b/docs/pages/cn/how-to/send-guaranteed-transactions.mdx new file mode 100644 index 0000000..805ce1d --- /dev/null +++ b/docs/pages/cn/how-to/send-guaranteed-transactions.mdx @@ -0,0 +1,151 @@ +--- +source_path: how-to/send-guaranteed-transactions.mdx +source_sha: 6da305c74e7ddb43f31a4ed11e7f4d5313f0cabe +title: "发送担保交易" +description: "使用 Enterprise SDK 在预留的 Enterprise-lane 区块空间中执行交易,并将担保区块空间与免燃气费结合起来,实现无燃气转发。" +diataxis: "how-to" +--- + +# 发送担保交易 + +使用 `@stablechain/enterprise` 通过预留的 Enterprise-lane 区块空间路由交易。`guaranteedBlock` 模块通过 Enterprise RPC 网关转发 GuaranteedTx (一种 `0x3F` 类型的 CustomTx),使其进入为企业工作负载预留的容量中。与免燃气费不同,签名者需要支付自己的燃气费,因此必须有足够的资金。 + +您可以将其与免燃气费结合使用,以获得[担保转发交易](/cn/how-to/guaranteed-relayed-transactions):免燃气费交易仍能进入企业通道。 + +## 先决条件 + +- Node.js 20 或更高版本,并安装 `@stablechain/enterprise` 和 `viem`。请参阅 [Enterprise SDK 参考](/cn/reference/enterprise-sdk#install)。 +- Enterprise RPC 网关 API 密钥。Enterprise SDK 目前仅支持测试网,并且访问受限:[联系 Stable](https://discord.gg/stablexyz) 获取网关端点。 +- 一个有资金的签名者,因为 GuaranteedTx 需要支付自己的燃气费。 + +:::warning +这是一个服务器端库。将 Enterprise RPC 网关 URL 保留在您的后端:它嵌入了您的 API 密钥。 +::: + +## 1. 使用担保区块空间模块创建客户端 + +将 `guaranteedBlock` 和 `enterpriseRpcEndpoints` 传递给 `createStableEnterprise`。网关是唯一接受 `0x3F` GuaranteedTxs 的端点,并且它包含您的 API 密钥。无效的 `laneId` 会被提前拒绝。 + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL], // the gateway URL Stable provisions + guaranteedBlock: { + account: privateKeyToAccount(process.env.SIGNER_PRIVATE_KEY as `0x${string}`), // must be funded + laneId: 0n, // Enterprise lane + }, +}); + +const gb = enterprise.guaranteedBlock; +``` + +```text +StableEnterpriseClient { gasWaiver: undefined, guaranteedBlock, guaranteedWaiver: undefined } +``` + +## 2. 发送单笔交易 + +使用签名者和可变字段调用 `send`。1559 费用字段是必需的,因为签名者需要支付燃气费。`chainId`、Enterprise `nonceKey` 和 2D nonce(从网关中发现)已为您处理。 + +```ts +import { createPublicClient, http } from "viem"; + +const publicClient = createPublicClient({ chain: stableTestnet, transport: http() }); +const gasPrice = await publicClient.getGasPrice(); + +const { txHash } = await gb.send(signer, { + to: recipient, + gas: 21_000n, + gasFeeCap: gasPrice * 2n, + gasTipCap: gasPrice, +}); +console.log("Guaranteed tx:", txHash); +``` + +```text +Guaranteed tx: 0xabcd...7890 +``` + +`gasFeeCap` 是每单位燃气的最大总费用(EIP-1559 `maxFeePerGas`),`gasTipCap` 是每单位燃气的最大优先费用(`maxPriorityFeePerGas`)。如上所示,将 `gasTipCap` 设置为当前燃气价格并将 `gasFeeCap` 设置为其两倍是一个安全的默认值。 + +签名者支付燃气费,因此请在转发前确认其持有余额。来自零余额账户的 GuaranteedTx 会失败。 + +## 3. 发送批处理 + +调用 `sendBatch` 发送多笔交易。Nonce 会从发现的基础自动排序,并且您会按输入顺序获得每个输入的一个结果。失败会使其后续交易受阻。 + +```ts +const results = await gb.sendBatch(signer, [ + { to: a, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice }, + { to: b, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice }, +]); + +for (const r of results) { + console.log(r.success ? `[${r.index}] ✔ ${r.txHash}` : `[${r.index}] ✖ ${r.error?.code}`); +} +``` + +```text +[0] ✔ 0xabcd...7890 +[1] ✔ 0x1f2e...66a1 +``` + +## 4. 发送预签名交易 + +对于非托管流程,使用 `buildGuaranteedTx` 在其他地方构建并签署 GuaranteedTx,然后只将签署的十六进制数据交给操作员。Enterprise nonce 密钥来自 `nonceKeyForLane`。 + +```ts +import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise"; + +const signed = await buildGuaranteedTx(toSigner(signer), stableTestnet.id, { + to: recipient, + gas: 21_000n, + gasFeeCap, + gasTipCap, + nonce, // the account's current 2D-lane nonce + nonceKey: nonceKeyForLane(0n), // Enterprise lane 0 +}); + +const { txHash } = await gb.relay(signed); +console.log("Guaranteed tx:", txHash); +``` + +```text +Guaranteed tx: 0xabcd...7890 +``` + +对于多笔预签名交易,请使用 `relayBatch`。 + +## 与免燃气费结合 + +要免除用户的燃气费并仍进入 Enterprise 通道,请使用 `guaranteedWaiver` 模块组合两个轨道。用户无需余额和费用字段,并且交易通过相同的网关进行路由。请参阅[担保转发交易](/cn/how-to/guaranteed-relayed-transactions)。 + +## 处理拒绝 + +`send` 和 `relay` 在拒绝时会抛出 `StableEnterpriseRelayError`。网关特定的错误代码包括 `GATEWAY_UNAUTHORIZED`(API 密钥缺失或无效)和 `QUOTA_EXCEEDED`(网关燃气配额已用尽)。 + +```ts +import { StableEnterpriseRelayError } from "@stablechain/enterprise"; + +try { + await gb.send(signer, { to: recipient, gas: 21_000n, gasFeeCap, gasTipCap }); +} catch (err) { + if (err instanceof StableEnterpriseRelayError && err.code === "GATEWAY_UNAUTHORIZED") { + // the Enterprise RPC gateway rejected the API key + } + throw err; +} +``` + +```text +StableEnterpriseRelayError: relay failed [GATEWAY_UNAUTHORIZED]: invalid api key +``` + +## 下一步 + +- [**担保转发交易**](/cn/how-to/guaranteed-relayed-transactions):一次调用即可免除用户的燃气费并进入 Enterprise 通道。 +- [**免燃气费转发**](/cn/how-to/relay-with-gas-waiver):为用户的燃气费提供赞助,以便他们在不持有 USDT0 的情况下进行交易。 +- [**Enterprise SDK 参考**](/cn/reference/enterprise-sdk#guaranteed-blockspace):所有方法、配置选项和错误类的完整说明。 diff --git a/docs/pages/cn/reference/enterprise-sdk.mdx b/docs/pages/cn/reference/enterprise-sdk.mdx new file mode 100644 index 0000000..b860a60 --- /dev/null +++ b/docs/pages/cn/reference/enterprise-sdk.mdx @@ -0,0 +1,530 @@ +--- +source_path: reference/enterprise-sdk.mdx +source_sha: d0bc44dc34b19b1a7f7cadeda2ceaa68d296220a +title: "企业级SDK参考" +description: "@stablechain/enterprise 的完整参考:createStableEnterprise、免Gas模块和保证区块空间模块、构建助手以及错误类。" +diataxis: "reference" +--- + +# 企业级SDK参考 + +`@stablechain/enterprise` 的完整表面。这涵盖了从 [`createStableEnterprise`](#createstableenterpriseconfig) 客户端开始,其三个功能([免Gas中继](#relay-with-gas-waiver)、[保证区块空间](#guaranteed-blockspace) 和 [保证中继交易](#guaranteed-relay-transactions)),低级别的构建助手,以及共享结果和错误类型。有关这些原理,请参阅[Stable企业级SDK](/cn/explanation/enterprise-sdk)、[免Gas](/cn/explanation/gas-waiver) 和 [保证区块空间](/cn/explanation/guaranteed-blockspace)。 + +:::note +企业级SDK目前仅在Stable测试网上可用,并且需要申请才能访问。要进行集成,请[联系Stable](https://discord.gg/stablexyz)以获取政fu注册的豁免密钥和企业级RPC网关API密钥。 +::: + +:::warning +这是一个使用私钥进行签名的服务器端库。切勿在浏览器中运行。请将豁免密钥和企业级RPC网关URL保留在您的后端。 +::: + +## 安装 + +```bash +npm install @stablechain/enterprise viem +``` + +```text +added 2 packages, audited 3 packages in 2s +``` + +`viem >= 2.0.0` 是一个对等依赖项。此包重新导出了 `viem/chains` 中的 `stable` 和 `stableTestnet`,因此您无需单独导入它们。 + +## `createStableEnterprise(config)` + +构造一个 `StableEnterpriseClient`。每个模块仅在您配置它时才存在,因此在使用前请进行空值检查。 + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { account: privateKeyToAccount("0xYOUR_WAIVER_KEY") }, +}); +``` + +```text +StableEnterpriseClient { gasWaiver, guaranteedBlock: undefined, guaranteedWaiver: undefined } +``` + +### `StableEnterpriseConfig` + +| **字段** | **类型** | **默认值** | **描述** | +| :--- | :--- | :--- | :--- | +| `chain` | `StableChain` | | 目标链。传递 `stable` 或 `stableTestnet`(由包重新导出)。必填。 | +| `rpcEndpoints` | `string[]?` | 链的内置RPC | 一个或多个Stable RPC端点,在失败时按顺序尝试。仅在指向私有端点时才覆盖。 | +| `enterpriseRpcEndpoints` | `string[]?` | | 一个或多个企业级RPC网关端点,在失败时按顺序尝试。`guaranteedBlock` 和 `guaranteedWaiver` 必填。 | +| `batchSizeLimit` | `number?` | `100` | 每个批处理RPC调用的最大交易数量。 | +| `signer` | `Signer?` | | 当模块没有自己的密钥时,免Gas模块(`gasWaiver`,`guaranteedWaiver`)的默认签名者。设置一次以通过单个托管后端驱动两者。请参阅[签名密钥和托管](#signing-keys-and-custody)。 | +| `gasWaiver` | `GasWaiverConfig?` | | 启用[免Gas中继](#relay-with-gas-waiver)。 | +| `guaranteedBlock` | `GuaranteedBlockConfig?` | | 启用[保证区块空间](#guaranteed-blockspace)。 | +| `guaranteedWaiver` | `GuaranteedWaiverConfig?` | | 启用[保证中继交易](#guaranteed-relay-transactions)。 | + +### 签名密钥和托管 + +持有豁免密钥的模块(`gasWaiver` 和 `guaranteedWaiver`)按顺序解析它:模块自己的 `signer`,然后是它的 `account`(一个进程内的viem密钥),然后是客户端配置中的顶层 [`signer`](#stableenterpriseconfig),然后是 `STABLE_ENTERPRISE_PRIVATE_KEY` 环境变量。设置顶层 `signer` 一次,即可通过单个托管后端驱动两个豁免模块。`guaranteedBlock` 使用其自身资助的 `account` 进行签名。 + +`Signer` 对 32 字节的摘要进行签名,因此密钥从不离开托管后端。对于AWS KMS,请使用 `awsKmsSigner`;要适配viem账户,请使用 `toSigner(account)`;对于进程内密钥,请使用 `privateKeySigner(key)` / `envSigner()`。传递给模块 `send` / `sendBatch` 的每次调用发送者也可以是viem账户或 `Signer`,因此KMS/HSM密钥可以签署内部交易而无需降级到 `relay`。 + +```bash +npm install @aws-sdk/client-kms +``` + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { awsKmsSigner } from "@stablechain/enterprise/aws-kms"; + +// KMS密钥必须是ECC_SECG_P256K1 (secp256k1);SDK会从它派生地址 +const signer = await awsKmsSigner({ keyId: process.env.AWS_KMS_KEY_ID }); + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { signer }, // 代替 `account` 的托管级免Gas密钥 +}); +``` + +`awsKmsSigner` 在 `@stablechain/enterprise/aws-kms` 子路径中提供,因此 `@aws-sdk/client-kms` 仍然是一个可选的对等依赖项,不属于核心安装。 + +### `StableEnterpriseClient` + +```ts +interface StableEnterpriseClient { + gasWaiver?: GasWaiverClient; + guaranteedBlock?: GuaranteedBlockClient; + guaranteedWaiver?: GuaranteedWaiverClient; +} +``` + +## 免Gas中继 + +`gasWaiver` 模块中继免Gas交易。一个白名单豁免账户将用户的零Gas交易(InnerTx)包装成WaiverTx并进行广播。用户不需要USDT0。每个方法都返回 `H_inner`,即InnerTx哈希,而不是包装器哈希。 + +在配置中通过 `gasWaiver` 启用它: + +```ts +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { + account: privateKeyToAccount("0xYOUR_WAIVER_KEY"), + // 可选的每个合作伙伴策略限制 + maxGasLimit: 500_000n, + allowedTargets: [{ address: "0xToken", selectors: ["0xa9059cbb"] }], + }, +}); + +const gw = enterprise.gasWaiver; +``` + +### `GasWaiverConfig` + +扩展 [`ValidationLimits`](#validationlimits) 和 [`SignerSource`](#signersource)。 + +| **字段** | **类型** | **描述** | +| :--- | :--- | :--- | +| `signer` | `Signer?` | 作为托管签名者(AWS KMS, HSM)的白名单、政fu注册豁免密钥。请参阅[签名密钥和托管](#signing-keys-and-custody)。优先于 `account`。 | +| `account` | `LocalAccount?` | 作为进程内viem账户的豁免密钥。当两者都未设置时,回退到 `STABLE_ENTERPRISE_PRIVATE_KEY`。 | +| `maxGasLimit` | `bigint?` | 继承自 `ValidationLimits`。 | +| `maxDataLength` | `number?` | 继承自 `ValidationLimits`。 | +| `allowedTargets` | `AllowedTarget[]?` | 继承自 `ValidationLimits`。 | + +### `send(account, tx)` + +构建、签名并中继一个`account`的InnerTx,一步到位。`gasPrice: 0`、遗留类型、`chainId`和待处理的nonce都为您处理。拒绝时抛出`StableEnterpriseRelayError`。 + +```ts +const { txHash } = await gw.send(user, { to: token, data, gas: 150_000n }); +``` + +```text +{ txHash: "0x8f3a...2d41" } +``` + +`tx` 参数是 [`WaiverInnerTx`](#waiverinnertx) 加上一个可选的 `nonce`。返回 [`RelayResult`](#relayresult)。 + +### `sendBatch(account, txs)` + +从一个`account`构建、签名并中继多个InnerTx。nonce从账户的待处理nonce开始自动排序。返回每个输入的结g果,按顺序。 + +```ts +const results = await gw.sendBatch(user, [ + { to: token, data: dataA, gas: 150_000n }, + { to: token, data: dataB, gas: 150_000n }, +]); +``` + +```text +[ { index: 0, success: true, txHash: "0x..." }, { index: 1, success: true, txHash: "0x..." } ] +``` + +`txs` 是一个[`WaiverInnerTx`](#waiverinnertx)的只读数组。返回[`BatchResultItem[]`](#batchresultitem)。 + +### `relay(signedInnerTxHex)` + +中继一个预签名的零Gas InnerTx。这用于非托管流程,其中用户在自己的环境中签名并只向您提供签名的十六进制数据,以便豁免操作员从不看到用户的密钥。使用 [`buildWaiverInnerTx`](#buildwaiverinnertxsigner-chainid-req) 构建一个。拒绝时抛出。 + +```ts +import { buildWaiverInnerTx, toSigner } from "@stablechain/enterprise"; + +const signed = await buildWaiverInnerTx(toSigner(user), stableTestnet.id, { to, data, gas: 150_000n, nonce }); +const { txHash } = await gw.relay(signed); +``` + +```text +{ txHash: "0x8f3a...2d41" } +``` + +### `relayBatch(signedInnerTxHexes)` + +中继一批预签名的 InnerTx。为每个输入按顺序返回一个 [`BatchResultItem`](#batchresultitem)。 + +```ts +const results = await gw.relayBatch([signed0, signed1]); +``` + +```text +[ { index: 0, success: true, txHash: "0x..." }, { index: 1, success: false, error: { code: "TARGET_NOT_ALLOWED", message: "..." } } ] +``` + +## 保证区块空间 + +`guaranteedBlock` 模块通过企业级 RPC 网关中继 GuaranteedTx(类型为 `0x3F` CustomTx),以便它们落在预留的企业专用区块空间中。与免Gas不同,签名者需要支付自己的Gas并必须有资金。每笔交易都带有与企业通道相关联的二维 Nonce,并且只能通过网关进行广播。 + +通过 `guaranteedBlock` 和 `enterpriseRpcEndpoints` 启用它: + +```ts +const enterprise = createStableEnterprise({ + chain: stableTestnet, + enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL], // Stable 提供的网关 URL + guaranteedBlock: { + account: privateKeyToAccount("0xFUNDED_SIGNER_KEY"), + laneId: 0n, + }, +}); + +const gb = enterprise.guaranteedBlock; +``` + +### `GuaranteedBlockConfig` + +| **字段** | **类型** | **描述** | +| :--- | :--- | :--- | +| `account` | `LocalAccount` | 签署每笔 GuaranteedTx 并支付其 Gas 的有资金账户。 | +| `laneId` | `bigint` | 企业通道ID。必须在 `[0, ENTERPRISE_MASK - 1]` 范围内。无效的ID将立即被拒绝。 | + +### `send(account, tx)` + +构建、签名并中继一个 GuaranteedTx。因为签名者要支付 Gas,所以需要 1559 费用字段。`chainId`、企业 `nonceKey` 和二维 Nonce(从网关发现)都会为您处理。 + +```ts +const gasPrice = await publicClient.getGasPrice(); + +const { txHash } = await gb.send(signer, { + to: recipient, + gas: 21_000n, + gasFeeCap: gasPrice * 2n, + gasTipCap: gasPrice, +}); +``` + +```text +{ txHash: "0xabcd...7890" } +``` + +`tx` 参数是 [`GuaranteedTxRequest`](#guaranteedtxrequest) 加上一个可选的 `nonce`。返回 [`RelayResult`](#relayresult)。 + +### `sendBatch(account, txs)` + +构建、签名并中继若干 GuaranteedTx。随机数从发现的基本值自动排序。失败会影响其后续结果。 + +```ts +const results = await gb.sendBatch(signer, [ + { to: a, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice }, + { to: b, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice }, +]); +``` + +```text +[ { index: 0, success: true, txHash: "0x..." }, { index: 1, success: true, txHash: "0x..." } ] +``` + +返回 [`BatchResultItem[]`](#batchresultitem)。 + +### `relay(signedTx)` / `relayBatch(signedTxs)` + +中继一个预签名的 GuaranteedTx,或批量中继。使用 [`buildGuaranteedTx`](#buildguaranteedtxsigner-chainid-req) 和 [`nonceKeyForLane`](#noncekeyforlanelaneid) 对企业级 Nonce 密钥在其他地方构建和签名交易,然后只将签名的十六进制数据交给操作员。 + +```ts +import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise"; + +const signed = await buildGuaranteedTx(toSigner(signer), stableTestnet.id, { + to, + gas: 21_000n, + gasFeeCap, + gasTipCap, + nonce, // 账户当前的 2D-通道 nonce + nonceKey: nonceKeyForLane(0n), +}); +const { txHash } = await gb.relay(signed); +``` + +```text +{ txHash: "0xabcd...7890" } +``` + +## 保证中继交易 + +`guaranteedWaiver` 模块结合了上述两种机制:通过保证区块空间路由的免Gas交易。内部和外部交易都是 `0x3F` 定制交易,共享一个企业 `nonceKey`,因此它像 `guaranteedBlock` 一样通过网关广播。免Gas方赞助 Gas,因此用户不需要余额和费用字段,就像 `gasWaiver` 一样。每个方法都返回 `H_inner`。 + +通过 `guaranteedWaiver` 和 `enterpriseRpcEndpoints` 启用它: + +```ts +const enterprise = createStableEnterprise({ + chain: stableTestnet, + enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL], // Stable 提供的网关 URL + guaranteedWaiver: { + account: privateKeyToAccount("0xYOUR_WAIVER_KEY"), + laneId: 0n, + }, +}); + +const gw = enterprise.guaranteedWaiver; +``` + +### `GuaranteedWaiverConfig` + +扩展 [`ValidationLimits`](#validationlimits) 和 [`SignerSource`](#signersource)。它外部包装豁免密钥的方式与 `GasWaiverConfig` 完全相同(`signer`,然后是 `account`,然后是环境变量),并接受相同的 `maxGasLimit`、`maxDataLength` 和 `allowedTargets` 策略控制。 + +| **字段** | **类型** | **描述** | +| :--- | :--- | :--- | +| `signer` | `Signer?` | 作为托管签名者(签署外部包装器)的白名单豁免密钥。优先于 `account`。请参阅[签名密钥和托管](#signing-keys-and-custody)。 | +| `account` | `LocalAccount?` | 作为进程内viem账户的豁免密钥。当两者都未设置时,回退到 `STABLE_ENTERPRISE_PRIVATE_KEY`。 | +| `laneId` | `bigint` | 企业通道ID。必须在 `[0, ENTERPRISE_MASK - 1]` 范围内。 | + +### `send(user, tx)` / `sendBatch(user, txs)` + +从`user`构建并签署内部`0x3F` CustomTx,用免Gas密钥包装,然后中继。由于Gas已免除,无需费用字段。内部2D nonce从网关发现,批次将从该基础自动排序。 + +```ts +// 单个 +const { txHash } = await gw.send(user, { to: recipient }); + +// 批次 +const results = await gw.sendBatch(user, [{ to: a }, { to: b }]); +``` + +```text +{ txHash: "0x8f3a...2d41" } +``` + +`tx` 参数是一个 [`GuaranteedWaiverTxRequest`](#guaranteedwaivertxrequest) 加上一个可选的 `nonce`。`send` 返回 [`RelayResult`](#relayresult);`sendBatch` 返回 [`BatchResultItem[]`](#batchresultitem)。 + +### `relay(signedInnerTx)` / `relayBatch(signedInnerTxs)` + +对于非托管流程,用户使用 [`buildGuaranteedTx`](#buildguaranteedtxsigner-chainid-req) 签署内部 `0x3F` CustomTx(费用为 `0n`,使用 `nonceKeyForLane(laneId)`),并只将签名的十六进制数据交给操作员。操作员用豁免密钥包装并中继。 + +```ts +import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise"; + +const signedInner = await buildGuaranteedTx(toSigner(user), stableTestnet.id, { + to, + gas: 100_000n, + gasFeeCap: 0n, // 已豁免 + gasTipCap: 0n, + nonce, + nonceKey: nonceKeyForLane(0n), +}); +const { txHash } = await gw.relay(signedInner); // 豁免包装 + 广播 → H_inner +``` + +```text +{ txHash: "0x8f3a...2d41" } +``` + +## 构建辅助函数 + +用于非托管 `relay` 路径的低级签名者。每个签名者都以 `Hex` 形式返回一个已签名交易,不进行 nonce 获取或费用估算。第一个参数是一个 [`Signer`](#signing-keys-and-custody):用 `toSigner(account)` 包装一个 viem 账户,或者传递一个托管签名者(例如 `awsKmsSigner`)。 + +### `buildWaiverInnerTx(signer, chainId, req)` + +使用预设的免Gas规则签名豁免版内部交易:`gasPrice: 0`、旧版类型和给定的 `chainId`。`req` 是 [`WaiverInnerTx`](#waiverinnertx) 加上一个必需的 `nonce`。 + +```ts +const signed = await buildWaiverInnerTx(toSigner(user), stableTestnet.id, { to, data, gas: 150_000n, nonce }); +``` + +### `buildGuaranteedTx(signer, chainId, req)` + +构建并签名一个 GuaranteedTx (`0x3F` CustomTx)。`req` 是一个 [`GuaranteedTxRequest`](#guaranteedtxrequest) 加上一个必需的 `nonce` 和 `nonceKey`。 + +### `buildGuaranteedWaiverTx(...)` + +将预签名的内部交易包装到外部 `0x3F` 免Gas自定义交易中。供 `guaranteedWaiver.relay` 内部使用;为高级流程导出。 + +### `nonceKeyForLane(laneId)` + +返回 `laneId` 对应的企业 `nonceKey`,供 `buildGuaranteedTx` 使用。 + +```ts +import { nonceKeyForLane } from "@stablechain/enterprise"; + +const nonceKey = nonceKeyForLane(0n); +``` + +## 类型 + +### `SignerSource` + +免Gas模块(`gasWaiver`、`guaranteedWaiver`)接受的签名密钥字段。按顺序解析:`signer`,然后是 `account`,然后是客户端的顶级 [`signer`](#stableenterpriseconfig),然后是 `STABLE_ENTERPRISE_PRIVATE_KEY` 环境变量。请参阅[签名密钥和 custod](#signing-keys-and-custody)。 + +| **字段** | **类型** | **描述** | +| :--- | :--- | :--- | +| `signer` | `Signer?` | 托管级签名者(AWS KMS、HSM 或 `privateKeySigner`)。优先于 `account`。 | +| `account` | `LocalAccount?` | 进程内viem账户,通过 `toSigner` 适配为 `Signer`。 | + +### `Signer` + +一个可插拔的签名器,SDK 通过它对 32 字节的摘要进行签名,因此密钥永远不会离开托管后端。可以使用 `awsKmsSigner`、`toSigner`、`privateKeySigner` 或 `envSigner` 来构造一个签名器。 + +```ts +interface Signer { + readonly address: Address; + signDigest(hash: Hex): Promise; +} +``` + +| **辅助函数** | **导入** | **描述** | +| :--- | :--- | :--- | +| `awsKmsSigner({ keyId, client? })` | `@stablechain/enterprise/aws-kms` | 由 AWS KMS `ECC_SECG_P256K1` 密钥支持的签名者。返回 `Promise`。 | +| `toSigner(account)` | `@stablechain/enterprise` | 将 viem `LocalAccount` 适配为 `Signer`。 | +| `privateKeySigner(key)` | `@stablechain/enterprise` | 包装进程中持有的原始私钥的签名者。 | +| `envSigner(varName?)` | `@stablechain/enterprise` | 从 `STABLE_ENTERPRISE_PRIVATE_KEY`(或 `varName`)读取密钥的签名者。 | + +### `WaiverInnerTx` + +每次调用都会变化的 InnerTx 字段。 + +| **字段** | **类型** | **默认值** | **描述** | +| :--- | :--- | :--- | :--- | +| `to` | `Address` | | 目标地址:用于ERC-20转账的代币合约,用于原生代币的接收方。 | +| `gas` | `bigint?` | `DEFAULT_INNER_GAS` (`150_000n`) | Gas限制。由于 `gasPrice` 为 0,高限制也是免费的。 | +| `data` | `Hex?` | `"0x"` | 调用数据。 | +| `value` | `bigint?` | `0n` | 发送的原生价值。 | + +### `GuaranteedTxRequest` + +| **字段** | **类型** | **默认值** | **描述** | +| :--- | :--- | :--- | :--- | +| `to` | `Address?` | | 目标地址。 | +| `gas` | `bigint` | | Gas限制。必填。 | +| `gasFeeCap` | `bigint` | | `maxFeePerGas`。必填。 | +| `gasTipCap` | `bigint` | | `maxPriorityFeePerGas`。必填。 | +| `data` | `Hex?` | `"0x"` | 调用数据。 | +| `value` | `bigint?` | `0n` | 发送的原生价值。 | + +### `GuaranteedWaiverTxRequest` + +费用字段始终为 0,因此此处省略。 + +| **字段** | **类型** | **默认值** | **描述** | +| :--- | :--- | :--- | :--- | +| `to` | `Address` | | 目标地址。 | +| `gas` | `bigint?` | 共享内部Gas默认值 | Gas限制。 | +| `data` | `Hex?` | `"0x"` | 调用数据。 | +| `value` | `bigint?` | `0n` | 发送的原生价值。免Gas允许价值转移。 | + +### `ValidationLimits` + +`gasWaiver` 和 `guaranteedWaiver` 对每个 InnerTx 应用的按合作伙伴策略。 + +| **字段** | **类型** | **默认值** | **描述** | +| :--- | :--- | :--- | :--- | +| `maxGasLimit` | `bigint?` | `10_000_000n` | InnerTx 的最大 Gas 限制。超过此值将导致 `GAS_LIMIT_EXCEEDED` 错误。 | +| `maxDataLength` | `number?` | `131_072` (128 KB) | 调用数据的最大大小(字节)。超过此值将导致 `DATA_TOO_LARGE` 错误。 | +| `allowedTargets` | `AllowedTarget[]?` | | 免Gas方可能赞助的合约和方法的白名单。设置后,不在列表中的目标将导致 `TARGET_NOT_ALLOWED` 错误。 | + +### `AllowedTarget` + +| **字段** | **类型** | **描述** | +| :--- | :--- | :--- | +| `address` | `Address \| "*"` | InnerTx 可以调用的合约,或 `"*"` 表示匹配任何合约。 | +| `selectors` | `Hex[]?` | 允许的 4 字节方法选择器(例如,ERC-20 `transfer` 的 `"0xa9059cbb"`)。省略或留空以允许 `address` 上的任何方法。 | + +### `RelayResult` + +```ts +interface RelayResult { + txHash: Hash; // 免Gas是 H_inner,独立保证区块是 GuaranteedTx hash +} +``` + +### `BatchResultItem` + +批次中每个输入一个条目,按输入顺序排列。批次会报告每个项目的结g果,而不是抛出异常。 + +| **字段** | **类型** | **描述** | +| :--- | :--- | :--- | +| `index` | `number` | 零基位置,与输入数组匹配。 | +| `success` | `boolean` | 此项目是否已中继。 | +| `txHash` | `Hash?` | 成功时存在:内部交易哈希。 | +| `error` | `{ code: ErrorCode; message: string }?` | 失败时存在。 | + +## 错误 + +单个交易方法 (`send`, `relay`) 在拒绝时抛出异常。批量方法在 [`BatchResultItem.error`](#batchresultitem) 中报告每个项目的失败。所有错误类都扩展自 `StableEnterpriseError`。 + +| **类** | **抛出条件** | **有用字段** | +| :--- | :--- | :--- | +| `StableEnterpriseError` | 所有SDK错误的基础类。 | `message` | +| `StableEnterpriseRelayError` | 交易在RPC或中继层被拒绝。 | `code` | +| `WaiverValidationError` | InnerTx在广播前未能通过策略检查(gas、数据大小或目标白名单)。 | `code` | + +```ts +import { StableEnterpriseRelayError } from "@stablechain/enterprise"; + +try { + await gw.send(user, { to: token, data }); +} catch (err) { + if (err instanceof StableEnterpriseRelayError && err.code === "TARGET_NOT_ALLOWED") { + // InnerTx 目标不在配置的白名单中 + } + throw err; +} +``` + +```text +StableEnterpriseRelayError: relay failed [TARGET_NOT_ALLOWED]: target 0x... not allowed +``` + +### `ErrorCode` + +抛出错误或 `BatchResultItem.error` 上的 `code` 之一是: + +| **代码** | **含义** | +| :--- | :--- | +| `UNKNOWN_ERROR` | 没有特定代码时的备用代码。 | +| `BROADCAST_FAILED` | 在RPC层广播被拒绝,或网关中继失败。 | +| `INVALID_TRANSACTION` | InnerTx解码或解析失败。 | +| `INVALID_SIGNATURE` | 签名验证失败。 | +| `UNSUPPORTED_TX_TYPE` | InnerTx类型不是legacy、eip2930或eip1559。 | +| `WRONG_CHAIN_ID` | InnerTx `chainId` 缺失或与目标链不匹配。 | +| `NON_ZERO_GAS_PRICE` | InnerTx带有非零gas price(免Gas时必须为零)。 | +| `GAS_LIMIT_EXCEEDED` | InnerTx 的 Gas 限制超过 `maxGasLimit`。 | +| `DATA_TOO_LARGE` | InnerTx 调用数据超过 `maxDataLength`。 | +| `TARGET_NOT_ALLOWED` | InnerTx 目标不在 `allowedTargets` 中。 | +| `GATEWAY_UNAUTHORIZED` | 企业级RPC网关拒绝了API密钥(缺失、无效或过期)。 | +| `QUOTA_EXCEEDED` | 企业级RPC网关Gas配额已用尽。 | + +## 常量 + +| **常数** | **类型** | **描述** | +| :--- | :--- | :--- | +| `DEFAULT_INNER_GAS` | `bigint` | 当省略 `gas` 时,默认的 InnerTx Gas 限制 (`150_000n`)。 | +| `ENTERPRISE_FLAG` | `bigint` | 设置在通道 `nonceKey` 上的企业位。 | +| `ENTERPRISE_MASK` | `bigint` | 通道 ID 的上限:`laneId` 必须在 `[0, ENTERPRISE_MASK - 1]` 范围内。 | + +## 接下来推荐 + +- [**Stable 企业级 SDK**](/cn/explanation/enterprise-sdk):详细了解这些机制及其使用场景。 +- [**免Gas协议**](/cn/reference/gas-waiver-api):交易格式、标记路由和政fu控制。 +- [**保证区块空间**](/cn/explanation/guaranteed-blockspace):Stable 如何为企业工作负载预留区块容量。 diff --git a/docs/pages/en/explanation/enterprise-sdk.mdx b/docs/pages/en/explanation/enterprise-sdk.mdx new file mode 100644 index 0000000..01045da --- /dev/null +++ b/docs/pages/en/explanation/enterprise-sdk.mdx @@ -0,0 +1,71 @@ +--- +title: "Stable Enterprise SDK" +description: "Relay gas-waived and guaranteed-blockspace transactions from your backend with the typed @stablechain/enterprise SDK." +diataxis: "explanation" +--- + +# Stable Enterprise SDK + +`@stablechain/enterprise` is a server-side TypeScript client for Stable's enterprise transaction rails. It signs and relays two kinds of privileged transactions: gas-waived transactions, where you sponsor a user's gas, and guaranteed-blockspace transactions, which land in a reserved Enterprise lane. You can enable either rail, or both, on one client. + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { account: privateKeyToAccount("0xYOUR_WAIVER_KEY") }, +}); + +const { txHash } = await enterprise.gasWaiver.send(user, { to: token, data }); +``` + +```text +txHash: 0x8f3a...2d41 +``` + +The call returns `H_inner`, the hash of the user's transaction, not the wrapper transaction that carried it. + +## What the SDK does + +The SDK exposes three features, one per module. Each is optional and independently configured, and each carries its own signing account so you can use separate keys. + +- **Relay with gas waiver** (`gasWaiver`): build, sign, and relay a gas-waived transaction. A whitelisted waiver account wraps a user's zero-gas transaction so the user transacts without holding any USDT0. See [Gas waiver](/en/explanation/gas-waiver). +- **Guaranteed blockspace** (`guaranteedBlock`): relay a transaction through the Enterprise RPC gateway so it lands in reserved Enterprise-lane blockspace. Unlike gas waiver, the signer pays its own gas. See [Guaranteed blockspace](/en/explanation/guaranteed-blockspace). +- **Guaranteed relay transactions** (`guaranteedWaiver`): both combined. A gas-waived transaction routed through guaranteed blockspace, so the user needs no balance and the transaction still lands in the Enterprise lane. + +Every feature offers the same four methods: `send` and `sendBatch` for the custodial path where the SDK signs, and `relay` and `relayBatch` for the non-custodial path where the user signs elsewhere and hands you only the signed hex. + +The SDK is published on npm as [`@stablechain/enterprise`](https://www.npmjs.com/package/@stablechain/enterprise) and requires `viem >= 2.0.0` as a peer dependency. + +## Access + +The Enterprise SDK is currently available on Stable Testnet only. + +Both rails are gated. Gas waiver requires a governance-registered waiver key, and guaranteed blockspace requires an Enterprise RPC gateway API key. To integrate, [contact Stable](https://discord.gg/stablexyz) to get access. Stable provisions the waiver key and the gateway endpoint you need. + +## Server-side only + +:::warning +The Enterprise SDK is a stateless, server-side library that signs with private keys. Never run it in a browser. Keep the waiver key and the Enterprise RPC gateway URL on your backend. +::: + +The waiver key is a whitelisted, governance-registered account: anyone holding it can sponsor gas under your policy. The Enterprise RPC gateway URL embeds your API key. Treat both as secrets. To keep the waiver key out of process, back it with a custody signer such as AWS KMS. See [Signing keys and custody](/en/reference/enterprise-sdk#signing-keys-and-custody). + +## When to use it + +Reach for the Enterprise SDK when you operate a backend and want to sponsor users' gas or guarantee inclusion for your own traffic. For everyday transfers, bridges, swaps, and vault yield from either a backend or a browser, use the general-purpose [Stable SDK](/en/explanation/sdk-overview) instead. + +## Start here + +- [**Relay with gas waiver**](/en/how-to/relay-with-gas-waiver): Sponsor a user's gas so they transact without holding USDT0. +- [**Send guaranteed transactions**](/en/how-to/send-guaranteed-transactions): Land transactions in reserved Enterprise-lane blockspace. +- [**Guaranteed relayed transactions**](/en/how-to/guaranteed-relayed-transactions): Combine both rails: gas-waived transactions in the Enterprise lane. +- [**Enterprise SDK reference**](/en/reference/enterprise-sdk): Every module, method, config option, and error class. + +## Next recommended + +- [**Install from npm**](https://www.npmjs.com/package/@stablechain/enterprise): View the package on npmjs.com and check the latest version. +- [**Gas waiver protocol**](/en/reference/gas-waiver-api): Transaction formats, marker routing, and governance controls. +- [**Stable SDK**](/en/explanation/sdk-overview): The general-purpose client for transfers, bridging, swaps, and yield. +- [**Connect to Stable**](/en/reference/connect): Chain IDs, RPC endpoints, and explorers for testnet. diff --git a/docs/pages/en/how-to/guaranteed-relayed-transactions.mdx b/docs/pages/en/how-to/guaranteed-relayed-transactions.mdx new file mode 100644 index 0000000..007be0e --- /dev/null +++ b/docs/pages/en/how-to/guaranteed-relayed-transactions.mdx @@ -0,0 +1,139 @@ +--- +title: "Guaranteed relayed transactions" +description: "Relay gas-waived transactions through guaranteed blockspace with the Enterprise SDK, so users need no balance and still land in the Enterprise lane." +diataxis: "how-to" +--- + +# Guaranteed relayed transactions + +Combine both Enterprise rails with `@stablechain/enterprise`. A guaranteed relayed transaction is a [gas-waived transaction](/en/how-to/relay-with-gas-waiver) routed through [guaranteed blockspace](/en/how-to/send-guaranteed-transactions): the waiver sponsors gas, so the user needs no balance and no fee fields, and the transaction still lands in the reserved Enterprise lane. + +The `guaranteedWaiver` module handles both sides. The user signs an inner `0x3F` CustomTx, the whitelisted waiver account wraps it in an outer `0x3F` CustomTx sharing the same Enterprise `nonceKey`, and it broadcasts through the Enterprise RPC gateway. Every method returns `H_inner`, the user's transaction hash. + +## Prerequisites + +- Node.js 20 or later, and `@stablechain/enterprise` plus `viem` installed. See the [Enterprise SDK reference](/en/reference/enterprise-sdk#install). +- A governance-registered waiver key and an Enterprise RPC gateway API key. The Enterprise SDK is testnet only for now, and access is on request: [contact Stable](https://discord.gg/stablexyz) to get both. + +:::warning +This is a server-side library. Keep the waiver key and the Enterprise RPC gateway URL on your backend: the gateway URL embeds your API key. +::: + +## 1. Create a client with the guaranteed waiver module + +Pass `guaranteedWaiver` and `enterpriseRpcEndpoints` to `createStableEnterprise`. The module takes the whitelisted waiver key (which signs the outer wrapper) plus the Enterprise lane. It also accepts the same `allowedTargets`, `maxGasLimit`, and `maxDataLength` policy limits as `gasWaiver`. + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL], // the gateway URL Stable provisions + guaranteedWaiver: { + account: privateKeyToAccount(process.env.WAIVER_PRIVATE_KEY as `0x${string}`), + laneId: 0n, // Enterprise lane + }, +}); + +const gw = enterprise.guaranteedWaiver; +``` + +```text +StableEnterpriseClient { gasWaiver: undefined, guaranteedBlock: undefined, guaranteedWaiver } +``` + +:::tip +To keep the waiver key in a custody backend, pass `signer` instead of `account`. `awsKmsSigner` from `@stablechain/enterprise/aws-kms` backs it with AWS KMS. See [Signing keys and custody](/en/reference/enterprise-sdk#signing-keys-and-custody). +::: + +## 2. Relay a single transaction + +Call `send` with the user's account and the fields that vary. Gas is waived, so the user needs no balance and no fee fields. The inner `0x3F` CustomTx, its Enterprise `nonceKey`, and the 2D nonce (discovered from the gateway) are handled for you. + +```ts +const user = privateKeyToAccount(process.env.USER_PRIVATE_KEY as `0x${string}`); + +const { txHash } = await gw.send(user, { to: recipient }); +console.log("H_inner:", txHash); +``` + +```text +H_inner: 0x8f3a...2d41 +``` + +`gas` defaults to the shared inner-gas default; pass it only to override. Value transfers are allowed, so you can also pass `value`. + +## 3. Relay a batch + +Call `sendBatch` to relay several transactions from one user. Inner nonces are auto-sequenced from a discovered base, and you get one result per input, in input order. A failure strands its successors. + +```ts +const results = await gw.sendBatch(user, [{ to: a }, { to: b }]); + +for (const r of results) { + console.log(r.success ? `[${r.index}] ✔ ${r.txHash}` : `[${r.index}] ✖ ${r.error?.code}`); +} +``` + +```text +[0] ✔ 0x8f3a...2d41 +[1] ✔ 0x2b7c...9e04 +``` + +## 4. Relay a pre-signed transaction + +For a non-custodial flow, the user signs the inner `0x3F` CustomTx in their own environment and hands you only the signed hex. Build it with `buildGuaranteedTx`, using `nonceKeyForLane` for the Enterprise nonce key and zero fees (gas is waived). Your backend wraps it with the waiver key and relays it with `relay`. + +```ts +import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise"; + +// on the user's side — buildGuaranteedTx signs through a Signer; toSigner adapts a viem account +const signedInner = await buildGuaranteedTx(toSigner(user), stableTestnet.id, { + to: recipient, + gas: 100_000n, + gasFeeCap: 0n, // waived + gasTipCap: 0n, + nonce, // the user's current 2D-lane nonce + nonceKey: nonceKeyForLane(0n), // Enterprise lane 0 +}); + +// on your backend +const { txHash } = await gw.relay(signedInner); // waiver wraps + broadcasts → H_inner +console.log("H_inner:", txHash); +``` + +```text +H_inner: 0x8f3a...2d41 +``` + +For several pre-signed transactions, use `relayBatch`. + +## Handle rejections + +`send` and `relay` throw `StableEnterpriseRelayError` on rejection. Because this flow routes through the gateway, gateway codes like `GATEWAY_UNAUTHORIZED` and `QUOTA_EXCEEDED` apply alongside the waiver policy codes such as `TARGET_NOT_ALLOWED`. + +```ts +import { StableEnterpriseRelayError } from "@stablechain/enterprise"; + +try { + await gw.send(user, { to: recipient }); +} catch (err) { + if (err instanceof StableEnterpriseRelayError && err.code === "GATEWAY_UNAUTHORIZED") { + // the Enterprise RPC gateway rejected the API key + } + throw err; +} +``` + +```text +StableEnterpriseRelayError: relay failed [GATEWAY_UNAUTHORIZED]: invalid api key +``` + +See the full [`ErrorCode`](/en/reference/enterprise-sdk#errorcode) table for every rejection reason. + +## Where to go next + +- [**Relay with gas waiver**](/en/how-to/relay-with-gas-waiver): Sponsor a user's gas without the guaranteed lane. +- [**Send guaranteed transactions**](/en/how-to/send-guaranteed-transactions): Use guaranteed blockspace on its own, where the signer pays gas. +- [**Enterprise SDK reference**](/en/reference/enterprise-sdk#guaranteed-relay-transactions): Every method, config option, and error class in full. diff --git a/docs/pages/en/how-to/relay-with-gas-waiver.mdx b/docs/pages/en/how-to/relay-with-gas-waiver.mdx new file mode 100644 index 0000000..c1b34aa --- /dev/null +++ b/docs/pages/en/how-to/relay-with-gas-waiver.mdx @@ -0,0 +1,155 @@ +--- +title: "Relay with gas waiver" +description: "Sponsor a user's gas with the Enterprise SDK: relay a zero-gas transaction, batch relays, enforce policy limits, and relay pre-signed transactions." +diataxis: "how-to" +--- + +# Relay with gas waiver + +Sponsor your users' gas with `@stablechain/enterprise`. A whitelisted waiver account wraps a user's zero-gas transaction (the InnerTx) and broadcasts it, so the user transacts without holding any USDT0. The `gasWaiver` module builds, signs, and relays in one call, so you call one method per action. + +Every method returns `H_inner`, the hash of the user's transaction, not the wrapper that carried it. + +## Prerequisites + +- Node.js 20 or later, and `@stablechain/enterprise` plus `viem` installed. See the [Enterprise SDK reference](/en/reference/enterprise-sdk#install). +- A governance-registered waiver key. The Enterprise SDK is testnet only for now, and access is gated: [contact Stable](https://discord.gg/stablexyz) to get a whitelisted waiver key. + +:::warning +This is a server-side library that signs with private keys. Never run it in a browser. Keep the waiver key on your backend. +::: + +## 1. Create a client with the gas waiver module + +Pass `gasWaiver: { account }` to `createStableEnterprise`, where `account` is your whitelisted waiver key. The module is present on the client only when you configure it. + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { account: privateKeyToAccount(process.env.WAIVER_PRIVATE_KEY as `0x${string}`) }, +}); + +const gw = enterprise.gasWaiver; +``` + +```text +StableEnterpriseClient { gasWaiver, guaranteedBlock: undefined, guaranteedWaiver: undefined } +``` + +The `rpcEndpoints` option is optional. Left unset, the client uses the chain's built-in RPC. + +:::tip +To keep the waiver key in a custody backend, pass `signer` instead of `account`. `awsKmsSigner` from `@stablechain/enterprise/aws-kms` backs it with AWS KMS. See [Signing keys and custody](/en/reference/enterprise-sdk#signing-keys-and-custody). +::: + +## 2. Relay a single transaction + +Call `send` with the user's account and the fields that vary. The `gasPrice: 0`, legacy type, `chainId`, and pending nonce are handled for you, so you pass only `to`, and optionally `data`, `value`, and `gas`. + +```ts +const user = privateKeyToAccount(process.env.USER_PRIVATE_KEY as `0x${string}`); + +const { txHash } = await gw.send(user, { to: token, data, gas: 150_000n }); +console.log("H_inner:", txHash); +``` + +```text +H_inner: 0x8f3a...2d41 +``` + +The user needs no USDT0: the waiver account sponsors the gas. `gas` defaults to `150_000n` (`DEFAULT_INNER_GAS`), which covers a token transfer or approval. Pass an explicit `gas` for heavier calls. + +## 3. Relay a batch + +Call `sendBatch` to relay several transactions from one account. Nonces are auto-sequenced from the account's pending nonce, and you get one result per input, in input order. + +```ts +const results = await gw.sendBatch(user, [ + { to: token, data: dataA, gas: 150_000n }, + { to: token, data: dataB, gas: 150_000n }, +]); + +for (const r of results) { + console.log(r.success ? `[${r.index}] ✔ ${r.txHash}` : `[${r.index}] ✖ ${r.error?.code}`); +} +``` + +```text +[0] ✔ 0x8f3a...2d41 +[1] ✔ 0x2b7c...9e04 +``` + +A batch reports failures per item in `result.error` instead of throwing, so one bad transaction doesn't sink the rest. + +## 4. Enforce per-partner policy limits + +Restrict what the waiver key may sponsor by adding policy limits to the config. An InnerTx that violates a limit is rejected before broadcast. + +```ts +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { + account: privateKeyToAccount(process.env.WAIVER_PRIVATE_KEY as `0x${string}`), + maxGasLimit: 500_000n, + maxDataLength: 4_096, + allowedTargets: [ + { address: token, selectors: ["0xa9059cbb"] }, // ERC-20 transfer only + ], + }, +}); +``` + +A transaction to a contract outside `allowedTargets` fails with `TARGET_NOT_ALLOWED`; one over `maxGasLimit` fails with `GAS_LIMIT_EXCEEDED`. Use `"*"` as the `address` to allow any contract, and omit `selectors` to allow any method. + +## 5. Relay a pre-signed transaction + +For a non-custodial flow, the user signs the InnerTx in their own environment and hands you only the signed hex, so you never see their key. Build it with `buildWaiverInnerTx`, then relay it with `relay`. + +```ts +import { buildWaiverInnerTx, toSigner } from "@stablechain/enterprise"; + +// on the user's side — buildWaiverInnerTx signs through a Signer; toSigner adapts a viem account +const signed = await buildWaiverInnerTx(toSigner(user), stableTestnet.id, { to: token, data, gas: 150_000n, nonce }); + +// on your backend +const { txHash } = await gw.relay(signed); +console.log("H_inner:", txHash); +``` + +```text +H_inner: 0x8f3a...2d41 +``` + +For several pre-signed transactions, use `relayBatch`, which returns one result per input like `sendBatch`. + +## Handle rejections + +`send` and `relay` throw `StableEnterpriseRelayError` on rejection, carrying a `code` you can branch on. + +```ts +import { StableEnterpriseRelayError } from "@stablechain/enterprise"; + +try { + await gw.send(user, { to: token, data }); +} catch (err) { + if (err instanceof StableEnterpriseRelayError && err.code === "TARGET_NOT_ALLOWED") { + // the InnerTx target is outside the configured allowlist + } + throw err; +} +``` + +```text +StableEnterpriseRelayError: relay failed [TARGET_NOT_ALLOWED]: target 0x... not allowed +``` + +See the full [`ErrorCode`](/en/reference/enterprise-sdk#errorcode) table for every rejection reason. + +## Where to go next + +- [**Send guaranteed transactions**](/en/how-to/send-guaranteed-transactions): Land transactions in reserved Enterprise-lane blockspace, and combine it with gas waiver. +- [**Enterprise SDK reference**](/en/reference/enterprise-sdk#relay-with-gas-waiver): Every method, config option, and error class in full. +- [**Gas waiver**](/en/explanation/gas-waiver): How zero-gas transactions work at the protocol level. diff --git a/docs/pages/en/how-to/send-guaranteed-transactions.mdx b/docs/pages/en/how-to/send-guaranteed-transactions.mdx new file mode 100644 index 0000000..6da305c --- /dev/null +++ b/docs/pages/en/how-to/send-guaranteed-transactions.mdx @@ -0,0 +1,149 @@ +--- +title: "Send guaranteed transactions" +description: "Land transactions in reserved Enterprise-lane blockspace with the Enterprise SDK, and combine guaranteed blockspace with gas waiver to relay them gas-free." +diataxis: "how-to" +--- + +# Send guaranteed transactions + +Route transactions through reserved Enterprise-lane blockspace with `@stablechain/enterprise`. The `guaranteedBlock` module relays a GuaranteedTx (a type `0x3F` CustomTx) through the Enterprise RPC gateway so it lands in capacity reserved for enterprise workloads. Unlike gas waiver, the signer pays its own gas, so it must be funded. + +You can combine this with gas waiver to get [guaranteed relayed transactions](/en/how-to/guaranteed-relayed-transactions): gas-waived transactions that still land in the Enterprise lane. + +## Prerequisites + +- Node.js 20 or later, and `@stablechain/enterprise` plus `viem` installed. See the [Enterprise SDK reference](/en/reference/enterprise-sdk#install). +- An Enterprise RPC gateway API key. The Enterprise SDK is testnet only for now, and access is gated: [contact Stable](https://discord.gg/stablexyz) to get a gateway endpoint. +- A funded signer, since a GuaranteedTx pays its own gas. + +:::warning +This is a server-side library. Keep the Enterprise RPC gateway URL on your backend: it embeds your API key. +::: + +## 1. Create a client with the guaranteed blockspace module + +Pass `guaranteedBlock` and `enterpriseRpcEndpoints` to `createStableEnterprise`. The gateway is the only endpoint that admits `0x3F` GuaranteedTxs, and it holds your API key. An invalid `laneId` is rejected up front. + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL], // the gateway URL Stable provisions + guaranteedBlock: { + account: privateKeyToAccount(process.env.SIGNER_PRIVATE_KEY as `0x${string}`), // must be funded + laneId: 0n, // Enterprise lane + }, +}); + +const gb = enterprise.guaranteedBlock; +``` + +```text +StableEnterpriseClient { gasWaiver: undefined, guaranteedBlock, guaranteedWaiver: undefined } +``` + +## 2. Send a single transaction + +Call `send` with the signer and the varying fields. The 1559 fee fields are required because the signer pays gas. The `chainId`, the Enterprise `nonceKey`, and the 2D nonce (discovered from the gateway) are handled for you. + +```ts +import { createPublicClient, http } from "viem"; + +const publicClient = createPublicClient({ chain: stableTestnet, transport: http() }); +const gasPrice = await publicClient.getGasPrice(); + +const { txHash } = await gb.send(signer, { + to: recipient, + gas: 21_000n, + gasFeeCap: gasPrice * 2n, + gasTipCap: gasPrice, +}); +console.log("Guaranteed tx:", txHash); +``` + +```text +Guaranteed tx: 0xabcd...7890 +``` + +`gasFeeCap` is the maximum total fee per gas (the EIP-1559 `maxFeePerGas`) and `gasTipCap` is the maximum priority fee per gas (`maxPriorityFeePerGas`). Setting `gasTipCap` to the current gas price and `gasFeeCap` to twice it, as above, is a safe default. + +The signer pays gas, so confirm it holds a balance before relaying. A GuaranteedTx from a zero-balance account fails. + +## 3. Send a batch + +Call `sendBatch` to send several transactions. Nonces are auto-sequenced from a discovered base, and you get one result per input, in input order. A failure strands its successors. + +```ts +const results = await gb.sendBatch(signer, [ + { to: a, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice }, + { to: b, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice }, +]); + +for (const r of results) { + console.log(r.success ? `[${r.index}] ✔ ${r.txHash}` : `[${r.index}] ✖ ${r.error?.code}`); +} +``` + +```text +[0] ✔ 0xabcd...7890 +[1] ✔ 0x1f2e...66a1 +``` + +## 4. Send a pre-signed transaction + +For a non-custodial flow, build and sign the GuaranteedTx elsewhere with `buildGuaranteedTx`, then hand the operator only the signed hex. The Enterprise nonce key comes from `nonceKeyForLane`. + +```ts +import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise"; + +const signed = await buildGuaranteedTx(toSigner(signer), stableTestnet.id, { + to: recipient, + gas: 21_000n, + gasFeeCap, + gasTipCap, + nonce, // the account's current 2D-lane nonce + nonceKey: nonceKeyForLane(0n), // Enterprise lane 0 +}); + +const { txHash } = await gb.relay(signed); +console.log("Guaranteed tx:", txHash); +``` + +```text +Guaranteed tx: 0xabcd...7890 +``` + +For several pre-signed transactions, use `relayBatch`. + +## Combine with gas waiver + +To waive the user's gas and still land in the Enterprise lane, compose both rails with the `guaranteedWaiver` module. The user needs no balance and no fee fields, and the transaction routes through the same gateway. See [Guaranteed relayed transactions](/en/how-to/guaranteed-relayed-transactions). + +## Handle rejections + +`send` and `relay` throw `StableEnterpriseRelayError` on rejection. Gateway-specific codes include `GATEWAY_UNAUTHORIZED` (a missing or invalid API key) and `QUOTA_EXCEEDED` (the gateway gas quota is exhausted). + +```ts +import { StableEnterpriseRelayError } from "@stablechain/enterprise"; + +try { + await gb.send(signer, { to: recipient, gas: 21_000n, gasFeeCap, gasTipCap }); +} catch (err) { + if (err instanceof StableEnterpriseRelayError && err.code === "GATEWAY_UNAUTHORIZED") { + // the Enterprise RPC gateway rejected the API key + } + throw err; +} +``` + +```text +StableEnterpriseRelayError: relay failed [GATEWAY_UNAUTHORIZED]: invalid api key +``` + +## Where to go next + +- [**Guaranteed relayed transactions**](/en/how-to/guaranteed-relayed-transactions): Waive the user's gas and land in the Enterprise lane in one call. +- [**Relay with gas waiver**](/en/how-to/relay-with-gas-waiver): Sponsor a user's gas so they transact without holding USDT0. +- [**Enterprise SDK reference**](/en/reference/enterprise-sdk#guaranteed-blockspace): Every method, config option, and error class in full. diff --git a/docs/pages/en/reference/enterprise-sdk.mdx b/docs/pages/en/reference/enterprise-sdk.mdx new file mode 100644 index 0000000..9d6954f --- /dev/null +++ b/docs/pages/en/reference/enterprise-sdk.mdx @@ -0,0 +1,528 @@ +--- +title: "Enterprise SDK reference" +description: "Complete reference for @stablechain/enterprise: createStableEnterprise, the gas waiver and guaranteed blockspace modules, build helpers, and error classes." +diataxis: "reference" +--- + +# Enterprise SDK reference + +Full surface of `@stablechain/enterprise`. This covers the client from [`createStableEnterprise`](#createstableenterpriseconfig), its three features ([relay with gas waiver](#relay-with-gas-waiver), [guaranteed blockspace](#guaranteed-blockspace), and [guaranteed relay transactions](#guaranteed-relay-transactions)), the low-level build helpers, and the shared result and error types. For what these rails are, see [Stable Enterprise SDK](/en/explanation/enterprise-sdk), [Gas waiver](/en/explanation/gas-waiver), and [Guaranteed blockspace](/en/explanation/guaranteed-blockspace). + +:::note +The Enterprise SDK is available on Stable Testnet only for now, and access is on request. To integrate, [contact Stable](https://discord.gg/stablexyz) to get a governance-registered waiver key and an Enterprise RPC gateway API key. +::: + +:::warning +This is a server-side library that signs with private keys. Never run it in a browser. Keep the waiver key and the Enterprise RPC gateway URL on your backend. +::: + +## Install + +```bash +npm install @stablechain/enterprise viem +``` + +```text +added 2 packages, audited 3 packages in 2s +``` + +`viem >= 2.0.0` is a peer dependency. The package is published as [`@stablechain/enterprise`](https://www.npmjs.com/package/@stablechain/enterprise) and re-exports `stable` and `stableTestnet` from `viem/chains`, so you don't import them separately. + +## `createStableEnterprise(config)` + +Construct a `StableEnterpriseClient`. Each module is present only when you configure it, so null-check it before use. + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { account: privateKeyToAccount("0xYOUR_WAIVER_KEY") }, +}); +``` + +```text +StableEnterpriseClient { gasWaiver, guaranteedBlock: undefined, guaranteedWaiver: undefined } +``` + +### `StableEnterpriseConfig` + +| **Field** | **Type** | **Default** | **Description** | +| :--- | :--- | :--- | :--- | +| `chain` | `StableChain` | | Target chain. Pass `stable` or `stableTestnet` (re-exported by the package). Required. | +| `rpcEndpoints` | `string[]?` | Chain's built-in RPC | One or more Stable RPC endpoints, tried in order on failure. Override only to point at a private endpoint. | +| `enterpriseRpcEndpoints` | `string[]?` | | One or more Enterprise RPC gateway endpoints, tried in order on failure. Required for `guaranteedBlock` and `guaranteedWaiver`. | +| `batchSizeLimit` | `number?` | `100` | Maximum transactions per batched RPC call. | +| `signer` | `Signer?` | | Default signer for the waiver modules (`gasWaiver`, `guaranteedWaiver`) when a module has no key of its own. Set it once to drive both from a single custody backend. See [Signing keys and custody](#signing-keys-and-custody). | +| `gasWaiver` | `GasWaiverConfig?` | | Enable [relay with gas waiver](#relay-with-gas-waiver). | +| `guaranteedBlock` | `GuaranteedBlockConfig?` | | Enable [guaranteed blockspace](#guaranteed-blockspace). | +| `guaranteedWaiver` | `GuaranteedWaiverConfig?` | | Enable [guaranteed relay transactions](#guaranteed-relay-transactions). | + +### Signing keys and custody + +The modules that hold the waiver key (`gasWaiver` and `guaranteedWaiver`) resolve it in order: the module's own `signer`, then its `account` (an in-process viem key), then the top-level [`signer`](#stableenterpriseconfig) on the client config, then the `STABLE_ENTERPRISE_PRIVATE_KEY` environment variable. Set the top-level `signer` once to drive both waiver modules from a single custody backend. `guaranteedBlock` signs with its own funded `account`. + +A `Signer` signs a 32-byte digest, so the key never leaves the custody backend. Use `awsKmsSigner` for AWS KMS, `toSigner(account)` to adapt a viem account, or `privateKeySigner(key)` / `envSigner()` for an in-process key. The per-call sender passed to a module's `send` / `sendBatch` may likewise be a viem account or a `Signer`, so a KMS/HSM key can sign inner transactions without dropping to `relay`. + +```bash +npm install @aws-sdk/client-kms +``` + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { awsKmsSigner } from "@stablechain/enterprise/aws-kms"; + +// the KMS key must be ECC_SECG_P256K1 (secp256k1); the SDK derives the address from it +const signer = await awsKmsSigner({ keyId: process.env.AWS_KMS_KEY_ID }); + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { signer }, // custody-grade waiver key, in place of `account` +}); +``` + +`awsKmsSigner` ships in the `@stablechain/enterprise/aws-kms` subpath so `@aws-sdk/client-kms` stays an optional peer dependency, out of the core install. + +### `StableEnterpriseClient` + +```ts +interface StableEnterpriseClient { + gasWaiver?: GasWaiverClient; + guaranteedBlock?: GuaranteedBlockClient; + guaranteedWaiver?: GuaranteedWaiverClient; +} +``` + +## Relay with gas waiver + +The `gasWaiver` module relays gas-waived transactions. A whitelisted waiver account wraps a user's zero-gas transaction (the InnerTx) into a WaiverTx and broadcasts it. The user needs no USDT0. Every method returns `H_inner`, the InnerTx hash, not the wrapper hash. + +Enable it with `gasWaiver` in the config: + +```ts +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { + account: privateKeyToAccount("0xYOUR_WAIVER_KEY"), + // optional per-partner policy limits + maxGasLimit: 500_000n, + allowedTargets: [{ address: "0xToken", selectors: ["0xa9059cbb"] }], + }, +}); + +const gw = enterprise.gasWaiver; +``` + +### `GasWaiverConfig` + +Extends [`ValidationLimits`](#validationlimits) and [`SignerSource`](#signersource). + +| **Field** | **Type** | **Description** | +| :--- | :--- | :--- | +| `signer` | `Signer?` | The whitelisted, governance-registered waiver key as a custody signer (AWS KMS, HSM). See [Signing keys and custody](#signing-keys-and-custody). Takes precedence over `account`. | +| `account` | `LocalAccount?` | The waiver key as an in-process viem account. Falls back to `STABLE_ENTERPRISE_PRIVATE_KEY` when neither is set. | +| `maxGasLimit` | `bigint?` | Inherited from `ValidationLimits`. | +| `maxDataLength` | `number?` | Inherited from `ValidationLimits`. | +| `allowedTargets` | `AllowedTarget[]?` | Inherited from `ValidationLimits`. | + +### `send(account, tx)` + +Build, sign, and relay one InnerTx from `account` in one call. The `gasPrice: 0`, legacy type, `chainId`, and pending nonce are handled for you. Throws `StableEnterpriseRelayError` on rejection. + +```ts +const { txHash } = await gw.send(user, { to: token, data, gas: 150_000n }); +``` + +```text +{ txHash: "0x8f3a...2d41" } +``` + +The `tx` argument is a [`WaiverInnerTx`](#waiverinnertx) plus an optional `nonce`. Returns [`RelayResult`](#relayresult). + +### `sendBatch(account, txs)` + +Build, sign, and relay several InnerTxs from one `account`. Nonces are auto-sequenced from the account's pending nonce. Returns one result per input, in order. + +```ts +const results = await gw.sendBatch(user, [ + { to: token, data: dataA, gas: 150_000n }, + { to: token, data: dataB, gas: 150_000n }, +]); +``` + +```text +[ { index: 0, success: true, txHash: "0x..." }, { index: 1, success: true, txHash: "0x..." } ] +``` + +`txs` is a readonly array of [`WaiverInnerTx`](#waiverinnertx). Returns [`BatchResultItem[]`](#batchresultitem). + +### `relay(signedInnerTxHex)` + +Relay a pre-signed zero-gas InnerTx. Use this for the non-custodial flow where the user signs in their own environment and hands you only the signed hex, so the waiver operator never sees the user's key. Build one with [`buildWaiverInnerTx`](#buildwaiverinnertxsigner-chainid-req). Throws on rejection. + +```ts +import { buildWaiverInnerTx, toSigner } from "@stablechain/enterprise"; + +const signed = await buildWaiverInnerTx(toSigner(user), stableTestnet.id, { to, data, gas: 150_000n, nonce }); +const { txHash } = await gw.relay(signed); +``` + +```text +{ txHash: "0x8f3a...2d41" } +``` + +### `relayBatch(signedInnerTxHexes)` + +Relay a batch of pre-signed InnerTxs. Returns one [`BatchResultItem`](#batchresultitem) per input, in order. + +```ts +const results = await gw.relayBatch([signed0, signed1]); +``` + +```text +[ { index: 0, success: true, txHash: "0x..." }, { index: 1, success: false, error: { code: "TARGET_NOT_ALLOWED", message: "..." } } ] +``` + +## Guaranteed blockspace + +The `guaranteedBlock` module relays GuaranteedTx (type `0x3F` CustomTx) through the Enterprise RPC gateway so they land in reserved Enterprise-lane blockspace. Unlike gas waiver, the signer pays its own gas and must be funded. Each transaction carries a 2D nonce keyed to an Enterprise lane, and broadcasting goes only through the gateway. + +Enable it with `guaranteedBlock` plus `enterpriseRpcEndpoints`: + +```ts +const enterprise = createStableEnterprise({ + chain: stableTestnet, + enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL], // the gateway URL Stable provisions + guaranteedBlock: { + account: privateKeyToAccount("0xFUNDED_SIGNER_KEY"), + laneId: 0n, + }, +}); + +const gb = enterprise.guaranteedBlock; +``` + +### `GuaranteedBlockConfig` + +| **Field** | **Type** | **Description** | +| :--- | :--- | :--- | +| `account` | `LocalAccount` | Funded account that signs each GuaranteedTx and pays its gas. | +| `laneId` | `bigint` | Enterprise lane id. Must be in `[0, ENTERPRISE_MASK - 1]`. An invalid id is rejected up front. | + +### `send(account, tx)` + +Build, sign, and relay one GuaranteedTx. The 1559 fee fields are required because the signer pays gas. The `chainId`, the Enterprise `nonceKey`, and the 2D nonce (discovered from the gateway) are handled for you. + +```ts +const gasPrice = await publicClient.getGasPrice(); + +const { txHash } = await gb.send(signer, { + to: recipient, + gas: 21_000n, + gasFeeCap: gasPrice * 2n, + gasTipCap: gasPrice, +}); +``` + +```text +{ txHash: "0xabcd...7890" } +``` + +The `tx` argument is a [`GuaranteedTxRequest`](#guaranteedtxrequest) plus an optional `nonce`. Returns [`RelayResult`](#relayresult). + +### `sendBatch(account, txs)` + +Build, sign, and relay several GuaranteedTxs. Nonces are auto-sequenced from the discovered base. A failure strands its successors. + +```ts +const results = await gb.sendBatch(signer, [ + { to: a, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice }, + { to: b, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice }, +]); +``` + +```text +[ { index: 0, success: true, txHash: "0x..." }, { index: 1, success: true, txHash: "0x..." } ] +``` + +Returns [`BatchResultItem[]`](#batchresultitem). + +### `relay(signedTx)` / `relayBatch(signedTxs)` + +Relay a pre-signed GuaranteedTx, or a batch of them. Build and sign the transaction elsewhere with [`buildGuaranteedTx`](#buildguaranteedtxsigner-chainid-req), using [`nonceKeyForLane`](#noncekeyforlanelaneid) for the Enterprise nonce key, then hand the operator only the signed hex. + +```ts +import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise"; + +const signed = await buildGuaranteedTx(toSigner(signer), stableTestnet.id, { + to, + gas: 21_000n, + gasFeeCap, + gasTipCap, + nonce, // the account's current 2D-lane nonce + nonceKey: nonceKeyForLane(0n), +}); +const { txHash } = await gb.relay(signed); +``` + +```text +{ txHash: "0xabcd...7890" } +``` + +## Guaranteed relay transactions + +The `guaranteedWaiver` module combines the two rails above: a gas-waived transaction routed through guaranteed blockspace. Both the inner and outer transaction are `0x3F` CustomTxs sharing one Enterprise `nonceKey`, so it broadcasts through the gateway like `guaranteedBlock`. The waiver sponsors gas, so the user needs no balance and no fee fields, like `gasWaiver`. Every method returns `H_inner`. + +Enable it with `guaranteedWaiver` plus `enterpriseRpcEndpoints`: + +```ts +const enterprise = createStableEnterprise({ + chain: stableTestnet, + enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL], // the gateway URL Stable provisions + guaranteedWaiver: { + account: privateKeyToAccount("0xYOUR_WAIVER_KEY"), + laneId: 0n, + }, +}); + +const gw = enterprise.guaranteedWaiver; +``` + +### `GuaranteedWaiverConfig` + +Extends [`ValidationLimits`](#validationlimits) and [`SignerSource`](#signersource). It sources the outer-wrapper waiver key exactly like `GasWaiverConfig` (`signer`, then `account`, then the env var) and accepts the same `maxGasLimit`, `maxDataLength`, and `allowedTargets` policy controls. + +| **Field** | **Type** | **Description** | +| :--- | :--- | :--- | +| `signer` | `Signer?` | The whitelisted waiver key (which signs the outer wrapper) as a custody signer. Takes precedence over `account`. See [Signing keys and custody](#signing-keys-and-custody). | +| `account` | `LocalAccount?` | The waiver key as an in-process viem account. Falls back to `STABLE_ENTERPRISE_PRIVATE_KEY` when neither is set. | +| `laneId` | `bigint` | Enterprise lane id. Must be in `[0, ENTERPRISE_MASK - 1]`. | + +### `send(user, tx)` / `sendBatch(user, txs)` + +Build and sign the inner `0x3F` CustomTx from `user`, wrap it with the waiver key, and relay. No fee fields are needed, since gas is waived. The inner 2D nonce is discovered from the gateway, and batches auto-sequence from that base. + +```ts +// single +const { txHash } = await gw.send(user, { to: recipient }); + +// batch +const results = await gw.sendBatch(user, [{ to: a }, { to: b }]); +``` + +```text +{ txHash: "0x8f3a...2d41" } +``` + +The `tx` argument is a [`GuaranteedWaiverTxRequest`](#guaranteedwaivertxrequest) plus an optional `nonce`. `send` returns [`RelayResult`](#relayresult); `sendBatch` returns [`BatchResultItem[]`](#batchresultitem). + +### `relay(signedInnerTx)` / `relayBatch(signedInnerTxs)` + +For the non-custodial flow, the user signs the inner `0x3F` CustomTx with [`buildGuaranteedTx`](#buildguaranteedtxsigner-chainid-req) (fees `0n`, using `nonceKeyForLane(laneId)`) and hands the operator only the signed hex. The operator wraps it with the waiver key and relays. + +```ts +import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise"; + +const signedInner = await buildGuaranteedTx(toSigner(user), stableTestnet.id, { + to, + gas: 100_000n, + gasFeeCap: 0n, // waived + gasTipCap: 0n, + nonce, + nonceKey: nonceKeyForLane(0n), +}); +const { txHash } = await gw.relay(signedInner); // waiver wraps + broadcasts → H_inner +``` + +```text +{ txHash: "0x8f3a...2d41" } +``` + +## Build helpers + +Low-level signers for the non-custodial `relay` paths. Each returns a signed transaction as `Hex`, with no nonce fetch or fee estimation. The first argument is a [`Signer`](#signing-keys-and-custody): wrap a viem account with `toSigner(account)`, or pass a custody signer such as `awsKmsSigner`. + +### `buildWaiverInnerTx(signer, chainId, req)` + +Sign a waiver-ready InnerTx with the waiver invariants baked in: `gasPrice: 0`, legacy type, and the given `chainId`. `req` is a [`WaiverInnerTx`](#waiverinnertx) plus a required `nonce`. + +```ts +const signed = await buildWaiverInnerTx(toSigner(user), stableTestnet.id, { to, data, gas: 150_000n, nonce }); +``` + +### `buildGuaranteedTx(signer, chainId, req)` + +Build and sign a GuaranteedTx (`0x3F` CustomTx). `req` is a [`GuaranteedTxRequest`](#guaranteedtxrequest) plus a required `nonce` and `nonceKey`. + +### `buildGuaranteedWaiverTx(...)` + +Wrap a pre-signed inner into the outer `0x3F` waiver CustomTx. Used internally by `guaranteedWaiver.relay`; exported for advanced flows. + +### `nonceKeyForLane(laneId)` + +Return the Enterprise `nonceKey` for a lane id, for use with `buildGuaranteedTx`. + +```ts +import { nonceKeyForLane } from "@stablechain/enterprise"; + +const nonceKey = nonceKeyForLane(0n); +``` + +## Types + +### `SignerSource` + +The signing-key fields a waiver module (`gasWaiver`, `guaranteedWaiver`) accepts. Resolved in order: `signer`, then `account`, then the client's top-level [`signer`](#stableenterpriseconfig), then the `STABLE_ENTERPRISE_PRIVATE_KEY` env var. See [Signing keys and custody](#signing-keys-and-custody). + +| **Field** | **Type** | **Description** | +| :--- | :--- | :--- | +| `signer` | `Signer?` | A custody-grade signer (AWS KMS, HSM, or `privateKeySigner`). Takes precedence over `account`. | +| `account` | `LocalAccount?` | An in-process viem account, adapted to a `Signer` via `toSigner`. | + +### `Signer` + +A pluggable signer the SDK signs a 32-byte digest through, so the key never leaves the custody backend. Construct one with `awsKmsSigner`, `toSigner`, `privateKeySigner`, or `envSigner`. + +```ts +interface Signer { + readonly address: Address; + signDigest(hash: Hex): Promise; +} +``` + +| **Helper** | **Import** | **Description** | +| :--- | :--- | :--- | +| `awsKmsSigner({ keyId, client? })` | `@stablechain/enterprise/aws-kms` | Signer backed by an AWS KMS `ECC_SECG_P256K1` key. Returns a `Promise`. | +| `toSigner(account)` | `@stablechain/enterprise` | Adapt a viem `LocalAccount` to a `Signer`. | +| `privateKeySigner(key)` | `@stablechain/enterprise` | Signer wrapping a raw private key held in process. | +| `envSigner(varName?)` | `@stablechain/enterprise` | Signer reading the key from `STABLE_ENTERPRISE_PRIVATE_KEY` (or `varName`). | + +### `WaiverInnerTx` + +The fields of an InnerTx that vary per call. + +| **Field** | **Type** | **Default** | **Description** | +| :--- | :--- | :--- | :--- | +| `to` | `Address` | | Target address: token contract for an ERC-20 transfer, recipient for native. | +| `gas` | `bigint?` | `DEFAULT_INNER_GAS` (`150_000n`) | Gas limit. Because `gasPrice` is 0, a generous limit is free. | +| `data` | `Hex?` | `"0x"` | Calldata. | +| `value` | `bigint?` | `0n` | Native value to send. | + +### `GuaranteedTxRequest` + +| **Field** | **Type** | **Default** | **Description** | +| :--- | :--- | :--- | :--- | +| `to` | `Address?` | | Target address. | +| `gas` | `bigint` | | Gas limit. Required. | +| `gasFeeCap` | `bigint` | | `maxFeePerGas`. Required. | +| `gasTipCap` | `bigint` | | `maxPriorityFeePerGas`. Required. | +| `data` | `Hex?` | `"0x"` | Calldata. | +| `value` | `bigint?` | `0n` | Native value to send. | + +### `GuaranteedWaiverTxRequest` + +The fee fields are always 0, so they're omitted here. + +| **Field** | **Type** | **Default** | **Description** | +| :--- | :--- | :--- | :--- | +| `to` | `Address` | | Target address. | +| `gas` | `bigint?` | shared inner-gas default | Gas limit. | +| `data` | `Hex?` | `"0x"` | Calldata. | +| `value` | `bigint?` | `0n` | Native value to send. Value transfer is allowed for waivers. | + +### `ValidationLimits` + +Per-partner policy applied to each InnerTx by `gasWaiver` and `guaranteedWaiver`. + +| **Field** | **Type** | **Default** | **Description** | +| :--- | :--- | :--- | :--- | +| `maxGasLimit` | `bigint?` | `10_000_000n` | Maximum gas limit for an InnerTx. Exceeding it fails with `GAS_LIMIT_EXCEEDED`. | +| `maxDataLength` | `number?` | `131_072` (128 KB) | Maximum calldata size in bytes. Exceeding it fails with `DATA_TOO_LARGE`. | +| `allowedTargets` | `AllowedTarget[]?` | | Allowlist of contracts and methods the waiver may sponsor. When set, a target outside the list fails with `TARGET_NOT_ALLOWED`. | + +### `AllowedTarget` + +| **Field** | **Type** | **Description** | +| :--- | :--- | :--- | +| `address` | `Address \| "*"` | Contract the InnerTx may call, or `"*"` to match any contract. | +| `selectors` | `Hex[]?` | Permitted 4-byte method selectors (e.g. `"0xa9059cbb"` for ERC-20 `transfer`). Omit or leave empty to allow any method on `address`. | + +### `RelayResult` + +```ts +interface RelayResult { + txHash: Hash; // H_inner for waivers, the GuaranteedTx hash for standalone guaranteed block +} +``` + +### `BatchResultItem` + +One entry per input in a batch, in input order. Instead of throwing, a batch surfaces per-item outcomes. + +| **Field** | **Type** | **Description** | +| :--- | :--- | :--- | +| `index` | `number` | Zero-based position matching the input array. | +| `success` | `boolean` | Whether this item was relayed. | +| `txHash` | `Hash?` | Present on success: the inner-tx hash. | +| `error` | `{ code: ErrorCode; message: string }?` | Present on failure. | + +## Errors + +Single-transaction methods (`send`, `relay`) throw on rejection. Batch methods report failures per item in [`BatchResultItem.error`](#batchresultitem) instead. All error classes extend `StableEnterpriseError`. + +| **Class** | **Thrown when** | **Useful fields** | +| :--- | :--- | :--- | +| `StableEnterpriseError` | Base class for every SDK error. | `message` | +| `StableEnterpriseRelayError` | A transaction is rejected at the RPC or relay layer. | `code` | +| `WaiverValidationError` | An InnerTx fails a policy check before broadcast (gas, data size, or target allowlist). | `code` | + +```ts +import { StableEnterpriseRelayError } from "@stablechain/enterprise"; + +try { + await gw.send(user, { to: token, data }); +} catch (err) { + if (err instanceof StableEnterpriseRelayError && err.code === "TARGET_NOT_ALLOWED") { + // the InnerTx target is outside the configured allowlist + } + throw err; +} +``` + +```text +StableEnterpriseRelayError: relay failed [TARGET_NOT_ALLOWED]: target 0x... not allowed +``` + +### `ErrorCode` + +The `code` on a thrown error or a `BatchResultItem.error` is one of: + +| **Code** | **Meaning** | +| :--- | :--- | +| `UNKNOWN_ERROR` | Fallback when no specific code is available. | +| `BROADCAST_FAILED` | Broadcast rejected at the RPC layer, or the gateway failed to relay. | +| `INVALID_TRANSACTION` | The InnerTx failed to decode or parse. | +| `INVALID_SIGNATURE` | Signature verification failed. | +| `UNSUPPORTED_TX_TYPE` | The InnerTx type is not legacy, eip2930, or eip1559. | +| `WRONG_CHAIN_ID` | The InnerTx `chainId` is missing or doesn't match the target chain. | +| `NON_ZERO_GAS_PRICE` | The InnerTx carries a non-zero gas price (must be zero for a waiver). | +| `GAS_LIMIT_EXCEEDED` | The InnerTx gas limit exceeds `maxGasLimit`. | +| `DATA_TOO_LARGE` | The InnerTx calldata exceeds `maxDataLength`. | +| `TARGET_NOT_ALLOWED` | The InnerTx target is not in `allowedTargets`. | +| `GATEWAY_UNAUTHORIZED` | The Enterprise RPC gateway rejected the API key (missing, invalid, or expired). | +| `QUOTA_EXCEEDED` | The Enterprise RPC gateway gas quota is exhausted. | + +## Constants + +| **Constant** | **Type** | **Description** | +| :--- | :--- | :--- | +| `DEFAULT_INNER_GAS` | `bigint` | Default InnerTx gas limit (`150_000n`) when `gas` is omitted. | +| `ENTERPRISE_FLAG` | `bigint` | Enterprise bit set on a lane's `nonceKey`. | +| `ENTERPRISE_MASK` | `bigint` | Upper bound for a lane id: `laneId` must be in `[0, ENTERPRISE_MASK - 1]`. | + +## Next recommended + +- [**Stable Enterprise SDK**](/en/explanation/enterprise-sdk): What the rails are and when to use them. +- [**Gas waiver protocol**](/en/reference/gas-waiver-api): Transaction formats, marker routing, and governance controls. +- [**Guaranteed blockspace**](/en/explanation/guaranteed-blockspace): How Stable reserves block capacity for enterprise workloads. diff --git a/docs/pages/ko/explanation/enterprise-sdk.mdx b/docs/pages/ko/explanation/enterprise-sdk.mdx new file mode 100644 index 0000000..495808d --- /dev/null +++ b/docs/pages/ko/explanation/enterprise-sdk.mdx @@ -0,0 +1,70 @@ +--- +source_path: explanation/enterprise-sdk.mdx +source_sha: a632f3be5fbff966d9da230ec302b4ce4e6773f2 +title: "Stable Enterprise SDK" +description: "유형화된 @stablechain/enterprise SDK를 사용하여 백엔드에서 가스 면제 및 블록 공간 보장 트랜잭션을 릴레이합니다." +diataxis: "explanation" +--- + +# Stable Enterprise SDK + +`@stablechain/enterprise`는 Stable의 엔터프라이즈 트랜잭션 레일을 위한 서버 측 TypeScript 클라이언트입니다. 사용자의 가스를 후원하는 가스 면제 트랜잭션과 예약된 엔터프라이즈 레인에 포함되는 블록 공간 보장 트랜잭션의 두 가지 종류의 특권 트랜잭션을 서명하고 릴레이합니다. 하나의 클라이언트에서 두 레일 중 하나 또는 둘 다를 활성화할 수 있습니다. + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { account: privateKeyToAccount("0xYOUR_WAIVER_KEY") }, +}); + +const { txHash } = await enterprise.gasWaiver.send(user, { to: token, data }); +``` + +```text +txHash: 0x8f3a...2d41 +``` + +호출은 사용자의 트랜잭션 해시인 `H_inner`를 반환하며, 이를 전달한 래퍼 트랜잭션은 반환하지 않습니다. + +## SDK의 기능 + +SDK는 모듈당 하나의 기능을 노출합니다. 각 기능은 선택 사항이며 독립적으로 구성되며, 각각 고유한 서명 계정을 가지고 있으므로 별도의 키를 사용할 수 있습니다. + +- **가스 면제 릴레이** (`gasWaiver`): 가스 면제 트랜잭션을 구축하고 서명하며 릴레이합니다. 화이트리스트에 등록된 면제 계정이 사용자의 제로 가스 트랜잭션을 래핑하여 사용자가 USDT0를 보유하지 않고도 트랜잭션을 할 수 있도록 합니다. [가스 면제](/ko/explanation/gas-waiver)를 참조하십시오. +- **블록 공간 보장** (`guaranteedBlock`): 엔터프라이즈 RPC 게이트웨이를 통해 트랜잭션을 릴레이하여 예약된 엔터프라이즈 레인 블록 공간에 포함되도록 합니다. 가스 면제와 달리 서명자는 자체 가스를 지불합니다. [블록 공간 보장](/ko/explanation/guaranteed-blockspace)을 참조하십시오. +- **보장된 릴레이 트랜잭션** (`guaranteedWaiver`): 두 가지를 모두 결합합니다. 블록 공간 보장을 통해 라우팅된 가스 면제 트랜잭션이므로 사용자는 잔고가 필요 없으며 트랜잭션은 여전히 엔터프라이즈 레인에 포함됩니다. + +모든 기능은 동일한 네 가지 메서드를 제공합니다. SDK가 서명하는 관리 경로의 `send` 및 `sendBatch`, 그리고 사용자가 다른 곳에서 서명하고 서명된 헥스만 전달하는 비관리 경로의 `relay` 및 `relayBatch`입니다. + +## 액세스 + +엔터프라이즈 SDK는 현재 Stable 테스트넷에서만 사용할 수 있습니다. + +두 레일 모두 게이트로 보호됩니다. 가스 면제에는 거버넌스에 등록된 면제 키가 필요하며, 블록 공간 보장에는 엔터프라이즈 RPC 게이트웨이 API 키가 필요합니다. 통합하려면 [Stable에 문의](https://discord.gg/stablexyz)하여 액세스 권한을 얻으십시오. Stable은 필요한 면제 키와 게이트웨이 엔드포인트를 제공합니다. + +## 서버 측 전용 + +:::warning +엔터프라이즈 SDK는 개인 키로 서명하는 상태 비저장 서버 측 라이브러리입니다. 브라우저에서 실행하지 마십시오. 면제 키와 엔터프라이즈 RPC 게이트웨이 URL을 백엔드에 보관하십시오. +::: + +면제 키는 화이트리스트에 등록된 거버넌스 등록 계정입니다. 이를 소유한 누구든지 귀하의 정책에 따라 가스를 후원할 수 있습니다. 엔터프라이즈 RPC 게이트웨이 URL에는 API 키가 포함되어 있습니다. 둘 다 비밀로 취급하십시오. 면제 키를 프로세스 외부에서 유지하려면 AWS KMS와 같은 보관 서명자로 백업하십시오. [서명 키 및 보관](/ko/reference/enterprise-sdk#signing-keys-and-custody)을 참조하십시오. + +## 언제 사용해야 할까요? + +백엔드를 운영하고 사용자의 가스를 후원하거나 자체 트래픽에 대한 포함을 보장하려는 경우 엔터프라이즈 SDK를 사용하십시오. 백엔드 또는 브라우저에서 일상적인 송금, 브리지, 스왑 및 볼트 수익을 위해서는 범용 [Stable SDK](/ko/explanation/sdk-overview)를 대신 사용하십시오. + +## 여기서 시작하세요 + +- [**가스 면제 릴레이**](/ko/how-to/relay-with-gas-waiver): 사용자가 USDT0를 보유하지 않고도 트랙잭션을 할 수 있도록 가스를 후원합니다. +- [**보장된 트랜잭션 보내기**](/ko/how-to/send-guaranteed-transactions): 예약된 엔터프라이즈 레인 블록 공간에 트랜잭션을 포함시킵니다. +- [**보장된 릴레이 트랜잭션**](/ko/how-to/guaranteed-relayed-transactions): 두 레일을 모두 결합합니다: 엔터프라이즈 레인의 가스 면제 트랜잭션. +- [**엔터프라이즈 SDK 참조**](/ko/reference/enterprise-sdk): 모든 모듈, 메서드, 구성 옵션 및 오류 클래스. + +## 다음 권장 사항 + +- [**가스 면제 프로토콜**](/ko/reference/gas-waiver-api): 트랜잭션 형식, 마커 라우팅 및 거버넌스 제어. +- [**Stable SDK**](/ko/explanation/sdk-overview): 송금, 브리징, 스왑 및 수익을 위한 범용 클라이언트. +- [**Stable에 연결**](/ko/reference/connect): 테스트넷의 체인 ID, RPC 엔드포인트 및 익스플로러. diff --git a/docs/pages/ko/how-to/guaranteed-relayed-transactions.mdx b/docs/pages/ko/how-to/guaranteed-relayed-transactions.mdx new file mode 100644 index 0000000..70eba20 --- /dev/null +++ b/docs/pages/ko/how-to/guaranteed-relayed-transactions.mdx @@ -0,0 +1,141 @@ +--- +source_path: how-to/guaranteed-relayed-transactions.mdx +source_sha: 007be0e546cc1251771678ee74523b6a79a2d76d +title: "보장된 중계 트랜잭션" +description: "Enterprise SDK를 사용하여 보장된 블록 공간을 통해 가스 면제 트랜잭션을 중계하여 사용자가 잔액 없이도 Enterprise 레인에 진입할 수 있도록 합니다." +diataxis: "how-to" +--- + +# 보장된 중계 트랜잭션 + +`@stablechain/enterprise`를 사용하여 두 가지 Enterprise 레일을 모두 결합합니다. 보장된 중계 트랜잭션은 [가스 면제 트랜잭션](/ko/how-to/relay-with-gas-waiver)을 [보장된 블록 공간](/ko/how-to/send-guaranteed-transactions)을 통해 라우팅하는 것입니다. 즉, 면제가 가스를 지원하므로 사용자는 잔액이나 수수료 필드가 필요 없으며 트랜잭션은 예약된 Enterprise 레인에 진입합니다. + +`guaranteedWaiver` 모듈은 양측을 모두 처리합니다. 사용자는 내부 `0x3F` CustomTx에 서명하고, 화이트리스트에 등록된 면제 계정은 동일한 Enterprise `nonceKey`를 공유하는 외부 `0x3F` CustomTx로 래핑하고, Enterprise RPC 게이트웨이를 통해 브로드캐스트합니다. 모든 메소드는 사용자의 트랜잭션 해시인 `H_inner`를 반환합니다. + +## 전제 조건 + +- Node.js 20 이상, 그리고 `@stablechain/enterprise` 및 `viem` 설치. [Enterprise SDK 참조](/ko/reference/enterprise-sdk#install)를 참조하세요. +- 거버넌스에 등록된 면제 키와 Enterprise RPC 게이트웨이 API 키. Enterprise SDK는 현재 테스트넷 전용이며 요청 시 액세스가 가능합니다. 둘 다 얻으려면 [Stable에 문의](https://discord.gg/stablexyz)하세요. + +:::warning +이것은 서버 측 라이브러리입니다. 면제 키와 Enterprise RPC 게이트웨이 URL을 백엔드에 보관하세요. 게이트웨이 URL에 API 키가 포함되어 있습니다. +::: + +## 1. 보장된 면제 모듈로 클라이언트 생성 + +`guaranteedWaiver`와 `enterpriseRpcEndpoints`를 `createStableEnterprise`에 전달합니다. 이 모듈은 화이트리스트에 등록된 면제 키(외부 래퍼에 서명)와 Enterprise 레인을 사용합니다. 또한 `gasWaiver`와 동일한 `allowedTargets`, `maxGasLimit`, `maxDataLength` 정책 제한을 허용합니다. + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL], // Stable에서 제공하는 게이트웨이 URL + guaranteedWaiver: { + account: privateKeyToAccount(process.env.WAIVER_PRIVATE_KEY as `0x${string}`), + laneId: 0n, // Enterprise 레인 + }, +}); + +const gw = enterprise.guaranteedWaiver; +``` + +```text +StableEnterpriseClient { gasWaiver: undefined, guaranteedBlock: undefined, guaranteedWaiver } +``` + +:::tip +면제 키를 보관 백엔드에 유지하려면 `account` 대신 `signer`를 전달하세요. `@stablechain/enterprise/aws-kms`의 `awsKmsSigner`는 AWS KMS로 이를 지원합니다. [서명 키 및 보관](/ko/reference/enterprise-sdk#signing-keys-and-custody)을 참조하세요. +::: + +## 2. 단일 트랜잭션 중계 + +사용자의 계정과 변경되는 필드를 사용하여 `send`를 호출합니다. 가스는 면제되므로 사용자는 잔액이나 수수료 필드가 필요 없습니다. 내부 `0x3F` CustomTx, 해당 Enterprise `nonceKey`, 그리고 (게이트웨이에서 발견된) 2D nonce는 자동으로 처리됩니다. + +```ts +const user = privateKeyToAccount(process.env.USER_PRIVATE_KEY as `0x${string}`); + +const { txHash } = await gw.send(user, { to: recipient }); +console.log("H_inner:", txHash); +``` + +```text +H_inner: 0x8f3a...2d41 +``` + +`gas`는 공유된 내부 가스 기본값으로 설정됩니다. 재정의하려면 필요할 때만 전달하세요. 값 전송이 허용되므로 `value`도 전달할 수 있습니다. + +## 3. 배치 중계 + +단일 사용자로부터 여러 트랜잭션을 중계하려면 `sendBatch`를 호출합니다. 내부 nonce는 발견된 기준에서 자동으로 순차적으로 생성되며, 입력 순서대로 입력당 하나의 결과를 얻습니다. 실패는 후속 항목을 중단시킵니다. + +```ts +const results = await gw.sendBatch(user, [{ to: a }, { to: b }]); + +for (const r of results) { + console.log(r.success ? `[${r.index}] ✔ ${r.txHash}` : `[${r.index}] ✖ ${r.error?.code}`); +} +``` + +```text +[0] ✔ 0x8f3a...2d41 +[1] ✔ 0x2b7c...9e04 +``` + +## 4. 사전 서명된 트랜잭션 중계 + +비수탁형 흐름의 경우, 사용자는 자신의 환경에서 내부 `0x3F` CustomTx에 서명하고 서명된 헥스만 전달합니다. Enterprise nonce 키에는 `nonceKeyForLane`을 사용하고 수수료는 0으로(가스 면제) 설정하여 `buildGuaranteedTx`로 빌드합니다. 백엔드는 이를 면제 키로 래핑하고 `relay`로 중계합니다. + +```ts +import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise"; + +// 사용자의 측면 - buildGuaranteedTx는 Signer를 통해 서명합니다. toSigner는 viem 계정을 조정합니다. +const signedInner = await buildGuaranteedTx(toSigner(user), stableTestnet.id, { + to: recipient, + gas: 100_000n, + gasFeeCap: 0n, // 면제 + gasTipCap: 0n, + nonce, // 사용자의 현재 2D-레인 nonce + nonceKey: nonceKeyForLane(0n), // Enterprise 레인 0 +}); + +// 백엔드에서 +const { txHash } = await gw.relay(signedInner); // waiver는 래핑 + 브로드캐스트 → H_inner +console.log("H_inner:", txHash); +``` + +```text +H_inner: 0x8f3a...2d41 +``` + +여러 사전 서명된 트랜잭션의 경우 `relayBatch`를 사용합니다. + +## 거부 처리 + +`send` 및 `relay`는 거부 시 `StableEnterpriseRelayError`를 throw합니다. 이 흐름은 게이트웨이를 통해 라우팅되므로 `GATEWAY_UNAUTHORIZED` 및 `QUOTA_EXCEEDED`와 같은 게이트웨이 코드는 `TARGET_NOT_ALLOWED`와 같은 면제 정책 코드와 함께 적용됩니다. + +```ts +import { StableEnterpriseRelayError } from "@stablechain/enterprise"; + +try { + await gw.send(user, { to: recipient }); +} catch (err) { + if (err instanceof StableEnterpriseRelayError && err.code === "GATEWAY_UNAUTHORIZED") { + // Enterprise RPC 게이트웨이가 API 키를 거부했습니다. + } + throw err; +} +``` + +```text +StableEnterpriseRelayError: relay failed [GATEWAY_UNAUTHORIZED]: invalid api key +``` + +모든 거부 이유에 대한 전체 [`ErrorCode`](/ko/reference/enterprise-sdk#errorcode) 표를 참조하세요. + +## 다음 단계 + +- [**가스 면제로 중계**](/ko/how-to/relay-with-gas-waiver): 보장된 레인 없이 사용자의 가스를 지원합니다. +- [**보장된 트랜잭션 전송**](/ko/how-to/send-guaranteed-transactions): 서명자가 가스를 지불하는 경우, 보장된 블록 공간을 단독으로 사용합니다. +- [**Enterprise SDK 참조**](/ko/reference/enterprise-sdk#guaranteed-relay-transactions): 모든 메소드, 구성 옵션 및 오류 클래스에 대한 전체 설명입니다. diff --git a/docs/pages/ko/how-to/relay-with-gas-waiver.mdx b/docs/pages/ko/how-to/relay-with-gas-waiver.mdx new file mode 100644 index 0000000..8ecef5c --- /dev/null +++ b/docs/pages/ko/how-to/relay-with-gas-waiver.mdx @@ -0,0 +1,157 @@ +--- +source_path: how-to/relay-with-gas-waiver.mdx +source_sha: c1b34aab292d8e9623b59810cd6b668468e834ce +title: "가스 면제 릴레이" +description: "Enterprise SDK로 사용자의 가스를 후원하세요: 제로 가스 트랜잭션을 릴레이하고, 릴레이를 일괄 처리하고, 정책 제한을 적용하고, 사전 서명된 트랜잭션을 릴레이합니다." +diataxis: "how-to" +--- + +# 가스 면제 릴레이 + +`@stablechain/enterprise`로 사용자의 가스를 후원하세요. 화이트리스트에 등록된 면제 계정은 사용자의 제로 가스 트랜잭션(InnerTx)을 래핑하여 브로드캐스트하므로, 사용자는 USDT0를 보유하지 않고도 트랜잭션을 수행할 수 있습니다. `gasWaiver` 모듈은 한 번의 호출로 빌드, 서명 및 릴레이를 수행하므로, 각 작업당 하나의 메서드를 호출합니다. + +모든 메서드는 트랜잭션을 담고 있는 래퍼가 아닌, 사용자 트랜잭션의 해시인 `H_inner`를 반환합니다. + +## 전제 조건 + +- Node.js 20 이상, 그리고 `@stablechain/enterprise`와 `viem`이 설치되어 있어야 합니다. [Enterprise SDK 참조](/ko/reference/enterprise-sdk#install)를 참조하세요. +- 거버넌스에 등록된 면제 키. Enterprise SDK는 현재 테스트넷 전용이며 접근이 제한되어 있습니다: 화이트리스트에 등록된 면제 키를 받으려면 [Stable에 문의](https://discord.gg/stablexyz)하세요. + +:::warning +이것은 개인 키로 서명하는 서버 측 라이브러리입니다. 브라우저에서 실행하지 마십시오. 면제 키는 백엔드에 보관하십시오. +::: + +## 1. 가스 면제 모듈로 클라이언트 생성 + +`gasWaiver: { account }`를 `createStableEnterprise`에 전달합니다. 여기서 `account`는 화이트리스트에 등록된 면제 키입니다. 모듈은 클라이언트에 구성해야만 존재합니다. + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { account: privateKeyToAccount(process.env.WAIVER_PRIVATE_KEY as `0x${string}`) }, +}); + +const gw = enterprise.gasWaiver; +``` + +```text +StableEnterpriseClient { gasWaiver, guaranteedBlock: undefined, guaranteedWaiver: undefined } +``` + +`rpcEndpoints` 옵션은 선택 사항입니다. 설정하지 않으면 클라이언트는 체인의 내장 RPC를 사용합니다. + +:::tip +면제 키를 보관 백엔드에 보관하려면 `account` 대신 `signer`를 전달하세요. `@stablechain/enterprise/aws-kms`의 `awsKmsSigner`는 AWS KMS로 이를 지원합니다. [서명 키 및 보관](/ko/reference/enterprise-sdk#signing-keys-and-custody)을 참조하세요. +::: + +## 2. 단일 트랜잭션 릴레이 + +사용자 계정 및 변경되는 필드와 함께 `send`를 호출합니다. `gasPrice: 0`, 레거시 유형, `chainId`, 보류 중인 논스가 자동으로 처리되므로, `to`만 전달하고 선택적으로 `data`, `value`, `gas`를 전달하면 됩니다. + +```ts +const user = privateKeyToAccount(process.env.USER_PRIVATE_KEY as `0x${string}`); + +const { txHash } = await gw.send(user, { to: token, data, gas: 150_000n }); +console.log("H_inner:", txHash); +``` + +```text +H_inner: 0x8f3a...2d41 +``` + +사용자는 USDT0가 필요하지 않습니다: 면제 계정이 가스를 후원합니다. `gas`는 토큰 전송 또는 승인을 포함하는 `150_000n` (`DEFAULT_INNER_GAS`)으로 기본 설정됩니다. 더 많은 가스가 필요한 호출의 경우 명시적인 `gas`를 전달하세요. + +## 3. 배치 릴레이 + +단일 계정에서 여러 트랜잭션을 릴레이하려면 `sendBatch`를 호출합니다. 논스는 계정의 보류 중인 논스에서 자동으로 순서가 지정되며, 입력 순서대로 각 입력당 하나의 결과를 얻습니다. + +```ts +const results = await gw.sendBatch(user, [ + { to: token, data: dataA, gas: 150_000n }, + { to: token, data: dataB, gas: 150_000n }, +]); + +for (const r of results) { + console.log(r.success ? `[${r.index}] ✔ ${r.txHash}` : `[${r.index}] ✖ ${r.error?.code}`); +} +``` + +```text +[0] ✔ 0x8f3a...2d41 +[1] ✔ 0x2b7c...9e04 +``` + +배치는 실패를 throw하는 대신 `result.error`에 항목별로 보고하므로, 하나의 잘못된 트랜잭션으로 인해 나머지가 모두 실패하지 않습니다. + +## 4. 파트너별 정책 제한 적용 + +설정에 정책 제한을 추가하여 면제 키가 후원할 수 있는 대상을 제한합니다. 제한을 위반하는 InnerTx는 방송 전에 거부됩니다. + +```ts +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { + account: privateKeyToAccount(process.env.WAIVER_PRIVATE_KEY as `0x${string}`), + maxGasLimit: 500_000n, + maxDataLength: 4_096, + allowedTargets: [ + { address: token, selectors: ["0xa9059cbb"] }, // ERC-20 전송만 허용 + ], + }, +}); +``` + +`allowedTargets` 외부에 있는 계약으로의 트랜잭션은 `TARGET_NOT_ALLOWED` 오류와 함께 실패하며, `maxGasLimit`을 초과하는 트랜잭션은 `GAS_LIMIT_EXCEEDED` 오류와 함께 실패합니다. `address`로 `"*"`를 사용하여 모든 계약을 허용하고, `selectors`를 생략하여 모든 메서드를 허용할 수 있습니다. + +## 5. 사전 서명된 트랜잭션 릴레이 + +비수탁형 흐름의 경우, 사용자는 자신의 환경에서 InnerTx를 서명하고 서명된 헥스만 전달하므로, 사용자의 키를 볼 필요가 없습니다. `buildWaiverInnerTx`로 빌드한 다음, `relay`로 릴레이합니다. + +```ts +import { buildWaiverInnerTx, toSigner } from "@stablechain/enterprise"; + +// 사용자 측에서 — buildWaiverInnerTx는 Signer를 통해 서명하며; toSigner는 viem 계정을 적용합니다. +const signed = await buildWaiverInnerTx(toSigner(user), stableTestnet.id, { to: token, data, gas: 150_000n, nonce }); + +// 백엔드에서 +const { txHash } = await gw.relay(signed); +console.log("H_inner:", txHash); +``` + +```text +H_inner: 0x8f3a...2d41 +``` + +여러 개의 사전 서명된 트랜잭션의 경우, `relayBatch`를 사용하세요. 이는 `sendBatch`와 같이 입력당 하나의 결과를 반환합니다. + +## 거부 처리 + +`send` 및 `relay`는 거부 시 `StableEnterpriseRelayError`를 throw하며, 분기할 수 있는 `code`를 포함합니다. + +```ts +import { StableEnterpriseRelayError } from "@stablechain/enterprise"; + +try { + await gw.send(user, { to: token, data }); +} catch (err) { + if (err instanceof StableEnterpriseRelayError && err.code === "TARGET_NOT_ALLOWED") { + // InnerTx 대상이 구성된 허용 목록 외부에 있습니다. + } + throw err; +} +``` + +```text +StableEnterpriseRelayError: relay failed [TARGET_NOT_ALLOWED]: target 0x... not allowed +``` + +모든 거부 이유에 대한 전체 [`ErrorCode`](/ko/reference/enterprise-sdk#errorcode) 테이블을 참조하세요. + +## 다음 단계 + +- [**보장된 트랜잭션 전송**](/ko/how-to/send-guaranteed-transactions): 예약된 엔터프라이즈 레인 블록 공간에 트랜잭션을 전송하고, 이를 가스 면제와 결합합니다. +- [**Enterprise SDK 참조**](/ko/reference/enterprise-sdk#relay-with-gas-waiver): 모든 메서드, 구성 옵션 및 오류 클래스의 전체 목록입니다. +- [**가스 면제**](/ko/explanation/gas-waiver): 프로토콜 수준에서 제로 가스 트랜잭션이 작동하는 방식. diff --git a/docs/pages/ko/how-to/send-guaranteed-transactions.mdx b/docs/pages/ko/how-to/send-guaranteed-transactions.mdx new file mode 100644 index 0000000..4ae6f08 --- /dev/null +++ b/docs/pages/ko/how-to/send-guaranteed-transactions.mdx @@ -0,0 +1,151 @@ +--- +source_path: how-to/send-guaranteed-transactions.mdx +source_sha: 6da305c74e7ddb43f31a4ed11e7f4d5313f0cabe +title: "보장된 트랜잭션 전송" +description: "Enterprise SDK를 사용하여 예약된 Enterprise 레인 블록스페이스에 트랜잭션을 전송하고, 보장된 블록스페이스와 가스 면제를 결합하여 가스 없이 트랜잭션을 릴레이합니다." +diataxis: "how-to" +--- + +# 보장된 트랜잭션 전송 + +`@stablechain/enterprise`를 사용하여 예약된 Enterprise 레인 블록스페이스를 통해 트랜잭션을 라우팅하세요. `guaranteedBlock` 모듈은 GuaranteedTx (0x3F 유형 CustomTx)를 Enterprise RPC 게이트웨이를 통해 릴레이하여 기업 워크로드용으로 예약된 용량에 들어가게 합니다. 가스 면제와 달리 서명자는 자체 가스를 지불해야 하므로 자금이 필요합니다. + +이를 가스 면제와 결합하여 [보장된 릴레이 트랜잭션](/ko/how-to/guaranteed-relayed-transactions)을 얻을 수 있습니다. 이는 Enterprise 레인에 들어가는 가스 면제 트랜잭션입니다. + +## 전제 조건 + +- Node.js 20 이상, 그리고 `@stablechain/enterprise` 및 `viem`이 설치되어 있어야 합니다. [Enterprise SDK 참조](/ko/reference/enterprise-sdk#install)를 확인하세요. +- Enterprise RPC 게이트웨이 API 키. Enterprise SDK는 현재 테스트넷 전용이며 접근이 제한되어 있습니다. 게이트웨이 엔드포인트를 얻으려면 [Stable에 문의하세요](https://discord.gg/stablexyz). +- GuaranteedTx는 자체 가스를 지불하므로 자금이 충분한 서명자가 필요합니다. + +:::warning +이것은 서버 측 라이브러리입니다. Enterprise RPC 게이트웨이 URL을 백엔드에 유지하세요. API 키가 포함되어 있습니다. +::: + +## 1. guaranteed blockspace 모듈로 클라이언트 생성 + +`guaranteedBlock` 및 `enterpriseRpcEndpoints`를 `createStableEnterprise`에 전달합니다. 게이트웨이는 `0x3F` GuaranteedTx를 허용하는 유일한 엔드포인트이며, API 키를 보유합니다. 유효하지 않은 `laneId`는 즉시 거부됩니다. + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL], // Stable이 제공하는 게이트웨이 URL + guaranteedBlock: { + account: privateKeyToAccount(process.env.SIGNER_PRIVATE_KEY as `0x${string}`), // 자금이 충분해야 함 + laneId: 0n, // Enterprise 레인 + }, +}); + +const gb = enterprise.guaranteedBlock; +``` + +```text +StableEnterpriseClient { gasWaiver: undefined, guaranteedBlock, guaranteedWaiver: undefined } +``` + +## 2. 단일 트랜잭션 전송 + +서명자와 다양한 필드를 사용하여 `send`를 호출합니다. 서명자가 가스를 지불하므로 1559 수수료 필드가 필요합니다. `chainId`, Enterprise `nonceKey`, 그리고 2D 논스(게이트웨이에서 발견됨)는 자동으로 처리됩니다. + +```ts +import { createPublicClient, http } from "viem"; + +const publicClient = createPublicClient({ chain: stableTestnet, transport: http() }); +const gasPrice = await publicClient.getGasPrice(); + +const { txHash } = await gb.send(signer, { + to: recipient, + gas: 21_000n, + gasFeeCap: gasPrice * 2n, + gasTipCap: gasPrice, +}); +console.log("Guaranteed tx:", txHash); +``` + +```text +Guaranteed tx: 0xabcd...7890 +``` + +`gasFeeCap`은 가스당 최대 총 수수료(EIP-1559 `maxFeePerGas`)이며, `gasTipCap`은 가스당 최대 우선순위 수수료(`maxPriorityFeePerGas`)입니다. 위와 같이 `gasTipCap`을 현재 가스 가격으로 설정하고 `gasFeeCap`을 그 두 배로 설정하는 것이 안전한 기본값입니다. + +서명자가 가스를 지불하므로 릴레이하기 전에 잔액이 있는지 확인하십시오. 잔액이 없는 계정에서 GuaranteedTx를 보내면 실패합니다. + +## 3. 일괄 전송 + +여러 트랜잭션을 보내려면 `sendBatch`를 호출합니다. 논스는 발견된 기본값에서 자동으로 순서가 지정되며, 입력 순서대로 입력당 하나의 결과가 나옵니다. 하나의 실패는 후속 트랜잭션에 영향을 미칩니다. + +```ts +const results = await gb.sendBatch(signer, [ + { to: a, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice }, + { to: b, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice }, +]); + +for (const r of results) { + console.log(r.success ? `[${r.index}] ✔ ${r.txHash}` : `[${r.index}] ✖ ${r.error?.code}`); +} +``` + +```text +[0] ✔ 0xabcd...7890 +[1] ✔ 0x1f2e...66a1 +``` + +## 4. 사전 서명된 트랜잭션 전송 + +비수탁형 흐름의 경우 `buildGuaranteedTx`를 사용하여 다른 곳에서 GuaranteedTx를 빌드하고 서명한 다음, 운영자에게 서명된 hex만 전달합니다. Enterprise 논스 키는 `nonceKeyForLane`에서 가져옵니다. + +```ts +import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise"; + +const signed = await buildGuaranteedTx(toSigner(signer), stableTestnet.id, { + to: recipient, + gas: 21_000n, + gasFeeCap, + gasTipCap, + nonce, // 계정의 현재 2D-레인 논스 + nonceKey: nonceKeyForLane(0n), // Enterprise 레인 0 +}); + +const { txHash } = await gb.relay(signed); +console.log("Guaranteed tx:", txHash); +``` + +```text +Guaranteed tx: 0xabcd...7890 +``` + +사전 서명된 여러 트랜잭션의 경우 `relayBatch`를 사용합니다. + +## 가스 면제와 결합 + +사용자의 가스를 면제하고 여전히 Enterprise 레인에 들어가려면 `guaranteedWaiver` 모듈로 두 레일을 구성합니다. 사용자는 잔액이나 수수료 필드가 필요 없으며 트랜잭션은 동일한 게이트웨이를 통해 라우팅됩니다. [보장된 릴레이 트랜잭션](/ko/how-to/guaranteed-relayed-transactions)을 참조하세요. + +## 거부 처리 + +`send` 및 `relay`는 거부 시 `StableEnterpriseRelayError`를 발생시킵니다. 게이트웨이별 코드에는 `GATEWAY_UNAUTHORIZED`(누락되거나 유효하지 않은 API 키) 및 `QUOTA_EXCEEDED`(게이트웨이 가스 할당량 소진)가 있습니다. + +```ts +import { StableEnterpriseRelayError } from "@stablechain/enterprise"; + +try { + await gb.send(signer, { to: recipient, gas: 21_000n, gasFeeCap, gasTipCap }); +} catch (err) { + if (err instanceof StableEnterpriseRelayError && err.code === "GATEWAY_UNAUTHORIZED") { + // Enterprise RPC 게이트웨이가 API 키를 거부했습니다. + } + throw err; +} +``` + +```text +StableEnterpriseRelayError: relay failed [GATEWAY_UNAUTHORIZED]: invalid api key +``` + +## 다음 단계 + +- [**보장된 릴레이 트랜잭션**](/ko/how-to/guaranteed-relayed-transactions): 한 번의 호출로 사용자의 가스를 면제하고 Enterprise 레인에 진입합니다. +- [**가스 면제로 릴레이**](/ko/how-to/relay-with-gas-waiver): 사용자의 가스를 후원하여 USDT0 없이 트랜잭션을 처리하게 합니다. +- [**Enterprise SDK 참조**](/ko/reference/enterprise-sdk#guaranteed-blockspace): 모든 메서드, 구성 옵션 및 오류 클래스에 대한 전체 설명입니다. diff --git a/docs/pages/ko/reference/enterprise-sdk.mdx b/docs/pages/ko/reference/enterprise-sdk.mdx new file mode 100644 index 0000000..c0be8c3 --- /dev/null +++ b/docs/pages/ko/reference/enterprise-sdk.mdx @@ -0,0 +1,530 @@ +--- +source_path: reference/enterprise-sdk.mdx +source_sha: d0bc44dc34b19b1a7f7cadeda2ceaa68d296220a +title: "엔터프라이즈 SDK 레퍼런스" +description: "@stablechain/enterprise에 대한 완벽한 레퍼런스: createStableEnterprise, 가스 면제 및 보장된 블록스페이스 모듈, 빌드 헬퍼, 그리고 오류 클래스." +diataxis: "reference" +--- + +# 엔터프라이즈 SDK 레퍼런스 + +`@stablechain/enterprise`의 완전한 표면입니다. 여기에는 [`createStableEnterprise`](#createstableenterpriseconfig)로부터의 클라이언트, 세 가지 기능([가스 면제 릴레이](#relay-with-gas-waiver), [보장된 블록스페이스](#guaranteed-blockspace), [보장된 릴레이 트랜잭션](#guaranteed-relay-transactions)), 하위 수준 빌드 헬퍼, 그리고 공유 결과 및 오류 유형이 포함됩니다. 이러한 레일이 무엇인지는 [Stable 엔터프라이즈 SDK](/ko/explanation/enterprise-sdk), [가스 면제](/ko/explanation/gas-waiver), 그리고 [보장된 블록스페이스](/ko/explanation/guaranteed-blockspace)를 참조하십시오. + +:::note +엔터프라이즈 SDK는 현재 Stable 테스트넷에서만 사용할 수 있으며, 액세스는 요청 시에 제공됩니다. 통합하려면 [Stable에 문의](https://discord.gg/stablexyz)하여 거버넌스에 등록된 면제 키와 엔터프라이즈 RPC 게이트웨이 API 키를 받으세요. +::: + +:::warning +이것은 개인 키로 서명하는 서버 측 라이브러리입니다. 브라우저에서 절대 실행하지 마세요. 면제 키와 엔터프라이즈 RPC 게이트웨이 URL은 백엔드에 보관하십시오. +::: + +## 설치 + +```bash +npm install @stablechain/enterprise viem +``` + +```text +added 2 packages, audited 3 packages in 2s +``` + +`viem >= 2.0.0`은 피어 종속성입니다. 이 패키지는 `viem/chains`에서 `stable` 및 `stableTestnet`을 다시 내보내므로 별도로 가져올 필요가 없습니다. + +## `createStableEnterprise(config)` + +`StableEnterpriseClient`를 생성합니다. 각 모듈은 구성할 때만 존재하므로 사용하기 전에 널 체크를 하세요. + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { privateKeyToAccount } from "viem/accounts"; + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { account: privateKeyToAccount("0xYOUR_WAIVER_KEY") }, +}); +``` + +```text +StableEnterpriseClient { gasWaiver, guaranteedBlock: undefined, guaranteedWaiver: undefined } +``` + +### `StableEnterpriseConfig` + +| **필드** | **유형** | **기본값** | **설명** | +| :--- | :--- | :--- | :--- | +| `chain` | `StableChain` | | 대상 체인. `stable` 또는 `stableTestnet`을 전달합니다(패키지에서 다시 내보냄). 필수. | +| `rpcEndpoints` | `string[]?` | 체인의 내장 RPC | 하나 이상의 Stable RPC 엔드포인트이며, 실패 시 순서대로 시도됩니다. 개인 엔드포인트를 가리키도록만 오버라이드합니다. | +| `enterpriseRpcEndpoints` | `string[]?` | | 하나 이상의 엔터프라이즈 RPC 게이트웨이 엔드포인트이며, 실패 시 순서대로 시도됩니다. `guaranteedBlock` 및 `guaranteedWaiver`에 필요합니다. | +| `batchSizeLimit` | `number?` | `100` | 배치된 RPC 호출당 최대 트랜잭션 수. | +| `signer` | `Signer?` | | 모듈 자체에 키가 없을 때 면제 모듈(`gasWaiver`, `guaranteedWaiver`)의 기본 서명자입니다. 단일 커스터디 백엔드에서 둘 다 구동하려면 한 번 설정합니다. [서명 키 및 커스터디](#signing-keys-and-custody)를 참조하십시오. | +| `gasWaiver` | `GasWaiverConfig?` | | [가스 면제 릴레이](#relay-with-gas-waiver)를 활성화합니다. | +| `guaranteedBlock` | `GuaranteedBlockConfig?` | | [보장된 블록스페이스](#guaranteed-blockspace)를 활성화합니다. | +| `guaranteedWaiver` | `GuaranteedWaiverConfig?` | | [보장된 릴레이 트랜잭션](#guaranteed-relay-transactions)을 활성화합니다. | + +### 서명 키 및 커스터디 + +면제 키를 보유하는 모듈(`gasWaiver` 및 `guaranteedWaiver`)은 순서대로 이를 해결합니다: 모듈 자체의 `signer`, 그 다음 해당 `account`(인 프로세스 viem 키), 그 다음 클라이언트 구성의 최상위 [`signer`](#stableenterpriseconfig), 그 다음 `STABLE_ENTERPRISE_PRIVATE_KEY` 환경 변수. 단일 커스터디 백엔드에서 두 면제 모듈을 모두 구동하려면 최상위 `signer`를 한 번 설정합니다. `guaranteedBlock`은 자체 자금 조달된 `account`로 서명합니다. + +`Signer`는 32바이트 다이제스트에 서명하므로, 키는 커스터디 백엔드를 떠나지 않습니다. AWS KMS의 경우 `awsKmsSigner`를 사용하고, viem 계정을 적용하기 위해 `toSigner(account)`를 사용하거나, 인 프로세스 키의 경우 `privateKeySigner(key)` / `envSigner()`를 사용합니다. 모듈의 `send` / `sendBatch`에 전달되는 호출당 발신자도 viem 계정 또는 `Signer`일 수 있으므로, KMS/HSM 키는 `relay`로 떨어뜨리지 않고 내부 트랜잭션에 서명할 수 있습니다. + +```bash +npm install @aws-sdk/client-kms +``` + +```ts +import { createStableEnterprise, stableTestnet } from "@stablechain/enterprise"; +import { awsKmsSigner } from "@stablechain/enterprise/aws-kms"; + +// KMS 키는 ECC_SECG_P256K1(secp256k1)이어야 합니다. SDK는 그로부터 주소를 파생합니다. +const signer = await awsKmsSigner({ keyId: process.env.AWS_KMS_KEY_ID }); + +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { signer }, // 'account' 대신 커스터디 등급의 면제 키 +}); +``` + +`awsKmsSigner`는 `@stablechain/enterprise/aws-kms` 하위 경로에서 제공되므로 `@aws-sdk/client-kms`는 코어 설치에서 제외되는 선택적 피어 종속성으로 유지됩니다. + +### `StableEnterpriseClient` + +```ts +interface StableEnterpriseClient { + gasWaiver?: GasWaiverClient; + guaranteedBlock?: GuaranteedBlockClient; + guaranteedWaiver?: GuaranteedWaiverClient; +} +``` + +## 가스 면제 릴레이 + +`gasWaiver` 모듈은 가스 면제 트랜잭션을 릴레이합니다. 화이트리스트된 면제 계정은 사용자의 제로 가스 트랜잭션(InnerTx)을 WaiverTx로 래핑하고 이를 브로드캐스트합니다. 사용자는 USDT0가 필요하지 않습니다. 모든 메서드는 래퍼 해시가 아닌 InnerTx 해시인 `H_inner`를 반환합니다. + +구성에서 `gasWaiver`로 활성화합니다. + +```ts +const enterprise = createStableEnterprise({ + chain: stableTestnet, + gasWaiver: { + account: privateKeyToAccount("0xYOUR_WAIVER_KEY"), + // 선택적 파트너별 정책 제한 + maxGasLimit: 500_000n, + allowedTargets: [{ address: "0xToken", selectors: ["0xa9059cbb"] }], + }, +}); + +const gw = enterprise.gasWaiver; +``` + +### `GasWaiverConfig` + +[`ValidationLimits`](#validationlimits) 및 [`SignerSource`](#signersource)를 확장합니다. + +| **필드** | **유형** | **설명** | +| :--- | :--- | :--- | +| `signer` | `Signer?` | 커스터디 서명자(AWS KMS, HSM)로 화이트리스트에 등록된 거버넌스 등록 면제 키. [서명 키 및 커스터디](#signing-keys-and-custody)를 참조하십시오. `account`보다 우선합니다. | +| `account` | `LocalAccount?` | 인 프로세스 viem 계정으로서의 면제 키. 둘 다 설정되지 않은 경우 `STABLE_ENTERPRISE_PRIVATE_KEY`로 폴백됩니다. | +| `maxGasLimit` | `bigint?` | `ValidationLimits`에서 상속됨. | +| `maxDataLength` | `number?` | `ValidationLimits`에서 상속됨. | +| `allowedTargets` | `AllowedTarget[]?` | `ValidationLimits`에서 상속됨. | + +### `send(account, tx)` + +`account`로부터 하나의 InnerTx를 한 번의 호출로 빌드, 서명 및 릴레이합니다. `gasPrice: 0`, 레거시 유형, `chainId`, 그리고 보류 중인 nonce는 자동으로 처리됩니다. 거부 시 `StableEnterpriseRelayError`를 throw 합니다. + +```ts +const { txHash } = await gw.send(user, { to: token, data, gas: 150_000n }); +``` + +```text +{ txHash: "0x8f3a...2d41" } +``` + +`tx` 인수는 [`WaiverInnerTx`](#waiverinnertx)와 선택적 `nonce`입니다. [`RelayResult`](#relayresult)를 반환합니다. + +### `sendBatch(account, txs)` + +하나의 `account`에서 여러 InnerTx를 빌드, 서명 및 릴레이합니다. nonce는 계정의 보류 중인 nonce를 기준으로 자동 시퀀싱됩니다. 입력 순서대로 각 입력에 대한 결과를 하나씩 반환합니다. + +```ts +const results = await gw.sendBatch(user, [ + { to: token, data: dataA, gas: 150_000n }, + { to: token, data: dataB, gas: 150_000n }, +]); +``` + +```text +[ { index: 0, success: true, txHash: "0x..." }, { index: 1, success: true, txHash: "0x..." } ] +``` + +`txs`는 [`WaiverInnerTx`](#waiverinnertx)의 읽기 전용 배열입니다. [`BatchResultItem[]`](#batchresultitem)를 반환합니다. + +### `relay(signedInnerTxHex)` + +미리 서명된 제로 가스 InnerTx를 릴레이합니다. 사용자가 자신의 환경에서 서명하고 서명된 헥스만 전달하는 비관리형 흐름에 사용하므로, 면제 운영자는 사용자의 키를 보지 않습니다. [`buildWaiverInnerTx`](#buildwaiverinnertxsigner-chainid-req)로 빌드합니다. 거부 시 예외를 발생시킵니다. + +```ts +import { buildWaiverInnerTx, toSigner } from "@stablechain/enterprise"; + +const signed = await buildWaiverInnerTx(toSigner(user), stableTestnet.id, { to, data, gas: 150_000n, nonce }); +const { txHash } = await gw.relay(signed); +``` + +```text +{ txHash: "0x8f3a...2d41" } +``` + +### `relayBatch(signedInnerTxHexes)` + +미리 서명된 InnerTx의 배치를 릴레이합니다. 입력 순서대로 각 입력에 대한 [`BatchResultItem`](#batchresultitem)을 하나씩 반환합니다. + +```ts +const results = await gw.relayBatch([signed0, signed1]); +``` + +```text +[ { index: 0, success: true, txHash: "0x..." }, { index: 1, success: false, error: { code: "TARGET_NOT_ALLOWED", message: "..." } } ] +``` + +## 보장된 블록스페이스 + +`guaranteedBlock` 모듈은 엔터프라이즈 RPC 게이트웨이를 통해 GuaranteedTx(유형 `0x3F` CustomTx)를 릴레이하여 예약된 엔터프라이즈 레인 블록스페이스에 도달하도록 합니다. 가스 면제와 달리, 서명자는 자신의 가스를 지불하며 자금이 있어야 합니다. 각 트랜잭션은 엔터프라이즈 레인에 키가 지정된 2D 논스를 포함하며, 브로드캐스팅은 게이트웨이를 통해서만 이루어집니다. + +`guaranteedBlock`과 `enterpriseRpcEndpoints`로 활성화합니다. + +```ts +const enterprise = createStableEnterprise({ + chain: stableTestnet, + enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL], // Stable에서 제공하는 게이트웨이 URL + guaranteedBlock: { + account: privateKeyToAccount("0xFUNDED_SIGNER_KEY"), + laneId: 0n, + }, +}); + +const gb = enterprise.guaranteedBlock; +``` + +### `GuaranteedBlockConfig` + +| **필드** | **유형** | **설명** | +| :--- | :--- | :--- | +| `account` | `LocalAccount` | 각 GuaranteedTx에 서명하고 가스를 지불하는 자금이 있는 계정. | +| `laneId` | `bigint` | 엔터프라이즈 레인 ID. `[0, ENTERPRISE_MASK - 1]` 범위 내에 있어야 합니다. 유효하지 않은 ID는 즉시 거부됩니다. | + +### `send(account, tx)` + +하나의 GuaranteedTx를 빌드, 서명 및 릴레이합니다. 서명자가 가스를 지불하므로 1559 수수료 필드가 필요합니다. `chainId`, 엔터프라이즈 `nonceKey`, 그리고 게이트웨이에서 검색된 2D nonce는 자동으로 처리됩니다. + +```ts +const gasPrice = await publicClient.getGasPrice(); + +const { txHash } = await gb.send(signer, { + to: recipient, + gas: 21_000n, + gasFeeCap: gasPrice * 2n, + gasTipCap: gasPrice, +}); +``` + +```text +{ txHash: "0xabcd...7890" } +``` + +`tx` 인수는 [`GuaranteedTxRequest`](#guaranteedtxrequest)에 선택적 `nonce`가 추가된 것입니다. [`RelayResult`](#relayresult)를 반환합니다. + +### `sendBatch(account, txs)` + +여러 GuaranteedTx를 빌드, 서명 및 릴레이합니다. 논스는 검색된 기본값에서 자동으로 시퀀싱됩니다. 실패는 후속 작업에 영향을 미칩니다. + +```ts +const results = await gb.sendBatch(signer, [ + { to: a, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice }, + { to: b, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice }, +]); +``` + +```text +[ { index: 0, success: true, txHash: "0x..." }, { index: 1, success: true, txHash: "0x..." } ] +``` + +[`BatchResultItem[]`](#batchresultitem)를 반환합니다. + +### `relay(signedTx)` / `relayBatch(signedTxs)` + +미리 서명된 GuaranteedTx 또는 배치로 릴레이합니다. 엔터프라이즈 nonce 키에는 [`nonceKeyForLane`](#noncekeyforlanelaneid)을 사용하여 [`buildGuaranteedTx`](#buildguaranteedtxsigner-chainid-req)로 트랜잭션을 다른 곳에서 빌드하고 서명한 다음, 서명된 헥스만 운영자에게 전달합니다. + +```ts +import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise"; + +const signed = await buildGuaranteedTx(toSigner(signer), stableTestnet.id, { + to, + gas: 21_000n, + gasFeeCap, + gasTipCap, + nonce, // 계정의 현재 2D 레인 nonce + nonceKey: nonceKeyForLane(0n), +}); +const { txHash } = await gb.relay(signed); +``` + +```text +{ txHash: "0xabcd...7890" } +``` + +## 보장된 릴레이 트랜잭션 + +`guaranteedWaiver` 모듈은 위 두 가지 레일을 결합합니다: 보장된 블록스페이스를 통해 라우팅되는 가스 면제 트랜잭션. 내부 및 외부 트랜잭션 모두 하나의 엔터프라이즈 `nonceKey`를 공유하는 `0x3F` CustomTx이므로, `guaranteedBlock`처럼 게이트웨이를 통해 브로드캐스트됩니다. 면제는 가스를 지원하므로, `gasWaiver`처럼 사용자는 잔액이나 수수료 필드가 필요하지 않습니다. 모든 메서드는 `H_inner`를 반환합니다. + +`guaranteedWaiver`와 `enterpriseRpcEndpoints`로 활성화합니다. + +```ts +const enterprise = createStableEnterprise({ + chain: stableTestnet, + enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL], // Stable에서 제공하는 게이트웨이 URL + guaranteedWaiver: { + account: privateKeyToAccount("0xYOUR_WAIVER_KEY"), + laneId: 0n, + }, +}); + +const gw = enterprise.guaranteedWaiver; +``` + +### `GuaranteedWaiverConfig` + +[`ValidationLimits`](#validationlimits) 및 [`SignerSource`](#signersource)를 확장합니다. 이는 `GasWaiverConfig`와 동일하게 외부 래퍼 면제 키를 소싱하며(`signer`, 그 다음 `account`, 그 다음 env 변수), 동일한 `maxGasLimit`, `maxDataLength`, `allowedTargets` 정책 컨트롤을 허용합니다. + +| **필드** | **유형** | **설명** | +| :--- | :--- | :--- | +| `signer` | `Signer?` | 커스터디 서명자로서의 화이트리스트된 면제 키(외부 래퍼에 서명). `account`보다 우선합니다. [서명 키 및 커스터디](#signing-keys-and-custody)를 참조하십시오. | +| `account` | `LocalAccount?` | 인 프로세스 viem 계정으로서의 면제 키. 둘 다 설정되지 않은 경우 `STABLE_ENTERPRISE_PRIVATE_KEY`로 폴백됩니다. | +| `laneId` | `bigint` | 엔터프라이즈 레인 ID. `[0, ENTERPRISE_MASK - 1]` 범위 내에 있어야 합니다. | + +### `send(user, tx)` / `sendBatch(user, txs)` + +`user`로부터 내부 `0x3F` CustomTx를 빌드하고 서명한 다음, 면제 키로 래핑하고 릴레이합니다. 가스가 면제되므로 수수료 필드는 필요하지 않습니다. 내부 2D 논스는 게이트웨이에서 검색되며, 배치는 해당 베이스에서 자동으로 시퀀싱됩니다. + +```ts +// 단일 +const { txHash } = await gw.send(user, { to: recipient }); + +// 배치 +const results = await gw.sendBatch(user, [{ to: a }, { to: b }]); +``` + +```text +{ txHash: "0x8f3a...2d41" } +``` + +`tx` 인수는 [`GuaranteedWaiverTxRequest`](#guaranteedwaivertxrequest)에 선택적 `nonce`가 추가된 것입니다. `send`는 [`RelayResult`](#relayresult)를 반환하고, `sendBatch`는 [`BatchResultItem[]`](#batchresultitem)를 반환합니다. + +### `relay(signedInnerTx)` / `relayBatch(signedInnerTxs)` + +비관리형 흐름의 경우, 사용자는 [`buildGuaranteedTx`](#buildguaranteedtxsigner-chainid-req)를 사용하여 내부 `0x3F` CustomTx에 서명하고(수수료 `0n`, `nonceKeyForLane(laneId)` 사용), 서명된 헥스만 운영자에게 전달합니다. 운영자는 이를 면제 키로 래핑하고 릴레이합니다. + +```ts +import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise"; + +const signedInner = await buildGuaranteedTx(toSigner(user), stableTestnet.id, { + to, + gas: 100_000n, + gasFeeCap: 0n, // 면제됨 + gasTipCap: 0n, + nonce, + nonceKey: nonceKeyForLane(0n), +}); +const { txHash } = await gw.relay(signedInner); // 면제 래핑 + 브로드캐스트 → H_inner +``` + +```text +{ txHash: "0x8f3a...2d41" } +``` + +## 빌드 헬퍼 + +비관리형 `relay` 경로를 위한 하위 수준 서명자입니다. 각 서명자는 서명된 트랜잭션을 `Hex`로 반환하며, 논스 가져오기 또는 수수료 추정은 없습니다. 첫 번째 인수는 [`Signer`](#signing-keys-and-custody)입니다: `toSigner(account)`로 viem 계정을 래핑하거나 `awsKmsSigner`와 같은 커스터디 서명자를 전달합니다. + +### `buildWaiverInnerTx(signer, chainId, req)` + +면제 준비된 InnerTx에 서명합니다. 면제 불변값이 내장되어 있습니다: `gasPrice: 0`, 레거시 유형, 그리고 주어진 `chainId`. `req`는 필수 `nonce`가 포함된 [`WaiverInnerTx`](#waiverinnertx)입니다. + +```ts +const signed = await buildWaiverInnerTx(toSigner(user), stableTestnet.id, { to, data, gas: 150_000n, nonce }); +``` + +### `buildGuaranteedTx(signer, chainId, req)` + +GuaranteedTx(`0x3F` CustomTx)를 빌드하고 서명합니다. `req`는 필수 `nonce` 및 `nonceKey`가 포함된 [`GuaranteedTxRequest`](#guaranteedtxrequest)입니다. + +### `buildGuaranteedWaiverTx(...)` + +미리 서명된 내부 트랜잭션을 외부 `0x3F` 면제 CustomTx로 래핑합니다. `guaranteedWaiver.relay`에서 내부적으로 사용됩니다. 고급 흐름을 위해 내보내집니다. + +### `nonceKeyForLane(laneId)` + +`buildGuaranteedTx`에서 사용하기 위해 레인 ID에 대한 엔터프라이즈 `nonceKey`를 반환합니다. + +```ts +import { nonceKeyForLane } from "@stablechain/enterprise"; + +const nonceKey = nonceKeyForLane(0n); +``` + +## 타입 + +### `SignerSource` + +면제 모듈(`gasWaiver`, `guaranteedWaiver`)이 허용하는 서명 키 필드입니다. 순서대로 해결됩니다: `signer`, 그 다음 `account`, 그 다음 클라이언트의 최상위 [`signer`](#stableenterpriseconfig), 그 다음 `STABLE_ENTERPRISE_PRIVATE_KEY` 환경 변수. [서명 키 및 커스터디](#signing-keys-and-custody)를 참조하십시오. + +| **필드** | **유형** | **설명** | +| :--- | :--- | :--- | +| `signer` | `Signer?` | 커스터디 등급의 서명자 (AWS KMS, HSM 또는 `privateKeySigner`). `account`보다 우선합니다. | +| `account` | `LocalAccount?` | `toSigner`를 통해 `Signer`로 변환된 인 프로세스 viem 계정. | + +### `Signer` + +SDK가 32바이트 다이제스트에 서명하는 플러그형 서명자이므로, 키는 커스터디 백엔드를 떠나지 않습니다. `awsKmsSigner`, `toSigner`, `privateKeySigner` 또는 `envSigner`로 하나를 구성합니다. + +```ts +interface Signer { + readonly address: Address; + signDigest(hash: Hex): Promise; +} +``` + +| **헬퍼** | **가져오기** | **설명** | +| :--- | :--- | :--- | +| `awsKmsSigner({ keyId, client? })` | `@stablechain/enterprise/aws-kms` | AWS KMS `ECC_SECG_P256K1` 키로 지원되는 서명자. `Promise`를 반환합니다. | +| `toSigner(account)` | `@stablechain/enterprise` | `LocalAccount` viem 계정을 `Signer`로 적용합니다. | +| `privateKeySigner(key)` | `@stablechain/enterprise` | 프로세스 내에서 유지되는 원시 개인 키를 래핑하는 서명자입니다. | +| `envSigner(varName?)` | `@stablechain/enterprise` | `STABLE_ENTERPRISE_PRIVATE_KEY`(또는 `varName`)에서 키를 읽는 서명자입니다. | + +### `WaiverInnerTx` + +호출마다 달라지는 InnerTx의 필드입니다. + +| **필드** | **유형** | **기본값** | **설명** | +| :--- | :--- | :--- | :--- | +| `to` | `Address` | | 대상 주소: ERC-20 전송을 위한 토큰 계약, 네이티브 전송을 위한 수신자. | +| `gas` | `bigint?` | `DEFAULT_INNER_GAS` (`150_000n`) | 가스 한도. `gasPrice`가 0이므로, 넉넉한 한도는 무료입니다. | +| `data` | `Hex?` | `"0x"` | Calldata. | +| `value` | `bigint?` | `0n` | 보낼 네이티브 값. | + +### `GuaranteedTxRequest` + +| **필드** | **유형** | **기본값** | **설명** | +| :--- | :--- | :--- | :--- | +| `to` | `Address?` | | 대상 주소. | +| `gas` | `bigint` | | 가스 한도. 필수. | +| `gasFeeCap` | `bigint` | | `maxFeePerGas`. 필수. | +| `gasTipCap` | `bigint` | | `maxPriorityFeePerGas`. 필수. | +| `data` | `Hex?` | `"0x"` | Calldata. | +| `value` | `bigint?` | `0n` | 보낼 네이티브 값. | + +### `GuaranteedWaiverTxRequest` + +수수료 필드는 항상 0이므로 여기서는 생략됩니다. + +| **필드** | **유형** | **기본값** | **설명** | +| :--- | :--- | :--- | :--- | +| `to` | `Address` | | 대상 주소. | +| `gas` | `bigint?` | 공유 내부 가스 기본값 | 가스 한도. | +| `data` | `Hex?` | `"0x"` | Calldata. | +| `value` | `bigint?` | `0n` | 보낼 네이티브 값. 면제를 위해 값 전송이 허용됩니다. | + +### `ValidationLimits` + +`gasWaiver` 및 `guaranteedWaiver`에 의해 각 InnerTx에 적용되는 파트너별 정책입니다. + +| **필드** | **유형** | **기본값** | **설명** | +| :--- | :--- | :--- | :--- | +| `maxGasLimit` | `bigint?` | `10_000_000n` | InnerTx의 최대 가스 한도. 이를 초과하면 `GAS_LIMIT_EXCEEDED`로 실패합니다. | +| `maxDataLength` | `number?` | `131_072` (128 KB) | 바이트 단위의 최대 calldata 크기. 이를 초과하면 `DATA_TOO_LARGE`로 실패합니다. | +| `allowedTargets` | `AllowedTarget[]?` | | 면제를 후원할 수 있는 계약 및 메서드의 허용 목록. 설정된 경우, 목록에 없는 대상은 `TARGET_NOT_ALLOWED`로 실패합니다. | + +### `AllowedTarget` + +| **필드** | **유형** | **설명** | +| :--- | :--- | :--- | +| `address` | `Address \| "*"` | InnerTx가 호출할 수 있는 계약, 또는 모든 계약과 일치하는 `"*"`입니다. | +| `selectors` | `Hex[]?` | 허용된 4바이트 메서드 선택자(예: ERC-20 `transfer`의 `"0xa9059cbb"`). `address`에서 모든 메서드를 허용하려면 생략하거나 비워 둡니다. | + +### `RelayResult` + +```ts +interface RelayResult { + txHash: Hash; // 면제의 경우 H_inner, 독립형 보장 블록의 경우 GuaranteedTx 해시 +} +``` + +### `BatchResultItem` + +배치에서 입력당 하나씩, 입력 순서대로 항목. 예외를 던지는 대신, 배치는 항목별 결과를 [`BatchResultItem.error`](#batchresultitem)로 표시합니다. + +| **필드** | **유형** | **설명** | +| :--- | :--- | :--- | +| `index` | `number` | 입력 배열과 일치하는 0부터 시작하는 위치. | +| `success` | `boolean` | 이 항목이 릴레이되었는지 여부. | +| `txHash` | `Hash?` | 성공 시 존재: 내부 트랜잭션 해시. | +| `error` | `{ code: ErrorCode; message: string }?` | 실패 시 존재. | + +## 오류 + +단일 트랜잭션 메서드(`send`, `relay`)는 거부 시 예외를 발생시킵니다. 배치 메서드는 대신 [`BatchResultItem.error`](#batchresultitem)에서 항목별 실패를 보고합니다. 모든 오류 클래스는 `StableEnterpriseError`를 확장합니다. + +| **클래스** | **발생 시점** | **유용한 필드** | +| :--- | :--- | :--- | +| `StableEnterpriseError` | 모든 SDK 오류의 기본 클래스. | `message` | +| `StableEnterpriseRelayError` | RPC 또는 릴레이 레이어에서 트랜잭션이 거부될 때. | `code` | +| `WaiverValidationError` | InnerTx가 브로드캐스트 전에 정책 검사(가스, 데이터 크기 또는 대상 허용 목록)에 실패하는 경우. | `code` | + +```ts +import { StableEnterpriseRelayError } from "@stablechain/enterprise"; + +try { + await gw.send(user, { to: token, data }); +} catch (err) { + if (err instanceof StableEnterpriseRelayError && err.code === "TARGET_NOT_ALLOWED") { + // InnerTx 대상이 구성된 허용 목록 외부에 있습니다 + } + throw err; +} +``` + +```text +StableEnterpriseRelayError: relay failed [TARGET_NOT_ALLOWED]: target 0x... not allowed +``` + +### `ErrorCode` + +던진 오류 또는 `BatchResultItem.error`의 `code`는 다음 중 하나입니다. + +| **코드** | **의미** | +| :--- | :--- | +| `UNKNOWN_ERROR` | 특정 코드를 사용할 수 없을 때의 폴백. | +| `BROADCAST_FAILED` | RPC 계층에서 브로드캐스트가 거부되거나 게이트웨이가 릴레이에 실패했습니다. | +| `INVALID_TRANSACTION` | InnerTx가 디코딩 또는 파싱에 실패했습니다. | +| `INVALID_SIGNATURE` | 서명 확인에 실패했습니다. | +| `UNSUPPORTED_TX_TYPE` | InnerTx 유형이 레거시, eip2930 또는 eip1559가 아닙니다. | +| `WRONG_CHAIN_ID` | InnerTx `chainId`가 없거나 대상 체인과 일치하지 않습니다. | +| `NON_ZERO_GAS_PRICE` | InnerTx는 0이 아닌 가스 가격을 가집니다(면제의 경우 0이어야 함). | +| `GAS_LIMIT_EXCEEDED` | InnerTx 가스 한도가 `maxGasLimit`을 초과합니다. | +| `DATA_TOO_LARGE` | InnerTx calldata가 `maxDataLength`를 초과합니다. | +| `TARGET_NOT_ALLOWED` | InnerTx 대상이 `allowedTargets`에 없습니다. | +| `GATEWAY_UNAUTHORIZED` | 엔터프라이즈 RPC 게이트웨이가 API 키를 거부했습니다(누락, 유효하지 않거나 만료됨). | +| `QUOTA_EXCEEDED` | 엔터프라이즈 RPC 게이트웨이 가스 할당량이 소진되었습니다. | + +## 상수 + +| **상수** | **유형** | **설명** | +| :--- | :--- | :--- | +| `DEFAULT_INNER_GAS` | `bigint` | `gas`가 생략될 때의 기본 InnerTx 가스 한도(`150_000n`). | +| `ENTERPRISE_FLAG` | `bigint` | 레인의 `nonceKey`에 설정된 엔터프라이즈 비트. | +| `ENTERPRISE_MASK` | `bigint` | 레인 ID의 상한: `laneId`는 `[0, ENTERPRISE_MASK - 1]` 범위 내에 있어야 합니다. | + +## 다음 권장 사항 + +- [**Stable 엔터프라이즈 SDK**](/ko/explanation/enterprise-sdk): 레일이 무엇이며 언제 사용해야 하는지. +- [**가스 면제 프로토콜**](/ko/reference/gas-waiver-api): 트랜잭션 형식, 마커 라우팅 및 거버넌스 제어. +- [**보장된 블록스페이스**](/ko/explanation/guaranteed-blockspace): Stable이 엔터프라이즈 워크로드에 대한 블록 용량을 예약하는 방법. diff --git a/docs/sidebar.json b/docs/sidebar.json index 7e8c00b..0c042e4 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -208,6 +208,32 @@ } ] }, + { + "text": "Enterprise SDK", + "collapsed": true, + "items": [ + { + "text": "Overview", + "link": "/en/explanation/enterprise-sdk" + }, + { + "text": "Relay Transactions", + "link": "/en/how-to/relay-with-gas-waiver" + }, + { + "text": "Guaranteed Blockspace", + "link": "/en/how-to/send-guaranteed-transactions" + }, + { + "text": "Guaranteed Relayed Transactions", + "link": "/en/how-to/guaranteed-relayed-transactions" + }, + { + "text": "Reference", + "link": "/en/reference/enterprise-sdk" + } + ] + }, { "text": "Onboard users", "collapsed": true, @@ -703,11 +729,11 @@ "link": "/ko/explanation/finality" }, { - "text": "가스 가격 책정", + "text": "가스 가격", "link": "/ko/explanation/gas-pricing" }, { - "text": "가스 가격 책정 API", + "text": "가스 가격 API", "link": "/ko/reference/gas-pricing-api" }, { @@ -753,7 +779,7 @@ "link": "/ko/explanation/guaranteed-blockspace" }, { - "text": "USDT 전송 애그리게이터", + "text": "USDT 전송 Aggregator", "link": "/ko/explanation/usdt-transfer-aggregator" }, { @@ -767,7 +793,7 @@ "collapsed": true, "items": [ { - "text": "코어 최적화 개요", + "text": "핵심 최적화 개요", "link": "/ko/explanation/core-optimization-overview" }, { @@ -793,7 +819,7 @@ ] }, { - "text": "사용 사례 내러티브", + "text": "사용 사례 설명", "collapsed": true, "items": [ { @@ -805,17 +831,17 @@ "link": "/ko/explanation/use-case-payroll" }, { - "text": "스폰서 사용 사례", + "text": "후원 사용 사례", "link": "/ko/explanation/use-case-sponsored" }, { - "text": "개인 사용 사례", + "text": "프라이빗 사용 사례", "link": "/ko/explanation/use-case-private" } ] }, { - "text": "토크노믹스", + "text": "토큰 경제학", "link": "/ko/reference/tokenomics" }, { @@ -853,7 +879,7 @@ "link": "/ko/tutorial/sdk-quickstart" }, { - "text": "레퍼런스", + "text": "참조", "link": "/ko/reference/sdk" }, { @@ -861,15 +887,41 @@ "link": "/ko/how-to/earn-yield" }, { - "text": "viem 통합", + "text": "Viem 통합", "link": "/ko/how-to/sdk-with-viem" }, { - "text": "wagmi 통합", + "text": "Wagmi 통합", "link": "/ko/how-to/sdk-with-wagmi" } ] }, + { + "text": "Enterprise SDK", + "collapsed": true, + "items": [ + { + "text": "개요", + "link": "/ko/explanation/enterprise-sdk" + }, + { + "text": "트랜잭션 중계", + "link": "/ko/how-to/relay-with-gas-waiver" + }, + { + "text": "보장된 블록 공간", + "link": "/ko/how-to/send-guaranteed-transactions" + }, + { + "text": "보장된 중계 트랜잭션", + "link": "/ko/how-to/guaranteed-relayed-transactions" + }, + { + "text": "참조", + "link": "/ko/reference/enterprise-sdk" + } + ] + }, { "text": "사용자 온보딩", "collapsed": true, @@ -879,7 +931,7 @@ "link": "/ko/explanation/accounts-overview" }, { - "text": "계정 색인", + "text": "계정 인덱스", "link": "/ko/explanation/accounts-guides" }, { @@ -921,7 +973,7 @@ "link": "/ko/how-to/zero-gas-transactions" }, { - "text": "USDT 가스 작업", + "text": "USDT 가스 사용", "link": "/ko/how-to/work-with-usdt-gas" }, { @@ -931,7 +983,7 @@ ] }, { - "text": "사용자에게 청구", + "text": "사용자에게 요금 부과", "collapsed": true, "items": [ { @@ -943,7 +995,7 @@ "link": "/ko/how-to/subscribe-and-collect" }, { - "text": "송장으로 결제", + "text": "인보이스로 결제", "link": "/ko/how-to/pay-with-invoice" } ] @@ -969,7 +1021,7 @@ "link": "/ko/reference/subscriptions" }, { - "text": "송장", + "text": "인보이스", "link": "/ko/reference/invoices" }, { @@ -1005,11 +1057,11 @@ "link": "/ko/tutorial/smart-contract" }, { - "text": "계약 확인", + "text": "계약 검증", "link": "/ko/how-to/verify-contract" }, { - "text": "색인 계약", + "text": "계약 색인", "link": "/ko/how-to/index-contract" } ] @@ -1031,19 +1083,19 @@ "link": "/ko/reference/system-modules-api-overview" }, { - "text": "뱅크 모듈", + "text": "은행 모듈", "link": "/ko/explanation/bank-module" }, { - "text": "뱅크 모듈 API", + "text": "은행 모듈 API", "link": "/ko/reference/bank-module-api" }, { - "text": "배포 모듈", + "text": "분배 모듈", "link": "/ko/explanation/distribution-module" }, { - "text": "배포 모듈 API", + "text": "분배 모듈 API", "link": "/ko/reference/distribution-module-api" }, { @@ -1067,7 +1119,7 @@ "link": "/ko/how-to/track-unbonding" }, { - "text": "유효성 검사기 데이터 색인", + "text": "검증인 데이터 색인", "link": "/ko/how-to/index-validator-data" } ] @@ -1165,7 +1217,7 @@ "link": "/ko/reference/bridges" }, { - "text": "덱스", + "text": "DEX", "link": "/ko/reference/dexes" }, { @@ -1173,7 +1225,7 @@ "link": "/ko/reference/oracles" }, { - "text": "RPC 제공업체", + "text": "RPC 제공자", "link": "/ko/reference/rpc-providers" }, { @@ -1181,7 +1233,7 @@ "link": "/ko/reference/network-routing" }, { - "text": "램프", + "text": "경사로", "link": "/ko/reference/ramps" }, { @@ -1189,7 +1241,7 @@ "link": "/ko/reference/wallets" }, { - "text": "수호", + "text": "수탁", "link": "/ko/reference/custody" }, { @@ -1243,7 +1295,7 @@ "link": "/ko/how-to/use-node-snapshots" }, { - "text": "유효성 검사기 실행", + "text": "검증자 실행", "link": "/ko/how-to/run-validator" }, { @@ -1273,11 +1325,11 @@ "link": "/ko/explanation/ai-agents-guides" }, { - "text": "x402 및 MPP를 통한 지불", + "text": "x402 및 MPP를 통한 결제", "collapsed": true, "items": [ { - "text": "X402", + "text": "x402", "link": "/ko/explanation/x402" }, { @@ -1311,7 +1363,7 @@ "link": "/ko/how-to/develop-with-ai" }, { - "text": "에이전틱 촉진자", + "text": "에이전트 촉진자", "link": "/ko/reference/agentic-facilitators" }, { @@ -1391,15 +1443,15 @@ "link": "/cn/explanation/flow-of-funds" }, { - "text": "USDT0 跨链桥接", + "text": "USDT0 跨链", "link": "/cn/explanation/usdt0-bridging" }, { - "text": "桥接安全性", + "text": "跨链桥安全性", "link": "/cn/explanation/bridge-security" }, { - "text": "将 USDT0 作为 Gas 代币", + "text": "USDT0 作为 Gas 代币", "link": "/cn/explanation/usdt-as-gas-token" }, { @@ -1407,7 +1459,7 @@ "link": "/cn/explanation/usdt0-behavior" }, { - "text": "Gas 豁免", + "text": "Gas 减免", "link": "/cn/explanation/gas-waiver" }, { @@ -1523,17 +1575,43 @@ "link": "/cn/how-to/earn-yield" }, { - "text": "viem 集成", + "text": "Viem 集成", "link": "/cn/how-to/sdk-with-viem" }, { - "text": "wagmi 集成", + "text": "Wagmi 集成", "link": "/cn/how-to/sdk-with-wagmi" } ] }, { - "text": "引导用户", + "text": "企业级 SDK", + "collapsed": true, + "items": [ + { + "text": "概览", + "link": "/cn/explanation/enterprise-sdk" + }, + { + "text": "中继交易", + "link": "/cn/how-to/relay-with-gas-waiver" + }, + { + "text": "保证区块空间", + "link": "/cn/how-to/send-guaranteed-transactions" + }, + { + "text": "保证中继交易", + "link": "/cn/how-to/guaranteed-relayed-transactions" + }, + { + "text": "参考", + "link": "/cn/reference/enterprise-sdk" + } + ] + }, + { + "text": "用户引导", "collapsed": true, "items": [ { @@ -1601,11 +1679,11 @@ "link": "/cn/how-to/build-p2p-payments" }, { - "text": "订阅和收款", + "text": "订阅与收款", "link": "/cn/how-to/subscribe-and-collect" }, { - "text": "凭发票支付", + "text": "通过发票支付", "link": "/cn/how-to/pay-with-invoice" } ] @@ -1647,7 +1725,7 @@ ] }, { - "text": "部署合约", + "text": "发布合约", "collapsed": true, "items": [ { @@ -1659,7 +1737,7 @@ "link": "/cn/explanation/contracts-guides" }, { - "text": "部署第一个合约", + "text": "部署您的第一个合约", "collapsed": true, "items": [ { @@ -1725,7 +1803,7 @@ "link": "/cn/reference/system-transactions-api" }, { - "text": "追踪解除绑定", + "text": "跟踪解除绑定", "link": "/cn/how-to/track-unbonding" }, { @@ -1801,19 +1879,19 @@ ] }, { - "text": "Gas 豁免服务", + "text": "Gas 减免服务", "collapsed": true, "items": [ { - "text": "集成 Gas 豁免", + "text": "集成 Gas 减免", "link": "/cn/how-to/integrate-gas-waiver" }, { - "text": "自托管 Gas 豁免", + "text": "自托管 Gas 减免", "link": "/cn/how-to/self-hosted-gas-waiver" }, { - "text": "Gas 豁免 API", + "text": "Gas 减免 API", "link": "/cn/reference/gas-waiver-api" } ] @@ -1823,11 +1901,11 @@ "collapsed": true, "items": [ { - "text": "桥", + "text": "跨链桥", "link": "/cn/reference/bridges" }, { - "text": "去中心化交易所", + "text": "DEX", "link": "/cn/reference/dexes" }, { @@ -1843,7 +1921,7 @@ "link": "/cn/reference/network-routing" }, { - "text": "出入金通道", + "text": "兑换", "link": "/cn/reference/ramps" }, { @@ -1905,7 +1983,7 @@ "link": "/cn/how-to/use-node-snapshots" }, { - "text": "运行验证者", + "text": "运行验证者节点", "link": "/cn/how-to/run-validator" }, { @@ -1959,7 +2037,7 @@ "link": "/cn/how-to/build-mpp-endpoint" }, { - "text": "使用 MCP 支付", + "text": "通过 MCP 支付", "link": "/cn/how-to/pay-with-mcp" } ] @@ -1969,7 +2047,7 @@ "collapsed": true, "items": [ { - "text": "使用 AI 开发", + "text": "用 AI 进行开发", "link": "/cn/how-to/develop-with-ai" }, { diff --git a/package.json b/package.json index 3159bd7..0348465 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "check": "npm run style:check && npm run i18n:check && npx tsc --noEmit && npm run structured-data:check && npm run docs:build", "check:node": "node docs/lib/check-node.mjs", "docs:dev": "vocs dev", - "docs:build": "NODE_OPTIONS=--max-old-space-size=6144 vocs build", + "docs:build": "NODE_OPTIONS=--max-old-space-size=7168 vocs build", "docs:preview": "vocs preview", "i18n:check": "node docs/lib/verify-i18n.mjs", "i18n:check:strict": "node docs/lib/verify-i18n.mjs --strict",