@piprail/sdk 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/dist/index.cjs +30 -7
- package/dist/index.d.cts +31 -9
- package/dist/index.d.ts +31 -9
- package/dist/index.js +28 -5
- package/dist/{near-DI2I3MAV.cjs → near-H5AQ253I.cjs} +300 -24
- package/dist/{near-MTYBCUYM.js → near-OTPQD6BI.js} +287 -11
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,40 @@ All notable changes to `@piprail/sdk` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
|
|
5
5
|
versions follow [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [2.5.0] — 2026-06-18 — gasless NEAR `exact` rail (NEP-366 meta-transactions)
|
|
8
|
+
|
|
9
|
+
Additive and backward-compatible — defaults and the zero-config 402 stay byte-identical; pure-EVM
|
|
10
|
+
installs still never download a non-EVM library (NEAR + its borsh decoder stay lazy-loaded — verified:
|
|
11
|
+
the built EVM bundle has zero static non-EVM imports, only lazy chunks).
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **NEAR `exact` rail — gasless for the buyer on a fifth family.** PipRail's `exact` scheme now covers
|
|
16
|
+
NEAR (NEP-141 tokens — USDC / USDT) via the ratified
|
|
17
|
+
[`scheme_exact_near`](https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_near.md)
|
|
18
|
+
(x402 v2). The buyer signs a **NEP-366 `SignedDelegateAction`** authorizing exactly one `ft_transfer`
|
|
19
|
+
with a **full-access key** and never broadcasts it or holds any NEAR; a relayer wraps it, prepays the
|
|
20
|
+
gas + the 1 yoctoNEAR, and submits — the agent is **completely gasless**. Opt-in
|
|
21
|
+
(`schemes: ['onchain-proof', 'exact']`); native NEAR is **not** exact-payable (the scheme is over
|
|
22
|
+
`ft_transfer`) and stays `onchain-proof`. Proven on NEAR mainnet for both USDC and USDT.
|
|
23
|
+
- New `payExactNear` (buyer build/sign) and `verifyAndSettleExactNear` (seller verify + relay) driver
|
|
24
|
+
functions; `resolveExactRail` / `payExact` / `settleExactSelf` are now implemented for NEAR.
|
|
25
|
+
- **Self-settle today** (`exact: { settle: 'self', relayer: { accountId, key } }`): the merchant runs
|
|
26
|
+
a small funded relayer that pays the sub-cent settle gas — the buyer stays gasless, exactly like the
|
|
27
|
+
Solana / Algorand / Aptos self-settle. The **facilitator** path (`settle: { facilitator }`) is wired
|
|
28
|
+
and will work the moment a real NEAR x402 facilitator ships; **none does yet** (some advertise
|
|
29
|
+
`near:mainnet` in `/supported` without settling it), so `exact: true` deliberately excludes NEAR and
|
|
30
|
+
self-settle is the supported gasless-for-buyer config. See the NEAR chain doc for the caveat.
|
|
31
|
+
- **Sponsor fee-drain guard**: the relayer prepays both the gas and the attached deposit, so the gate
|
|
32
|
+
rejects any delegate whose attached `deposit` ≠ exactly 1 yoctoNEAR or whose `gas` exceeds the
|
|
33
|
+
300 TGas cap (re-derived from the trusted rail) **before** the relayer signs.
|
|
34
|
+
|
|
35
|
+
### Dependencies
|
|
36
|
+
|
|
37
|
+
- Added **`borsh` (`>=2 <3`) as an OPTIONAL peer dependency** — used only to decode the inbound NEAR
|
|
38
|
+
`SignedDelegateAction` during self-settle, lazy-loaded inside the NEAR driver. NEAR users already have
|
|
39
|
+
it via `near-api-js`; pure-EVM (and non-NEAR) installs never load it.
|
|
40
|
+
|
|
7
41
|
## [2.4.0] — 2026-06-17 — gasless Algorand & Aptos rails + keyless gasless on SIX chains (incl. Algorand & BNB)
|
|
8
42
|
|
|
9
43
|
Additive and backward-compatible — defaults and the zero-config 402 stay byte-identical; pure-EVM
|
|
@@ -1347,6 +1381,7 @@ straight into your wallet. The API is small and self-contained.
|
|
|
1347
1381
|
to your wallet; PipRail never holds funds.
|
|
1348
1382
|
- `viem ^2.21` is a peer dependency. Node 20+ or a modern browser.
|
|
1349
1383
|
|
|
1384
|
+
[2.5.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
1350
1385
|
[2.4.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
1351
1386
|
[2.3.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
1352
1387
|
[2.2.0]: https://www.npmjs.com/package/@piprail/sdk
|
package/dist/index.cjs
CHANGED
|
@@ -1396,7 +1396,7 @@ function chainIdFromNetwork(network) {
|
|
|
1396
1396
|
const match = /^eip155:(\d+)$/.exec(network);
|
|
1397
1397
|
if (!match || !match[1]) return null;
|
|
1398
1398
|
const n = Number(match[1]);
|
|
1399
|
-
return Number.isSafeInteger(n) ? n : null;
|
|
1399
|
+
return Number.isSafeInteger(n) && n > 0 ? n : null;
|
|
1400
1400
|
}
|
|
1401
1401
|
function buildChallengeHeader(challenge) {
|
|
1402
1402
|
return toBase64Json(challenge);
|
|
@@ -1469,6 +1469,9 @@ function parseExactPaymentHeader(value) {
|
|
|
1469
1469
|
const x402Version = typeof v.x402Version === "number" ? v.x402Version : 2;
|
|
1470
1470
|
const asset = accepted && typeof accepted.asset === "string" ? accepted.asset : void 0;
|
|
1471
1471
|
const base2 = { x402Version, network, ...asset ? { asset } : {}, raw: v };
|
|
1472
|
+
if (typeof payload.signedDelegateAction === "string") {
|
|
1473
|
+
return { ...base2, method: "near", payload: { signedDelegateAction: payload.signedDelegateAction } };
|
|
1474
|
+
}
|
|
1472
1475
|
if (typeof payload.transaction === "string" && typeof payload.senderAuth === "string") {
|
|
1473
1476
|
return { ...base2, method: "aptos", payload: { transaction: payload.transaction, senderAuth: payload.senderAuth } };
|
|
1474
1477
|
}
|
|
@@ -1810,6 +1813,9 @@ function makeEvmNetwork(resolved) {
|
|
|
1810
1813
|
if ("paymentGroup" in payload) {
|
|
1811
1814
|
return { ok: false, error: "signature_invalid", detail: "An Algorand payload was submitted to an EVM exact rail." };
|
|
1812
1815
|
}
|
|
1816
|
+
if ("signedDelegateAction" in payload) {
|
|
1817
|
+
return { ok: false, error: "signature_invalid", detail: "A NEAR payload was submitted to an EVM exact rail." };
|
|
1818
|
+
}
|
|
1813
1819
|
return verifyAndSettleExactEvm({
|
|
1814
1820
|
publicClient,
|
|
1815
1821
|
walletClient: a.walletClient,
|
|
@@ -1906,7 +1912,7 @@ var loaders = {
|
|
|
1906
1912
|
near: async () => {
|
|
1907
1913
|
let mod;
|
|
1908
1914
|
try {
|
|
1909
|
-
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./near-
|
|
1915
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./near-H5AQ253I.cjs")));
|
|
1910
1916
|
} catch (cause) {
|
|
1911
1917
|
throw new (0, _chunkJG6KRAW6cjs.MissingDriverError)(
|
|
1912
1918
|
`NEAR selected, but its package isn't installed. Run: npm install near-api-js`,
|
|
@@ -3242,7 +3248,7 @@ var PipRailClient = (_class2 = class {
|
|
|
3242
3248
|
);
|
|
3243
3249
|
if (schemes.includes("exact") && exactOnNet && typeof net.payExact !== "function") {
|
|
3244
3250
|
throw new (0, _chunkJG6KRAW6cjs.UnsupportedSchemeError)(
|
|
3245
|
-
`This 402 offers a standard 'exact' rail on ${net.network}, but the ${net.family} family can't pay 'exact' (supported on EVM, Solana +
|
|
3251
|
+
`This 402 offers a standard 'exact' rail on ${net.network}, but the ${net.family} family can't pay 'exact' (supported on EVM, Solana, Algorand + NEAR today), and no 'onchain-proof' rail was offered.`
|
|
3246
3252
|
);
|
|
3247
3253
|
}
|
|
3248
3254
|
if (!schemes.includes("exact") && exactOnNet && typeof net.payExact === "function") {
|
|
@@ -3638,7 +3644,7 @@ var PipRailClient = (_class2 = class {
|
|
|
3638
3644
|
async payExactRail(net, wallet, accept, url, init, quote) {
|
|
3639
3645
|
if (!net.payExact) {
|
|
3640
3646
|
throw new (0, _chunkJG6KRAW6cjs.UnsupportedSchemeError)(
|
|
3641
|
-
`the ${net.family} family can't pay a standard 'exact' rail (supported on EVM, Solana +
|
|
3647
|
+
`the ${net.family} family can't pay a standard 'exact' rail (supported on EVM, Solana, Algorand + NEAR today).`
|
|
3642
3648
|
);
|
|
3643
3649
|
}
|
|
3644
3650
|
throwIfAborted(_optionalChain([init, 'optionalAccess', _48 => _48.signal]));
|
|
@@ -5061,6 +5067,17 @@ var KNOWN_FACILITATORS = {
|
|
|
5061
5067
|
note: "Corbits \u2014 keyless, Solana-first fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx BCreYer\u2026)."
|
|
5062
5068
|
}
|
|
5063
5069
|
]
|
|
5070
|
+
// NEAR (near:mainnet) — DELIBERATELY UNSEEDED: no x402 facilitator settles NEAR yet.
|
|
5071
|
+
// The NEAR `exact` BUYER payload PipRail builds (drivers/near/exact.ts) is LIVE-PROVEN on mainnet —
|
|
5072
|
+
// a real NEP-366 meta-transaction settles a USDC/USDT ft_transfer gaslessly (buyer 0 NEAR, single-use
|
|
5073
|
+
// via the access-key nonce; relay txs CMnQJzrLvwk… USDT + BCCnVHbSCMY… USDC, 2026-06-18). What's
|
|
5074
|
+
// missing is the FACILITATOR side: the public x402-rs (which Ultravioleta DAO runs) has NO NEAR chain
|
|
5075
|
+
// crate (only eip155/solana/aptos), and UVD's `/verify` 400s on a near:mainnet request even though its
|
|
5076
|
+
// `/supported` ADVERTISES `near:mainnet` + feePayer `uvd-facilitator.near` — i.e. the listing is
|
|
5077
|
+
// aspirational, not settle-capable (verified 2026-06-18). So `exact: true` must NOT auto-pick a NEAR
|
|
5078
|
+
// facilitator. Seed here ONLY after a real keyless settle through a facilitator that actually
|
|
5079
|
+
// implements scheme_exact_near.md (THE RULE). Merchants can still pass an explicit
|
|
5080
|
+
// `exact: { settle: { facilitator } }` for any facilitator they've confirmed settles near:mainnet.
|
|
5064
5081
|
};
|
|
5065
5082
|
function knownFacilitatorsFor(network) {
|
|
5066
5083
|
return _nullishCoalesce(KNOWN_FACILITATORS[network], () => ( []));
|
|
@@ -5476,6 +5493,12 @@ function createPaymentGate(options) {
|
|
|
5476
5493
|
return t;
|
|
5477
5494
|
}
|
|
5478
5495
|
}).join("|");
|
|
5496
|
+
} else if ("signedDelegateAction" in exact.payload) {
|
|
5497
|
+
try {
|
|
5498
|
+
nonce = Buffer.from(exact.payload.signedDelegateAction, "base64").toString("base64");
|
|
5499
|
+
} catch (e41) {
|
|
5500
|
+
nonce = exact.payload.signedDelegateAction;
|
|
5501
|
+
}
|
|
5479
5502
|
} else if ("permit2Authorization" in exact.payload) {
|
|
5480
5503
|
evmAuth = exact.payload.permit2Authorization;
|
|
5481
5504
|
nonce = evmAuth.nonce;
|
|
@@ -5494,7 +5517,7 @@ function createPaymentGate(options) {
|
|
|
5494
5517
|
result = await spec.net.settleExactSelf({ relayer: mode.relayer, payload: exact.payload, accept });
|
|
5495
5518
|
} else {
|
|
5496
5519
|
const ftMethod = accept.extra.assetTransferMethod;
|
|
5497
|
-
const needsFeePayer = ftMethod === "svm" || ftMethod === "algorand" || ftMethod === "aptos";
|
|
5520
|
+
const needsFeePayer = ftMethod === "svm" || ftMethod === "algorand" || ftMethod === "aptos" || ftMethod === "near";
|
|
5498
5521
|
if (needsFeePayer && !accept.extra.feePayer) {
|
|
5499
5522
|
throw new (0, _chunkJG6KRAW6cjs.SettlementError)(
|
|
5500
5523
|
`exact settle: the ${ftMethod} facilitator rail is missing extra.feePayer (the gas sponsor) \u2014 cannot settle.`
|
|
@@ -5614,7 +5637,7 @@ async function signBody(secret, body) {
|
|
|
5614
5637
|
const sig = await subtle.sign("HMAC", key, enc.encode(body));
|
|
5615
5638
|
const hex = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
5616
5639
|
return `sha256=${hex}`;
|
|
5617
|
-
} catch (
|
|
5640
|
+
} catch (e42) {
|
|
5618
5641
|
return null;
|
|
5619
5642
|
}
|
|
5620
5643
|
}
|
|
@@ -5675,7 +5698,7 @@ async function deliverReceipt(receipt, options) {
|
|
|
5675
5698
|
const willRetry = !ok && retryable && attempt < maxAttempts;
|
|
5676
5699
|
try {
|
|
5677
5700
|
_optionalChain([onAttempt, 'optionalCall', _91 => _91({ attempt, ok, ...status !== void 0 ? { status } : {}, ...error ? { error } : {}, willRetry })]);
|
|
5678
|
-
} catch (
|
|
5701
|
+
} catch (e43) {
|
|
5679
5702
|
}
|
|
5680
5703
|
if (ok) return { delivered: true, attempts: attempt, status };
|
|
5681
5704
|
if (!willRetry) {
|
package/dist/index.d.cts
CHANGED
|
@@ -94,8 +94,11 @@ interface X402ExactAcceptEntry {
|
|
|
94
94
|
* keyless facilitator) signs that fee txn + submits. **Aptos: `'aptos'`** — the payer signs a
|
|
95
95
|
* fee-payer (sponsored) `primary_fungible_store::transfer` to `payTo` (per
|
|
96
96
|
* `scheme_exact_aptos.md`); the gate (or a keyless facilitator) adds the fee-payer signature
|
|
97
|
-
* + submits, paying gas.
|
|
98
|
-
|
|
97
|
+
* + submits, paying gas. **NEAR: `'near'`** — the payer signs a NEP-366 `SignedDelegateAction`
|
|
98
|
+
* authorizing exactly one NEP-141 `ft_transfer` to `payTo` (per `scheme_exact_near.md`); a
|
|
99
|
+
* facilitator-selected relayer (`feePayer` below) prepays gas + the 1 yoctoNEAR and submits, so
|
|
100
|
+
* the buyer holds zero NEAR. PipRail self-settles ALL. */
|
|
101
|
+
assetTransferMethod: 'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos' | 'near';
|
|
99
102
|
/** EIP-712 domain name of the token. OPTIONAL per the exact-EVM scheme (only
|
|
100
103
|
* `assetTransferMethod` is required) — a foreign rail may omit it. NEVER assumed
|
|
101
104
|
* from the symbol (USDC's on-chain name() is "USD Coin", not "USDC"); a PipRail gate
|
|
@@ -255,9 +258,24 @@ interface ExactAptosPaymentPayload {
|
|
|
255
258
|
/** Base64 BCS-serialized buyer (sender) `AccountAuthenticator`. */
|
|
256
259
|
senderAuth: string;
|
|
257
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* The `payload` a client sends for the **NEAR `exact`** variant, per `scheme_exact_near.md`:
|
|
263
|
+
* a base64-encoded, Borsh-serialized NEP-366 `SignedDelegateAction` whose single delegated action
|
|
264
|
+
* is one NEP-141 `ft_transfer` (to `payTo`, the exact `amount`, `deposit: 1` yoctoNEAR). The buyer
|
|
265
|
+
* signs the delegate action with a FULL-ACCESS key (a function-call key can't attach the 1 yocto and
|
|
266
|
+
* is rejected); a facilitator-selected relayer wraps it, prepays gas + the yocto, and submits. The
|
|
267
|
+
* signed delegate action IS the proof — there's no separate authorization object (the NEAR analogue
|
|
268
|
+
* of EIP-3009's `authorization` / SVM's `transaction`). Its single self-contained string field also
|
|
269
|
+
* distinguishes it from every other family's payload shape.
|
|
270
|
+
*/
|
|
271
|
+
interface ExactNearPaymentPayload {
|
|
272
|
+
/** Base64 of the Borsh-encoded NEP-366 `SignedDelegateAction` (one `ft_transfer`). */
|
|
273
|
+
signedDelegateAction: string;
|
|
274
|
+
}
|
|
258
275
|
/** Any `exact`-rail payload shape — EIP-3009 (`authorization`), Permit2 (`permit2Authorization`),
|
|
259
|
-
* SVM (`transaction`), Algorand (`paymentGroup`),
|
|
260
|
-
|
|
276
|
+
* SVM (`transaction`), Algorand (`paymentGroup`), Aptos (`transaction` + `senderAuth`), or NEAR
|
|
277
|
+
* (`signedDelegateAction`). */
|
|
278
|
+
type ExactPaymentPayloadAny = ExactPaymentPayload | Permit2PaymentPayload | ExactSvmPaymentPayload | ExactAlgorandPaymentPayload | ExactAptosPaymentPayload | ExactNearPaymentPayload;
|
|
261
279
|
interface ParsedExactBase {
|
|
262
280
|
x402Version: number;
|
|
263
281
|
/** The client's claimed network (slug or CAIP-2) — for matching, not trust. */
|
|
@@ -277,7 +295,8 @@ interface ParsedExactBase {
|
|
|
277
295
|
* (`authorization`), `'permit2'` → {@link Permit2PaymentPayload} (`permit2Authorization`),
|
|
278
296
|
* `'svm'` → {@link ExactSvmPaymentPayload} (`transaction`), `'algorand'` →
|
|
279
297
|
* {@link ExactAlgorandPaymentPayload} (`paymentGroup`); `'aptos'` →
|
|
280
|
-
* {@link ExactAptosPaymentPayload} (`transaction` + `senderAuth`)
|
|
298
|
+
* {@link ExactAptosPaymentPayload} (`transaction` + `senderAuth`); `'near'` →
|
|
299
|
+
* {@link ExactNearPaymentPayload} (`signedDelegateAction`).
|
|
281
300
|
*/
|
|
282
301
|
type ParsedExactPayment = (ParsedExactBase & {
|
|
283
302
|
method: 'eip3009';
|
|
@@ -294,6 +313,9 @@ type ParsedExactPayment = (ParsedExactBase & {
|
|
|
294
313
|
}) | (ParsedExactBase & {
|
|
295
314
|
method: 'aptos';
|
|
296
315
|
payload: ExactAptosPaymentPayload;
|
|
316
|
+
}) | (ParsedExactBase & {
|
|
317
|
+
method: 'near';
|
|
318
|
+
payload: ExactNearPaymentPayload;
|
|
297
319
|
});
|
|
298
320
|
interface X402Receipt {
|
|
299
321
|
scheme: 'onchain-proof' | 'exact';
|
|
@@ -4313,10 +4335,10 @@ interface DiscoverySigner {
|
|
|
4313
4335
|
* chain-agnostic — it never names a family, it just merges `extra`.
|
|
4314
4336
|
*/
|
|
4315
4337
|
interface ExactRailInfo {
|
|
4316
|
-
method: 'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos';
|
|
4338
|
+
method: 'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos' | 'near';
|
|
4317
4339
|
/** Family-specific `extra` keys merged into the exact accept (e.g. `{ name, version }`
|
|
4318
4340
|
* for EVM EIP-3009, `{ feePayer, tokenProgram }` for Solana, `{ feePayer }` for
|
|
4319
|
-
* Algorand/Aptos). */
|
|
4341
|
+
* Algorand/Aptos/NEAR). */
|
|
4320
4342
|
extra?: Record<string, unknown>;
|
|
4321
4343
|
}
|
|
4322
4344
|
/**
|
|
@@ -6759,7 +6781,7 @@ interface KnownFacilitator {
|
|
|
6759
6781
|
/** The x402 schemes it settles (today only `exact`). */
|
|
6760
6782
|
schemes: ReadonlyArray<'exact'>;
|
|
6761
6783
|
/** The exact transfer methods it can settle on this network. */
|
|
6762
|
-
settles: ReadonlyArray<'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos'>;
|
|
6784
|
+
settles: ReadonlyArray<'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos' | 'near'>;
|
|
6763
6785
|
/** A short human note (who it is / caveat). */
|
|
6764
6786
|
note?: string;
|
|
6765
6787
|
}
|
|
@@ -6780,7 +6802,7 @@ declare function knownFacilitatorsFor(network: Caip2): ReadonlyArray<KnownFacili
|
|
|
6780
6802
|
* specific transfer `method`). Returns `undefined` when none is known — the `exact: true`
|
|
6781
6803
|
* shorthand branches on that to throw a coverage-specific guidance error.
|
|
6782
6804
|
*/
|
|
6783
|
-
declare function firstKeylessFacilitator(network: Caip2, method?: 'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos'): KnownFacilitator | undefined;
|
|
6805
|
+
declare function firstKeylessFacilitator(network: Caip2, method?: 'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos' | 'near'): KnownFacilitator | undefined;
|
|
6784
6806
|
|
|
6785
6807
|
/**
|
|
6786
6808
|
* Reliable receipt delivery — the durable webhook a stateless gate can't be.
|
package/dist/index.d.ts
CHANGED
|
@@ -94,8 +94,11 @@ interface X402ExactAcceptEntry {
|
|
|
94
94
|
* keyless facilitator) signs that fee txn + submits. **Aptos: `'aptos'`** — the payer signs a
|
|
95
95
|
* fee-payer (sponsored) `primary_fungible_store::transfer` to `payTo` (per
|
|
96
96
|
* `scheme_exact_aptos.md`); the gate (or a keyless facilitator) adds the fee-payer signature
|
|
97
|
-
* + submits, paying gas.
|
|
98
|
-
|
|
97
|
+
* + submits, paying gas. **NEAR: `'near'`** — the payer signs a NEP-366 `SignedDelegateAction`
|
|
98
|
+
* authorizing exactly one NEP-141 `ft_transfer` to `payTo` (per `scheme_exact_near.md`); a
|
|
99
|
+
* facilitator-selected relayer (`feePayer` below) prepays gas + the 1 yoctoNEAR and submits, so
|
|
100
|
+
* the buyer holds zero NEAR. PipRail self-settles ALL. */
|
|
101
|
+
assetTransferMethod: 'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos' | 'near';
|
|
99
102
|
/** EIP-712 domain name of the token. OPTIONAL per the exact-EVM scheme (only
|
|
100
103
|
* `assetTransferMethod` is required) — a foreign rail may omit it. NEVER assumed
|
|
101
104
|
* from the symbol (USDC's on-chain name() is "USD Coin", not "USDC"); a PipRail gate
|
|
@@ -255,9 +258,24 @@ interface ExactAptosPaymentPayload {
|
|
|
255
258
|
/** Base64 BCS-serialized buyer (sender) `AccountAuthenticator`. */
|
|
256
259
|
senderAuth: string;
|
|
257
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* The `payload` a client sends for the **NEAR `exact`** variant, per `scheme_exact_near.md`:
|
|
263
|
+
* a base64-encoded, Borsh-serialized NEP-366 `SignedDelegateAction` whose single delegated action
|
|
264
|
+
* is one NEP-141 `ft_transfer` (to `payTo`, the exact `amount`, `deposit: 1` yoctoNEAR). The buyer
|
|
265
|
+
* signs the delegate action with a FULL-ACCESS key (a function-call key can't attach the 1 yocto and
|
|
266
|
+
* is rejected); a facilitator-selected relayer wraps it, prepays gas + the yocto, and submits. The
|
|
267
|
+
* signed delegate action IS the proof — there's no separate authorization object (the NEAR analogue
|
|
268
|
+
* of EIP-3009's `authorization` / SVM's `transaction`). Its single self-contained string field also
|
|
269
|
+
* distinguishes it from every other family's payload shape.
|
|
270
|
+
*/
|
|
271
|
+
interface ExactNearPaymentPayload {
|
|
272
|
+
/** Base64 of the Borsh-encoded NEP-366 `SignedDelegateAction` (one `ft_transfer`). */
|
|
273
|
+
signedDelegateAction: string;
|
|
274
|
+
}
|
|
258
275
|
/** Any `exact`-rail payload shape — EIP-3009 (`authorization`), Permit2 (`permit2Authorization`),
|
|
259
|
-
* SVM (`transaction`), Algorand (`paymentGroup`),
|
|
260
|
-
|
|
276
|
+
* SVM (`transaction`), Algorand (`paymentGroup`), Aptos (`transaction` + `senderAuth`), or NEAR
|
|
277
|
+
* (`signedDelegateAction`). */
|
|
278
|
+
type ExactPaymentPayloadAny = ExactPaymentPayload | Permit2PaymentPayload | ExactSvmPaymentPayload | ExactAlgorandPaymentPayload | ExactAptosPaymentPayload | ExactNearPaymentPayload;
|
|
261
279
|
interface ParsedExactBase {
|
|
262
280
|
x402Version: number;
|
|
263
281
|
/** The client's claimed network (slug or CAIP-2) — for matching, not trust. */
|
|
@@ -277,7 +295,8 @@ interface ParsedExactBase {
|
|
|
277
295
|
* (`authorization`), `'permit2'` → {@link Permit2PaymentPayload} (`permit2Authorization`),
|
|
278
296
|
* `'svm'` → {@link ExactSvmPaymentPayload} (`transaction`), `'algorand'` →
|
|
279
297
|
* {@link ExactAlgorandPaymentPayload} (`paymentGroup`); `'aptos'` →
|
|
280
|
-
* {@link ExactAptosPaymentPayload} (`transaction` + `senderAuth`)
|
|
298
|
+
* {@link ExactAptosPaymentPayload} (`transaction` + `senderAuth`); `'near'` →
|
|
299
|
+
* {@link ExactNearPaymentPayload} (`signedDelegateAction`).
|
|
281
300
|
*/
|
|
282
301
|
type ParsedExactPayment = (ParsedExactBase & {
|
|
283
302
|
method: 'eip3009';
|
|
@@ -294,6 +313,9 @@ type ParsedExactPayment = (ParsedExactBase & {
|
|
|
294
313
|
}) | (ParsedExactBase & {
|
|
295
314
|
method: 'aptos';
|
|
296
315
|
payload: ExactAptosPaymentPayload;
|
|
316
|
+
}) | (ParsedExactBase & {
|
|
317
|
+
method: 'near';
|
|
318
|
+
payload: ExactNearPaymentPayload;
|
|
297
319
|
});
|
|
298
320
|
interface X402Receipt {
|
|
299
321
|
scheme: 'onchain-proof' | 'exact';
|
|
@@ -4313,10 +4335,10 @@ interface DiscoverySigner {
|
|
|
4313
4335
|
* chain-agnostic — it never names a family, it just merges `extra`.
|
|
4314
4336
|
*/
|
|
4315
4337
|
interface ExactRailInfo {
|
|
4316
|
-
method: 'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos';
|
|
4338
|
+
method: 'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos' | 'near';
|
|
4317
4339
|
/** Family-specific `extra` keys merged into the exact accept (e.g. `{ name, version }`
|
|
4318
4340
|
* for EVM EIP-3009, `{ feePayer, tokenProgram }` for Solana, `{ feePayer }` for
|
|
4319
|
-
* Algorand/Aptos). */
|
|
4341
|
+
* Algorand/Aptos/NEAR). */
|
|
4320
4342
|
extra?: Record<string, unknown>;
|
|
4321
4343
|
}
|
|
4322
4344
|
/**
|
|
@@ -6759,7 +6781,7 @@ interface KnownFacilitator {
|
|
|
6759
6781
|
/** The x402 schemes it settles (today only `exact`). */
|
|
6760
6782
|
schemes: ReadonlyArray<'exact'>;
|
|
6761
6783
|
/** The exact transfer methods it can settle on this network. */
|
|
6762
|
-
settles: ReadonlyArray<'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos'>;
|
|
6784
|
+
settles: ReadonlyArray<'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos' | 'near'>;
|
|
6763
6785
|
/** A short human note (who it is / caveat). */
|
|
6764
6786
|
note?: string;
|
|
6765
6787
|
}
|
|
@@ -6780,7 +6802,7 @@ declare function knownFacilitatorsFor(network: Caip2): ReadonlyArray<KnownFacili
|
|
|
6780
6802
|
* specific transfer `method`). Returns `undefined` when none is known — the `exact: true`
|
|
6781
6803
|
* shorthand branches on that to throw a coverage-specific guidance error.
|
|
6782
6804
|
*/
|
|
6783
|
-
declare function firstKeylessFacilitator(network: Caip2, method?: 'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos'): KnownFacilitator | undefined;
|
|
6805
|
+
declare function firstKeylessFacilitator(network: Caip2, method?: 'eip3009' | 'permit2' | 'svm' | 'algorand' | 'aptos' | 'near'): KnownFacilitator | undefined;
|
|
6784
6806
|
|
|
6785
6807
|
/**
|
|
6786
6808
|
* Reliable receipt delivery — the durable webhook a stateless gate can't be.
|
package/dist/index.js
CHANGED
|
@@ -1396,7 +1396,7 @@ function chainIdFromNetwork(network) {
|
|
|
1396
1396
|
const match = /^eip155:(\d+)$/.exec(network);
|
|
1397
1397
|
if (!match || !match[1]) return null;
|
|
1398
1398
|
const n = Number(match[1]);
|
|
1399
|
-
return Number.isSafeInteger(n) ? n : null;
|
|
1399
|
+
return Number.isSafeInteger(n) && n > 0 ? n : null;
|
|
1400
1400
|
}
|
|
1401
1401
|
function buildChallengeHeader(challenge) {
|
|
1402
1402
|
return toBase64Json(challenge);
|
|
@@ -1469,6 +1469,9 @@ function parseExactPaymentHeader(value) {
|
|
|
1469
1469
|
const x402Version = typeof v.x402Version === "number" ? v.x402Version : 2;
|
|
1470
1470
|
const asset = accepted && typeof accepted.asset === "string" ? accepted.asset : void 0;
|
|
1471
1471
|
const base2 = { x402Version, network, ...asset ? { asset } : {}, raw: v };
|
|
1472
|
+
if (typeof payload.signedDelegateAction === "string") {
|
|
1473
|
+
return { ...base2, method: "near", payload: { signedDelegateAction: payload.signedDelegateAction } };
|
|
1474
|
+
}
|
|
1472
1475
|
if (typeof payload.transaction === "string" && typeof payload.senderAuth === "string") {
|
|
1473
1476
|
return { ...base2, method: "aptos", payload: { transaction: payload.transaction, senderAuth: payload.senderAuth } };
|
|
1474
1477
|
}
|
|
@@ -1810,6 +1813,9 @@ function makeEvmNetwork(resolved) {
|
|
|
1810
1813
|
if ("paymentGroup" in payload) {
|
|
1811
1814
|
return { ok: false, error: "signature_invalid", detail: "An Algorand payload was submitted to an EVM exact rail." };
|
|
1812
1815
|
}
|
|
1816
|
+
if ("signedDelegateAction" in payload) {
|
|
1817
|
+
return { ok: false, error: "signature_invalid", detail: "A NEAR payload was submitted to an EVM exact rail." };
|
|
1818
|
+
}
|
|
1813
1819
|
return verifyAndSettleExactEvm({
|
|
1814
1820
|
publicClient,
|
|
1815
1821
|
walletClient: a.walletClient,
|
|
@@ -1906,7 +1912,7 @@ var loaders = {
|
|
|
1906
1912
|
near: async () => {
|
|
1907
1913
|
let mod;
|
|
1908
1914
|
try {
|
|
1909
|
-
mod = await import("./near-
|
|
1915
|
+
mod = await import("./near-OTPQD6BI.js");
|
|
1910
1916
|
} catch (cause) {
|
|
1911
1917
|
throw new MissingDriverError(
|
|
1912
1918
|
`NEAR selected, but its package isn't installed. Run: npm install near-api-js`,
|
|
@@ -3242,7 +3248,7 @@ var PipRailClient = class {
|
|
|
3242
3248
|
);
|
|
3243
3249
|
if (schemes.includes("exact") && exactOnNet && typeof net.payExact !== "function") {
|
|
3244
3250
|
throw new UnsupportedSchemeError(
|
|
3245
|
-
`This 402 offers a standard 'exact' rail on ${net.network}, but the ${net.family} family can't pay 'exact' (supported on EVM, Solana +
|
|
3251
|
+
`This 402 offers a standard 'exact' rail on ${net.network}, but the ${net.family} family can't pay 'exact' (supported on EVM, Solana, Algorand + NEAR today), and no 'onchain-proof' rail was offered.`
|
|
3246
3252
|
);
|
|
3247
3253
|
}
|
|
3248
3254
|
if (!schemes.includes("exact") && exactOnNet && typeof net.payExact === "function") {
|
|
@@ -3638,7 +3644,7 @@ var PipRailClient = class {
|
|
|
3638
3644
|
async payExactRail(net, wallet, accept, url, init, quote) {
|
|
3639
3645
|
if (!net.payExact) {
|
|
3640
3646
|
throw new UnsupportedSchemeError(
|
|
3641
|
-
`the ${net.family} family can't pay a standard 'exact' rail (supported on EVM, Solana +
|
|
3647
|
+
`the ${net.family} family can't pay a standard 'exact' rail (supported on EVM, Solana, Algorand + NEAR today).`
|
|
3642
3648
|
);
|
|
3643
3649
|
}
|
|
3644
3650
|
throwIfAborted(init?.signal);
|
|
@@ -5061,6 +5067,17 @@ var KNOWN_FACILITATORS = {
|
|
|
5061
5067
|
note: "Corbits \u2014 keyless, Solana-first fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx BCreYer\u2026)."
|
|
5062
5068
|
}
|
|
5063
5069
|
]
|
|
5070
|
+
// NEAR (near:mainnet) — DELIBERATELY UNSEEDED: no x402 facilitator settles NEAR yet.
|
|
5071
|
+
// The NEAR `exact` BUYER payload PipRail builds (drivers/near/exact.ts) is LIVE-PROVEN on mainnet —
|
|
5072
|
+
// a real NEP-366 meta-transaction settles a USDC/USDT ft_transfer gaslessly (buyer 0 NEAR, single-use
|
|
5073
|
+
// via the access-key nonce; relay txs CMnQJzrLvwk… USDT + BCCnVHbSCMY… USDC, 2026-06-18). What's
|
|
5074
|
+
// missing is the FACILITATOR side: the public x402-rs (which Ultravioleta DAO runs) has NO NEAR chain
|
|
5075
|
+
// crate (only eip155/solana/aptos), and UVD's `/verify` 400s on a near:mainnet request even though its
|
|
5076
|
+
// `/supported` ADVERTISES `near:mainnet` + feePayer `uvd-facilitator.near` — i.e. the listing is
|
|
5077
|
+
// aspirational, not settle-capable (verified 2026-06-18). So `exact: true` must NOT auto-pick a NEAR
|
|
5078
|
+
// facilitator. Seed here ONLY after a real keyless settle through a facilitator that actually
|
|
5079
|
+
// implements scheme_exact_near.md (THE RULE). Merchants can still pass an explicit
|
|
5080
|
+
// `exact: { settle: { facilitator } }` for any facilitator they've confirmed settles near:mainnet.
|
|
5064
5081
|
};
|
|
5065
5082
|
function knownFacilitatorsFor(network) {
|
|
5066
5083
|
return KNOWN_FACILITATORS[network] ?? [];
|
|
@@ -5476,6 +5493,12 @@ function createPaymentGate(options) {
|
|
|
5476
5493
|
return t;
|
|
5477
5494
|
}
|
|
5478
5495
|
}).join("|");
|
|
5496
|
+
} else if ("signedDelegateAction" in exact.payload) {
|
|
5497
|
+
try {
|
|
5498
|
+
nonce = Buffer.from(exact.payload.signedDelegateAction, "base64").toString("base64");
|
|
5499
|
+
} catch {
|
|
5500
|
+
nonce = exact.payload.signedDelegateAction;
|
|
5501
|
+
}
|
|
5479
5502
|
} else if ("permit2Authorization" in exact.payload) {
|
|
5480
5503
|
evmAuth = exact.payload.permit2Authorization;
|
|
5481
5504
|
nonce = evmAuth.nonce;
|
|
@@ -5494,7 +5517,7 @@ function createPaymentGate(options) {
|
|
|
5494
5517
|
result = await spec.net.settleExactSelf({ relayer: mode.relayer, payload: exact.payload, accept });
|
|
5495
5518
|
} else {
|
|
5496
5519
|
const ftMethod = accept.extra.assetTransferMethod;
|
|
5497
|
-
const needsFeePayer = ftMethod === "svm" || ftMethod === "algorand" || ftMethod === "aptos";
|
|
5520
|
+
const needsFeePayer = ftMethod === "svm" || ftMethod === "algorand" || ftMethod === "aptos" || ftMethod === "near";
|
|
5498
5521
|
if (needsFeePayer && !accept.extra.feePayer) {
|
|
5499
5522
|
throw new SettlementError(
|
|
5500
5523
|
`exact settle: the ${ftMethod} facilitator rail is missing extra.feePayer (the gas sponsor) \u2014 cannot settle.`
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
|
|
12
|
+
|
|
11
13
|
var _chunkJG6KRAW6cjs = require('./chunk-JG6KRAW6.cjs');
|
|
12
14
|
|
|
13
15
|
// src/drivers/near/index.ts
|
|
@@ -15,6 +17,11 @@ var _nearapijs = require('near-api-js');
|
|
|
15
17
|
|
|
16
18
|
// src/drivers/near/chains.ts
|
|
17
19
|
var NEAR_DECIMALS = 24;
|
|
20
|
+
function isValidNearAccountId(id) {
|
|
21
|
+
if (id.startsWith("0x")) return false;
|
|
22
|
+
if (id.length < 2 || id.length > 64) return false;
|
|
23
|
+
return /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/.test(id);
|
|
24
|
+
}
|
|
18
25
|
var NEAR_MAINNET = {
|
|
19
26
|
caip2: "near:mainnet",
|
|
20
27
|
defaultRpc: "https://free.rpc.fastnear.com",
|
|
@@ -92,6 +99,190 @@ function isNearAffordability(err) {
|
|
|
92
99
|
return /not enough|doesn't have enough|does not have enough|lack ?balance|exceeds .*balance|insufficient/i.test(m);
|
|
93
100
|
}
|
|
94
101
|
|
|
102
|
+
// src/drivers/near/exact.ts
|
|
103
|
+
|
|
104
|
+
var _borsh = require('borsh');
|
|
105
|
+
var FT_TRANSFER_GAS2 = 30000000000000n;
|
|
106
|
+
var ONE_YOCTO2 = 1n;
|
|
107
|
+
var ESTIMATED_BLOCK_SECONDS = 1;
|
|
108
|
+
var NONCE_BLOCK_MULTIPLIER = 1000000n;
|
|
109
|
+
async function payExactNear(input) {
|
|
110
|
+
const { signer, senderId, blockHeight, accessKeyNonce, accept } = input;
|
|
111
|
+
if (accept.asset === "native") {
|
|
112
|
+
throw new (0, _chunkJG6KRAW6cjs.UnsupportedSchemeError)(
|
|
113
|
+
"NEAR exact is NEP-141-only (an ft_transfer); native NEAR is not exact-payable. Pay via onchain-proof."
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (!isValidNearAccountId(accept.asset)) {
|
|
117
|
+
throw new (0, _chunkJG6KRAW6cjs.UnsupportedSchemeError)(`NEAR exact: asset "${accept.asset}" must be a NEP-141 contract account id.`);
|
|
118
|
+
}
|
|
119
|
+
if (!isValidNearAccountId(accept.payTo)) {
|
|
120
|
+
throw new (0, _chunkJG6KRAW6cjs.UnsupportedSchemeError)(`NEAR exact: payTo "${accept.payTo}" is not a valid NEAR account id.`);
|
|
121
|
+
}
|
|
122
|
+
if (!isValidNearAccountId(senderId)) {
|
|
123
|
+
throw new (0, _chunkJG6KRAW6cjs.UnsupportedSchemeError)(`NEAR exact: sender "${senderId}" is not a valid NEAR account id.`);
|
|
124
|
+
}
|
|
125
|
+
const t = accept.maxTimeoutSeconds;
|
|
126
|
+
if (!Number.isInteger(t) || t <= 0) {
|
|
127
|
+
throw new (0, _chunkJG6KRAW6cjs.UnsupportedSchemeError)("NEAR exact: maxTimeoutSeconds must be a positive integer.");
|
|
128
|
+
}
|
|
129
|
+
const timeoutBlocks = BigInt(Math.max(1, Math.ceil(t / ESTIMATED_BLOCK_SECONDS)));
|
|
130
|
+
const maxBlockHeight = blockHeight + timeoutBlocks;
|
|
131
|
+
const nonce = accessKeyNonce + 1n;
|
|
132
|
+
if (nonce >= blockHeight * NONCE_BLOCK_MULTIPLIER) {
|
|
133
|
+
throw new (0, _chunkJG6KRAW6cjs.UnsupportedSchemeError)(
|
|
134
|
+
"NEAR exact: the access-key nonce is at the protocol ceiling for this block height; cannot build a delegate action."
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const publicKey = await signer.getPublicKey();
|
|
138
|
+
const action = _nearapijs.actions.functionCall(
|
|
139
|
+
"ft_transfer",
|
|
140
|
+
{ receiver_id: accept.payTo, amount: accept.amount },
|
|
141
|
+
FT_TRANSFER_GAS2,
|
|
142
|
+
ONE_YOCTO2
|
|
143
|
+
);
|
|
144
|
+
const delegateAction = _nearapijs.buildDelegateAction.call(void 0, {
|
|
145
|
+
senderId,
|
|
146
|
+
receiverId: accept.asset,
|
|
147
|
+
actions: [action],
|
|
148
|
+
nonce,
|
|
149
|
+
maxBlockHeight,
|
|
150
|
+
publicKey
|
|
151
|
+
});
|
|
152
|
+
const { signedDelegate } = await signer.signDelegateAction(delegateAction);
|
|
153
|
+
const encoded = _nearapijs.encodeSignedDelegate.call(void 0, signedDelegate);
|
|
154
|
+
return {
|
|
155
|
+
payload: { signedDelegateAction: Buffer.from(encoded).toString("base64") },
|
|
156
|
+
payerFrom: senderId,
|
|
157
|
+
// A stable id for THIS authorization (single-use on-chain via the access-key nonce): the
|
|
158
|
+
// client records it as the spend ref and re-presents the SAME signed action on a retry.
|
|
159
|
+
nonce: `${senderId}:${nonce.toString()}`
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
var MAX_RELAY_GAS = 300000000000000n;
|
|
163
|
+
var b64decode = (s) => new Uint8Array(Buffer.from(s, "base64"));
|
|
164
|
+
function shorten(msg) {
|
|
165
|
+
const oneLine = msg.replace(/\s+/g, " ").trim();
|
|
166
|
+
return oneLine.length > 200 ? `${oneLine.slice(0, 200)}\u2026` : oneLine;
|
|
167
|
+
}
|
|
168
|
+
function fail(error, detail) {
|
|
169
|
+
return { ok: false, error, detail };
|
|
170
|
+
}
|
|
171
|
+
function verifyExactNear(payload, accept, currentBlockHeight) {
|
|
172
|
+
if (accept.asset === "native" || !isValidNearAccountId(accept.asset)) {
|
|
173
|
+
return fail("transfer_not_found", `NEAR exact rail asset "${accept.asset}" is not a NEP-141 contract.`);
|
|
174
|
+
}
|
|
175
|
+
if (typeof _optionalChain([payload, 'optionalAccess', _ => _.signedDelegateAction]) !== "string") {
|
|
176
|
+
return fail("signature_invalid", "NEAR exact payload is missing signedDelegateAction.");
|
|
177
|
+
}
|
|
178
|
+
let decoded;
|
|
179
|
+
try {
|
|
180
|
+
decoded = _borsh.deserialize.call(void 0, _nearapijs.SCHEMA.SignedDelegate, b64decode(payload.signedDelegateAction));
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return fail("signature_invalid", `Unparseable SignedDelegateAction: ${shorten(err instanceof Error ? err.message : String(err))}.`);
|
|
183
|
+
}
|
|
184
|
+
const da = _optionalChain([decoded, 'optionalAccess', _2 => _2.delegateAction]);
|
|
185
|
+
const sig = _optionalChain([decoded, 'optionalAccess', _3 => _3.signature]);
|
|
186
|
+
if (!da || sig == null) return fail("signature_invalid", "SignedDelegateAction is missing its delegate action or signature.");
|
|
187
|
+
if (!Array.isArray(da.actions) || da.actions.length !== 1) {
|
|
188
|
+
return fail("transfer_not_found", `the delegate must carry exactly one action, got ${_nullishCoalesce(_optionalChain([da, 'access', _4 => _4.actions, 'optionalAccess', _5 => _5.length]), () => ( 0))}.`);
|
|
189
|
+
}
|
|
190
|
+
const fc = _optionalChain([da, 'access', _6 => _6.actions, 'access', _7 => _7[0], 'optionalAccess', _8 => _8.functionCall]);
|
|
191
|
+
if (!fc || fc.methodName !== "ft_transfer") {
|
|
192
|
+
return fail("transfer_not_found", `the delegated action is not an ft_transfer (got "${_nullishCoalesce(_optionalChain([fc, 'optionalAccess', _9 => _9.methodName]), () => ( "none"))}").`);
|
|
193
|
+
}
|
|
194
|
+
if (da.receiverId !== accept.asset) {
|
|
195
|
+
return fail("transfer_not_found", `delegate receiver ${da.receiverId} \u2260 token contract ${accept.asset}.`);
|
|
196
|
+
}
|
|
197
|
+
let args;
|
|
198
|
+
try {
|
|
199
|
+
args = JSON.parse(Buffer.from(_nullishCoalesce(fc.args, () => ( new Uint8Array()))).toString());
|
|
200
|
+
} catch (e2) {
|
|
201
|
+
return fail("transfer_not_found", "the ft_transfer args are not valid JSON.");
|
|
202
|
+
}
|
|
203
|
+
if (args.receiver_id !== accept.payTo) {
|
|
204
|
+
return fail("wrong_recipient", `ft_transfer pays ${String(args.receiver_id)}, not payTo ${accept.payTo}.`);
|
|
205
|
+
}
|
|
206
|
+
if (typeof args.amount !== "string" || !/^\d+$/.test(args.amount) || BigInt(args.amount) < BigInt(accept.amount)) {
|
|
207
|
+
return fail("amount_too_low", `ft_transfer pays ${String(args.amount)} of the token, required ${accept.amount}.`);
|
|
208
|
+
}
|
|
209
|
+
let deposit;
|
|
210
|
+
let gas;
|
|
211
|
+
try {
|
|
212
|
+
deposit = BigInt(_nullishCoalesce(fc.deposit, () => ( 0)));
|
|
213
|
+
gas = BigInt(_nullishCoalesce(fc.gas, () => ( 0)));
|
|
214
|
+
} catch (e3) {
|
|
215
|
+
return fail("signature_invalid", "the ft_transfer has a malformed gas/deposit.");
|
|
216
|
+
}
|
|
217
|
+
if (deposit !== ONE_YOCTO2) {
|
|
218
|
+
return fail("signature_invalid", `attached deposit ${deposit} \u2260 the required 1 yoctoNEAR (relayer drain guard).`);
|
|
219
|
+
}
|
|
220
|
+
if (gas > MAX_RELAY_GAS) {
|
|
221
|
+
return fail("signature_invalid", `delegated gas ${gas} exceeds the ${MAX_RELAY_GAS} cap (relayer drain guard).`);
|
|
222
|
+
}
|
|
223
|
+
if (currentBlockHeight !== null) {
|
|
224
|
+
let maxBlock;
|
|
225
|
+
try {
|
|
226
|
+
maxBlock = BigInt(_nullishCoalesce(da.maxBlockHeight, () => ( 0)));
|
|
227
|
+
} catch (e4) {
|
|
228
|
+
return fail("signature_invalid", "the delegate has a malformed max_block_height.");
|
|
229
|
+
}
|
|
230
|
+
if (maxBlock <= currentBlockHeight) {
|
|
231
|
+
return fail("payment_expired", `the delegate expired (max_block_height ${maxBlock} \u2264 current ${currentBlockHeight}).`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (!da.senderId || !isValidNearAccountId(String(da.senderId))) {
|
|
235
|
+
return fail("signature_invalid", `the delegate sender "${String(da.senderId)}" is not a valid NEAR account id.`);
|
|
236
|
+
}
|
|
237
|
+
return { ok: true, senderId: String(da.senderId), delegateAction: new (0, _nearapijs.DelegateAction)(da), signature: sig };
|
|
238
|
+
}
|
|
239
|
+
async function verifyAndSettleExactNear(input) {
|
|
240
|
+
const { client, payload, accept } = input;
|
|
241
|
+
let height = null;
|
|
242
|
+
try {
|
|
243
|
+
height = await client.currentBlockHeight();
|
|
244
|
+
} catch (e5) {
|
|
245
|
+
}
|
|
246
|
+
const v = verifyExactNear(payload, accept, height);
|
|
247
|
+
if (!v.ok) return v;
|
|
248
|
+
let result;
|
|
249
|
+
try {
|
|
250
|
+
result = await client.relay({ senderId: v.senderId, delegateAction: v.delegateAction, signature: v.signature });
|
|
251
|
+
} catch (err) {
|
|
252
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
253
|
+
if (/invalid.*nonce|nonce.*used|DelegateActionInvalidNonce/i.test(m)) {
|
|
254
|
+
return fail("tx_already_used", `this delegate was already used (single-use nonce): ${shorten(m)}.`);
|
|
255
|
+
}
|
|
256
|
+
if (/expired|DelegateActionExpired|max_block_height|deadline/i.test(m)) {
|
|
257
|
+
return fail("payment_expired", `the delegate expired before it landed: ${shorten(m)}.`);
|
|
258
|
+
}
|
|
259
|
+
if (/not enough|insufficient|exceeded the prepaid gas|NotEnoughBalance|doesn't have enough|is not registered/i.test(m)) {
|
|
260
|
+
return fail("tx_reverted", `the ft_transfer would fail on chain: ${shorten(m)}.`);
|
|
261
|
+
}
|
|
262
|
+
throw new (0, _chunkJG6KRAW6cjs.SettlementError)(
|
|
263
|
+
`NEAR exact settle: the relayer could not submit the delegate (${shorten(m)}). The buyer's signed delegate is still valid \u2014 fund/fix the relayer and the buyer can re-present it.`,
|
|
264
|
+
{ cause: err }
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
if (!result.innerSuccess) {
|
|
268
|
+
return fail("tx_reverted", `the ft_transfer receipt did not succeed on chain (tx ${result.txHash}).`);
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
ok: true,
|
|
272
|
+
receipt: {
|
|
273
|
+
scheme: "exact",
|
|
274
|
+
success: true,
|
|
275
|
+
network: accept.network,
|
|
276
|
+
transaction: result.txHash,
|
|
277
|
+
asset: accept.asset,
|
|
278
|
+
amount: accept.amount,
|
|
279
|
+
payer: v.senderId,
|
|
280
|
+
payTo: accept.payTo,
|
|
281
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
95
286
|
// src/drivers/near/verify.ts
|
|
96
287
|
function parseFtTransferEvent(line) {
|
|
97
288
|
const marker = "EVENT_JSON:";
|
|
@@ -102,7 +293,7 @@ function parseFtTransferEvent(line) {
|
|
|
102
293
|
if (ev.standard === "nep141" && ev.event === "ft_transfer" && Array.isArray(ev.data)) {
|
|
103
294
|
return ev.data;
|
|
104
295
|
}
|
|
105
|
-
} catch (
|
|
296
|
+
} catch (e6) {
|
|
106
297
|
}
|
|
107
298
|
return null;
|
|
108
299
|
}
|
|
@@ -113,7 +304,7 @@ async function verifyNear(params) {
|
|
|
113
304
|
let tx;
|
|
114
305
|
try {
|
|
115
306
|
tx = await reader.txStatus(hash, senderId);
|
|
116
|
-
} catch (
|
|
307
|
+
} catch (e7) {
|
|
117
308
|
return txNotFound(hash);
|
|
118
309
|
}
|
|
119
310
|
if (!tx) return txNotFound(hash);
|
|
@@ -134,7 +325,7 @@ async function verifyNear(params) {
|
|
|
134
325
|
let paid2;
|
|
135
326
|
try {
|
|
136
327
|
paid2 = BigInt(_nullishCoalesce(tx.nativeDeposit, () => ( "0")));
|
|
137
|
-
} catch (
|
|
328
|
+
} catch (e8) {
|
|
138
329
|
paid2 = 0n;
|
|
139
330
|
}
|
|
140
331
|
if (paid2 < required) {
|
|
@@ -176,7 +367,7 @@ async function verifyNear(params) {
|
|
|
176
367
|
if (d.new_owner_id === accept.payTo && (_nullishCoalesce(d.memo, () => ( ""))) === nonce) {
|
|
177
368
|
try {
|
|
178
369
|
paid += BigInt(_nullishCoalesce(d.amount, () => ( "0")));
|
|
179
|
-
} catch (
|
|
370
|
+
} catch (e9) {
|
|
180
371
|
}
|
|
181
372
|
if (!payer) payer = _nullishCoalesce(d.old_owner_id, () => ( ""));
|
|
182
373
|
}
|
|
@@ -253,11 +444,6 @@ var nearDriver = {
|
|
|
253
444
|
return makeNearNetwork(NEAR_MAINNET, rpcUrl);
|
|
254
445
|
}
|
|
255
446
|
};
|
|
256
|
-
function isValidNearAccountId(id) {
|
|
257
|
-
if (id.startsWith("0x")) return false;
|
|
258
|
-
if (id.length < 2 || id.length > 64) return false;
|
|
259
|
-
return /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/.test(id);
|
|
260
|
-
}
|
|
261
447
|
function makeNearNetwork(preset, rpcUrl) {
|
|
262
448
|
const provider = new (0, _nearapijs.JsonRpcProvider)({ url: rpcUrl });
|
|
263
449
|
const network = preset.caip2;
|
|
@@ -276,18 +462,18 @@ function makeNearNetwork(preset, rpcUrl) {
|
|
|
276
462
|
logs: _nullishCoalesce(r.outcome.logs, () => ( []))
|
|
277
463
|
}));
|
|
278
464
|
const txn = outcome.transaction;
|
|
279
|
-
const receiverId = _optionalChain([txn, 'optionalAccess',
|
|
280
|
-
const nativeDeposit = sumTransferDeposits(_optionalChain([txn, 'optionalAccess',
|
|
465
|
+
const receiverId = _optionalChain([txn, 'optionalAccess', _10 => _10.receiver_id]);
|
|
466
|
+
const nativeDeposit = sumTransferDeposits(_optionalChain([txn, 'optionalAccess', _11 => _11.actions])).toString();
|
|
281
467
|
let timestampMs;
|
|
282
468
|
try {
|
|
283
|
-
const blockHash = _optionalChain([outcome, 'access',
|
|
469
|
+
const blockHash = _optionalChain([outcome, 'access', _12 => _12.transaction_outcome, 'optionalAccess', _13 => _13.block_hash]);
|
|
284
470
|
if (blockHash) {
|
|
285
471
|
const block = await provider.viewBlock({ blockId: blockHash });
|
|
286
472
|
const header = block.header;
|
|
287
|
-
const ns = _nullishCoalesce(_optionalChain([header, 'optionalAccess',
|
|
473
|
+
const ns = _nullishCoalesce(_optionalChain([header, 'optionalAccess', _14 => _14.timestamp_nanosec]), () => ( (_optionalChain([header, 'optionalAccess', _15 => _15.timestamp]) != null ? String(header.timestamp) : void 0)));
|
|
288
474
|
if (ns != null) timestampMs = Number(BigInt(ns) / 1000000n);
|
|
289
475
|
}
|
|
290
|
-
} catch (
|
|
476
|
+
} catch (e10) {
|
|
291
477
|
}
|
|
292
478
|
return { success, receipts, receiverId, nativeDeposit, timestampMs };
|
|
293
479
|
}
|
|
@@ -349,7 +535,7 @@ function makeNearNetwork(preset, rpcUrl) {
|
|
|
349
535
|
const { accountId, signer } = resolveNearWallet(wallet._native);
|
|
350
536
|
const account = new (0, _nearapijs.Account)(accountId, provider, signer);
|
|
351
537
|
const hashOf = (outcome) => {
|
|
352
|
-
const hash2 = _optionalChain([outcome, 'access',
|
|
538
|
+
const hash2 = _optionalChain([outcome, 'access', _16 => _16.transaction, 'optionalAccess', _17 => _17.hash]);
|
|
353
539
|
if (!hash2) throw new Error("NEAR: signAndSendTransaction returned no tx hash.");
|
|
354
540
|
return hash2;
|
|
355
541
|
};
|
|
@@ -379,7 +565,7 @@ function makeNearNetwork(preset, rpcUrl) {
|
|
|
379
565
|
try {
|
|
380
566
|
const tx = await reader.txStatus(hash, senderId);
|
|
381
567
|
if (tx && tx.success) return { height: "0" };
|
|
382
|
-
} catch (
|
|
568
|
+
} catch (e11) {
|
|
383
569
|
}
|
|
384
570
|
throw new (0, _chunkJG6KRAW6cjs.ConfirmationTimeoutError)(`NEAR tx ${hash} not confirmed in time.`);
|
|
385
571
|
},
|
|
@@ -396,7 +582,7 @@ function makeNearNetwork(preset, rpcUrl) {
|
|
|
396
582
|
let accountId;
|
|
397
583
|
try {
|
|
398
584
|
accountId = resolveNearWallet(wallet._native).accountId;
|
|
399
|
-
} catch (
|
|
585
|
+
} catch (e12) {
|
|
400
586
|
return { token: null, native: null };
|
|
401
587
|
}
|
|
402
588
|
let native = null;
|
|
@@ -408,7 +594,7 @@ function makeNearNetwork(preset, rpcUrl) {
|
|
|
408
594
|
});
|
|
409
595
|
native = r.amount != null ? BigInt(r.amount) : null;
|
|
410
596
|
} catch (e) {
|
|
411
|
-
native = /does not exist|UNKNOWN_ACCOUNT/i.test(String(_nullishCoalesce(_optionalChain([e, 'optionalAccess',
|
|
597
|
+
native = /does not exist|UNKNOWN_ACCOUNT/i.test(String(_nullishCoalesce(_optionalChain([e, 'optionalAccess', _18 => _18.message]), () => ( e)))) ? 0n : null;
|
|
412
598
|
}
|
|
413
599
|
if (asset === "native") return { token: native, native };
|
|
414
600
|
let token = null;
|
|
@@ -422,7 +608,7 @@ function makeNearNetwork(preset, rpcUrl) {
|
|
|
422
608
|
});
|
|
423
609
|
token = r.result ? BigInt(JSON.parse(Buffer.from(r.result).toString())) : null;
|
|
424
610
|
} catch (e) {
|
|
425
|
-
token = /does not exist|not registered|UNKNOWN_ACCOUNT/i.test(String(_nullishCoalesce(_optionalChain([e, 'optionalAccess',
|
|
611
|
+
token = /does not exist|not registered|UNKNOWN_ACCOUNT/i.test(String(_nullishCoalesce(_optionalChain([e, 'optionalAccess', _19 => _19.message]), () => ( e)))) ? 0n : null;
|
|
426
612
|
}
|
|
427
613
|
return { token, native };
|
|
428
614
|
},
|
|
@@ -438,13 +624,103 @@ function makeNearNetwork(preset, rpcUrl) {
|
|
|
438
624
|
});
|
|
439
625
|
const parsed = r.result ? JSON.parse(Buffer.from(r.result).toString()) : null;
|
|
440
626
|
return parsed == null ? { ready: false, reason: "NOT_REGISTERED" } : { ready: true };
|
|
441
|
-
} catch (
|
|
627
|
+
} catch (e13) {
|
|
442
628
|
return { ready: "unknown" };
|
|
443
629
|
}
|
|
444
630
|
},
|
|
445
631
|
async verify(ref, accept) {
|
|
446
632
|
const { senderId, hash } = decodeRef(ref);
|
|
447
633
|
return verifyNear({ reader, hash, senderId, accept });
|
|
634
|
+
},
|
|
635
|
+
// Standard x402 `exact` rail, BUYER side — build a NEP-366 SignedDelegateAction authorizing one
|
|
636
|
+
// NEP-141 ft_transfer (per scheme_exact_near.md). The buyer signs with its FULL-ACCESS key and
|
|
637
|
+
// spends ZERO NEAR; a keyless facilitator's relayer prepays the gas + 1 yoctoNEAR and submits.
|
|
638
|
+
// NEAR exact is NEP-141-only + facilitator-settled (see ./exact.ts for why) — native NEAR isn't
|
|
639
|
+
// exact-payable. Pre-reads the access-key nonce + final block height, then defers to payExactNear.
|
|
640
|
+
async payExact(wallet, accept) {
|
|
641
|
+
const { accountId, signer } = resolveNearWallet(wallet._native);
|
|
642
|
+
const publicKey = await signer.getPublicKey();
|
|
643
|
+
const [accessKey, block] = await Promise.all([
|
|
644
|
+
provider.query({
|
|
645
|
+
request_type: "view_access_key",
|
|
646
|
+
finality: "final",
|
|
647
|
+
account_id: accountId,
|
|
648
|
+
public_key: publicKey.toString()
|
|
649
|
+
}),
|
|
650
|
+
provider.viewBlock({ finality: "final" })
|
|
651
|
+
]);
|
|
652
|
+
const accessKeyNonce = BigInt(_nullishCoalesce(accessKey.nonce, () => ( 0)));
|
|
653
|
+
const blockHeight = BigInt(_nullishCoalesce(_optionalChain([block, 'access', _20 => _20.header, 'optionalAccess', _21 => _21.height]), () => ( 0)));
|
|
654
|
+
const { payload, payerFrom, nonce } = await payExactNear({
|
|
655
|
+
signer,
|
|
656
|
+
senderId: accountId,
|
|
657
|
+
blockHeight,
|
|
658
|
+
accessKeyNonce,
|
|
659
|
+
accept
|
|
660
|
+
});
|
|
661
|
+
return { payload, accepted: accept, payerFrom, nonce };
|
|
662
|
+
},
|
|
663
|
+
// The gate's rail-advertisement SPI. NEAR exact is NEP-141-only. The fee payer (the relayer that
|
|
664
|
+
// prepays gas + the 1 yocto, so the BUYER pays nothing) comes from EITHER the merchant's own bound
|
|
665
|
+
// `relayer` (SELF mode — what works today) OR a facilitator-provided `feePayer` (facilitator mode —
|
|
666
|
+
// for when a NEAR x402 facilitator ships; none does yet). `null` for native / non-NEP-141 / when no
|
|
667
|
+
// fee payer is available. The buyer's SignedDelegateAction is self-contained, so `feePayer` rides in
|
|
668
|
+
// `extra` only for observability + (future) facilitator forwarding.
|
|
669
|
+
async resolveExactRail({ asset, relayer, feePayer }) {
|
|
670
|
+
if (asset === "native" || !isValidNearAccountId(asset)) return null;
|
|
671
|
+
let fp = feePayer;
|
|
672
|
+
if (!fp && relayer) {
|
|
673
|
+
try {
|
|
674
|
+
fp = resolveNearWallet(relayer._native).accountId;
|
|
675
|
+
} catch (e14) {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (!fp) return null;
|
|
680
|
+
return { method: "near", extra: { feePayer: fp } };
|
|
681
|
+
},
|
|
682
|
+
// Standard x402 `exact` rail, SELLER side (SELF-SETTLE) — decode + verify the inbound delegate
|
|
683
|
+
// against the trusted accept, then RELAY it from the merchant's own NEAR relayer (which prepays the
|
|
684
|
+
// sub-cent gas + the 1 yocto). The buyer stays gasless. THIS is how a NEAR gate gets paid today
|
|
685
|
+
// (no third-party facilitator settles NEAR yet). The drain guard (gas/deposit caps) lives in
|
|
686
|
+
// verifyAndSettleExactNear, invisible to the gate.
|
|
687
|
+
async settleExactSelf({ relayer, payload, accept }) {
|
|
688
|
+
if (!("signedDelegateAction" in payload)) {
|
|
689
|
+
return { ok: false, error: "signature_invalid", detail: "NEAR exact expects a { signedDelegateAction } payload." };
|
|
690
|
+
}
|
|
691
|
+
let relayerWallet;
|
|
692
|
+
try {
|
|
693
|
+
relayerWallet = resolveNearWallet(relayer._native);
|
|
694
|
+
} catch (err) {
|
|
695
|
+
throw new (0, _chunkJG6KRAW6cjs.SettlementError)(
|
|
696
|
+
`NEAR exact settle: the relayer wallet is invalid (${err instanceof Error ? err.message : String(err)}).`,
|
|
697
|
+
{ cause: err }
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
const relayerAccount = new (0, _nearapijs.Account)(relayerWallet.accountId, provider, relayerWallet.signer);
|
|
701
|
+
const isSuccess = (s) => typeof s === "object" && s !== null && "SuccessValue" in s;
|
|
702
|
+
const client = {
|
|
703
|
+
async currentBlockHeight() {
|
|
704
|
+
try {
|
|
705
|
+
const b = await provider.viewBlock({ finality: "final" });
|
|
706
|
+
return _optionalChain([b, 'access', _22 => _22.header, 'optionalAccess', _23 => _23.height]) != null ? BigInt(b.header.height) : null;
|
|
707
|
+
} catch (e15) {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
async relay({ senderId, delegateAction, signature }) {
|
|
712
|
+
const outcome = await relayerAccount.signAndSendTransaction({
|
|
713
|
+
receiverId: senderId,
|
|
714
|
+
actions: [_nearapijs.actions.signedDelegate({ delegateAction, signature })],
|
|
715
|
+
waitUntil: "FINAL"
|
|
716
|
+
});
|
|
717
|
+
const txHash = _nullishCoalesce(_optionalChain([outcome, 'access', _24 => _24.transaction, 'optionalAccess', _25 => _25.hash]), () => ( ""));
|
|
718
|
+
const tokenReceipt = (_nullishCoalesce(outcome.receipts_outcome, () => ( []))).find((r) => _optionalChain([r, 'access', _26 => _26.outcome, 'optionalAccess', _27 => _27.executor_id]) === accept.asset);
|
|
719
|
+
const innerSuccess = tokenReceipt ? isSuccess(_optionalChain([tokenReceipt, 'access', _28 => _28.outcome, 'optionalAccess', _29 => _29.status])) : isSuccess(outcome.status);
|
|
720
|
+
return { txHash, innerSuccess };
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
return verifyAndSettleExactNear({ client, payload, accept });
|
|
448
724
|
}
|
|
449
725
|
};
|
|
450
726
|
}
|
|
@@ -456,15 +732,15 @@ function decodeRef(ref) {
|
|
|
456
732
|
if (i < 0) return { senderId: "", hash: ref };
|
|
457
733
|
return { senderId: ref.slice(0, i), hash: ref.slice(i + 1) };
|
|
458
734
|
}
|
|
459
|
-
function sumTransferDeposits(
|
|
460
|
-
if (!Array.isArray(
|
|
735
|
+
function sumTransferDeposits(actions3) {
|
|
736
|
+
if (!Array.isArray(actions3)) return 0n;
|
|
461
737
|
let sum = 0n;
|
|
462
|
-
for (const a of
|
|
738
|
+
for (const a of actions3) {
|
|
463
739
|
const t = _nullishCoalesce(a.Transfer, () => ( a.transfer));
|
|
464
740
|
if (t && t.deposit != null) {
|
|
465
741
|
try {
|
|
466
742
|
sum += BigInt(t.deposit);
|
|
467
|
-
} catch (
|
|
743
|
+
} catch (e16) {
|
|
468
744
|
}
|
|
469
745
|
}
|
|
470
746
|
}
|
|
@@ -2,7 +2,9 @@ import {
|
|
|
2
2
|
ConfirmationTimeoutError,
|
|
3
3
|
InsufficientFundsError,
|
|
4
4
|
RecipientNotReadyError,
|
|
5
|
+
SettlementError,
|
|
5
6
|
UnknownTokenError,
|
|
7
|
+
UnsupportedSchemeError,
|
|
6
8
|
WrongFamilyError,
|
|
7
9
|
assertNoLegacyWalletKey,
|
|
8
10
|
nativeCost,
|
|
@@ -11,10 +13,15 @@ import {
|
|
|
11
13
|
} from "./chunk-7XK22JSQ.js";
|
|
12
14
|
|
|
13
15
|
// src/drivers/near/index.ts
|
|
14
|
-
import { JsonRpcProvider, Account, actions } from "near-api-js";
|
|
16
|
+
import { JsonRpcProvider, Account, actions as actions2 } from "near-api-js";
|
|
15
17
|
|
|
16
18
|
// src/drivers/near/chains.ts
|
|
17
19
|
var NEAR_DECIMALS = 24;
|
|
20
|
+
function isValidNearAccountId(id) {
|
|
21
|
+
if (id.startsWith("0x")) return false;
|
|
22
|
+
if (id.length < 2 || id.length > 64) return false;
|
|
23
|
+
return /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/.test(id);
|
|
24
|
+
}
|
|
18
25
|
var NEAR_MAINNET = {
|
|
19
26
|
caip2: "near:mainnet",
|
|
20
27
|
defaultRpc: "https://free.rpc.fastnear.com",
|
|
@@ -92,6 +99,190 @@ function isNearAffordability(err) {
|
|
|
92
99
|
return /not enough|doesn't have enough|does not have enough|lack ?balance|exceeds .*balance|insufficient/i.test(m);
|
|
93
100
|
}
|
|
94
101
|
|
|
102
|
+
// src/drivers/near/exact.ts
|
|
103
|
+
import { actions, buildDelegateAction, encodeSignedDelegate, DelegateAction, SCHEMA } from "near-api-js";
|
|
104
|
+
import { deserialize } from "borsh";
|
|
105
|
+
var FT_TRANSFER_GAS2 = 30000000000000n;
|
|
106
|
+
var ONE_YOCTO2 = 1n;
|
|
107
|
+
var ESTIMATED_BLOCK_SECONDS = 1;
|
|
108
|
+
var NONCE_BLOCK_MULTIPLIER = 1000000n;
|
|
109
|
+
async function payExactNear(input) {
|
|
110
|
+
const { signer, senderId, blockHeight, accessKeyNonce, accept } = input;
|
|
111
|
+
if (accept.asset === "native") {
|
|
112
|
+
throw new UnsupportedSchemeError(
|
|
113
|
+
"NEAR exact is NEP-141-only (an ft_transfer); native NEAR is not exact-payable. Pay via onchain-proof."
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (!isValidNearAccountId(accept.asset)) {
|
|
117
|
+
throw new UnsupportedSchemeError(`NEAR exact: asset "${accept.asset}" must be a NEP-141 contract account id.`);
|
|
118
|
+
}
|
|
119
|
+
if (!isValidNearAccountId(accept.payTo)) {
|
|
120
|
+
throw new UnsupportedSchemeError(`NEAR exact: payTo "${accept.payTo}" is not a valid NEAR account id.`);
|
|
121
|
+
}
|
|
122
|
+
if (!isValidNearAccountId(senderId)) {
|
|
123
|
+
throw new UnsupportedSchemeError(`NEAR exact: sender "${senderId}" is not a valid NEAR account id.`);
|
|
124
|
+
}
|
|
125
|
+
const t = accept.maxTimeoutSeconds;
|
|
126
|
+
if (!Number.isInteger(t) || t <= 0) {
|
|
127
|
+
throw new UnsupportedSchemeError("NEAR exact: maxTimeoutSeconds must be a positive integer.");
|
|
128
|
+
}
|
|
129
|
+
const timeoutBlocks = BigInt(Math.max(1, Math.ceil(t / ESTIMATED_BLOCK_SECONDS)));
|
|
130
|
+
const maxBlockHeight = blockHeight + timeoutBlocks;
|
|
131
|
+
const nonce = accessKeyNonce + 1n;
|
|
132
|
+
if (nonce >= blockHeight * NONCE_BLOCK_MULTIPLIER) {
|
|
133
|
+
throw new UnsupportedSchemeError(
|
|
134
|
+
"NEAR exact: the access-key nonce is at the protocol ceiling for this block height; cannot build a delegate action."
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const publicKey = await signer.getPublicKey();
|
|
138
|
+
const action = actions.functionCall(
|
|
139
|
+
"ft_transfer",
|
|
140
|
+
{ receiver_id: accept.payTo, amount: accept.amount },
|
|
141
|
+
FT_TRANSFER_GAS2,
|
|
142
|
+
ONE_YOCTO2
|
|
143
|
+
);
|
|
144
|
+
const delegateAction = buildDelegateAction({
|
|
145
|
+
senderId,
|
|
146
|
+
receiverId: accept.asset,
|
|
147
|
+
actions: [action],
|
|
148
|
+
nonce,
|
|
149
|
+
maxBlockHeight,
|
|
150
|
+
publicKey
|
|
151
|
+
});
|
|
152
|
+
const { signedDelegate } = await signer.signDelegateAction(delegateAction);
|
|
153
|
+
const encoded = encodeSignedDelegate(signedDelegate);
|
|
154
|
+
return {
|
|
155
|
+
payload: { signedDelegateAction: Buffer.from(encoded).toString("base64") },
|
|
156
|
+
payerFrom: senderId,
|
|
157
|
+
// A stable id for THIS authorization (single-use on-chain via the access-key nonce): the
|
|
158
|
+
// client records it as the spend ref and re-presents the SAME signed action on a retry.
|
|
159
|
+
nonce: `${senderId}:${nonce.toString()}`
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
var MAX_RELAY_GAS = 300000000000000n;
|
|
163
|
+
var b64decode = (s) => new Uint8Array(Buffer.from(s, "base64"));
|
|
164
|
+
function shorten(msg) {
|
|
165
|
+
const oneLine = msg.replace(/\s+/g, " ").trim();
|
|
166
|
+
return oneLine.length > 200 ? `${oneLine.slice(0, 200)}\u2026` : oneLine;
|
|
167
|
+
}
|
|
168
|
+
function fail(error, detail) {
|
|
169
|
+
return { ok: false, error, detail };
|
|
170
|
+
}
|
|
171
|
+
function verifyExactNear(payload, accept, currentBlockHeight) {
|
|
172
|
+
if (accept.asset === "native" || !isValidNearAccountId(accept.asset)) {
|
|
173
|
+
return fail("transfer_not_found", `NEAR exact rail asset "${accept.asset}" is not a NEP-141 contract.`);
|
|
174
|
+
}
|
|
175
|
+
if (typeof payload?.signedDelegateAction !== "string") {
|
|
176
|
+
return fail("signature_invalid", "NEAR exact payload is missing signedDelegateAction.");
|
|
177
|
+
}
|
|
178
|
+
let decoded;
|
|
179
|
+
try {
|
|
180
|
+
decoded = deserialize(SCHEMA.SignedDelegate, b64decode(payload.signedDelegateAction));
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return fail("signature_invalid", `Unparseable SignedDelegateAction: ${shorten(err instanceof Error ? err.message : String(err))}.`);
|
|
183
|
+
}
|
|
184
|
+
const da = decoded?.delegateAction;
|
|
185
|
+
const sig = decoded?.signature;
|
|
186
|
+
if (!da || sig == null) return fail("signature_invalid", "SignedDelegateAction is missing its delegate action or signature.");
|
|
187
|
+
if (!Array.isArray(da.actions) || da.actions.length !== 1) {
|
|
188
|
+
return fail("transfer_not_found", `the delegate must carry exactly one action, got ${da.actions?.length ?? 0}.`);
|
|
189
|
+
}
|
|
190
|
+
const fc = da.actions[0]?.functionCall;
|
|
191
|
+
if (!fc || fc.methodName !== "ft_transfer") {
|
|
192
|
+
return fail("transfer_not_found", `the delegated action is not an ft_transfer (got "${fc?.methodName ?? "none"}").`);
|
|
193
|
+
}
|
|
194
|
+
if (da.receiverId !== accept.asset) {
|
|
195
|
+
return fail("transfer_not_found", `delegate receiver ${da.receiverId} \u2260 token contract ${accept.asset}.`);
|
|
196
|
+
}
|
|
197
|
+
let args;
|
|
198
|
+
try {
|
|
199
|
+
args = JSON.parse(Buffer.from(fc.args ?? new Uint8Array()).toString());
|
|
200
|
+
} catch {
|
|
201
|
+
return fail("transfer_not_found", "the ft_transfer args are not valid JSON.");
|
|
202
|
+
}
|
|
203
|
+
if (args.receiver_id !== accept.payTo) {
|
|
204
|
+
return fail("wrong_recipient", `ft_transfer pays ${String(args.receiver_id)}, not payTo ${accept.payTo}.`);
|
|
205
|
+
}
|
|
206
|
+
if (typeof args.amount !== "string" || !/^\d+$/.test(args.amount) || BigInt(args.amount) < BigInt(accept.amount)) {
|
|
207
|
+
return fail("amount_too_low", `ft_transfer pays ${String(args.amount)} of the token, required ${accept.amount}.`);
|
|
208
|
+
}
|
|
209
|
+
let deposit;
|
|
210
|
+
let gas;
|
|
211
|
+
try {
|
|
212
|
+
deposit = BigInt(fc.deposit ?? 0);
|
|
213
|
+
gas = BigInt(fc.gas ?? 0);
|
|
214
|
+
} catch {
|
|
215
|
+
return fail("signature_invalid", "the ft_transfer has a malformed gas/deposit.");
|
|
216
|
+
}
|
|
217
|
+
if (deposit !== ONE_YOCTO2) {
|
|
218
|
+
return fail("signature_invalid", `attached deposit ${deposit} \u2260 the required 1 yoctoNEAR (relayer drain guard).`);
|
|
219
|
+
}
|
|
220
|
+
if (gas > MAX_RELAY_GAS) {
|
|
221
|
+
return fail("signature_invalid", `delegated gas ${gas} exceeds the ${MAX_RELAY_GAS} cap (relayer drain guard).`);
|
|
222
|
+
}
|
|
223
|
+
if (currentBlockHeight !== null) {
|
|
224
|
+
let maxBlock;
|
|
225
|
+
try {
|
|
226
|
+
maxBlock = BigInt(da.maxBlockHeight ?? 0);
|
|
227
|
+
} catch {
|
|
228
|
+
return fail("signature_invalid", "the delegate has a malformed max_block_height.");
|
|
229
|
+
}
|
|
230
|
+
if (maxBlock <= currentBlockHeight) {
|
|
231
|
+
return fail("payment_expired", `the delegate expired (max_block_height ${maxBlock} \u2264 current ${currentBlockHeight}).`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (!da.senderId || !isValidNearAccountId(String(da.senderId))) {
|
|
235
|
+
return fail("signature_invalid", `the delegate sender "${String(da.senderId)}" is not a valid NEAR account id.`);
|
|
236
|
+
}
|
|
237
|
+
return { ok: true, senderId: String(da.senderId), delegateAction: new DelegateAction(da), signature: sig };
|
|
238
|
+
}
|
|
239
|
+
async function verifyAndSettleExactNear(input) {
|
|
240
|
+
const { client, payload, accept } = input;
|
|
241
|
+
let height = null;
|
|
242
|
+
try {
|
|
243
|
+
height = await client.currentBlockHeight();
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
const v = verifyExactNear(payload, accept, height);
|
|
247
|
+
if (!v.ok) return v;
|
|
248
|
+
let result;
|
|
249
|
+
try {
|
|
250
|
+
result = await client.relay({ senderId: v.senderId, delegateAction: v.delegateAction, signature: v.signature });
|
|
251
|
+
} catch (err) {
|
|
252
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
253
|
+
if (/invalid.*nonce|nonce.*used|DelegateActionInvalidNonce/i.test(m)) {
|
|
254
|
+
return fail("tx_already_used", `this delegate was already used (single-use nonce): ${shorten(m)}.`);
|
|
255
|
+
}
|
|
256
|
+
if (/expired|DelegateActionExpired|max_block_height|deadline/i.test(m)) {
|
|
257
|
+
return fail("payment_expired", `the delegate expired before it landed: ${shorten(m)}.`);
|
|
258
|
+
}
|
|
259
|
+
if (/not enough|insufficient|exceeded the prepaid gas|NotEnoughBalance|doesn't have enough|is not registered/i.test(m)) {
|
|
260
|
+
return fail("tx_reverted", `the ft_transfer would fail on chain: ${shorten(m)}.`);
|
|
261
|
+
}
|
|
262
|
+
throw new SettlementError(
|
|
263
|
+
`NEAR exact settle: the relayer could not submit the delegate (${shorten(m)}). The buyer's signed delegate is still valid \u2014 fund/fix the relayer and the buyer can re-present it.`,
|
|
264
|
+
{ cause: err }
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
if (!result.innerSuccess) {
|
|
268
|
+
return fail("tx_reverted", `the ft_transfer receipt did not succeed on chain (tx ${result.txHash}).`);
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
ok: true,
|
|
272
|
+
receipt: {
|
|
273
|
+
scheme: "exact",
|
|
274
|
+
success: true,
|
|
275
|
+
network: accept.network,
|
|
276
|
+
transaction: result.txHash,
|
|
277
|
+
asset: accept.asset,
|
|
278
|
+
amount: accept.amount,
|
|
279
|
+
payer: v.senderId,
|
|
280
|
+
payTo: accept.payTo,
|
|
281
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
95
286
|
// src/drivers/near/verify.ts
|
|
96
287
|
function parseFtTransferEvent(line) {
|
|
97
288
|
const marker = "EVENT_JSON:";
|
|
@@ -253,11 +444,6 @@ var nearDriver = {
|
|
|
253
444
|
return makeNearNetwork(NEAR_MAINNET, rpcUrl);
|
|
254
445
|
}
|
|
255
446
|
};
|
|
256
|
-
function isValidNearAccountId(id) {
|
|
257
|
-
if (id.startsWith("0x")) return false;
|
|
258
|
-
if (id.length < 2 || id.length > 64) return false;
|
|
259
|
-
return /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/.test(id);
|
|
260
|
-
}
|
|
261
447
|
function makeNearNetwork(preset, rpcUrl) {
|
|
262
448
|
const provider = new JsonRpcProvider({ url: rpcUrl });
|
|
263
449
|
const network = preset.caip2;
|
|
@@ -358,7 +544,7 @@ function makeNearNetwork(preset, rpcUrl) {
|
|
|
358
544
|
const outcome = await account.signAndSendTransaction({
|
|
359
545
|
receiverId: contractId,
|
|
360
546
|
actions: [
|
|
361
|
-
|
|
547
|
+
actions2.functionCall("ft_transfer", { receiver_id: receiverId, amount, memo }, gas, deposit)
|
|
362
548
|
]
|
|
363
549
|
});
|
|
364
550
|
return { hash: hashOf(outcome) };
|
|
@@ -366,7 +552,7 @@ function makeNearNetwork(preset, rpcUrl) {
|
|
|
366
552
|
async nativeTransfer({ receiverId, amount }) {
|
|
367
553
|
const outcome = await account.signAndSendTransaction({
|
|
368
554
|
receiverId,
|
|
369
|
-
actions: [
|
|
555
|
+
actions: [actions2.transfer(BigInt(amount))]
|
|
370
556
|
});
|
|
371
557
|
return { hash: hashOf(outcome) };
|
|
372
558
|
}
|
|
@@ -445,6 +631,96 @@ function makeNearNetwork(preset, rpcUrl) {
|
|
|
445
631
|
async verify(ref, accept) {
|
|
446
632
|
const { senderId, hash } = decodeRef(ref);
|
|
447
633
|
return verifyNear({ reader, hash, senderId, accept });
|
|
634
|
+
},
|
|
635
|
+
// Standard x402 `exact` rail, BUYER side — build a NEP-366 SignedDelegateAction authorizing one
|
|
636
|
+
// NEP-141 ft_transfer (per scheme_exact_near.md). The buyer signs with its FULL-ACCESS key and
|
|
637
|
+
// spends ZERO NEAR; a keyless facilitator's relayer prepays the gas + 1 yoctoNEAR and submits.
|
|
638
|
+
// NEAR exact is NEP-141-only + facilitator-settled (see ./exact.ts for why) — native NEAR isn't
|
|
639
|
+
// exact-payable. Pre-reads the access-key nonce + final block height, then defers to payExactNear.
|
|
640
|
+
async payExact(wallet, accept) {
|
|
641
|
+
const { accountId, signer } = resolveNearWallet(wallet._native);
|
|
642
|
+
const publicKey = await signer.getPublicKey();
|
|
643
|
+
const [accessKey, block] = await Promise.all([
|
|
644
|
+
provider.query({
|
|
645
|
+
request_type: "view_access_key",
|
|
646
|
+
finality: "final",
|
|
647
|
+
account_id: accountId,
|
|
648
|
+
public_key: publicKey.toString()
|
|
649
|
+
}),
|
|
650
|
+
provider.viewBlock({ finality: "final" })
|
|
651
|
+
]);
|
|
652
|
+
const accessKeyNonce = BigInt(accessKey.nonce ?? 0);
|
|
653
|
+
const blockHeight = BigInt(block.header?.height ?? 0);
|
|
654
|
+
const { payload, payerFrom, nonce } = await payExactNear({
|
|
655
|
+
signer,
|
|
656
|
+
senderId: accountId,
|
|
657
|
+
blockHeight,
|
|
658
|
+
accessKeyNonce,
|
|
659
|
+
accept
|
|
660
|
+
});
|
|
661
|
+
return { payload, accepted: accept, payerFrom, nonce };
|
|
662
|
+
},
|
|
663
|
+
// The gate's rail-advertisement SPI. NEAR exact is NEP-141-only. The fee payer (the relayer that
|
|
664
|
+
// prepays gas + the 1 yocto, so the BUYER pays nothing) comes from EITHER the merchant's own bound
|
|
665
|
+
// `relayer` (SELF mode — what works today) OR a facilitator-provided `feePayer` (facilitator mode —
|
|
666
|
+
// for when a NEAR x402 facilitator ships; none does yet). `null` for native / non-NEP-141 / when no
|
|
667
|
+
// fee payer is available. The buyer's SignedDelegateAction is self-contained, so `feePayer` rides in
|
|
668
|
+
// `extra` only for observability + (future) facilitator forwarding.
|
|
669
|
+
async resolveExactRail({ asset, relayer, feePayer }) {
|
|
670
|
+
if (asset === "native" || !isValidNearAccountId(asset)) return null;
|
|
671
|
+
let fp = feePayer;
|
|
672
|
+
if (!fp && relayer) {
|
|
673
|
+
try {
|
|
674
|
+
fp = resolveNearWallet(relayer._native).accountId;
|
|
675
|
+
} catch {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (!fp) return null;
|
|
680
|
+
return { method: "near", extra: { feePayer: fp } };
|
|
681
|
+
},
|
|
682
|
+
// Standard x402 `exact` rail, SELLER side (SELF-SETTLE) — decode + verify the inbound delegate
|
|
683
|
+
// against the trusted accept, then RELAY it from the merchant's own NEAR relayer (which prepays the
|
|
684
|
+
// sub-cent gas + the 1 yocto). The buyer stays gasless. THIS is how a NEAR gate gets paid today
|
|
685
|
+
// (no third-party facilitator settles NEAR yet). The drain guard (gas/deposit caps) lives in
|
|
686
|
+
// verifyAndSettleExactNear, invisible to the gate.
|
|
687
|
+
async settleExactSelf({ relayer, payload, accept }) {
|
|
688
|
+
if (!("signedDelegateAction" in payload)) {
|
|
689
|
+
return { ok: false, error: "signature_invalid", detail: "NEAR exact expects a { signedDelegateAction } payload." };
|
|
690
|
+
}
|
|
691
|
+
let relayerWallet;
|
|
692
|
+
try {
|
|
693
|
+
relayerWallet = resolveNearWallet(relayer._native);
|
|
694
|
+
} catch (err) {
|
|
695
|
+
throw new SettlementError(
|
|
696
|
+
`NEAR exact settle: the relayer wallet is invalid (${err instanceof Error ? err.message : String(err)}).`,
|
|
697
|
+
{ cause: err }
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
const relayerAccount = new Account(relayerWallet.accountId, provider, relayerWallet.signer);
|
|
701
|
+
const isSuccess = (s) => typeof s === "object" && s !== null && "SuccessValue" in s;
|
|
702
|
+
const client = {
|
|
703
|
+
async currentBlockHeight() {
|
|
704
|
+
try {
|
|
705
|
+
const b = await provider.viewBlock({ finality: "final" });
|
|
706
|
+
return b.header?.height != null ? BigInt(b.header.height) : null;
|
|
707
|
+
} catch {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
async relay({ senderId, delegateAction, signature }) {
|
|
712
|
+
const outcome = await relayerAccount.signAndSendTransaction({
|
|
713
|
+
receiverId: senderId,
|
|
714
|
+
actions: [actions2.signedDelegate({ delegateAction, signature })],
|
|
715
|
+
waitUntil: "FINAL"
|
|
716
|
+
});
|
|
717
|
+
const txHash = outcome.transaction?.hash ?? "";
|
|
718
|
+
const tokenReceipt = (outcome.receipts_outcome ?? []).find((r) => r.outcome?.executor_id === accept.asset);
|
|
719
|
+
const innerSuccess = tokenReceipt ? isSuccess(tokenReceipt.outcome?.status) : isSuccess(outcome.status);
|
|
720
|
+
return { txHash, innerSuccess };
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
return verifyAndSettleExactNear({ client, payload, accept });
|
|
448
724
|
}
|
|
449
725
|
};
|
|
450
726
|
}
|
|
@@ -456,10 +732,10 @@ function decodeRef(ref) {
|
|
|
456
732
|
if (i < 0) return { senderId: "", hash: ref };
|
|
457
733
|
return { senderId: ref.slice(0, i), hash: ref.slice(i + 1) };
|
|
458
734
|
}
|
|
459
|
-
function sumTransferDeposits(
|
|
460
|
-
if (!Array.isArray(
|
|
735
|
+
function sumTransferDeposits(actions3) {
|
|
736
|
+
if (!Array.isArray(actions3)) return 0n;
|
|
461
737
|
let sum = 0n;
|
|
462
|
-
for (const a of
|
|
738
|
+
for (const a of actions3) {
|
|
463
739
|
const t = a.Transfer ?? a.transfer;
|
|
464
740
|
if (t && t.deposit != null) {
|
|
465
741
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@piprail/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Accept x402 crypto payments across 29 chains — every major EVM chain plus Solana, TON, Tron, NEAR, Sui, Aptos, Algorand, Stellar & XRPL — in a couple of lines. No backend, no database, no fee; payments settle straight to your wallet.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
"@ton/crypto": ">=3 <4",
|
|
87
87
|
"@ton/ton": ">=15 <17",
|
|
88
88
|
"algosdk": ">=3 <4",
|
|
89
|
+
"borsh": ">=2 <3",
|
|
89
90
|
"bs58": "^5.0.0",
|
|
90
91
|
"near-api-js": ">=7 <8",
|
|
91
92
|
"tronweb": ">=6 <7",
|
|
@@ -131,6 +132,9 @@
|
|
|
131
132
|
},
|
|
132
133
|
"algosdk": {
|
|
133
134
|
"optional": true
|
|
135
|
+
},
|
|
136
|
+
"borsh": {
|
|
137
|
+
"optional": true
|
|
134
138
|
}
|
|
135
139
|
},
|
|
136
140
|
"devDependencies": {
|
|
@@ -144,6 +148,7 @@
|
|
|
144
148
|
"@ton/ton": "^16.2.4",
|
|
145
149
|
"@types/node": "^22.10.0",
|
|
146
150
|
"algosdk": "^3.5.2",
|
|
151
|
+
"borsh": "^2.0.0",
|
|
147
152
|
"bs58": "^5.0.0",
|
|
148
153
|
"near-api-js": "^7.2.0",
|
|
149
154
|
"tronweb": "^6.3.0",
|