feat(eip712): decode address as Casper Keys, date presentation & CAIP-2#51
Conversation
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.
ad29dea to
e79a7da
Compare
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.
| } | ||
| try { | ||
| const n = BigInt(value); | ||
| if (n === BigInt(0)) return 'Always'; |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
| } else { | ||
| let anyOk = false; | ||
| let anyFailed = false; | ||
| for (const packageHash of packageHashes) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| ); | ||
| } | ||
| } | ||
| contractPackageStatus = anyOk ? 'ok' : anyFailed ? 'failed' : 'absent'; |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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>
What
EIP-712 typed-data signing improvements for WALLET-1251, driven by QA feedback and the CEP-2612 / CEP-3009 specs.
Changes
addressis a 33-byte Casper Key —0x00= account hash,0x01= contract package hash — not a public key. Previously a0x01-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 apackageHash -> IContractPackagemap. New decoder inutils/eip712/address.ts(decodeEip712Address); no public-key derivation.validAfter,validBefore,deadline) render as human-readable dates; sentinels are labelled (0-> "Always",u64::MAX-> "No expiry").getCasperNetworkByChainNameresolves CAIP-2 names (e.g.casper:casper-test); refactored to a type guard instead of unsafe union casts.Notes
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.