@rhinestone/shared-configs 2.11.0 → 2.12.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/dist/configs/chains.json +232 -2
- package/dist/scripts/generate.js +16 -0
- package/dist/scripts/validation.js +44 -0
- package/dist/src/__tests__/paymentTokens.test.d.ts +1 -0
- package/dist/src/__tests__/paymentTokens.test.js +89 -0
- package/dist/src/chains.d.ts +15 -0
- package/dist/src/chains.js +232 -2
- package/dist/src/generated/packageVersion.d.ts +1 -1
- package/dist/src/generated/packageVersion.js +1 -1
- package/dist/src/types.d.ts +50 -0
- package/package.json +1 -1
package/dist/scripts/generate.js
CHANGED
|
@@ -110,6 +110,20 @@ function renderChains(chainRegistry, mainnets, testnets) {
|
|
|
110
110
|
" * the on-chain address (non-EVM chains). */",
|
|
111
111
|
" nearAssetId?: string;",
|
|
112
112
|
" unpriced?: boolean;",
|
|
113
|
+
" /** Whether this token may collateralise an unsponsored gas refund. */",
|
|
114
|
+
" gasRefund?: boolean;",
|
|
115
|
+
" /** Whether this token may carry an app-fee carve. */",
|
|
116
|
+
" appFee?: boolean;",
|
|
117
|
+
"}",
|
|
118
|
+
"",
|
|
119
|
+
"/** A token accepted as payment that is not in the routing catalog. */",
|
|
120
|
+
"interface PaymentToken {",
|
|
121
|
+
" symbol: string;",
|
|
122
|
+
" address: string;",
|
|
123
|
+
" decimals: number;",
|
|
124
|
+
" priceSymbol?: string;",
|
|
125
|
+
" appFee?: boolean;",
|
|
126
|
+
" gasRefund?: boolean;",
|
|
113
127
|
"}",
|
|
114
128
|
"",
|
|
115
129
|
"interface NativeToken {",
|
|
@@ -156,6 +170,8 @@ function renderChains(chainRegistry, mainnets, testnets) {
|
|
|
156
170
|
" nativeToken: NativeToken;",
|
|
157
171
|
" wrappedNativeToken: NativeToken;",
|
|
158
172
|
" tokens: Token[];",
|
|
173
|
+
" /** Tokens accepted as payment that are not in `tokens`. */",
|
|
174
|
+
" paymentTokens?: PaymentToken[];",
|
|
159
175
|
" settlementLayers: SettlementLayer[];",
|
|
160
176
|
" /** Per-vendor addressing for the enabled layers that need it (Rhino chain",
|
|
161
177
|
" * name, CCTP domain, NEAR prefix, OFT adapter). Third-party addresses —",
|
|
@@ -291,6 +291,46 @@ function structuralProblems(registryChainId, chain) {
|
|
|
291
291
|
if (!isNonNegativeInt(token.decimals))
|
|
292
292
|
problems.push(`${field}.decimals is not a non-negative integer`);
|
|
293
293
|
}
|
|
294
|
+
if (chain.paymentTokens !== undefined) {
|
|
295
|
+
if (!Array.isArray(chain.paymentTokens)) {
|
|
296
|
+
problems.push('paymentTokens is not an array');
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
const catalog = new Set((Array.isArray(chain.tokens) ? chain.tokens : [])
|
|
300
|
+
.map((t) => (isObject(t) && typeof t.address === 'string' ? t.address.toLowerCase() : ''))
|
|
301
|
+
.filter(Boolean));
|
|
302
|
+
chain.paymentTokens.forEach((token, i) => {
|
|
303
|
+
const at = `paymentTokens[${i}]`;
|
|
304
|
+
if (!isObject(token)) {
|
|
305
|
+
problems.push(`${at} is not an object`);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (typeof token.address !== 'string')
|
|
309
|
+
problems.push(`${at}.address is not a string`);
|
|
310
|
+
if (!isNonEmptyString(token.symbol))
|
|
311
|
+
problems.push(`${at}.symbol is not a non-empty string`);
|
|
312
|
+
if (!isNonNegativeInt(token.decimals))
|
|
313
|
+
problems.push(`${at}.decimals is not a non-negative integer`);
|
|
314
|
+
if (!isAbsentOr(token.priceSymbol, isNonEmptyString))
|
|
315
|
+
problems.push(`${at}.priceSymbol is not a string`);
|
|
316
|
+
if (!isAbsentOr(token.appFee, (v) => typeof v === 'boolean'))
|
|
317
|
+
problems.push(`${at}.appFee is not a boolean`);
|
|
318
|
+
if (!isAbsentOr(token.gasRefund, (v) => typeof v === 'boolean'))
|
|
319
|
+
problems.push(`${at}.gasRefund is not a boolean`);
|
|
320
|
+
if (token.appFee !== true && token.gasRefund !== true) {
|
|
321
|
+
problems.push(`${at} declares no payment purpose; drop it or set appFee/gasRefund`);
|
|
322
|
+
}
|
|
323
|
+
// A duplicate would make the same token reachable through two lists
|
|
324
|
+
// with different flags, and nothing downstream defines which wins.
|
|
325
|
+
if (typeof token.address === 'string' && catalog.has(token.address.toLowerCase())) {
|
|
326
|
+
problems.push(`${at}.address is already in tokens; declare the flags there instead`);
|
|
327
|
+
}
|
|
328
|
+
if (chain.vmType !== 'evm') {
|
|
329
|
+
problems.push(`${at} is only valid on an EVM chain — both payment paths are EVM calls`);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
294
334
|
if (!Array.isArray(chain.tokens)) {
|
|
295
335
|
problems.push('tokens is not an array');
|
|
296
336
|
}
|
|
@@ -321,6 +361,10 @@ function structuralProblems(registryChainId, chain) {
|
|
|
321
361
|
problems.push(`${at}.nearAssetId is not a string`);
|
|
322
362
|
if (!isAbsentOr(token.unpriced, (v) => typeof v === 'boolean'))
|
|
323
363
|
problems.push(`${at}.unpriced is not a boolean`);
|
|
364
|
+
if (!isAbsentOr(token.gasRefund, (v) => typeof v === 'boolean'))
|
|
365
|
+
problems.push(`${at}.gasRefund is not a boolean`);
|
|
366
|
+
if (!isAbsentOr(token.appFee, (v) => typeof v === 'boolean'))
|
|
367
|
+
problems.push(`${at}.appFee is not a boolean`);
|
|
324
368
|
if (!isAbsentOr(token.pegGroup, (v) => PEG_GROUPS.has(v))) {
|
|
325
369
|
problems.push(`${at}.pegGroup ${JSON.stringify(token.pegGroup)} is not a known peg group`);
|
|
326
370
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const bun_test_1 = require("bun:test");
|
|
4
|
+
const index_1 = require("../index");
|
|
5
|
+
const entries = Object.entries(index_1.chainRegistry);
|
|
6
|
+
/**
|
|
7
|
+
* `<chainId>:<symbol>` for a catalog token we have DECIDED not to accept as
|
|
8
|
+
* payment. Empty today — every real EVM catalog token is accepted. An entry
|
|
9
|
+
* here records a decision; it is not a backlog.
|
|
10
|
+
*/
|
|
11
|
+
const NOT_ACCEPTED_AS_PAYMENT = new Set();
|
|
12
|
+
(0, bun_test_1.describe)("payment-token eligibility", () => {
|
|
13
|
+
(0, bun_test_1.it)("is only ever present as `true`", () => {
|
|
14
|
+
// `false` and absent mean the same thing, so writing `false` would imply a
|
|
15
|
+
// distinction the consumer cannot act on.
|
|
16
|
+
for (const [chainId, chain] of entries) {
|
|
17
|
+
for (const token of chain.tokens) {
|
|
18
|
+
if ("gasRefund" in token) {
|
|
19
|
+
(0, bun_test_1.expect)(`${chainId}:${token.symbol}=${token.gasRefund}`).toBe(`${chainId}:${token.symbol}=true`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
(0, bun_test_1.it)("forces a decision on every real EVM catalog token", () => {
|
|
25
|
+
// The flags are opt-in, so a token added without one is silently refused as
|
|
26
|
+
// payment rather than loudly rejected — which is how Plasma USDC nearly
|
|
27
|
+
// shipped ineligible. Failing here makes the next author choose.
|
|
28
|
+
//
|
|
29
|
+
// Virtual chains are exempt: their token rows are not what the payment
|
|
30
|
+
// paths consult, which resolve to the settlement chain first. Non-EVM is
|
|
31
|
+
// exempt by the test below.
|
|
32
|
+
const undecided = entries
|
|
33
|
+
.filter(([, chain]) => chain.vmType === "evm" && chain.virtual !== true)
|
|
34
|
+
.flatMap(([chainId, chain]) => chain.tokens
|
|
35
|
+
.filter((token) => token.address.toLowerCase() !==
|
|
36
|
+
chain.nativeToken.address.toLowerCase())
|
|
37
|
+
.filter((token) => !token.gasRefund || !token.appFee)
|
|
38
|
+
.map((token) => `${chainId}:${token.symbol}`))
|
|
39
|
+
.filter((key) => !NOT_ACCEPTED_AS_PAYMENT.has(key));
|
|
40
|
+
(0, bun_test_1.expect)(undecided).toEqual([]);
|
|
41
|
+
});
|
|
42
|
+
(0, bun_test_1.it)("is never set on a non-EVM chain", () => {
|
|
43
|
+
// The refund settles as an EVM `transferFrom` through the Paymaster, so an
|
|
44
|
+
// SPL mint or a Stellar SAC marked eligible would be uncollectable.
|
|
45
|
+
const offenders = entries
|
|
46
|
+
.filter(([, chain]) => chain.vmType !== "evm")
|
|
47
|
+
.flatMap(([chainId, chain]) => chain.tokens
|
|
48
|
+
.filter((token) => token.gasRefund)
|
|
49
|
+
.map((token) => `${chainId}:${token.symbol}`));
|
|
50
|
+
(0, bun_test_1.expect)(offenders).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
(0, bun_test_1.it)("is not set on a native token entry", () => {
|
|
53
|
+
// A chain's native token is eligible by construction — the Paymaster
|
|
54
|
+
// settles it from a deposit, not a transfer — so declaring it here would
|
|
55
|
+
// let a chain look ineligible for its own gas token.
|
|
56
|
+
const offenders = entries
|
|
57
|
+
.flatMap(([chainId, chain]) => chain.tokens
|
|
58
|
+
.filter((token) => token.gasRefund &&
|
|
59
|
+
token.address.toLowerCase() ===
|
|
60
|
+
chain.nativeToken.address.toLowerCase())
|
|
61
|
+
.map((token) => `${chainId}:${token.symbol}`));
|
|
62
|
+
(0, bun_test_1.expect)(offenders).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
(0, bun_test_1.describe)("chain.paymentTokens", () => {
|
|
66
|
+
(0, bun_test_1.it)("never repeats an address already in the routing catalog", () => {
|
|
67
|
+
// The same token reachable through both lists could carry different flags,
|
|
68
|
+
// and nothing downstream defines which wins.
|
|
69
|
+
for (const [chainId, chain] of entries) {
|
|
70
|
+
const catalog = new Set(chain.tokens.map((token) => token.address.toLowerCase()));
|
|
71
|
+
for (const token of chain.paymentTokens ?? []) {
|
|
72
|
+
(0, bun_test_1.expect)(`${chainId}:${token.symbol}:${catalog.has(token.address.toLowerCase())}`).toBe(`${chainId}:${token.symbol}:false`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
(0, bun_test_1.it)("declares at least one payment purpose per entry", () => {
|
|
77
|
+
const purposeless = entries.flatMap(([chainId, chain]) => (chain.paymentTokens ?? [])
|
|
78
|
+
.filter((token) => !token.appFee && !token.gasRefund)
|
|
79
|
+
.map((token) => `${chainId}:${token.symbol}`));
|
|
80
|
+
(0, bun_test_1.expect)(purposeless).toEqual([]);
|
|
81
|
+
});
|
|
82
|
+
(0, bun_test_1.it)("is never set on a non-EVM chain", () => {
|
|
83
|
+
const offenders = entries
|
|
84
|
+
.filter(([, chain]) => chain.vmType !== "evm")
|
|
85
|
+
.filter(([, chain]) => (chain.paymentTokens ?? []).length > 0)
|
|
86
|
+
.map(([chainId]) => chainId);
|
|
87
|
+
(0, bun_test_1.expect)(offenders).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
});
|
package/dist/src/chains.d.ts
CHANGED
|
@@ -18,6 +18,19 @@ interface Token {
|
|
|
18
18
|
* the on-chain address (non-EVM chains). */
|
|
19
19
|
nearAssetId?: string;
|
|
20
20
|
unpriced?: boolean;
|
|
21
|
+
/** Whether this token may collateralise an unsponsored gas refund. */
|
|
22
|
+
gasRefund?: boolean;
|
|
23
|
+
/** Whether this token may carry an app-fee carve. */
|
|
24
|
+
appFee?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** A token accepted as payment that is not in the routing catalog. */
|
|
27
|
+
interface PaymentToken {
|
|
28
|
+
symbol: string;
|
|
29
|
+
address: string;
|
|
30
|
+
decimals: number;
|
|
31
|
+
priceSymbol?: string;
|
|
32
|
+
appFee?: boolean;
|
|
33
|
+
gasRefund?: boolean;
|
|
21
34
|
}
|
|
22
35
|
interface NativeToken {
|
|
23
36
|
/** EVM: zero address placeholder. SVM: wSOL mint. TVM: zero address placeholder. */
|
|
@@ -64,6 +77,8 @@ interface Chain {
|
|
|
64
77
|
nativeToken: NativeToken;
|
|
65
78
|
wrappedNativeToken: NativeToken;
|
|
66
79
|
tokens: Token[];
|
|
80
|
+
/** Tokens accepted as payment that are not in `tokens`. */
|
|
81
|
+
paymentTokens?: PaymentToken[];
|
|
67
82
|
settlementLayers: SettlementLayer[];
|
|
68
83
|
/** Per-vendor addressing for the enabled layers that need it (Rhino chain
|
|
69
84
|
* name, CCTP domain, NEAR prefix, OFT adapter). Third-party addresses —
|