@x1id/resolve 0.16.0 → 0.18.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 CHANGED
@@ -415,8 +415,11 @@ holds keys. Every discriminator and account order matches the deployed program.
415
415
  ```ts
416
416
  import { fetchRecords, liveRecords, valueToAddress } from "@x1id/resolve";
417
417
 
418
- // handleAccount = the ["handle", name] PDA; registeredAt from the handle account
419
- const records = await fetchRecords(rpc, programId, handleAccount, registeredAt);
418
+ // handleAccount = the ["handle", name] PDA; registeredAt + recordsClearedAt
419
+ // both from the handle account (recordsClearedAt gates the #8139 stale-record
420
+ // rule — see fetchRecords' signature; a tokenized handle also passes its
421
+ // holderEpoch to fetchAttestations).
422
+ const records = await fetchRecords(rpc, programId, handleAccount, registeredAt, recordsClearedAt);
420
423
  for (const r of liveRecords(records)) {
421
424
  console.log(r.chain, valueToAddress(r.chain, r.value));
422
425
  }
package/dist/index.js CHANGED
@@ -234,7 +234,7 @@ export function createResolver(config) {
234
234
  // behind by a previous registration of the same name is never resolved,
235
235
  // and a stale `verified: true` is never surfaced.
236
236
  if (chain !== "X1" && chain !== "SOL") {
237
- const live = liveRecords(await fetchRecords(rpc, programBase58, pda, handle.registeredAt, handle.recordsClearedAt));
237
+ const live = liveRecords(await fetchRecords(rpc, programBase58, pda, handle.registeredAt, handle.recordsClearedAt, handle.holderEpoch));
238
238
  const record = live.find((r) => r.coinType === CHAIN_COIN_TYPE[chain]);
239
239
  if (!record) {
240
240
  throw new ResolveError("no-record-for-chain", `@${canonical} has no ${chain} record`, input);
@@ -341,7 +341,7 @@ export function createResolver(config) {
341
341
  if (chain !== "X1" && chain !== "SOL") {
342
342
  // ETH/BTC addresses live in per-chain `Record` accounts — same staleness
343
343
  // rule as a bare handle (fetchRecords → liveRecords).
344
- const live = liveRecords(await fetchRecords(rpc, programBase58, pda, handle.registeredAt, handle.recordsClearedAt));
344
+ const live = liveRecords(await fetchRecords(rpc, programBase58, pda, handle.registeredAt, handle.recordsClearedAt, handle.holderEpoch));
345
345
  const record = live.find((r) => r.coinType === CHAIN_COIN_TYPE[chain]);
346
346
  if (!record) {
347
347
  throw new ResolveError("no-record-for-chain", `${display} has no ${chain} record`, input);
@@ -645,7 +645,7 @@ export function createResolver(config) {
645
645
  throw new ResolveError("unrecognized", `per-chain records exist only for @handles, not .${parsed.namespace} domains`, input);
646
646
  }
647
647
  const { pda, handle } = await fetchHandle(parsed.canonical, input);
648
- return fetchRecords(rpc, programBase58, pda, handle.registeredAt, handle.recordsClearedAt);
648
+ return fetchRecords(rpc, programBase58, pda, handle.registeredAt, handle.recordsClearedAt, handle.holderEpoch);
649
649
  }
650
650
  async function resolve(input, opts) {
651
651
  const chain = opts?.chain ?? "X1";
@@ -34,7 +34,7 @@
34
34
  * {@link integratorAccountsForRegister} / {@link integratorAccountsForCreateVoucher}
35
35
  * to build the pair.
36
36
  * - The rate is read from the on-chain `AllowlistEntry`, never trusted from
37
- * the transaction. It is capped at {@link MAX_INTEGRATOR_RATE_BPS} (40%);
37
+ * the transaction. It is capped at {@link MAX_INTEGRATOR_RATE_BPS} (20%);
38
38
  * {@link DEFAULT_INTEGRATOR_RATE_BPS} (20%) applies when `add_integrator`
39
39
  * omits a rate.
40
40
  */
@@ -43,8 +43,12 @@ import type { AddressLike, BuiltInstruction, InstructionKey } from "./delegate.j
43
43
  export declare const INTEGRATOR_SEED = "integrator";
44
44
  /** Default integrator share when `add_integrator` omits a rate: 20%. */
45
45
  export declare const DEFAULT_INTEGRATOR_RATE_BPS = 2000;
46
- /** Hard cap on an integrator's share of a mint fee: 40%. */
47
- export declare const MAX_INTEGRATOR_RATE_BPS = 4000;
46
+ /** Hard cap on an integrator's share of a mint fee: 20%. Mirrors the program's
47
+ * `MAX_INTEGRATOR_RATE_BPS` (lib.rs) — the admin `register_integrator` /
48
+ * `set_integrator_rate` paths `require!(rate <= MAX_INTEGRATOR_RATE_BPS)` and
49
+ * REJECT anything higher, so a looser SDK cap only builds txs the program
50
+ * refuses. (Self-service paths clamp down to it instead of rejecting.) */
51
+ export declare const MAX_INTEGRATOR_RATE_BPS = 2000;
48
52
  /** Anchor instruction discriminator `sha256("global:add_integrator")[0..8]`. */
49
53
  export declare const ADD_INTEGRATOR_DISCRIMINATOR: Uint8Array;
50
54
  /** Anchor instruction discriminator `sha256("global:set_integrator_rate")[0..8]`. */
@@ -34,7 +34,7 @@
34
34
  * {@link integratorAccountsForRegister} / {@link integratorAccountsForCreateVoucher}
35
35
  * to build the pair.
36
36
  * - The rate is read from the on-chain `AllowlistEntry`, never trusted from
37
- * the transaction. It is capped at {@link MAX_INTEGRATOR_RATE_BPS} (40%);
37
+ * the transaction. It is capped at {@link MAX_INTEGRATOR_RATE_BPS} (20%);
38
38
  * {@link DEFAULT_INTEGRATOR_RATE_BPS} (20%) applies when `add_integrator`
39
39
  * omits a rate.
40
40
  */
@@ -43,8 +43,12 @@ import { encodeBase58, decodeBase58_32 } from "./base58.js";
43
43
  export const INTEGRATOR_SEED = "integrator";
44
44
  /** Default integrator share when `add_integrator` omits a rate: 20%. */
45
45
  export const DEFAULT_INTEGRATOR_RATE_BPS = 2_000;
46
- /** Hard cap on an integrator's share of a mint fee: 40%. */
47
- export const MAX_INTEGRATOR_RATE_BPS = 4_000;
46
+ /** Hard cap on an integrator's share of a mint fee: 20%. Mirrors the program's
47
+ * `MAX_INTEGRATOR_RATE_BPS` (lib.rs) — the admin `register_integrator` /
48
+ * `set_integrator_rate` paths `require!(rate <= MAX_INTEGRATOR_RATE_BPS)` and
49
+ * REJECT anything higher, so a looser SDK cap only builds txs the program
50
+ * refuses. (Self-service paths clamp down to it instead of rejecting.) */
51
+ export const MAX_INTEGRATOR_RATE_BPS = 2_000;
48
52
  /** Anchor instruction discriminator `sha256("global:add_integrator")[0..8]`. */
49
53
  export const ADD_INTEGRATOR_DISCRIMINATOR = Uint8Array.from([85, 249, 72, 201, 17, 187, 227, 38]);
50
54
  /** Anchor instruction discriminator `sha256("global:set_integrator_rate")[0..8]`. */
package/dist/records.d.ts CHANGED
@@ -95,7 +95,7 @@ export interface HandleRecord {
95
95
  * so a handle-only memcmp scan DOES return them — they are rejected here by
96
96
  * tag and size, not by luck.
97
97
  */
98
- export declare function decodeRecord(raw: Uint8Array, account: string, registeredAt: bigint, recordsClearedAt: bigint): HandleRecord | null;
98
+ export declare function decodeRecord(raw: Uint8Array, account: string, registeredAt: bigint, recordsClearedAt: bigint, holderEpoch?: bigint): HandleRecord | null;
99
99
  /** The records the CURRENT owner actually has — `stale` ones excluded. This
100
100
  * is the list to resolve against, display to a visitor, and count. */
101
101
  export declare function liveRecords(records: readonly HandleRecord[]): HandleRecord[];
@@ -127,4 +127,4 @@ export declare function valueToAddress(chain: Chain | null, value: Uint8Array):
127
127
  * left behind; anything that resolves, displays to a visitor, or counts
128
128
  * takes `liveRecords(...)`.
129
129
  */
130
- export declare function fetchRecords(rpc: RpcFn, programId: string, handleAccount: string, registeredAt: bigint, recordsClearedAt: bigint): Promise<HandleRecord[]>;
130
+ export declare function fetchRecords(rpc: RpcFn, programId: string, handleAccount: string, registeredAt: bigint, recordsClearedAt: bigint, holderEpoch?: bigint): Promise<HandleRecord[]>;
package/dist/records.js CHANGED
@@ -72,7 +72,17 @@ export function chainForCoinType(coinType) {
72
72
  * so a handle-only memcmp scan DOES return them — they are rejected here by
73
73
  * tag and size, not by luck.
74
74
  */
75
- export function decodeRecord(raw, account, registeredAt, recordsClearedAt) {
75
+ export function decodeRecord(raw, account, registeredAt, recordsClearedAt,
76
+ // W122: a record written before the CURRENT holder's epoch belongs to a
77
+ // previous holder and must not resolve. `holder_epoch` (H5 / PR #466) is
78
+ // bumped by a NATIVE tokenized sale (buy_handle) INSTEAD of registered_at
79
+ // (which must not move on a tokenized sale), so without this a transferred
80
+ // tokenized name kept the old holder's verified pay-to records live — a
81
+ // payment-misrouting risk. Mirrors the attestation staleness rule exactly.
82
+ // (Defaults 0n: untokenized names and callers that don't track it are
83
+ // unaffected; an EXTERNAL/plain Metaplex transfer stamps NOTHING, so it is
84
+ // NOT covered here — see the module header / docs caveat.)
85
+ holderEpoch = 0n) {
76
86
  if (raw.length !== RECORD_LEN)
77
87
  return null;
78
88
  for (let i = 0; i < 8; i++) {
@@ -95,7 +105,8 @@ export function decodeRecord(raw, account, registeredAt, recordsClearedAt) {
95
105
  const verified = raw[o] === 1;
96
106
  o += 1;
97
107
  const updatedAt = dv.getBigInt64(o, true);
98
- const stale = updatedAt < registeredAt || updatedAt < recordsClearedAt;
108
+ const epoch = registeredAt > holderEpoch ? registeredAt : holderEpoch;
109
+ const stale = updatedAt < epoch || updatedAt < recordsClearedAt;
99
110
  const chain = chainForCoinType(coinType);
100
111
  return {
101
112
  account,
@@ -164,7 +175,10 @@ function hexToBytes(hex) {
164
175
  * left behind; anything that resolves, displays to a visitor, or counts
165
176
  * takes `liveRecords(...)`.
166
177
  */
167
- export async function fetchRecords(rpc, programId, handleAccount, registeredAt, recordsClearedAt) {
178
+ export async function fetchRecords(rpc, programId, handleAccount, registeredAt, recordsClearedAt,
179
+ // W122 — pass the handle's `holderEpoch` so a native tokenized holder change
180
+ // stales the previous holder's records (see decodeRecord). Defaults 0n.
181
+ holderEpoch = 0n) {
168
182
  const res = (await rpc("getProgramAccounts", [
169
183
  programId,
170
184
  {
@@ -183,7 +197,7 @@ export async function fetchRecords(rpc, programId, handleAccount, registeredAt,
183
197
  const raw = new Uint8Array(bin.length);
184
198
  for (let i = 0; i < bin.length; i++)
185
199
  raw[i] = bin.charCodeAt(i);
186
- const r = decodeRecord(raw, a.pubkey, registeredAt, recordsClearedAt);
200
+ const r = decodeRecord(raw, a.pubkey, registeredAt, recordsClearedAt, holderEpoch);
187
201
  // Defence in depth: the memcmp filter should guarantee the handle match,
188
202
  // but a wrong offset would silently attribute someone else's record to
189
203
  // this handle. A malformed account is skipped, not fatal to the list.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x1id/resolve",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Resolve @handles and X1NS names on X1. Never silently picks between namespaces.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",