Skip to content

feat(eip712): decode address as Casper Keys, date presentation & CAIP-2#51

Merged
Comp0te merged 6 commits into
masterfrom
wallet-1251-eip712-date-presentation
Jun 24, 2026
Merged

feat(eip712): decode address as Casper Keys, date presentation & CAIP-2#51
Comp0te merged 6 commits into
masterfrom
wallet-1251-eip712-date-presentation

Conversation

@ost-ptk

@ost-ptk ost-ptk commented Jun 23, 2026

Copy link
Copy Markdown
Member

What

EIP-712 typed-data signing improvements for WALLET-1251, driven by QA feedback and the CEP-2612 / CEP-3009 specs.

Changes

  • Address values decoded as Casper Keys (CEP-2612 / CEP-3009): an EIP-712 address is a 33-byte Casper Key — 0x00 = account hash, 0x01 = contract package hash — not a public key. Previously a 0x01-tagged package address was misidentified as an ed25519 public key. Account addresses are enriched via account info; package addresses are resolved as contract packages. A payload can carry several package addresses, so enrichment uses a packageHash -> IContractPackage map. New decoder in utils/eip712/address.ts (decodeEip712Address); no public-key derivation.
  • Date presentation: known timestamp fields (validAfter, validBefore, deadline) render as human-readable dates; sentinels are labelled (0 -> "Always", u64::MAX -> "No expiry").
  • CAIP-2 chain names: getCasperNetworkByChainName resolves CAIP-2 names (e.g. casper:casper-test); refactored to a type guard instead of unsafe union casts.

Notes

  • Uses BigInt(...) calls (not literals) so consumers compiling this source under TS target es2017 (the wallet) build cleanly.

Tests

Full jest suite green. New/updated tests cover address kinds, package enrichment, date trim + sentinels, and CAIP-2.

ost-ptk added 2 commits June 23, 2026 16:35
Add 'date' to EIP712FieldPresentation union, export formatEip712Date
helper (unix-seconds or ms heuristic via 1e12 threshold), and detect
known timestamp field names (validAfter, validBefore, etc.) by name+type
so toRow emits presentation:'date' with a human-readable displayValue.
Also extend keyToLabel to split camelCase into spaced words.
@ost-ptk ost-ptk force-pushed the wallet-1251-eip712-date-presentation branch from ad29dea to e79a7da Compare June 23, 2026 13:36
ost-ptk added 3 commits June 24, 2026 10:39
Decode EIP-712 `address` values per CEP-2612/CEP-3009 as 33-byte Casper Keys
(0x00 = account hash, 0x01 = contract package hash) instead of public keys, so
a package address is no longer mistaken for an ed25519 public key. Render account
addresses via account-info enrichment and package addresses as contract packages
(via a contractPackageHash -> IContractPackage map, since a payload can now carry
several package addresses). Also trim the date field set to validAfter/validBefore/
deadline and label timestamp sentinels (0 -> Always, u64::MAX -> No expiry).
getCasperNetworkByChainName now resolves CAIP-2 chain names (e.g.
"casper:casper-test") by their reference segment, and uses a type guard +
widened record lookup instead of unsafe union casts.
casper-wallet compiles this source under tsconfig target es2017, where BigInt
literals (0n) are a TS2737 error. Use BigInt(0)/BigInt('1844...') calls; the
BigInt function is available via lib esnext. No behavior change.
@ost-ptk ost-ptk changed the title feat(eip712): render known timestamp fields as dates feat(eip712): decode address as Casper Keys, date presentation & CAIP-2 Jun 24, 2026
@ost-ptk ost-ptk marked this pull request as ready for review June 24, 2026 08:05
@ost-ptk ost-ptk requested a review from Comp0te June 24, 2026 08:27
Comment thread src/utils/eip712/displayModel.ts Outdated
}
try {
const n = BigInt(value);
if (n === BigInt(0)) return 'Always';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dateSentinelLabel applies the same sentinel meaning to every field, so it mislabels the "off-direction" value of upper-bound fields. dateSentinelLabel('0') always returns 'Always' regardless of field — but for an upper bound this is inverted: a deadline: '0' or validBefore: '0' renders "Always", telling the user the permit/authorization never expires when 0 actually means it is already expired / never valid. (Symmetrically, validAfter = u64::MAX renders "No expiry" instead of "never valid".)

Confirmed deterministic: buildTypedDataEIP712DisplayModel(td({ deadline: '0' }, [{ name: 'deadline', type: 'uint256' }])) → row displayValue === 'Always'. In a signing UI this misrepresents validity. Consider keying the sentinel label on the field role (lower- vs upper-bound).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 8b45734. Split the date-field set into lower-bound (validAfter) and upper-bound (validBefore, deadline), and dateSentinelLabel(name, value) now keys the label on the field role: lower-bound 0 → "Always" / u64::MAX → "Never"; upper-bound 0 → "Expired" / u64::MAX → "No expiry". A deadline: '0' now renders "Expired" instead of "Always". Added a test covering the off-direction values.

Comment thread src/data/repositories/eip712/index.ts Outdated
} else {
let anyOk = false;
let anyFailed = false;
for (const packageHash of packageHashes) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Package lookups run sequentially. Each getContractPackage is awaited one at a time in this for...of loop, but the calls are independent (unlike account info, which is a single batched call). A payload with multiple package addresses pays (N-1) × latency of avoidable serial round-trips on the user-facing signing-prep path. Promise.allSettled over packageHashes would parallelize them while preserving the per-package try/catch.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8b45734. Replaced the sequential for...of await loop with Promise.allSettled over packageHashes, so the independent lookups run concurrently. Per-package isolation is preserved — each rejected result is logged individually and never rejects the batch.

Comment thread src/data/repositories/eip712/index.ts Outdated
);
}
}
contractPackageStatus = anyOk ? 'ok' : anyFailed ? 'failed' : 'absent';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial package-fetch failure is reported as 'ok'. If one package resolves and another throws, anyOk is true, so the status is 'ok' even though the failed package is silently missing from contractPackageMap. A consumer relying on enrichment.contractPackage === 'failed' to surface a degraded-enrichment warning won't see it. Per-row degradation is graceful, but the coarse status no longer reflects the partial failure — consider a distinct 'partial' state (or 'failed' when any lookup throws).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8b45734. Added a 'partial' member to EIP712ContractEnrichmentStatus. The status is now anyOk ? (anyFailed ? 'partial' : 'ok') : 'failed', so a mixed outcome (some packages resolve, some throw) surfaces as 'partial' instead of 'ok'. Resolved packages are still mapped; the failed ones are absent. Added a test asserting the partial path. No internal consumer switches exhaustively on this type, so the widened union is non-breaking.

…tial status

Address PR review feedback:

- Key timestamp sentinel labels on field role instead of value alone.
  Lower-bound fields (validAfter) map 0 -> "Always", u64::MAX -> "Never";
  upper-bound fields (validBefore, deadline) map 0 -> "Expired",
  u64::MAX -> "No expiry". Previously every field shared one mapping, so
  a deadline of 0 rendered "Always", misrepresenting an expired permit in
  the signing UI.

- Parallelize independent contract-package lookups with Promise.allSettled
  instead of awaiting them one at a time, preserving per-package isolation.

- Add a 'partial' EIP712ContractEnrichmentStatus so a mixed outcome (some
  package lookups resolve, some throw) is no longer reported as 'ok'.

Signed-off-by: ost-ptk <ostap@make.services>
@ost-ptk ost-ptk requested a review from Comp0te June 24, 2026 15:45
@Comp0te Comp0te merged commit 4947d06 into master Jun 24, 2026
4 checks passed
@Comp0te Comp0te mentioned this pull request Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants