@t402/near 2.3.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/README.md +173 -0
- package/dist/cjs/exact-direct/client/index.d.ts +105 -0
- package/dist/cjs/exact-direct/client/index.js +156 -0
- package/dist/cjs/exact-direct/client/index.js.map +1 -0
- package/dist/cjs/exact-direct/facilitator/index.d.ts +114 -0
- package/dist/cjs/exact-direct/facilitator/index.js +304 -0
- package/dist/cjs/exact-direct/facilitator/index.js.map +1 -0
- package/dist/cjs/exact-direct/server/index.d.ts +129 -0
- package/dist/cjs/exact-direct/server/index.js +253 -0
- package/dist/cjs/exact-direct/server/index.js.map +1 -0
- package/dist/cjs/index.d.ts +226 -0
- package/dist/cjs/index.js +711 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/types-Ca7ztL_f.d.ts +169 -0
- package/dist/esm/chunk-B3RHERRA.mjs +162 -0
- package/dist/esm/chunk-B3RHERRA.mjs.map +1 -0
- package/dist/esm/chunk-BU2BQECZ.mjs +206 -0
- package/dist/esm/chunk-BU2BQECZ.mjs.map +1 -0
- package/dist/esm/chunk-G35SAYZI.mjs +67 -0
- package/dist/esm/chunk-G35SAYZI.mjs.map +1 -0
- package/dist/esm/chunk-WANNPL6S.mjs +155 -0
- package/dist/esm/chunk-WANNPL6S.mjs.map +1 -0
- package/dist/esm/chunk-YXBOH4MJ.mjs +103 -0
- package/dist/esm/chunk-YXBOH4MJ.mjs.map +1 -0
- package/dist/esm/exact-direct/client/index.d.mts +105 -0
- package/dist/esm/exact-direct/client/index.mjs +10 -0
- package/dist/esm/exact-direct/client/index.mjs.map +1 -0
- package/dist/esm/exact-direct/facilitator/index.d.mts +114 -0
- package/dist/esm/exact-direct/facilitator/index.mjs +11 -0
- package/dist/esm/exact-direct/facilitator/index.mjs.map +1 -0
- package/dist/esm/exact-direct/server/index.d.mts +129 -0
- package/dist/esm/exact-direct/server/index.mjs +11 -0
- package/dist/esm/exact-direct/server/index.mjs.map +1 -0
- package/dist/esm/index.d.mts +226 -0
- package/dist/esm/index.mjs +97 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/types-Ca7ztL_f.d.mts +169 -0
- package/package.json +97 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
DEFAULT_FT_TRANSFER_GAS: () => DEFAULT_FT_TRANSFER_GAS,
|
|
24
|
+
DEFAULT_STORAGE_DEPOSIT: () => DEFAULT_STORAGE_DEPOSIT,
|
|
25
|
+
DEFAULT_STORAGE_DEPOSIT_GAS: () => DEFAULT_STORAGE_DEPOSIT_GAS,
|
|
26
|
+
DEFAULT_VALIDITY_DURATION: () => DEFAULT_VALIDITY_DURATION,
|
|
27
|
+
ExactDirectNearClient: () => ExactDirectNearClient,
|
|
28
|
+
ExactDirectNearFacilitator: () => ExactDirectNearFacilitator,
|
|
29
|
+
ExactDirectNearServer: () => ExactDirectNearServer,
|
|
30
|
+
FT_TRANSFER_DEPOSIT: () => FT_TRANSFER_DEPOSIT,
|
|
31
|
+
MAX_TRANSACTION_AGE: () => MAX_TRANSACTION_AGE,
|
|
32
|
+
NEAR_CAIP2_NAMESPACE: () => NEAR_CAIP2_NAMESPACE,
|
|
33
|
+
NEAR_MAINNET_CAIP2: () => NEAR_MAINNET_CAIP2,
|
|
34
|
+
NEAR_MAINNET_RPC: () => NEAR_MAINNET_RPC,
|
|
35
|
+
NEAR_NETWORKS: () => NEAR_NETWORKS,
|
|
36
|
+
NEAR_NETWORK_IDS: () => NEAR_NETWORK_IDS,
|
|
37
|
+
NEAR_TESTNET_CAIP2: () => NEAR_TESTNET_CAIP2,
|
|
38
|
+
NEAR_TESTNET_RPC: () => NEAR_TESTNET_RPC,
|
|
39
|
+
NEP141_FT_BALANCE_OF: () => NEP141_FT_BALANCE_OF,
|
|
40
|
+
NEP141_FT_TRANSFER: () => NEP141_FT_TRANSFER,
|
|
41
|
+
NEP141_STORAGE_BALANCE_OF: () => NEP141_STORAGE_BALANCE_OF,
|
|
42
|
+
NEP141_STORAGE_DEPOSIT: () => NEP141_STORAGE_DEPOSIT,
|
|
43
|
+
NETWORK_RPC_ENDPOINTS: () => NETWORK_RPC_ENDPOINTS,
|
|
44
|
+
SCHEME_EXACT_DIRECT: () => SCHEME_EXACT_DIRECT,
|
|
45
|
+
TOKEN_REGISTRY: () => TOKEN_REGISTRY,
|
|
46
|
+
extractNetworkId: () => extractNetworkId,
|
|
47
|
+
formatAmount: () => formatAmount,
|
|
48
|
+
getDefaultToken: () => getDefaultToken,
|
|
49
|
+
getNetworkTokens: () => getNetworkTokens,
|
|
50
|
+
getRpcEndpoint: () => getRpcEndpoint,
|
|
51
|
+
getTokenByContract: () => getTokenByContract,
|
|
52
|
+
getTokenConfig: () => getTokenConfig,
|
|
53
|
+
isNetworkSupported: () => isNetworkSupported,
|
|
54
|
+
isTransactionSuccessful: () => isTransactionSuccessful,
|
|
55
|
+
isValidAccountId: () => isValidAccountId,
|
|
56
|
+
normalizeNetwork: () => normalizeNetwork,
|
|
57
|
+
parseFtTransferArgs: () => parseFtTransferArgs,
|
|
58
|
+
queryTokenBalance: () => queryTokenBalance,
|
|
59
|
+
queryTransaction: () => queryTransaction,
|
|
60
|
+
registerExactDirectNearClient: () => registerExactDirectNearClient,
|
|
61
|
+
registerExactDirectNearFacilitator: () => registerExactDirectNearFacilitator,
|
|
62
|
+
registerExactDirectNearServer: () => registerExactDirectNearServer,
|
|
63
|
+
rpcCall: () => rpcCall,
|
|
64
|
+
toTokenUnits: () => toTokenUnits
|
|
65
|
+
});
|
|
66
|
+
module.exports = __toCommonJS(src_exports);
|
|
67
|
+
|
|
68
|
+
// src/constants.ts
|
|
69
|
+
var NEAR_MAINNET_CAIP2 = "near:mainnet";
|
|
70
|
+
var NEAR_TESTNET_CAIP2 = "near:testnet";
|
|
71
|
+
var NEAR_NETWORKS = [NEAR_MAINNET_CAIP2, NEAR_TESTNET_CAIP2];
|
|
72
|
+
var NEAR_NETWORK_IDS = {
|
|
73
|
+
[NEAR_MAINNET_CAIP2]: "mainnet",
|
|
74
|
+
[NEAR_TESTNET_CAIP2]: "testnet"
|
|
75
|
+
};
|
|
76
|
+
var NEAR_MAINNET_RPC = "https://rpc.mainnet.near.org";
|
|
77
|
+
var NEAR_TESTNET_RPC = "https://rpc.testnet.near.org";
|
|
78
|
+
var NETWORK_RPC_ENDPOINTS = {
|
|
79
|
+
[NEAR_MAINNET_CAIP2]: NEAR_MAINNET_RPC,
|
|
80
|
+
[NEAR_TESTNET_CAIP2]: NEAR_TESTNET_RPC
|
|
81
|
+
};
|
|
82
|
+
var NEP141_FT_TRANSFER = "ft_transfer";
|
|
83
|
+
var NEP141_FT_BALANCE_OF = "ft_balance_of";
|
|
84
|
+
var NEP141_STORAGE_DEPOSIT = "storage_deposit";
|
|
85
|
+
var NEP141_STORAGE_BALANCE_OF = "storage_balance_of";
|
|
86
|
+
var DEFAULT_FT_TRANSFER_GAS = "30000000000000";
|
|
87
|
+
var DEFAULT_STORAGE_DEPOSIT_GAS = "10000000000000";
|
|
88
|
+
var FT_TRANSFER_DEPOSIT = "1";
|
|
89
|
+
var DEFAULT_STORAGE_DEPOSIT = "1250000000000000000000";
|
|
90
|
+
var SCHEME_EXACT_DIRECT = "exact-direct";
|
|
91
|
+
var DEFAULT_VALIDITY_DURATION = 3600;
|
|
92
|
+
var MAX_TRANSACTION_AGE = 5 * 60 * 1e3;
|
|
93
|
+
var NEAR_CAIP2_NAMESPACE = "near";
|
|
94
|
+
|
|
95
|
+
// src/tokens.ts
|
|
96
|
+
var TOKEN_REGISTRY = {
|
|
97
|
+
[NEAR_MAINNET_CAIP2]: [
|
|
98
|
+
{
|
|
99
|
+
// USDC on NEAR (Rainbow Bridge)
|
|
100
|
+
contractId: "17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
|
|
101
|
+
symbol: "USDC",
|
|
102
|
+
name: "USD Coin",
|
|
103
|
+
decimals: 6,
|
|
104
|
+
priority: 1
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
// USDT on NEAR
|
|
108
|
+
contractId: "usdt.tether-token.near",
|
|
109
|
+
symbol: "USDT",
|
|
110
|
+
name: "Tether USD",
|
|
111
|
+
decimals: 6,
|
|
112
|
+
priority: 2
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
[NEAR_TESTNET_CAIP2]: [
|
|
116
|
+
{
|
|
117
|
+
// Fake USDC on testnet
|
|
118
|
+
contractId: "usdc.fakes.testnet",
|
|
119
|
+
symbol: "USDC",
|
|
120
|
+
name: "USD Coin (Testnet)",
|
|
121
|
+
decimals: 6,
|
|
122
|
+
priority: 1
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
};
|
|
126
|
+
function getTokenConfig(network, symbol) {
|
|
127
|
+
const tokens = TOKEN_REGISTRY[network];
|
|
128
|
+
if (!tokens) return void 0;
|
|
129
|
+
return tokens.find((t) => t.symbol.toUpperCase() === symbol.toUpperCase());
|
|
130
|
+
}
|
|
131
|
+
function getTokenByContract(network, contractId) {
|
|
132
|
+
const tokens = TOKEN_REGISTRY[network];
|
|
133
|
+
if (!tokens) return void 0;
|
|
134
|
+
return tokens.find((t) => t.contractId === contractId);
|
|
135
|
+
}
|
|
136
|
+
function getDefaultToken(network) {
|
|
137
|
+
const tokens = TOKEN_REGISTRY[network];
|
|
138
|
+
if (!tokens || tokens.length === 0) return void 0;
|
|
139
|
+
return [...tokens].sort((a, b) => a.priority - b.priority)[0];
|
|
140
|
+
}
|
|
141
|
+
function getNetworkTokens(network) {
|
|
142
|
+
return TOKEN_REGISTRY[network] || [];
|
|
143
|
+
}
|
|
144
|
+
function isNetworkSupported(network) {
|
|
145
|
+
return network in TOKEN_REGISTRY;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/utils.ts
|
|
149
|
+
function normalizeNetwork(network) {
|
|
150
|
+
if (network.startsWith(`${NEAR_CAIP2_NAMESPACE}:`)) {
|
|
151
|
+
return network;
|
|
152
|
+
}
|
|
153
|
+
return `${NEAR_CAIP2_NAMESPACE}:${network}`;
|
|
154
|
+
}
|
|
155
|
+
function extractNetworkId(network) {
|
|
156
|
+
if (network.includes(":")) {
|
|
157
|
+
return network.split(":")[1];
|
|
158
|
+
}
|
|
159
|
+
return network;
|
|
160
|
+
}
|
|
161
|
+
function isValidAccountId(accountId) {
|
|
162
|
+
if (!accountId || accountId.length < 2 || accountId.length > 64) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
const regex = /^[a-z0-9]([a-z0-9_-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9_-]*[a-z0-9])?)*$/;
|
|
166
|
+
return regex.test(accountId);
|
|
167
|
+
}
|
|
168
|
+
function getRpcEndpoint(network) {
|
|
169
|
+
const normalizedNetwork = normalizeNetwork(network);
|
|
170
|
+
return NETWORK_RPC_ENDPOINTS[normalizedNetwork] || NETWORK_RPC_ENDPOINTS["near:mainnet"];
|
|
171
|
+
}
|
|
172
|
+
async function rpcCall(network, method, params) {
|
|
173
|
+
const endpoint = getRpcEndpoint(network);
|
|
174
|
+
const request = {
|
|
175
|
+
jsonrpc: "2.0",
|
|
176
|
+
id: `t402-${Date.now()}`,
|
|
177
|
+
method,
|
|
178
|
+
params
|
|
179
|
+
};
|
|
180
|
+
const response = await fetch(endpoint, {
|
|
181
|
+
method: "POST",
|
|
182
|
+
headers: { "Content-Type": "application/json" },
|
|
183
|
+
body: JSON.stringify(request)
|
|
184
|
+
});
|
|
185
|
+
const data = await response.json();
|
|
186
|
+
if (data.error) {
|
|
187
|
+
throw new Error(`NEAR RPC error: ${data.error.message}`);
|
|
188
|
+
}
|
|
189
|
+
return data.result;
|
|
190
|
+
}
|
|
191
|
+
async function queryTransaction(network, txHash, senderAccountId) {
|
|
192
|
+
return rpcCall(network, "tx", [txHash, senderAccountId]);
|
|
193
|
+
}
|
|
194
|
+
async function queryTokenBalance(network, accountId, tokenContract) {
|
|
195
|
+
try {
|
|
196
|
+
const result = await rpcCall(network, "query", {
|
|
197
|
+
request_type: "call_function",
|
|
198
|
+
finality: "final",
|
|
199
|
+
account_id: tokenContract,
|
|
200
|
+
method_name: "ft_balance_of",
|
|
201
|
+
args_base64: btoa(JSON.stringify({ account_id: accountId }))
|
|
202
|
+
});
|
|
203
|
+
const bytes = new Uint8Array(result.result);
|
|
204
|
+
const text = new TextDecoder().decode(bytes);
|
|
205
|
+
const balance = text.replace(/"/g, "");
|
|
206
|
+
return BigInt(balance);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error("Error fetching token balance:", error);
|
|
209
|
+
return 0n;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function parseFtTransferArgs(argsBase64) {
|
|
213
|
+
try {
|
|
214
|
+
const argsJson = atob(argsBase64);
|
|
215
|
+
return JSON.parse(argsJson);
|
|
216
|
+
} catch {
|
|
217
|
+
try {
|
|
218
|
+
return JSON.parse(argsBase64);
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function isTransactionSuccessful(status) {
|
|
225
|
+
return status.SuccessValue !== void 0 && status.Failure === void 0;
|
|
226
|
+
}
|
|
227
|
+
function formatAmount(amount, decimals) {
|
|
228
|
+
const divisor = BigInt(10 ** decimals);
|
|
229
|
+
const whole = amount / divisor;
|
|
230
|
+
const remainder = amount % divisor;
|
|
231
|
+
const decimal = remainder.toString().padStart(decimals, "0").slice(0, 2);
|
|
232
|
+
return `${whole}.${decimal}`;
|
|
233
|
+
}
|
|
234
|
+
function toTokenUnits(decimalAmount, decimals) {
|
|
235
|
+
const amount = typeof decimalAmount === "string" ? parseFloat(decimalAmount) : decimalAmount;
|
|
236
|
+
if (isNaN(amount)) {
|
|
237
|
+
throw new Error(`Invalid amount: ${decimalAmount}`);
|
|
238
|
+
}
|
|
239
|
+
const multiplier = 10 ** decimals;
|
|
240
|
+
return BigInt(Math.floor(amount * multiplier));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/exact-direct/client/scheme.ts
|
|
244
|
+
var ExactDirectNearClient = class {
|
|
245
|
+
/**
|
|
246
|
+
* Creates a new ExactDirectNearScheme instance.
|
|
247
|
+
*
|
|
248
|
+
* @param signer - The NEAR signer for client operations
|
|
249
|
+
* @param config - Optional configuration overrides
|
|
250
|
+
*/
|
|
251
|
+
constructor(signer, config = {}) {
|
|
252
|
+
this.signer = signer;
|
|
253
|
+
this.config = config;
|
|
254
|
+
this.scheme = SCHEME_EXACT_DIRECT;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Creates a payment payload by executing the transfer.
|
|
258
|
+
*
|
|
259
|
+
* Unlike other schemes where the client creates a signed message for
|
|
260
|
+
* the facilitator to execute, the exact-direct scheme has the client
|
|
261
|
+
* execute the transfer directly. The transaction hash is then used
|
|
262
|
+
* as proof of payment.
|
|
263
|
+
*
|
|
264
|
+
* @param t402Version - The t402 protocol version
|
|
265
|
+
* @param paymentRequirements - The payment requirements
|
|
266
|
+
* @returns Promise resolving to a payment payload with transaction hash
|
|
267
|
+
*/
|
|
268
|
+
async createPaymentPayload(t402Version, paymentRequirements) {
|
|
269
|
+
normalizeNetwork(paymentRequirements.network);
|
|
270
|
+
if (!paymentRequirements.asset) {
|
|
271
|
+
throw new Error("Asset (token contract address) is required");
|
|
272
|
+
}
|
|
273
|
+
if (!paymentRequirements.payTo) {
|
|
274
|
+
throw new Error("PayTo address is required");
|
|
275
|
+
}
|
|
276
|
+
if (!paymentRequirements.amount) {
|
|
277
|
+
throw new Error("Amount is required");
|
|
278
|
+
}
|
|
279
|
+
if (!isValidAccountId(paymentRequirements.payTo)) {
|
|
280
|
+
throw new Error(`Invalid recipient account ID: ${paymentRequirements.payTo}`);
|
|
281
|
+
}
|
|
282
|
+
if (!isValidAccountId(this.signer.accountId)) {
|
|
283
|
+
throw new Error(`Invalid sender account ID: ${this.signer.accountId}`);
|
|
284
|
+
}
|
|
285
|
+
const tokenContract = paymentRequirements.asset;
|
|
286
|
+
const recipient = paymentRequirements.payTo;
|
|
287
|
+
const amount = paymentRequirements.amount;
|
|
288
|
+
const ftTransferArgs = {
|
|
289
|
+
receiver_id: recipient,
|
|
290
|
+
amount
|
|
291
|
+
};
|
|
292
|
+
if (this.config.memo) {
|
|
293
|
+
ftTransferArgs.memo = this.config.memo;
|
|
294
|
+
}
|
|
295
|
+
const txHash = await this.signer.signAndSendTransaction(
|
|
296
|
+
tokenContract,
|
|
297
|
+
"ft_transfer",
|
|
298
|
+
ftTransferArgs,
|
|
299
|
+
this.config.gasAmount || DEFAULT_FT_TRANSFER_GAS,
|
|
300
|
+
FT_TRANSFER_DEPOSIT
|
|
301
|
+
);
|
|
302
|
+
const payload = {
|
|
303
|
+
txHash,
|
|
304
|
+
from: this.signer.accountId,
|
|
305
|
+
to: recipient,
|
|
306
|
+
amount
|
|
307
|
+
};
|
|
308
|
+
return {
|
|
309
|
+
t402Version,
|
|
310
|
+
payload
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/exact-direct/client/register.ts
|
|
316
|
+
function registerExactDirectNearClient(client, config) {
|
|
317
|
+
const scheme = new ExactDirectNearClient(config.signer, config.schemeConfig);
|
|
318
|
+
if (config.networks && config.networks.length > 0) {
|
|
319
|
+
config.networks.forEach((network) => {
|
|
320
|
+
client.register(network, scheme);
|
|
321
|
+
});
|
|
322
|
+
} else {
|
|
323
|
+
client.register("near:*", scheme);
|
|
324
|
+
}
|
|
325
|
+
if (config.policies) {
|
|
326
|
+
config.policies.forEach((policy) => {
|
|
327
|
+
client.registerPolicy(policy);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return client;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/exact-direct/server/scheme.ts
|
|
334
|
+
var ExactDirectNearServer = class {
|
|
335
|
+
constructor(config = {}) {
|
|
336
|
+
this.scheme = SCHEME_EXACT_DIRECT;
|
|
337
|
+
this.moneyParsers = [];
|
|
338
|
+
this.config = config;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Register a custom money parser in the parser chain.
|
|
342
|
+
* Multiple parsers can be registered - they will be tried in registration order.
|
|
343
|
+
* Each parser receives a decimal amount (e.g., 1.50 for $1.50).
|
|
344
|
+
* If a parser returns null, the next parser in the chain will be tried.
|
|
345
|
+
* The default parser is always the final fallback.
|
|
346
|
+
*
|
|
347
|
+
* @param parser - Custom function to convert amount to AssetAmount (or null to skip)
|
|
348
|
+
* @returns The server instance for chaining
|
|
349
|
+
*/
|
|
350
|
+
registerMoneyParser(parser) {
|
|
351
|
+
this.moneyParsers.push(parser);
|
|
352
|
+
return this;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Parses a price into an asset amount.
|
|
356
|
+
* If price is already an AssetAmount, returns it directly.
|
|
357
|
+
* If price is Money (string | number), parses to decimal and tries custom parsers.
|
|
358
|
+
* Falls back to default conversion if all custom parsers return null.
|
|
359
|
+
*
|
|
360
|
+
* @param price - The price to parse
|
|
361
|
+
* @param network - The network to use
|
|
362
|
+
* @returns Promise that resolves to the parsed asset amount
|
|
363
|
+
*/
|
|
364
|
+
async parsePrice(price, network) {
|
|
365
|
+
const normalizedNetwork = normalizeNetwork(network);
|
|
366
|
+
if (typeof price === "object" && price !== null && "amount" in price) {
|
|
367
|
+
if (!price.asset) {
|
|
368
|
+
throw new Error(`Asset address must be specified for AssetAmount on network ${network}`);
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
amount: price.amount,
|
|
372
|
+
asset: price.asset,
|
|
373
|
+
extra: price.extra || {}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const amount = this.parseMoneyToDecimal(price);
|
|
377
|
+
for (const parser of this.moneyParsers) {
|
|
378
|
+
const result = await parser(amount, normalizedNetwork);
|
|
379
|
+
if (result !== null) {
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return this.defaultMoneyConversion(amount, normalizedNetwork);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Build payment requirements for this scheme/network combination.
|
|
387
|
+
*
|
|
388
|
+
* @param paymentRequirements - Base payment requirements with amount/asset already set
|
|
389
|
+
* @param supportedKind - The supported kind from facilitator's /supported endpoint
|
|
390
|
+
* @param extensionKeys - Extensions supported by the facilitator
|
|
391
|
+
* @returns Enhanced payment requirements ready to be sent to clients
|
|
392
|
+
*/
|
|
393
|
+
async enhancePaymentRequirements(paymentRequirements, supportedKind, extensionKeys) {
|
|
394
|
+
void extensionKeys;
|
|
395
|
+
const extra = { ...paymentRequirements.extra };
|
|
396
|
+
if (supportedKind.extra?.assetSymbol) {
|
|
397
|
+
extra.assetSymbol = supportedKind.extra.assetSymbol;
|
|
398
|
+
}
|
|
399
|
+
if (supportedKind.extra?.assetDecimals) {
|
|
400
|
+
extra.assetDecimals = supportedKind.extra.assetDecimals;
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
...paymentRequirements,
|
|
404
|
+
extra
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Parse Money (string | number) to a decimal number.
|
|
409
|
+
* Handles formats like "$1.50", "1.50", 1.50, etc.
|
|
410
|
+
*/
|
|
411
|
+
parseMoneyToDecimal(money) {
|
|
412
|
+
if (typeof money === "number") {
|
|
413
|
+
return money;
|
|
414
|
+
}
|
|
415
|
+
const cleanMoney = money.replace(/^\$/, "").trim();
|
|
416
|
+
const amount = parseFloat(cleanMoney);
|
|
417
|
+
if (isNaN(amount)) {
|
|
418
|
+
throw new Error(`Invalid money format: ${money}`);
|
|
419
|
+
}
|
|
420
|
+
return amount;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Default money conversion implementation.
|
|
424
|
+
* Converts decimal amount to the preferred token on the specified network.
|
|
425
|
+
*/
|
|
426
|
+
defaultMoneyConversion(amount, network) {
|
|
427
|
+
const token = this.getDefaultAsset(network);
|
|
428
|
+
const tokenAmount = toTokenUnits(amount, token.decimals);
|
|
429
|
+
return {
|
|
430
|
+
amount: tokenAmount.toString(),
|
|
431
|
+
asset: token.contractId,
|
|
432
|
+
extra: {
|
|
433
|
+
symbol: token.symbol,
|
|
434
|
+
name: token.name,
|
|
435
|
+
decimals: token.decimals
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Get the default asset info for a network.
|
|
441
|
+
* Priority: configured preferredToken > network default
|
|
442
|
+
*/
|
|
443
|
+
getDefaultAsset(network) {
|
|
444
|
+
if (this.config.preferredToken) {
|
|
445
|
+
const preferred = getTokenConfig(network, this.config.preferredToken);
|
|
446
|
+
if (preferred) return preferred;
|
|
447
|
+
}
|
|
448
|
+
const defaultToken = getDefaultToken(network);
|
|
449
|
+
if (defaultToken) return defaultToken;
|
|
450
|
+
throw new Error(`No tokens configured for network ${network}`);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Get all supported networks
|
|
454
|
+
*/
|
|
455
|
+
static getSupportedNetworks() {
|
|
456
|
+
return Object.keys(TOKEN_REGISTRY);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Check if a network is supported
|
|
460
|
+
*/
|
|
461
|
+
static isNetworkSupported(network) {
|
|
462
|
+
return network in TOKEN_REGISTRY;
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// src/exact-direct/server/register.ts
|
|
467
|
+
function registerExactDirectNearServer(server, config = {}) {
|
|
468
|
+
const scheme = new ExactDirectNearServer(config.schemeConfig);
|
|
469
|
+
if (config.networks && config.networks.length > 0) {
|
|
470
|
+
config.networks.forEach((network) => {
|
|
471
|
+
server.register(network, scheme);
|
|
472
|
+
});
|
|
473
|
+
} else {
|
|
474
|
+
server.register("near:*", scheme);
|
|
475
|
+
}
|
|
476
|
+
return server;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/exact-direct/facilitator/scheme.ts
|
|
480
|
+
var ExactDirectNearFacilitator = class {
|
|
481
|
+
constructor(signer, config) {
|
|
482
|
+
this.signer = signer;
|
|
483
|
+
this.scheme = SCHEME_EXACT_DIRECT;
|
|
484
|
+
this.caipFamily = `${NEAR_CAIP2_NAMESPACE}:*`;
|
|
485
|
+
this.usedTxs = /* @__PURE__ */ new Map();
|
|
486
|
+
this.config = {
|
|
487
|
+
maxTransactionAge: config?.maxTransactionAge ?? MAX_TRANSACTION_AGE,
|
|
488
|
+
usedTxCacheDuration: config?.usedTxCacheDuration ?? 24 * 60 * 60 * 1e3
|
|
489
|
+
// 24 hours
|
|
490
|
+
};
|
|
491
|
+
this.startCleanupInterval();
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Get extra data for a supported kind
|
|
495
|
+
*/
|
|
496
|
+
getExtra(network) {
|
|
497
|
+
const token = getDefaultToken(network);
|
|
498
|
+
if (!token) {
|
|
499
|
+
return void 0;
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
assetSymbol: token.symbol,
|
|
503
|
+
assetDecimals: token.decimals
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Get signer addresses for a network
|
|
508
|
+
*/
|
|
509
|
+
getSigners(network) {
|
|
510
|
+
return this.signer.getAddresses(network);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Verify a payment payload
|
|
514
|
+
*/
|
|
515
|
+
async verify(payload, requirements) {
|
|
516
|
+
const network = normalizeNetwork(requirements.network);
|
|
517
|
+
if (payload.accepted.scheme !== SCHEME_EXACT_DIRECT) {
|
|
518
|
+
return {
|
|
519
|
+
isValid: false,
|
|
520
|
+
invalidReason: "invalid_scheme"
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
if (normalizeNetwork(payload.accepted.network) !== network) {
|
|
524
|
+
return {
|
|
525
|
+
isValid: false,
|
|
526
|
+
invalidReason: "network_mismatch"
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
const nearPayload = payload.payload;
|
|
530
|
+
if (!nearPayload.txHash) {
|
|
531
|
+
return {
|
|
532
|
+
isValid: false,
|
|
533
|
+
invalidReason: "missing_tx_hash"
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
if (!nearPayload.from || !isValidAccountId(nearPayload.from)) {
|
|
537
|
+
return {
|
|
538
|
+
isValid: false,
|
|
539
|
+
invalidReason: "invalid_from_address"
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
if (this.isTxUsed(nearPayload.txHash)) {
|
|
543
|
+
return {
|
|
544
|
+
isValid: false,
|
|
545
|
+
invalidReason: "transaction_already_used",
|
|
546
|
+
payer: nearPayload.from
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
try {
|
|
550
|
+
const tx = await this.signer.queryTransaction(nearPayload.txHash, nearPayload.from);
|
|
551
|
+
if (!isTransactionSuccessful(tx.status)) {
|
|
552
|
+
return {
|
|
553
|
+
isValid: false,
|
|
554
|
+
invalidReason: "transaction_failed",
|
|
555
|
+
payer: nearPayload.from
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
if (tx.transaction.receiver_id !== requirements.asset) {
|
|
559
|
+
return {
|
|
560
|
+
isValid: false,
|
|
561
|
+
invalidReason: "wrong_token_contract",
|
|
562
|
+
payer: nearPayload.from
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
let ftTransferArgs = null;
|
|
566
|
+
for (const action of tx.transaction.actions) {
|
|
567
|
+
if (action.FunctionCall?.method_name === "ft_transfer") {
|
|
568
|
+
ftTransferArgs = parseFtTransferArgs(action.FunctionCall.args);
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (!ftTransferArgs) {
|
|
573
|
+
return {
|
|
574
|
+
isValid: false,
|
|
575
|
+
invalidReason: "no_ft_transfer_action",
|
|
576
|
+
payer: nearPayload.from
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
if (ftTransferArgs.receiver_id !== requirements.payTo) {
|
|
580
|
+
return {
|
|
581
|
+
isValid: false,
|
|
582
|
+
invalidReason: "wrong_recipient",
|
|
583
|
+
payer: nearPayload.from
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
const txAmount = BigInt(ftTransferArgs.amount);
|
|
587
|
+
const requiredAmount = BigInt(requirements.amount);
|
|
588
|
+
if (txAmount < requiredAmount) {
|
|
589
|
+
return {
|
|
590
|
+
isValid: false,
|
|
591
|
+
invalidReason: "insufficient_amount",
|
|
592
|
+
payer: nearPayload.from
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
this.markTxUsed(nearPayload.txHash);
|
|
596
|
+
return {
|
|
597
|
+
isValid: true,
|
|
598
|
+
payer: nearPayload.from
|
|
599
|
+
};
|
|
600
|
+
} catch {
|
|
601
|
+
return {
|
|
602
|
+
isValid: false,
|
|
603
|
+
invalidReason: "transaction_not_found",
|
|
604
|
+
payer: nearPayload.from
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Settle a payment - for exact-direct, the transfer is already complete
|
|
610
|
+
*/
|
|
611
|
+
async settle(payload, requirements) {
|
|
612
|
+
const verifyResult = await this.verify(payload, requirements);
|
|
613
|
+
if (!verifyResult.isValid) {
|
|
614
|
+
return {
|
|
615
|
+
success: false,
|
|
616
|
+
errorReason: verifyResult.invalidReason || "verification_failed",
|
|
617
|
+
payer: verifyResult.payer,
|
|
618
|
+
transaction: "",
|
|
619
|
+
network: normalizeNetwork(requirements.network)
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
const nearPayload = payload.payload;
|
|
623
|
+
return {
|
|
624
|
+
success: true,
|
|
625
|
+
transaction: nearPayload.txHash,
|
|
626
|
+
network: normalizeNetwork(requirements.network),
|
|
627
|
+
payer: nearPayload.from
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Check if a transaction has been used
|
|
632
|
+
*/
|
|
633
|
+
isTxUsed(txHash) {
|
|
634
|
+
return this.usedTxs.has(txHash);
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Mark a transaction as used
|
|
638
|
+
*/
|
|
639
|
+
markTxUsed(txHash) {
|
|
640
|
+
this.usedTxs.set(txHash, Date.now());
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Start the cleanup interval for used transactions
|
|
644
|
+
*/
|
|
645
|
+
startCleanupInterval() {
|
|
646
|
+
setInterval(
|
|
647
|
+
() => {
|
|
648
|
+
const cutoff = Date.now() - this.config.usedTxCacheDuration;
|
|
649
|
+
for (const [txHash, usedAt] of this.usedTxs.entries()) {
|
|
650
|
+
if (usedAt < cutoff) {
|
|
651
|
+
this.usedTxs.delete(txHash);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
60 * 60 * 1e3
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
// src/exact-direct/facilitator/register.ts
|
|
661
|
+
function registerExactDirectNearFacilitator(facilitator, config) {
|
|
662
|
+
const scheme = new ExactDirectNearFacilitator(config.signer, config.schemeConfig);
|
|
663
|
+
facilitator.register(config.networks, scheme);
|
|
664
|
+
return facilitator;
|
|
665
|
+
}
|
|
666
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
667
|
+
0 && (module.exports = {
|
|
668
|
+
DEFAULT_FT_TRANSFER_GAS,
|
|
669
|
+
DEFAULT_STORAGE_DEPOSIT,
|
|
670
|
+
DEFAULT_STORAGE_DEPOSIT_GAS,
|
|
671
|
+
DEFAULT_VALIDITY_DURATION,
|
|
672
|
+
ExactDirectNearClient,
|
|
673
|
+
ExactDirectNearFacilitator,
|
|
674
|
+
ExactDirectNearServer,
|
|
675
|
+
FT_TRANSFER_DEPOSIT,
|
|
676
|
+
MAX_TRANSACTION_AGE,
|
|
677
|
+
NEAR_CAIP2_NAMESPACE,
|
|
678
|
+
NEAR_MAINNET_CAIP2,
|
|
679
|
+
NEAR_MAINNET_RPC,
|
|
680
|
+
NEAR_NETWORKS,
|
|
681
|
+
NEAR_NETWORK_IDS,
|
|
682
|
+
NEAR_TESTNET_CAIP2,
|
|
683
|
+
NEAR_TESTNET_RPC,
|
|
684
|
+
NEP141_FT_BALANCE_OF,
|
|
685
|
+
NEP141_FT_TRANSFER,
|
|
686
|
+
NEP141_STORAGE_BALANCE_OF,
|
|
687
|
+
NEP141_STORAGE_DEPOSIT,
|
|
688
|
+
NETWORK_RPC_ENDPOINTS,
|
|
689
|
+
SCHEME_EXACT_DIRECT,
|
|
690
|
+
TOKEN_REGISTRY,
|
|
691
|
+
extractNetworkId,
|
|
692
|
+
formatAmount,
|
|
693
|
+
getDefaultToken,
|
|
694
|
+
getNetworkTokens,
|
|
695
|
+
getRpcEndpoint,
|
|
696
|
+
getTokenByContract,
|
|
697
|
+
getTokenConfig,
|
|
698
|
+
isNetworkSupported,
|
|
699
|
+
isTransactionSuccessful,
|
|
700
|
+
isValidAccountId,
|
|
701
|
+
normalizeNetwork,
|
|
702
|
+
parseFtTransferArgs,
|
|
703
|
+
queryTokenBalance,
|
|
704
|
+
queryTransaction,
|
|
705
|
+
registerExactDirectNearClient,
|
|
706
|
+
registerExactDirectNearFacilitator,
|
|
707
|
+
registerExactDirectNearServer,
|
|
708
|
+
rpcCall,
|
|
709
|
+
toTokenUnits
|
|
710
|
+
});
|
|
711
|
+
//# sourceMappingURL=index.js.map
|