uvd-x402-sdk 2.71.0 → 2.72.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/index.d.mts CHANGED
@@ -842,6 +842,163 @@ interface SignatureParamsInput {
842
842
  */
843
843
  declare function buildSignatureParams(params: SignatureParamsInput): string;
844
844
 
845
+ /**
846
+ * EIP-7702: make a DELEGATED EOA's EIP-3009 signature settle again.
847
+ *
848
+ * **THE PROBLEM.** A gasless-wallet provider's first sponsored operation on a
849
+ * chain may delegate the user's EOA (EIP-7702) to a smart-account
850
+ * implementation — Alchemy's `SemiModularAccount7702` (`0x69007702…`) is the one
851
+ * seen in the wild. From then on the address HAS code, so Circle's USDC (and any
852
+ * `SignatureChecker` consumer) verifies signatures via **ERC-1271 only**. A raw
853
+ * ECDSA authorization, however perfect, is rejected: `0x151d90fe`. The account is
854
+ * not broken and the signature is not wrong — they simply no longer speak the
855
+ * same dialect.
856
+ *
857
+ * Consequence for anything x402: a delegated payer's `transferWithAuthorization`
858
+ * / `receiveWithAuthorization` becomes **unsettleable on-chain**, so direct x402
859
+ * payments AND marketplace escrow locks both fail. Measured in production
860
+ * 2026-07-31: 14 of 14 delegated agents failed their escrow lock; the one
861
+ * non-delegated agent locked fine. The failure is silent in the worst way — the
862
+ * sellers looked broken and they were correct.
863
+ *
864
+ * **THE FIX** (verified on-chain; the wallet provider needs to change nothing).
865
+ * That account's `isValidSignature` DOES accept the EOA's own ECDSA — it just
866
+ * wants it in the account's envelope. Two steps, both provable against the
867
+ * verified source (`SemiModularAccountBase._exec1271Validation` +
868
+ * `SparseCalldataSegmentLib`):
869
+ *
870
+ * 1. The account does not check `hash` directly; it checks a REPLAY-SAFE hash =
871
+ * EIP-712 over the account's OWN domain
872
+ * `EIP712Domain(uint256 chainId, address verifyingContract=account)` with
873
+ * struct `ReplaySafeHash(bytes32 hash)`. Sign THAT, not the transfer digest.
874
+ * 2. Wrap the 65-byte signature with the account's fallback-validation locator:
875
+ * `0x00 00000000` (validation type 0, entity id 0 = FALLBACK_VALIDATION_ID)
876
+ * `FF` (RESERVED_VALIDATION_DATA_INDEX, the final segment) `00`
877
+ * (SignatureType.EOA).
878
+ *
879
+ * Because step 1 is still an ordinary typed-data signature, a REMOTE signer (a
880
+ * delegated agentic wallet) can produce it: the private key is never needed
881
+ * locally.
882
+ *
883
+ * **THE WRAP IS DELEGATE-SPECIFIC, NOT "delegated == wrap".** This is the part
884
+ * that bit us on 2026-08-25, and it is why this module exists in TypeScript at
885
+ * all: the moment a worker rates an agent through the facilitator's EIP-7702
886
+ * feedback rail, their EOA becomes delegated to Execution Market's
887
+ * `FeedbackDelegate` — which validates PLAIN ECDSA
888
+ * (`ECDSA.recover(hash, sig) == address(this)`). Wrapping that signature makes it
889
+ * invalid, so *the act of rating would break the rater's next payment*. Same
890
+ * silent-at-lock-time failure the wrap was built to fix, now caused by
891
+ * over-applying it.
892
+ *
893
+ * **DETECTION IS INJECTABLE, ON PURPOSE.** Knowing whether an address is
894
+ * delegated needs one `eth_getCode` — a chain read, and this SDK does not own an
895
+ * RPC policy. So detection is a callable you pass in ({@link DelegationResolver}).
896
+ * {@link rpcDelegationResolver} ships a default over `fetch`; a caller with its
897
+ * own endpoints, proxy or rotation passes its own.
898
+ *
899
+ * **THE THIRD STATE IS LOAD-BEARING.** A resolver returns `true` / `false` /
900
+ * **`null` = could not tell**. `null` must never collapse to "not delegated":
901
+ * that is exactly how the original bug survived eight days — the resolver failed,
902
+ * returned nothing, and the caller's `if (delegated)` read it as falsy and signed
903
+ * raw. **An unreadable answer is not a negative answer.**
904
+ *
905
+ * Port of `uvd_x402_sdk.erc7702` (Python). Keep the two in step: they are the
906
+ * same protocol seen from two languages, and a divergence here is a signature
907
+ * that cannot settle.
908
+ */
909
+ /** EIP-7702 delegation designator. */
910
+ declare const DELEGATE_PREFIX = "ef0100";
911
+ /**
912
+ * Delegate targets whose ERC-1271 needs the account-envelope wrap.
913
+ *
914
+ * Only a known Alchemy SMA implementation needs it. Every other delegate signs
915
+ * PLAIN (the standard 1271 "smart-EOA" pattern accepts the EOA's own ECDSA), and
916
+ * if some exotic future delegate needs a third dialect it fails VISIBLY at lock
917
+ * time — never silently.
918
+ *
919
+ * Notably NOT here: Execution Market's `FeedbackDelegate` (all nine deploys). An
920
+ * account that has rated through the facilitator's rail is delegated to it and
921
+ * must sign plain.
922
+ */
923
+ declare const SMA_WRAP_TARGETS: readonly string[];
924
+ /**
925
+ * `true` only when this delegate target needs the account-envelope wrap.
926
+ *
927
+ * A plain EOA (`target` null/undefined) and any other delegate — FeedbackDelegate,
928
+ * a standard 1271 smart-EOA — do NOT: they take the ordinary ECDSA signature.
929
+ */
930
+ declare function needsAccountWrap(target: string | null | undefined): boolean;
931
+ /**
932
+ * The delegate target this EOA's EIP-7702 code points at, or `null`.
933
+ *
934
+ * `null` for a plain EOA, for non-7702 code, and for anything unparseable — the
935
+ * caller confirms the implementation before applying the wrap.
936
+ */
937
+ declare function delegateTarget(code: string | null | undefined): string | null;
938
+ /** Wrap a 65-byte ECDSA signature in the account's fallback-EOA envelope. */
939
+ declare function wrapSignature(innerSignature: string): string;
940
+ /**
941
+ * The typed data whose signature the delegated account accepts.
942
+ *
943
+ * The domain is the ACCOUNT's own — `chainId` + `verifyingContract=account`, the
944
+ * two fields its typehash declares — and the struct is
945
+ * `ReplaySafeHash(bytes32 hash)` over the inner EIP-3009 digest. Signing THIS,
946
+ * not the digest, is what validates.
947
+ */
948
+ declare function replaySafeTypedData(innerDigest: string, chainId: number, account: string): {
949
+ domain: Record<string, unknown>;
950
+ types: Record<string, unknown>;
951
+ message: Record<string, unknown>;
952
+ };
953
+ /**
954
+ * `(address, network) -> target | true | false | null`.
955
+ *
956
+ * Four answers, richer than a boolean on purpose: a resolver MAY return the
957
+ * delegate **target address** (a string) instead of just `true` — that is what
958
+ * lets the caller pick the right signing dialect (SMA wrap vs plain). `true` =
959
+ * delegated, target unknown; `false` = not delegated; `null` = UNKNOWN
960
+ * (unreadable chain), never "no".
961
+ */
962
+ type DelegationResolver = (address: string, network: string) => Promise<string | boolean | null> | (string | boolean | null);
963
+ /**
964
+ * A default resolver over plain JSON-RPC `eth_getCode`, using `fetch`.
965
+ *
966
+ * Rotates through `urls` and returns `null` when **every** endpoint failed: an
967
+ * unreadable chain is not a "not delegated" verdict.
968
+ *
969
+ * A caller with its own RPC policy (a signed proxy, paid endpoints, per-chain
970
+ * routing) should pass its own resolver instead; that is the whole point of the
971
+ * injection.
972
+ */
973
+ declare function rpcDelegationResolver(urls: string | string[], timeoutMs?: number): DelegationResolver;
974
+ /** `{ delegated, target }` for one address on one network. */
975
+ interface DelegationVerdict {
976
+ /** `null` = UNKNOWN. Never treat it as `false`. */
977
+ delegated: boolean | null;
978
+ /** The delegate implementation, when the resolver could name it. */
979
+ target: string | null;
980
+ }
981
+ /**
982
+ * Resolve one address's delegation on one network.
983
+ *
984
+ * - `{delegated: null, target: null}` — unknown (no resolver, or an unreadable
985
+ * chain). NEVER "no".
986
+ * - `{delegated: false, target: null}` — a plain EOA.
987
+ * - `{delegated: true, target: '0x…'}` — delegated, and we know to WHAT (pick the
988
+ * dialect from it).
989
+ * - `{delegated: true, target: null}` — delegated, target unknown (a legacy
990
+ * boolean-only resolver).
991
+ */
992
+ declare function resolveDelegation(address: string, network: string, resolver?: DelegationResolver): Promise<DelegationVerdict>;
993
+ /**
994
+ * `true` / `false` / `null` (**unknown**) for one address on one network.
995
+ *
996
+ * Without a resolver the answer is `null`, never `false`: "nobody asked" and "the
997
+ * address is a plain EOA" are different facts and only one of them is safe to
998
+ * sign on.
999
+ */
1000
+ declare function isDelegated(address: string, network: string, resolver?: DelegationResolver): Promise<boolean | null>;
1001
+
845
1002
  /**
846
1003
  * uvd-x402-sdk - Escrow Pre-Auth Builder (sign-on-assignment)
847
1004
  *
@@ -892,6 +1049,7 @@ declare function buildSignatureParams(params: SignatureParamsInput): string;
892
1049
  * (production reference, derived from `uvd_x402_sdk.advanced_escrow`)
893
1050
  * - Browser (viem): Execution Market `dashboard/src/services/h2aSigning.ts`
894
1051
  */
1052
+
895
1053
  /** AuthCaptureEscrow deposit condition: $100 max per deposit. */
896
1054
  declare const ESCROW_DEPOSIT_LIMIT_USD = 100;
897
1055
  /**
@@ -1001,6 +1159,20 @@ interface EscrowPreAuthParams {
1001
1159
  * publishes a different limit.
1002
1160
  */
1003
1161
  depositLimitUsd?: number;
1162
+ /**
1163
+ * How to find out whether `payerWallet` is EIP-7702-delegated, and to what.
1164
+ *
1165
+ * Optional, and its absence is honest rather than convenient: with no resolver
1166
+ * the verdict is UNKNOWN and this function signs the ordinary way, exactly as
1167
+ * it did before. Pass one — {@link rpcDelegationResolver} or your own — as soon
1168
+ * as your payers can be delegated accounts, because a delegated payer signing
1169
+ * the wrong dialect produces an authorization that **cannot settle on-chain**
1170
+ * and only fails at lock time.
1171
+ *
1172
+ * When a resolver IS supplied and it reaches no verdict, this throws instead of
1173
+ * guessing: an unreadable chain is not a "not delegated" answer.
1174
+ */
1175
+ delegationResolver?: DelegationResolver;
1004
1176
  }
1005
1177
  /**
1006
1178
  * Build + sign the escrow lock authorization AT ASSIGNMENT time and return
@@ -1015,4 +1187,4 @@ interface EscrowPreAuthParams {
1015
1187
  */
1016
1188
  declare function buildEscrowPreAuth(wallet: EscrowPreAuthSigner, params: EscrowPreAuthParams): Promise<string>;
1017
1189
 
1018
- export { ANCHOR_MAX_REQUEST_BYTES, type AnchorOptions, type AnchoredEvidence, type BackendOffer, ContentHashMismatch, type CreateSignedFetchConfig, DX402Error, type ERC8128RequestOptions, ESCROW_DEPOSIT_LIMIT_USD, ESCROW_TIER_WINDOWS, EVENT_KINDS, EVIDENCE_HEADER, type EscrowNetworkConfig, type EscrowPaymentInfo, type EscrowPreAuthParams, type EscrowPreAuthSigner, type EscrowTierWindows, type EvidenceMode, EvidenceSkipped, FACILITATOR_ADDRESSES, type FacilitatorAddresses, KEEPALIVE_INTERVAL_MS, OPERATOR_FEE_BPS, type RecipientRole, type SSEFrame, SSEParser, type SignRequestOptions, type SignRequestWithSignerOptions, type SignatureBaseParams, type SignatureHeaders, type SignatureParamsInput, SigningWalletAdapter, type StreamTrafficEventsOptions, type TrafficEvent, type TrafficEventKind, TrafficStreamError, ZERO_ADDRESS, anchorDigest, anchorEvidence, availableBackends, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, evidenceHeader, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, payerKeyFromSolanaAddress, recoverEvidence, sealEvidence, sealEvidenceTo, sealedRoles, sellerDigestFor, signAnchorEd25519, signAnchorEvm, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal };
1190
+ export { ANCHOR_MAX_REQUEST_BYTES, type AnchorOptions, type AnchoredEvidence, type BackendOffer, ContentHashMismatch, type CreateSignedFetchConfig, DELEGATE_PREFIX, DX402Error, type DelegationResolver, type DelegationVerdict, type ERC8128RequestOptions, ESCROW_DEPOSIT_LIMIT_USD, ESCROW_TIER_WINDOWS, EVENT_KINDS, EVIDENCE_HEADER, type EscrowNetworkConfig, type EscrowPaymentInfo, type EscrowPreAuthParams, type EscrowPreAuthSigner, type EscrowTierWindows, type EvidenceMode, EvidenceSkipped, FACILITATOR_ADDRESSES, type FacilitatorAddresses, KEEPALIVE_INTERVAL_MS, OPERATOR_FEE_BPS, type RecipientRole, SMA_WRAP_TARGETS, type SSEFrame, SSEParser, type SignRequestOptions, type SignRequestWithSignerOptions, type SignatureBaseParams, type SignatureHeaders, type SignatureParamsInput, SigningWalletAdapter, type StreamTrafficEventsOptions, type TrafficEvent, type TrafficEventKind, TrafficStreamError, ZERO_ADDRESS, anchorDigest, anchorEvidence, availableBackends, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, delegateTarget, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, evidenceHeader, fetchNonce, getFacilitatorAddress, isDelegated, isEndToEnd, matchesFilters, needsAccountWrap, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, payerKeyFromSolanaAddress, recoverEvidence, replaySafeTypedData, resolveDelegation, rpcDelegationResolver, sealEvidence, sealEvidenceTo, sealedRoles, sellerDigestFor, signAnchorEd25519, signAnchorEvm, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal, wrapSignature };
package/dist/index.d.ts CHANGED
@@ -842,6 +842,163 @@ interface SignatureParamsInput {
842
842
  */
843
843
  declare function buildSignatureParams(params: SignatureParamsInput): string;
844
844
 
845
+ /**
846
+ * EIP-7702: make a DELEGATED EOA's EIP-3009 signature settle again.
847
+ *
848
+ * **THE PROBLEM.** A gasless-wallet provider's first sponsored operation on a
849
+ * chain may delegate the user's EOA (EIP-7702) to a smart-account
850
+ * implementation — Alchemy's `SemiModularAccount7702` (`0x69007702…`) is the one
851
+ * seen in the wild. From then on the address HAS code, so Circle's USDC (and any
852
+ * `SignatureChecker` consumer) verifies signatures via **ERC-1271 only**. A raw
853
+ * ECDSA authorization, however perfect, is rejected: `0x151d90fe`. The account is
854
+ * not broken and the signature is not wrong — they simply no longer speak the
855
+ * same dialect.
856
+ *
857
+ * Consequence for anything x402: a delegated payer's `transferWithAuthorization`
858
+ * / `receiveWithAuthorization` becomes **unsettleable on-chain**, so direct x402
859
+ * payments AND marketplace escrow locks both fail. Measured in production
860
+ * 2026-07-31: 14 of 14 delegated agents failed their escrow lock; the one
861
+ * non-delegated agent locked fine. The failure is silent in the worst way — the
862
+ * sellers looked broken and they were correct.
863
+ *
864
+ * **THE FIX** (verified on-chain; the wallet provider needs to change nothing).
865
+ * That account's `isValidSignature` DOES accept the EOA's own ECDSA — it just
866
+ * wants it in the account's envelope. Two steps, both provable against the
867
+ * verified source (`SemiModularAccountBase._exec1271Validation` +
868
+ * `SparseCalldataSegmentLib`):
869
+ *
870
+ * 1. The account does not check `hash` directly; it checks a REPLAY-SAFE hash =
871
+ * EIP-712 over the account's OWN domain
872
+ * `EIP712Domain(uint256 chainId, address verifyingContract=account)` with
873
+ * struct `ReplaySafeHash(bytes32 hash)`. Sign THAT, not the transfer digest.
874
+ * 2. Wrap the 65-byte signature with the account's fallback-validation locator:
875
+ * `0x00 00000000` (validation type 0, entity id 0 = FALLBACK_VALIDATION_ID)
876
+ * `FF` (RESERVED_VALIDATION_DATA_INDEX, the final segment) `00`
877
+ * (SignatureType.EOA).
878
+ *
879
+ * Because step 1 is still an ordinary typed-data signature, a REMOTE signer (a
880
+ * delegated agentic wallet) can produce it: the private key is never needed
881
+ * locally.
882
+ *
883
+ * **THE WRAP IS DELEGATE-SPECIFIC, NOT "delegated == wrap".** This is the part
884
+ * that bit us on 2026-08-25, and it is why this module exists in TypeScript at
885
+ * all: the moment a worker rates an agent through the facilitator's EIP-7702
886
+ * feedback rail, their EOA becomes delegated to Execution Market's
887
+ * `FeedbackDelegate` — which validates PLAIN ECDSA
888
+ * (`ECDSA.recover(hash, sig) == address(this)`). Wrapping that signature makes it
889
+ * invalid, so *the act of rating would break the rater's next payment*. Same
890
+ * silent-at-lock-time failure the wrap was built to fix, now caused by
891
+ * over-applying it.
892
+ *
893
+ * **DETECTION IS INJECTABLE, ON PURPOSE.** Knowing whether an address is
894
+ * delegated needs one `eth_getCode` — a chain read, and this SDK does not own an
895
+ * RPC policy. So detection is a callable you pass in ({@link DelegationResolver}).
896
+ * {@link rpcDelegationResolver} ships a default over `fetch`; a caller with its
897
+ * own endpoints, proxy or rotation passes its own.
898
+ *
899
+ * **THE THIRD STATE IS LOAD-BEARING.** A resolver returns `true` / `false` /
900
+ * **`null` = could not tell**. `null` must never collapse to "not delegated":
901
+ * that is exactly how the original bug survived eight days — the resolver failed,
902
+ * returned nothing, and the caller's `if (delegated)` read it as falsy and signed
903
+ * raw. **An unreadable answer is not a negative answer.**
904
+ *
905
+ * Port of `uvd_x402_sdk.erc7702` (Python). Keep the two in step: they are the
906
+ * same protocol seen from two languages, and a divergence here is a signature
907
+ * that cannot settle.
908
+ */
909
+ /** EIP-7702 delegation designator. */
910
+ declare const DELEGATE_PREFIX = "ef0100";
911
+ /**
912
+ * Delegate targets whose ERC-1271 needs the account-envelope wrap.
913
+ *
914
+ * Only a known Alchemy SMA implementation needs it. Every other delegate signs
915
+ * PLAIN (the standard 1271 "smart-EOA" pattern accepts the EOA's own ECDSA), and
916
+ * if some exotic future delegate needs a third dialect it fails VISIBLY at lock
917
+ * time — never silently.
918
+ *
919
+ * Notably NOT here: Execution Market's `FeedbackDelegate` (all nine deploys). An
920
+ * account that has rated through the facilitator's rail is delegated to it and
921
+ * must sign plain.
922
+ */
923
+ declare const SMA_WRAP_TARGETS: readonly string[];
924
+ /**
925
+ * `true` only when this delegate target needs the account-envelope wrap.
926
+ *
927
+ * A plain EOA (`target` null/undefined) and any other delegate — FeedbackDelegate,
928
+ * a standard 1271 smart-EOA — do NOT: they take the ordinary ECDSA signature.
929
+ */
930
+ declare function needsAccountWrap(target: string | null | undefined): boolean;
931
+ /**
932
+ * The delegate target this EOA's EIP-7702 code points at, or `null`.
933
+ *
934
+ * `null` for a plain EOA, for non-7702 code, and for anything unparseable — the
935
+ * caller confirms the implementation before applying the wrap.
936
+ */
937
+ declare function delegateTarget(code: string | null | undefined): string | null;
938
+ /** Wrap a 65-byte ECDSA signature in the account's fallback-EOA envelope. */
939
+ declare function wrapSignature(innerSignature: string): string;
940
+ /**
941
+ * The typed data whose signature the delegated account accepts.
942
+ *
943
+ * The domain is the ACCOUNT's own — `chainId` + `verifyingContract=account`, the
944
+ * two fields its typehash declares — and the struct is
945
+ * `ReplaySafeHash(bytes32 hash)` over the inner EIP-3009 digest. Signing THIS,
946
+ * not the digest, is what validates.
947
+ */
948
+ declare function replaySafeTypedData(innerDigest: string, chainId: number, account: string): {
949
+ domain: Record<string, unknown>;
950
+ types: Record<string, unknown>;
951
+ message: Record<string, unknown>;
952
+ };
953
+ /**
954
+ * `(address, network) -> target | true | false | null`.
955
+ *
956
+ * Four answers, richer than a boolean on purpose: a resolver MAY return the
957
+ * delegate **target address** (a string) instead of just `true` — that is what
958
+ * lets the caller pick the right signing dialect (SMA wrap vs plain). `true` =
959
+ * delegated, target unknown; `false` = not delegated; `null` = UNKNOWN
960
+ * (unreadable chain), never "no".
961
+ */
962
+ type DelegationResolver = (address: string, network: string) => Promise<string | boolean | null> | (string | boolean | null);
963
+ /**
964
+ * A default resolver over plain JSON-RPC `eth_getCode`, using `fetch`.
965
+ *
966
+ * Rotates through `urls` and returns `null` when **every** endpoint failed: an
967
+ * unreadable chain is not a "not delegated" verdict.
968
+ *
969
+ * A caller with its own RPC policy (a signed proxy, paid endpoints, per-chain
970
+ * routing) should pass its own resolver instead; that is the whole point of the
971
+ * injection.
972
+ */
973
+ declare function rpcDelegationResolver(urls: string | string[], timeoutMs?: number): DelegationResolver;
974
+ /** `{ delegated, target }` for one address on one network. */
975
+ interface DelegationVerdict {
976
+ /** `null` = UNKNOWN. Never treat it as `false`. */
977
+ delegated: boolean | null;
978
+ /** The delegate implementation, when the resolver could name it. */
979
+ target: string | null;
980
+ }
981
+ /**
982
+ * Resolve one address's delegation on one network.
983
+ *
984
+ * - `{delegated: null, target: null}` — unknown (no resolver, or an unreadable
985
+ * chain). NEVER "no".
986
+ * - `{delegated: false, target: null}` — a plain EOA.
987
+ * - `{delegated: true, target: '0x…'}` — delegated, and we know to WHAT (pick the
988
+ * dialect from it).
989
+ * - `{delegated: true, target: null}` — delegated, target unknown (a legacy
990
+ * boolean-only resolver).
991
+ */
992
+ declare function resolveDelegation(address: string, network: string, resolver?: DelegationResolver): Promise<DelegationVerdict>;
993
+ /**
994
+ * `true` / `false` / `null` (**unknown**) for one address on one network.
995
+ *
996
+ * Without a resolver the answer is `null`, never `false`: "nobody asked" and "the
997
+ * address is a plain EOA" are different facts and only one of them is safe to
998
+ * sign on.
999
+ */
1000
+ declare function isDelegated(address: string, network: string, resolver?: DelegationResolver): Promise<boolean | null>;
1001
+
845
1002
  /**
846
1003
  * uvd-x402-sdk - Escrow Pre-Auth Builder (sign-on-assignment)
847
1004
  *
@@ -892,6 +1049,7 @@ declare function buildSignatureParams(params: SignatureParamsInput): string;
892
1049
  * (production reference, derived from `uvd_x402_sdk.advanced_escrow`)
893
1050
  * - Browser (viem): Execution Market `dashboard/src/services/h2aSigning.ts`
894
1051
  */
1052
+
895
1053
  /** AuthCaptureEscrow deposit condition: $100 max per deposit. */
896
1054
  declare const ESCROW_DEPOSIT_LIMIT_USD = 100;
897
1055
  /**
@@ -1001,6 +1159,20 @@ interface EscrowPreAuthParams {
1001
1159
  * publishes a different limit.
1002
1160
  */
1003
1161
  depositLimitUsd?: number;
1162
+ /**
1163
+ * How to find out whether `payerWallet` is EIP-7702-delegated, and to what.
1164
+ *
1165
+ * Optional, and its absence is honest rather than convenient: with no resolver
1166
+ * the verdict is UNKNOWN and this function signs the ordinary way, exactly as
1167
+ * it did before. Pass one — {@link rpcDelegationResolver} or your own — as soon
1168
+ * as your payers can be delegated accounts, because a delegated payer signing
1169
+ * the wrong dialect produces an authorization that **cannot settle on-chain**
1170
+ * and only fails at lock time.
1171
+ *
1172
+ * When a resolver IS supplied and it reaches no verdict, this throws instead of
1173
+ * guessing: an unreadable chain is not a "not delegated" answer.
1174
+ */
1175
+ delegationResolver?: DelegationResolver;
1004
1176
  }
1005
1177
  /**
1006
1178
  * Build + sign the escrow lock authorization AT ASSIGNMENT time and return
@@ -1015,4 +1187,4 @@ interface EscrowPreAuthParams {
1015
1187
  */
1016
1188
  declare function buildEscrowPreAuth(wallet: EscrowPreAuthSigner, params: EscrowPreAuthParams): Promise<string>;
1017
1189
 
1018
- export { ANCHOR_MAX_REQUEST_BYTES, type AnchorOptions, type AnchoredEvidence, type BackendOffer, ContentHashMismatch, type CreateSignedFetchConfig, DX402Error, type ERC8128RequestOptions, ESCROW_DEPOSIT_LIMIT_USD, ESCROW_TIER_WINDOWS, EVENT_KINDS, EVIDENCE_HEADER, type EscrowNetworkConfig, type EscrowPaymentInfo, type EscrowPreAuthParams, type EscrowPreAuthSigner, type EscrowTierWindows, type EvidenceMode, EvidenceSkipped, FACILITATOR_ADDRESSES, type FacilitatorAddresses, KEEPALIVE_INTERVAL_MS, OPERATOR_FEE_BPS, type RecipientRole, type SSEFrame, SSEParser, type SignRequestOptions, type SignRequestWithSignerOptions, type SignatureBaseParams, type SignatureHeaders, type SignatureParamsInput, SigningWalletAdapter, type StreamTrafficEventsOptions, type TrafficEvent, type TrafficEventKind, TrafficStreamError, ZERO_ADDRESS, anchorDigest, anchorEvidence, availableBackends, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, evidenceHeader, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, payerKeyFromSolanaAddress, recoverEvidence, sealEvidence, sealEvidenceTo, sealedRoles, sellerDigestFor, signAnchorEd25519, signAnchorEvm, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal };
1190
+ export { ANCHOR_MAX_REQUEST_BYTES, type AnchorOptions, type AnchoredEvidence, type BackendOffer, ContentHashMismatch, type CreateSignedFetchConfig, DELEGATE_PREFIX, DX402Error, type DelegationResolver, type DelegationVerdict, type ERC8128RequestOptions, ESCROW_DEPOSIT_LIMIT_USD, ESCROW_TIER_WINDOWS, EVENT_KINDS, EVIDENCE_HEADER, type EscrowNetworkConfig, type EscrowPaymentInfo, type EscrowPreAuthParams, type EscrowPreAuthSigner, type EscrowTierWindows, type EvidenceMode, EvidenceSkipped, FACILITATOR_ADDRESSES, type FacilitatorAddresses, KEEPALIVE_INTERVAL_MS, OPERATOR_FEE_BPS, type RecipientRole, SMA_WRAP_TARGETS, type SSEFrame, SSEParser, type SignRequestOptions, type SignRequestWithSignerOptions, type SignatureBaseParams, type SignatureHeaders, type SignatureParamsInput, SigningWalletAdapter, type StreamTrafficEventsOptions, type TrafficEvent, type TrafficEventKind, TrafficStreamError, ZERO_ADDRESS, anchorDigest, anchorEvidence, availableBackends, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, delegateTarget, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, evidenceHeader, fetchNonce, getFacilitatorAddress, isDelegated, isEndToEnd, matchesFilters, needsAccountWrap, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, payerKeyFromSolanaAddress, recoverEvidence, replaySafeTypedData, resolveDelegation, rpcDelegationResolver, sealEvidence, sealEvidenceTo, sealedRoles, sellerDigestFor, signAnchorEd25519, signAnchorEvm, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal, wrapSignature };
package/dist/index.js CHANGED
@@ -3424,6 +3424,88 @@ function buildSignatureParams(params) {
3424
3424
  parts.push(`alg="${ALG}"`);
3425
3425
  return parts.join(";");
3426
3426
  }
3427
+
3428
+ // src/erc7702.ts
3429
+ var DELEGATE_PREFIX = "ef0100";
3430
+ var SMA_WRAP_TARGETS = [
3431
+ "0x69007702764179f14f51cdce752f4f775d74e139"
3432
+ // Alchemy SemiModularAccount7702
3433
+ ];
3434
+ function needsAccountWrap(target) {
3435
+ return !!target && SMA_WRAP_TARGETS.includes(target.toLowerCase());
3436
+ }
3437
+ var WRAP_PREFIX = "0000000000ff00";
3438
+ function delegateTarget(code) {
3439
+ if (!code) return null;
3440
+ const h = (code.startsWith("0x") ? code.slice(2) : code).toLowerCase();
3441
+ if (!h.startsWith(DELEGATE_PREFIX) || h.length < 46) return null;
3442
+ return `0x${h.slice(6, 46)}`;
3443
+ }
3444
+ function wrapSignature(innerSignature) {
3445
+ const s = innerSignature.startsWith("0x") ? innerSignature.slice(2) : innerSignature;
3446
+ return `0x${WRAP_PREFIX}${s}`;
3447
+ }
3448
+ function replaySafeTypedData(innerDigest, chainId, account) {
3449
+ return {
3450
+ domain: { chainId, verifyingContract: account },
3451
+ types: { ReplaySafeHash: [{ name: "hash", type: "bytes32" }] },
3452
+ message: { hash: innerDigest }
3453
+ };
3454
+ }
3455
+ function rpcDelegationResolver(urls, timeoutMs = 6e3) {
3456
+ const endpoints = typeof urls === "string" ? [urls] : [...urls];
3457
+ return async (address) => {
3458
+ const addr = (address || "").trim();
3459
+ if (!addr || endpoints.length === 0) return null;
3460
+ for (const url of endpoints) {
3461
+ const controller = new AbortController();
3462
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
3463
+ try {
3464
+ const res = await fetch(url, {
3465
+ method: "POST",
3466
+ headers: { "Content-Type": "application/json" },
3467
+ body: JSON.stringify({
3468
+ jsonrpc: "2.0",
3469
+ id: 1,
3470
+ method: "eth_getCode",
3471
+ params: [addr.toLowerCase(), "latest"]
3472
+ }),
3473
+ signal: controller.signal
3474
+ });
3475
+ clearTimeout(timer);
3476
+ if (!res.ok) continue;
3477
+ const code = (await res.json())?.result;
3478
+ if (typeof code === "string" && code.startsWith("0x")) {
3479
+ return delegateTarget(code) ?? false;
3480
+ }
3481
+ } catch {
3482
+ clearTimeout(timer);
3483
+ }
3484
+ }
3485
+ return null;
3486
+ };
3487
+ }
3488
+ async function resolveDelegation(address, network, resolver) {
3489
+ if (!resolver) return { delegated: null, target: null };
3490
+ let v;
3491
+ try {
3492
+ v = await resolver(address, network);
3493
+ } catch {
3494
+ return { delegated: null, target: null };
3495
+ }
3496
+ if (typeof v === "string") {
3497
+ const t = v.trim();
3498
+ if (/^0x[0-9a-fA-F]{40}$/.test(t)) return { delegated: true, target: t };
3499
+ return { delegated: null, target: null };
3500
+ }
3501
+ if (typeof v === "boolean") return { delegated: v, target: null };
3502
+ return { delegated: null, target: null };
3503
+ }
3504
+ async function isDelegated(address, network, resolver) {
3505
+ return (await resolveDelegation(address, network, resolver)).delegated;
3506
+ }
3507
+
3508
+ // src/escrow-preauth.ts
3427
3509
  var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
3428
3510
  var USDC_DECIMALS = 6;
3429
3511
  var ESCROW_DEPOSIT_LIMIT_USD = 100;
@@ -3569,26 +3651,56 @@ async function buildEscrowPreAuth(wallet, params) {
3569
3651
  );
3570
3652
  const payer = ethers.ethers.getAddress(params.payerWallet);
3571
3653
  const tokenCollector = ethers.ethers.getAddress(cfg.token_collector);
3572
- const { signature } = await wallet.signTypedData(
3573
- JSON.stringify({
3574
- domain: {
3575
- name: cfg.usdc_domain_name,
3576
- version: cfg.usdc_domain_version,
3577
- chainId: cfg.chain_id,
3578
- verifyingContract: ethers.ethers.getAddress(cfg.usdc)
3579
- },
3580
- types: RECEIVE_WITH_AUTHORIZATION_TYPES,
3581
- primaryType: "ReceiveWithAuthorization",
3582
- message: {
3583
- from: payer,
3584
- to: tokenCollector,
3585
- value: maxAmount.toString(),
3586
- validAfter: "0",
3587
- validBefore: String(paymentInfo.preApprovalExpiry),
3588
- nonce
3589
- }
3590
- })
3654
+ const typedData = {
3655
+ domain: {
3656
+ name: cfg.usdc_domain_name,
3657
+ version: cfg.usdc_domain_version,
3658
+ chainId: cfg.chain_id,
3659
+ verifyingContract: ethers.ethers.getAddress(cfg.usdc)
3660
+ },
3661
+ types: RECEIVE_WITH_AUTHORIZATION_TYPES,
3662
+ primaryType: "ReceiveWithAuthorization",
3663
+ message: {
3664
+ from: payer,
3665
+ to: tokenCollector,
3666
+ value: maxAmount.toString(),
3667
+ validAfter: "0",
3668
+ validBefore: String(paymentInfo.preApprovalExpiry),
3669
+ nonce
3670
+ }
3671
+ };
3672
+ const { delegated, target } = await resolveDelegation(
3673
+ payer,
3674
+ `eip155:${cfg.chain_id}`,
3675
+ params.delegationResolver
3591
3676
  );
3677
+ if (delegated === null && params.delegationResolver) {
3678
+ throw new X402Error(
3679
+ `Could not determine whether ${payer} is EIP-7702-delegated on chain ${cfg.chain_id} (the resolver gave no verdict) \u2014 refusing to sign an escrow authorization blindly: the wrong dialect is unsettleable on-chain and the failure only shows up at lock time. Retry when the chain is readable.`,
3680
+ "INVALID_CONFIG"
3681
+ );
3682
+ }
3683
+ let signature;
3684
+ if (delegated && (needsAccountWrap(target) || target === null)) {
3685
+ const innerDigest = ethers.ethers.TypedDataEncoder.hash(
3686
+ typedData.domain,
3687
+ typedData.types,
3688
+ typedData.message
3689
+ );
3690
+ const replaySafe = replaySafeTypedData(innerDigest, cfg.chain_id, payer);
3691
+ const wrapped = await wallet.signTypedData(
3692
+ JSON.stringify({ ...replaySafe, primaryType: "ReplaySafeHash" })
3693
+ );
3694
+ if (!wrapped?.signature) {
3695
+ throw new X402Error(
3696
+ "The wallet returned no signature for the replay-safe wrap \u2014 refusing to build an unsettleable authorization.",
3697
+ "INVALID_CONFIG"
3698
+ );
3699
+ }
3700
+ signature = wrapSignature(wrapped.signature);
3701
+ } else {
3702
+ signature = (await wallet.signTypedData(JSON.stringify(typedData))).signature;
3703
+ }
3592
3704
  return JSON.stringify({
3593
3705
  x402Version: 2,
3594
3706
  scheme: "escrow",
@@ -4404,6 +4516,7 @@ exports.DEFAULT_CHAIN = DEFAULT_CHAIN;
4404
4516
  exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
4405
4517
  exports.DEFAULT_FACILITATOR_URL = DEFAULT_FACILITATOR_URL;
4406
4518
  exports.DEFAULT_PAYMENT_HEADER = DEFAULT_PAYMENT_HEADER;
4519
+ exports.DELEGATE_PREFIX = DELEGATE_PREFIX;
4407
4520
  exports.DX402Error = DX402Error;
4408
4521
  exports.ESCROW_DEPOSIT_LIMIT_USD = ESCROW_DEPOSIT_LIMIT_USD;
4409
4522
  exports.ESCROW_TIER_WINDOWS = ESCROW_TIER_WINDOWS;
@@ -4417,6 +4530,7 @@ exports.KEEPALIVE_INTERVAL_MS = KEEPALIVE_INTERVAL_MS;
4417
4530
  exports.OPERATOR_FEE_BPS = OPERATOR_FEE_BPS;
4418
4531
  exports.OWSWalletAdapter = OWSWalletAdapter;
4419
4532
  exports.PAYMENT_HEADER_NAMES = PAYMENT_HEADER_NAMES;
4533
+ exports.SMA_WRAP_TARGETS = SMA_WRAP_TARGETS;
4420
4534
  exports.SSEParser = SSEParser;
4421
4535
  exports.SUPPORTED_CHAINS = SUPPORTED_CHAINS;
4422
4536
  exports.TrafficStreamError = TrafficStreamError;
@@ -4452,6 +4566,7 @@ exports.createX402V1Header = createX402V1Header;
4452
4566
  exports.createX402V2Header = createX402V2Header;
4453
4567
  exports.decodeBase64Utf8 = decodeBase64Utf8;
4454
4568
  exports.decodeX402Header = decodeX402Header;
4569
+ exports.delegateTarget = delegateTarget;
4455
4570
  exports.dereferencePointer = dereferencePointer;
4456
4571
  exports.detectX402Version = detectX402Version;
4457
4572
  exports.dx402PaymentId = paymentId;
@@ -4486,12 +4601,14 @@ exports.getXRPLChains = getXRPLChains;
4486
4601
  exports.isAlgorandChain = isAlgorandChain;
4487
4602
  exports.isCAIP2Format = isCAIP2Format;
4488
4603
  exports.isChainSupported = isChainSupported;
4604
+ exports.isDelegated = isDelegated;
4489
4605
  exports.isEndToEnd = isEndToEnd;
4490
4606
  exports.isSVMChain = isSVMChain;
4491
4607
  exports.isSuiChain = isSuiChain;
4492
4608
  exports.isTokenSupported = isTokenSupported;
4493
4609
  exports.isXRPLChain = isXRPLChain;
4494
4610
  exports.matchesFilters = matchesFilters;
4611
+ exports.needsAccountWrap = needsAccountWrap;
4495
4612
  exports.parseEvidenceHeader = parseEvidenceHeader;
4496
4613
  exports.parseNetworkIdentifier = parseNetworkIdentifier;
4497
4614
  exports.parseSealed = parseSealed;
@@ -4500,6 +4617,9 @@ exports.payerKeyFromEvmSignature = payerKeyFromEvmSignature;
4500
4617
  exports.payerKeyFromSolanaAddress = payerKeyFromSolanaAddress;
4501
4618
  exports.paymentChallengeFrom = paymentChallengeFrom;
4502
4619
  exports.recoverEvidence = recoverEvidence;
4620
+ exports.replaySafeTypedData = replaySafeTypedData;
4621
+ exports.resolveDelegation = resolveDelegation;
4622
+ exports.rpcDelegationResolver = rpcDelegationResolver;
4503
4623
  exports.sealEvidence = sealEvidence;
4504
4624
  exports.sealEvidenceTo = sealEvidenceTo;
4505
4625
  exports.sealedRoles = sealedRoles;
@@ -4513,5 +4633,6 @@ exports.streamTrafficEvents = streamTrafficEvents;
4513
4633
  exports.unseal = unseal;
4514
4634
  exports.validateAmount = validateAmount;
4515
4635
  exports.validateRecipient = validateRecipient;
4636
+ exports.wrapSignature = wrapSignature;
4516
4637
  //# sourceMappingURL=index.js.map
4517
4638
  //# sourceMappingURL=index.js.map