@chipi-stack/backend 14.4.0 → 14.7.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 +1581 -7
- package/dist/index.d.ts +1581 -7
- package/dist/index.js +2493 -321
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2411 -317
- package/dist/index.mjs.map +1 -1
- package/dist/skuPurchases.js +413 -19
- package/dist/skuPurchases.js.map +1 -1
- package/dist/skuPurchases.mjs +413 -19
- package/dist/skuPurchases.mjs.map +1 -1
- package/dist/transactions.js +413 -19
- package/dist/transactions.js.map +1 -1
- package/dist/transactions.mjs +413 -19
- package/dist/transactions.mjs.map +1 -1
- package/dist/wallets-CYCLyyXY.d.mts +221 -0
- package/dist/wallets-DxKnh21Q.d.ts +221 -0
- package/dist/wallets.d.mts +3 -56
- package/dist/wallets.d.ts +3 -56
- package/dist/wallets.js +926 -31
- package/dist/wallets.js.map +1 -1
- package/dist/wallets.mjs +926 -31
- package/dist/wallets.mjs.map +1 -1
- package/package.json +10 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as _chipi_stack_types from '@chipi-stack/types';
|
|
2
|
-
import { CreateSessionKeyParams, SessionKeyData, AddSessionKeyParams, RevokeSessionKeyParams, GetSessionDataParams, SessionDataResponse, ExecuteWithSessionParams, SetSpendingPolicyParams, GetSpendingPolicyParams, SpendingPolicyData, RemoveSpendingPolicyParams, Currency, PaymasterAdapter, PaymasterAccount, X402FacilitatorConfig, NonceStore, PaymentPayload, VerifyResponse, SettleResponse, ChipiSDKConfig, ExecuteTransactionParams, SignTypedDataParams, SignTypedDataResult, TransferParams, ApproveParams, CallAnyContractParams, CreateWalletParams, CreateWalletResponse, RecordSendTransactionParams, Transaction, GetTransactionListQuery, PaginatedResponse, GetWalletParams, GetTokenBalanceParams, GetTokenBalanceResponse, CreateSkuPurchaseParams, GetSkuListQuery, Sku, TransactionStatusResponse, PrepareWalletUpgradeParams, PrepareWalletUpgradeResponse, ExecuteWalletUpgradeParams, ExecuteWalletUpgradeResponse, ChipiServerSDKConfig, ChipiBrowserSDKConfig, X402PaymentConfig } from '@chipi-stack/types';
|
|
2
|
+
import { CreateSessionKeyParams, SessionKeyData, AddSessionKeyParams, RevokeSessionKeyParams, GetSessionDataParams, SessionDataResponse, ExecuteWithSessionParams, SetSpendingPolicyParams, GetSpendingPolicyParams, SpendingPolicyData, RemoveSpendingPolicyParams, Currency, PaymasterAdapter, PaymasterAccount, X402FacilitatorConfig, NonceStore, PaymentPayload, VerifyResponse, SettleResponse, ChipiSDKConfig, ExecuteTransactionParams, SignTypedDataParams, SignTypedDataResult, TransferParams, ApproveParams, CallAnyContractParams, CreateWalletParams, CreateWalletResponse, RecordSendTransactionParams, Transaction, GetTransactionListQuery, PaginatedResponse, GetWalletParams, GetTokenBalanceParams, GetTokenBalanceResponse, CreateSkuPurchaseParams, GetSkuListQuery, Sku, TransactionStatusResponse, PrepareWalletUpgradeParams, PrepareWalletUpgradeResponse, ExecuteWalletUpgradeParams, ExecuteWalletUpgradeResponse, ChipiServerSDKConfig, ChipiBrowserSDKConfig, X402ClientConfig, PaymentRequirement, X402PaymentConfig, ShhhSignerKind } from '@chipi-stack/types';
|
|
3
3
|
export * from '@chipi-stack/types';
|
|
4
4
|
export { Chain, ChainToken, Currency } from '@chipi-stack/types';
|
|
5
|
-
import { ChipiWallets } from './wallets.js';
|
|
5
|
+
import { C as ChipiWallets } from './wallets-DxKnh21Q.js';
|
|
6
|
+
export { E as ExecuteMigrateResponse, M as MigrateChipiWalletToShhhParams, a as MigrationOutcome, P as PrepareMigrateResponse, m as migrateChipiWalletToShhh } from './wallets-DxKnh21Q.js';
|
|
6
7
|
import { ChipiTransactions } from './transactions.js';
|
|
7
8
|
import { ChipiSkuPurchases } from './skuPurchases.js';
|
|
8
9
|
import { ChipiSkus } from './skus.js';
|
|
@@ -21,10 +22,22 @@ declare class ChipiSessions {
|
|
|
21
22
|
private client;
|
|
22
23
|
constructor(client: ChipiClient);
|
|
23
24
|
/**
|
|
24
|
-
* Validate that the wallet
|
|
25
|
-
*
|
|
25
|
+
* Validate that the wallet supports SNIP-163 session keys.
|
|
26
|
+
*
|
|
27
|
+
* Both CHIPI v29 (sessions-smart-contract) and SHHH V8.4
|
|
28
|
+
* (ShhhAccount) embed the same SNIP-163 SessionKeyComponent, so
|
|
29
|
+
* `add_or_update_session_key` / `revoke_session_key` /
|
|
30
|
+
* `get_session_data` selectors exist on both. The 4-felt session
|
|
31
|
+
* signature shape (`[session_pubkey, r, s, valid_until]`) is the
|
|
32
|
+
* same on both — V8.4's `execute_from_outside_v2` checks
|
|
33
|
+
* `signature.len() == 4` at account.cairo:389 BEFORE the
|
|
34
|
+
* V2_SNIP12 envelope dispatch, so sessions land through the
|
|
35
|
+
* legacy fast path on V8.4 wallets too.
|
|
36
|
+
*
|
|
37
|
+
* READY (Argent X v0.4.0) does NOT implement SNIP-163; reject with
|
|
38
|
+
* a clear error.
|
|
26
39
|
*/
|
|
27
|
-
private
|
|
40
|
+
private validateWalletSupportsSessions;
|
|
28
41
|
/**
|
|
29
42
|
* Convert a Uint8Array to hex string with 0x prefix.
|
|
30
43
|
*/
|
|
@@ -38,6 +51,15 @@ declare class ChipiSessions {
|
|
|
38
51
|
*
|
|
39
52
|
* After generating, call `addSessionKeyToContract()` to register the session on-chain.
|
|
40
53
|
*
|
|
54
|
+
* **PIN is weak — not recommended for production.** `encryptKey` here
|
|
55
|
+
* is the symmetric key the SDK uses to encrypt the session's private
|
|
56
|
+
* key at rest. If you're passing a user-typed PIN, anyone who
|
|
57
|
+
* observes that PIN can decrypt the session. Derive `encryptKey`
|
|
58
|
+
* from a platform passkey (`@chipi-stack/chipi-passkey`) — for SHHH
|
|
59
|
+
* V8.4 wallets prefer `createShhhPasskey` + `signShhhMessage`, where
|
|
60
|
+
* the wallet's primary key never needs to be decrypted client-side
|
|
61
|
+
* at all.
|
|
62
|
+
*
|
|
41
63
|
* @param params - Session creation parameters
|
|
42
64
|
* @returns SessionKeyData for external storage
|
|
43
65
|
*
|
|
@@ -459,7 +481,7 @@ declare class ChipiSDK {
|
|
|
459
481
|
* });
|
|
460
482
|
* ```
|
|
461
483
|
*/
|
|
462
|
-
signTypedData({ params
|
|
484
|
+
signTypedData({ params }: {
|
|
463
485
|
params: SignTypedDataParams;
|
|
464
486
|
}): Promise<SignTypedDataResult>;
|
|
465
487
|
/**
|
|
@@ -625,6 +647,126 @@ declare const signTypedData: ({ params, }: {
|
|
|
625
647
|
declare const encryptPrivateKey: (privateKey: string, password: string) => string;
|
|
626
648
|
declare const decryptPrivateKey: (encryptedPrivateKey: string, password: string) => string;
|
|
627
649
|
|
|
650
|
+
/**
|
|
651
|
+
* SHHH V8.4 buyer client for the x402 payment protocol.
|
|
652
|
+
*
|
|
653
|
+
* The CHIPI v29 / Argent `X402Client` (in `@chipi-stack/core`) signs
|
|
654
|
+
* a SNIP-12 typed-data message with `account.signMessage()` and lets
|
|
655
|
+
* the facilitator build the on-chain USDC transfer. That path does
|
|
656
|
+
* not work for SHHH V8.4 wallets: V8.4's
|
|
657
|
+
* `execute_from_outside_v2` at `account.cairo:389` rejects raw
|
|
658
|
+
* `{r, s}` (2-felt) signatures and dispatches based on
|
|
659
|
+
* `signature[0]` to either the V2_SNIP12 envelope path (single-owner
|
|
660
|
+
* primary verifier) or the 4-felt session-sig fast path.
|
|
661
|
+
*
|
|
662
|
+
* `X402ShhhClient` builds the full execute_from_outside_v2 calldata
|
|
663
|
+
* + signs the V8.4 SNIP-12 hash locally with the primary STARK key,
|
|
664
|
+
* then ships the pre-signed span in the `payload.shhh.oeCalldata`
|
|
665
|
+
* field. The facilitator (`X402Facilitator.settle`) detects
|
|
666
|
+
* `payload.shhh` and forwards verbatim through
|
|
667
|
+
* `/transactions/execute-sponsored-raw`. Self-custodial:
|
|
668
|
+
* `encryptKey` decrypts the wallet's STARK key client-side; chipi-
|
|
669
|
+
* back never sees the cleartext.
|
|
670
|
+
*
|
|
671
|
+
* Same `fetch(url, init)` surface as `X402Client` so an integrator
|
|
672
|
+
* can pick the right client at construction time based on their
|
|
673
|
+
* wallet type:
|
|
674
|
+
*
|
|
675
|
+
* const client = wallet.walletType === "SHHH"
|
|
676
|
+
* ? new X402ShhhClient({ wallet, encryptKey, chipiClient, bearerToken })
|
|
677
|
+
* : new X402Client(account);
|
|
678
|
+
*
|
|
679
|
+
* Wave B ships STARK + ED25519 primary signers; the SDK rejects
|
|
680
|
+
* other kinds with a clear error so a future external-rooted SHHH
|
|
681
|
+
* wallet (MetaMask, Phantom, passkey, Apple) doesn't silently
|
|
682
|
+
* produce a sig that reverts on chain.
|
|
683
|
+
*/
|
|
684
|
+
|
|
685
|
+
interface FetchResponse {
|
|
686
|
+
status: number;
|
|
687
|
+
headers: {
|
|
688
|
+
get(name: string): string | null;
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* The minimal wallet shape the SHHH client needs to sign payments.
|
|
693
|
+
* Matches the `WalletData`-derived row callers already pass to the
|
|
694
|
+
* rest of the SDK.
|
|
695
|
+
*/
|
|
696
|
+
interface ShhhX402Wallet {
|
|
697
|
+
/** Deployed account address (NOT the raw STARK pubkey). */
|
|
698
|
+
publicKey: string;
|
|
699
|
+
/** Wallet's PIN/passkey-encrypted STARK private key (ciphertext). */
|
|
700
|
+
encryptedPrivateKey: string;
|
|
701
|
+
/**
|
|
702
|
+
* Defaults to `"STARK"`. Future external-rooted kinds will need
|
|
703
|
+
* their own signing path; the client throws when handed an
|
|
704
|
+
* unsupported kind so silent on-chain reverts are impossible.
|
|
705
|
+
*/
|
|
706
|
+
signerKind?: "STARK" | "ED25519";
|
|
707
|
+
}
|
|
708
|
+
interface X402ShhhClientConfig extends X402ClientConfig {
|
|
709
|
+
/**
|
|
710
|
+
* Override the chain id used for SHHH SNIP-12 hashing. Defaults to
|
|
711
|
+
* Starknet mainnet per CLAUDE.md; sepolia is auto-inferred from
|
|
712
|
+
* the chipi-back base URL when present. Drift here = the on-chain
|
|
713
|
+
* V8.4 wallet rejects the signature.
|
|
714
|
+
*/
|
|
715
|
+
chainId?: bigint | string;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* SHHH-aware buyer client.
|
|
719
|
+
*/
|
|
720
|
+
declare class X402ShhhClient {
|
|
721
|
+
private readonly wallet;
|
|
722
|
+
private readonly encryptKey;
|
|
723
|
+
private readonly chipiClient;
|
|
724
|
+
private readonly bearerToken;
|
|
725
|
+
private readonly config;
|
|
726
|
+
private readonly fetchFn;
|
|
727
|
+
constructor(opts: {
|
|
728
|
+
/** The SHHH wallet making the payment. */
|
|
729
|
+
wallet: ShhhX402Wallet;
|
|
730
|
+
/** PIN / passkey-derived encryption key for the wallet's STARK key. */
|
|
731
|
+
encryptKey: string;
|
|
732
|
+
/** Chipi HTTP client; only used by the facilitator side. The client side
|
|
733
|
+
* keeps it for forward-compat if a future verifier flow needs to
|
|
734
|
+
* query chipi-back's chain-id helper at sign-time. */
|
|
735
|
+
chipiClient: ChipiClient;
|
|
736
|
+
/** Bearer token for chipi-back. Server-side only; never crosses the wire
|
|
737
|
+
* to an x402 seller. */
|
|
738
|
+
bearerToken: string;
|
|
739
|
+
config?: X402ShhhClientConfig;
|
|
740
|
+
});
|
|
741
|
+
/**
|
|
742
|
+
* Drop-in `fetch` replacement. If the seller returns 402, signs a
|
|
743
|
+
* payment + retries with the X-PAYMENT header. Same surface as
|
|
744
|
+
* `X402Client.fetch`.
|
|
745
|
+
*/
|
|
746
|
+
fetch(url: string, init?: Record<string, any>): Promise<FetchResponse>;
|
|
747
|
+
parsePaymentRequired(response: FetchResponse): PaymentRequirement;
|
|
748
|
+
private validateRequirement;
|
|
749
|
+
/**
|
|
750
|
+
* Sign an x402 payment as a SHHH V8.4 buyer. Builds the V8.4 OE
|
|
751
|
+
* containing the single USDC.transfer call, computes the SNIP-12
|
|
752
|
+
* hash against the wallet address + chain id, signs locally with
|
|
753
|
+
* the decrypted STARK private key, wraps the result in a V2_SNIP12
|
|
754
|
+
* envelope, and serializes the full execute_from_outside_v2
|
|
755
|
+
* calldata. Returns a PaymentPayload with the `shhh` extension
|
|
756
|
+
* populated; the facilitator's settle branch forwards
|
|
757
|
+
* `payload.shhh.oeCalldata` verbatim to
|
|
758
|
+
* `/transactions/execute-sponsored-raw`.
|
|
759
|
+
*
|
|
760
|
+
* The `signature: {r, s}` field is also populated with the inner
|
|
761
|
+
* `(r, s)` pair so the wire format stays valid for receivers that
|
|
762
|
+
* only inspect the legacy field. The facilitator uses the SHHH
|
|
763
|
+
* branch when `payload.shhh` is present, so the redundant `{r, s}`
|
|
764
|
+
* is informational, not load-bearing.
|
|
765
|
+
*/
|
|
766
|
+
signPayment(requirement: PaymentRequirement): Promise<PaymentPayload>;
|
|
767
|
+
private retryWithPayment;
|
|
768
|
+
}
|
|
769
|
+
|
|
628
770
|
interface X402MiddlewareConfig {
|
|
629
771
|
/** x402 facilitator instance for verify + settle */
|
|
630
772
|
facilitator: X402Facilitator;
|
|
@@ -674,4 +816,1436 @@ interface X402MiddlewareFlatConfig {
|
|
|
674
816
|
*/
|
|
675
817
|
declare function x402Middleware(config: X402MiddlewareConfig | X402MiddlewareFlatConfig): (req: any, res: any, next: any) => Promise<any>;
|
|
676
818
|
|
|
677
|
-
|
|
819
|
+
/**
|
|
820
|
+
* SNIP-12 typed-data hash for `OutsideExecution` — TypeScript reference.
|
|
821
|
+
*
|
|
822
|
+
* Vendored from haycarlitos/shhh-wallet-cairo @ v8.4 commit 3975532
|
|
823
|
+
* source: scripts/ts/snip12-hash.ts
|
|
824
|
+
* license: MIT (Copyright (c) 2026 Carlos Castillo and contributors)
|
|
825
|
+
*
|
|
826
|
+
* MUST produce byte-identical output to `compute_snip12_hash` in
|
|
827
|
+
* `src/outside_execution.cairo` at the same commit. If the canonical
|
|
828
|
+
* Cairo source ships a breaking change, the upstream Shhh maintainer
|
|
829
|
+
* will open a PR against this file (per Q3 of SHHH_INTEGRATION_ANSWERS.md).
|
|
830
|
+
*/
|
|
831
|
+
|
|
832
|
+
declare const STARKNET_MESSAGE_PREFIX: string;
|
|
833
|
+
declare const OE_DOMAIN_NAME: string;
|
|
834
|
+
declare const OE_DOMAIN_VERSION = 2n;
|
|
835
|
+
declare const OE_DOMAIN_REVISION = 1n;
|
|
836
|
+
declare const SIG_VERSION_V1_HEX_ASCII: string;
|
|
837
|
+
declare const SIG_VERSION_V2_SNIP12: string;
|
|
838
|
+
declare const SIG_VERSION_V2_THRESHOLD: string;
|
|
839
|
+
declare const ISRC9_V2_ID: bigint;
|
|
840
|
+
declare const ISIGNER_ID: bigint;
|
|
841
|
+
declare const ISIGNER_CANONICAL_LABEL = "ISigner_V1";
|
|
842
|
+
/** Recomputes the canonical `ISIGNER_ID` at runtime. */
|
|
843
|
+
declare function computeISignerId(): bigint;
|
|
844
|
+
type OutsideExecution = {
|
|
845
|
+
caller: bigint | string;
|
|
846
|
+
nonce: bigint | string;
|
|
847
|
+
execute_after: bigint;
|
|
848
|
+
execute_before: bigint;
|
|
849
|
+
calls: Call[];
|
|
850
|
+
};
|
|
851
|
+
declare function hashCall(call: Call): bigint;
|
|
852
|
+
declare function hashCalls(calls: Call[]): bigint;
|
|
853
|
+
declare function hashStarknetDomain(chainIdFelt: bigint | string): bigint;
|
|
854
|
+
declare function hashOutsideExecutionStruct(oe: OutsideExecution): bigint;
|
|
855
|
+
/**
|
|
856
|
+
* The authoritative SNIP-12 message hash for an OutsideExecution.
|
|
857
|
+
* Signers sign over this felt252 (or its hex-ASCII re-encoding for
|
|
858
|
+
* Phantom UX compatibility — see snip12HashToHexAsciiBytes).
|
|
859
|
+
*/
|
|
860
|
+
declare function computeSnip12Hash(oe: OutsideExecution, contractAddress: bigint | string, chainIdFelt: bigint | string): bigint;
|
|
861
|
+
/**
|
|
862
|
+
* Phantom hex-ASCII envelope helper. Phantom signs a string; we give it
|
|
863
|
+
* a 64-byte hex-ASCII representation of the SNIP-12 hash, and the Cairo
|
|
864
|
+
* verifier re-computes the hash and compares byte-by-byte.
|
|
865
|
+
*/
|
|
866
|
+
declare function snip12HashToHexAsciiBytes(h: bigint): Uint8Array;
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Deterministic ShhhAccount (V8.4) address derivation.
|
|
870
|
+
*
|
|
871
|
+
* Vendored from haycarlitos/shhh-wallet-cairo @ v8.4 commit 3975532
|
|
872
|
+
* source: scripts/ts/compute-wallet-address.ts
|
|
873
|
+
* license: MIT (Copyright (c) 2026 Carlos Castillo and contributors)
|
|
874
|
+
*
|
|
875
|
+
* Given the primary owner (kind + raw pubkey bytes) and the
|
|
876
|
+
* ShhhAccount class hash, returns the Starknet address the account
|
|
877
|
+
* will deploy to.
|
|
878
|
+
*
|
|
879
|
+
* MUST match `src/account.cairo::constructor` at the same commit. If
|
|
880
|
+
* the upstream constructor changes shape, the Shhh maintainer will
|
|
881
|
+
* open a PR against this file.
|
|
882
|
+
*/
|
|
883
|
+
type ComputeAddressInput = {
|
|
884
|
+
/** SHHH ShhhAccount class hash — see WALLET_CLASS_HASHES.SHHH in @chipi-stack/shared. */
|
|
885
|
+
classHash: bigint;
|
|
886
|
+
/** Primary signer kind, e.g. 'STARK' / 'ED25519' / 'WEBAUTHN_P256'. */
|
|
887
|
+
primaryKind: string;
|
|
888
|
+
/** Raw pubkey bytes interpreted as felt252-sized halves (kind-specific layout). */
|
|
889
|
+
pubkey: bigint[];
|
|
890
|
+
/** Verifier class hash for primaryKind — see SHHH_VERIFIER_CLASS_HASHES. */
|
|
891
|
+
verifierClassHash: bigint;
|
|
892
|
+
/** Optional user-facing tag bound into the constructor (default 'primary'). */
|
|
893
|
+
label?: string;
|
|
894
|
+
/** Factory / UDC address; default 0 for self-deploy. */
|
|
895
|
+
deployerAddress?: bigint;
|
|
896
|
+
};
|
|
897
|
+
/** Mirrors `src/signer/interface.cairo::owner_commitment` = poseidon(kind_tag, pubkey...). */
|
|
898
|
+
declare function ownerCommitment(kindTag: bigint, pubkey: bigint[]): bigint;
|
|
899
|
+
/**
|
|
900
|
+
* Mirrors the V8.4 constructor's salt derivation:
|
|
901
|
+
* salt = poseidon(primary_kind, primary_pubkey_hash)
|
|
902
|
+
* where primary_pubkey_hash = owner_commitment(primary_kind, pubkey).
|
|
903
|
+
* Binding kind into the salt ensures the same raw key under two
|
|
904
|
+
* different kinds produces two distinct addresses.
|
|
905
|
+
*/
|
|
906
|
+
declare function computeAddressSalt(primaryKind: string, pubkey: bigint[]): bigint;
|
|
907
|
+
/** Computes the deterministic deployment address for a SHHH ShhhAccount. */
|
|
908
|
+
declare function computeShhhAddress(input: ComputeAddressInput): bigint;
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Outside-Execution construction for SHHH V8.4 ShhhAccount.
|
|
912
|
+
*
|
|
913
|
+
* Wraps the canonical SNIP-12 hash + envelope assembly so SDK callers
|
|
914
|
+
* never touch the underlying felt-array shapes directly. Implements
|
|
915
|
+
* the envelope routing per V8.4 handover §5:
|
|
916
|
+
*
|
|
917
|
+
* [ signature[0] ]
|
|
918
|
+
* ├─ 'V2_SNIP12' → [owner_id, kind_tag, ...payload] (single-owner)
|
|
919
|
+
* ├─ 'V2_THRESHOLD' → [n, env_1_len, ..., env_n_len, ...] (multisig)
|
|
920
|
+
* └─ (else, len==4) → SNIP-163 session-key envelope
|
|
921
|
+
*
|
|
922
|
+
* Source of truth: `src/account.cairo::execute_from_outside_v2` at
|
|
923
|
+
* shhh-wallet-cairo v8.4 commit 3975532.
|
|
924
|
+
*/
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* The `caller` sentinel used for paymaster-sponsored OE flows. Any
|
|
928
|
+
* forwarder may submit the OE; binding `caller = ANY_CALLER` lifts the
|
|
929
|
+
* usual caller-equality check inside the account.
|
|
930
|
+
*/
|
|
931
|
+
declare const ANY_CALLER_FELT: bigint;
|
|
932
|
+
/**
|
|
933
|
+
* Per audit M-2, OE validity windows must not exceed 2 hours. The
|
|
934
|
+
* V8.4 account enforces this on-chain; we expose the constant so SDK
|
|
935
|
+
* callers can default sensibly.
|
|
936
|
+
*/
|
|
937
|
+
declare const MAX_OE_VALIDITY_WINDOW_SECONDS: number;
|
|
938
|
+
/**
|
|
939
|
+
* Build a standard paymaster-sponsored OutsideExecution with sensible
|
|
940
|
+
* defaults. Callers can override any field; the typical happy path is
|
|
941
|
+
* to pass only `nonce` + `calls`.
|
|
942
|
+
*/
|
|
943
|
+
declare function buildOutsideExecution(params: {
|
|
944
|
+
/** Unique per-account replay nonce. The account stores these in `oe_nonces`. */
|
|
945
|
+
nonce: bigint | string;
|
|
946
|
+
/** The calls the account should execute on success. */
|
|
947
|
+
calls: Call[];
|
|
948
|
+
/** Override `caller`; defaults to ANY_CALLER for paymaster flows. */
|
|
949
|
+
caller?: bigint | string;
|
|
950
|
+
/**
|
|
951
|
+
* UNIX seconds. Defaults to `now - OE_EXECUTE_AFTER_CLOCK_SKEW_BUFFER`
|
|
952
|
+
* (60s in the past) so the OE is valid by the time the next block is
|
|
953
|
+
* minted regardless of small clock drifts between the SDK host and
|
|
954
|
+
* the sequencer. The wallet's check is strict `<` (`account.cairo:377`
|
|
955
|
+
* `'SRC9: now <= execute_after'`) — without the buffer, a fast
|
|
956
|
+
* SDK→paymaster round trip can land in the SAME block as the OE
|
|
957
|
+
* was built in and revert with `'SRC9: too early'`. PR sdks#289
|
|
958
|
+
* (this builder fix) made the buffer the default after the first
|
|
959
|
+
* session smoke run reproduced the race.
|
|
960
|
+
*/
|
|
961
|
+
executeAfter?: bigint;
|
|
962
|
+
/** UNIX seconds; defaults to now + 2h (the maximum window per audit M-2). */
|
|
963
|
+
executeBefore?: bigint;
|
|
964
|
+
}): OutsideExecution;
|
|
965
|
+
/**
|
|
966
|
+
* Compute the SNIP-12 hash for the given OutsideExecution against
|
|
967
|
+
* a specific account address and chain. This is what the signer
|
|
968
|
+
* (STARK / Ed25519 / EIP-191 / WebAuthn / etc.) commits to.
|
|
969
|
+
*
|
|
970
|
+
* For Phantom UX where the user signs a string, hex-encode the
|
|
971
|
+
* resulting hash via `snip12HashToHexAsciiBytes` from snip12-hash.
|
|
972
|
+
*/
|
|
973
|
+
declare function computeOeHash(oe: OutsideExecution, accountAddress: bigint | string, chainId: bigint | string): bigint;
|
|
974
|
+
/**
|
|
975
|
+
* Single-owner V2_SNIP12 envelope.
|
|
976
|
+
*
|
|
977
|
+
* The account strips signature[0..2] = ['V2_SNIP12', owner_id, kind_tag]
|
|
978
|
+
* and library_calls the matching verifier with `payload`. Chipi
|
|
979
|
+
* paymaster passes the entire envelope verbatim to
|
|
980
|
+
* `execute_from_outside_v2`; it does NOT need to know the kind.
|
|
981
|
+
*
|
|
982
|
+
* **`ownerId` is a u32 INDEX into the wallet's owner_set**, NOT the
|
|
983
|
+
* poseidon `owner_commitment`. Primary owner = `0n`; subsequent owners
|
|
984
|
+
* added via `propose_add_owner` get `1n`, `2n`, etc. The V8.4
|
|
985
|
+
* dispatcher (`src/account.cairo:480`) panics with `'SHHH: bad
|
|
986
|
+
* owner_id'` if signature[1] does not fit in u32 — which happens
|
|
987
|
+
* silently at the wallet level (outer paymaster tx still reports
|
|
988
|
+
* SUCCEEDED). Discovered 2026-05-27 in a production trace audit.
|
|
989
|
+
*
|
|
990
|
+
* Returns the `signature: felt252[]` span you pass to
|
|
991
|
+
* `paymaster_executeSponsoredRaw.calldata`.
|
|
992
|
+
*/
|
|
993
|
+
declare function wrapV2Snip12Envelope(params: {
|
|
994
|
+
/** u32 INDEX into owner_set (0n for primary). NOT the commitment. */
|
|
995
|
+
ownerId: bigint;
|
|
996
|
+
kindTag: string | bigint;
|
|
997
|
+
payload: Array<bigint | string>;
|
|
998
|
+
}): bigint[];
|
|
999
|
+
/**
|
|
1000
|
+
* @deprecated since Wave B B2 — use `buildThresholdEnvelope` from
|
|
1001
|
+
* `@chipi-stack/backend/shhh/threshold` instead. This wrapper exists
|
|
1002
|
+
* for backward compat with v13.x and will be removed one minor
|
|
1003
|
+
* release after `chipi-stack@14.5.0` ships.
|
|
1004
|
+
*
|
|
1005
|
+
* Forwards to the validated builder. Note that callers passing
|
|
1006
|
+
* fewer than 2 envelopes (which the old version silently accepted
|
|
1007
|
+
* with a bogus layout) will now correctly throw — Cairo rejects
|
|
1008
|
+
* `n < 2` thresholds.
|
|
1009
|
+
*/
|
|
1010
|
+
declare function wrapV2ThresholdEnvelope(innerEnvelopes: bigint[][]): bigint[];
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* STARK ECDSA signer for SHHH V8.4 ShhhAccount.
|
|
1014
|
+
*
|
|
1015
|
+
* Cheapest verifier path (~2M l2_gas). Native Stark-curve ECDSA over
|
|
1016
|
+
* the SNIP-12 hash; the verifier class is a thin syscall wrapper.
|
|
1017
|
+
*
|
|
1018
|
+
* Reference: shhh-wallet-cairo/scripts/ts/gen-phase3-account-fixture.mjs
|
|
1019
|
+
* (same Stark signature shape, wrapped in V2_SNIP12).
|
|
1020
|
+
*
|
|
1021
|
+
* For non-STARK kinds (Ed25519, EIP-191, WebAuthn, JWT, BLS) the
|
|
1022
|
+
* envelope payload is per-kind — see sibling files in this directory.
|
|
1023
|
+
*/
|
|
1024
|
+
/**
|
|
1025
|
+
* Build a V2_SNIP12 envelope for the STARK kind from a private key +
|
|
1026
|
+
* the OE's SNIP-12 hash. The envelope is the `signature` span you
|
|
1027
|
+
* pass to `paymaster_executeSponsoredRaw.calldata`.
|
|
1028
|
+
*
|
|
1029
|
+
* Payload for STARK is exactly `[r, s]` — the verifier reads these
|
|
1030
|
+
* two felts and invokes the chain's `ec_op` syscall.
|
|
1031
|
+
*
|
|
1032
|
+
* **Envelope layout (V8.4):** `[V2_SNIP12, owner_id, kind_tag, ...payload]`
|
|
1033
|
+
*
|
|
1034
|
+
* `owner_id` at position [1] is a **u32 INDEX into the owner_set**,
|
|
1035
|
+
* NOT the poseidon `owner_commitment`. For the primary owner of a
|
|
1036
|
+
* fresh deploy that index is `0n`; for additional owners added via
|
|
1037
|
+
* `propose_add_owner` it's `1n`, `2n`, etc. The V8.4 dispatcher
|
|
1038
|
+
* (`src/account.cairo:480`) does `(*signature.at(1)).try_into::<u32>()`
|
|
1039
|
+
* and panics with `'SHHH: bad owner_id'` if the felt exceeds u32 MAX —
|
|
1040
|
+
* which is exactly what happens if a poseidon commitment is passed
|
|
1041
|
+
* here instead of the index. (Discovered 2026-05-27 trace audit on
|
|
1042
|
+
* tx 0x1d1ee02e0dd1ffdbc6f0dddb426098d0dc87800eee3d0d0201e74d97ddc0a55.)
|
|
1043
|
+
*/
|
|
1044
|
+
declare function buildStarkEnvelope(params: {
|
|
1045
|
+
/** STARK private key as bigint or 0x-prefixed hex. */
|
|
1046
|
+
privateKey: bigint | string;
|
|
1047
|
+
/** SNIP-12 hash of the OutsideExecution — see oe.ts::computeOeHash. */
|
|
1048
|
+
messageHash: bigint;
|
|
1049
|
+
/**
|
|
1050
|
+
* Owner index in the wallet's owner_set. **Defaults to 0n (primary).**
|
|
1051
|
+
* Override for additional owners added via `propose_add_owner`.
|
|
1052
|
+
* MUST fit in u32 (i.e., < 2^32). Passing the poseidon
|
|
1053
|
+
* `owner_commitment` here will revert on chain with
|
|
1054
|
+
* `'SHHH: bad owner_id'`.
|
|
1055
|
+
*/
|
|
1056
|
+
ownerIndex?: bigint;
|
|
1057
|
+
}): bigint[];
|
|
1058
|
+
/**
|
|
1059
|
+
* Compute the V8.4 owner_id for a STARK-kind owner from its public
|
|
1060
|
+
* key. owner_id = owner_commitment(kind_tag, [pubkey]) per
|
|
1061
|
+
* `src/signer/interface.cairo::owner_commitment`. The account uses
|
|
1062
|
+
* this to dedupe owners and select the verifier class.
|
|
1063
|
+
*/
|
|
1064
|
+
declare function starkOwnerId(starkPubKey: bigint): bigint;
|
|
1065
|
+
/**
|
|
1066
|
+
* STARK pubkey layout for the V8.4 constructor / propose_add_owner.
|
|
1067
|
+
* The Stark curve pubkey fits in one felt252, so this is a 1-element
|
|
1068
|
+
* array. Kept as a function for parity with kinds that split the
|
|
1069
|
+
* pubkey into multiple felts (Ed25519, secp256k1, P-256, BLS).
|
|
1070
|
+
*/
|
|
1071
|
+
declare function starkPubkeyFelts(starkPubKey: bigint | string): bigint[];
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Ed25519 signer for SHHH V8.4 ShhhAccount.
|
|
1075
|
+
*
|
|
1076
|
+
* Used by Phantom (Solana) flows and any generic Ed25519 signer. The
|
|
1077
|
+
* V8.4 verifier (class `0x030a7dfc…949f74`) consumes a Cairo-ready
|
|
1078
|
+
* calldata array produced by Garaga's `eddsaCalldataBuilder` — it
|
|
1079
|
+
* contains the signature halves plus elliptic-curve hints the Cairo
|
|
1080
|
+
* verifier needs to keep gas bounded.
|
|
1081
|
+
*
|
|
1082
|
+
* Reference: shhh-wallet-cairo/scripts/ts/regen-ed25519-fixtures.mjs
|
|
1083
|
+
*
|
|
1084
|
+
* Verifier overhead: ~33M l2_gas (per SHHH_KIND_GAS_OVERHEAD).
|
|
1085
|
+
*
|
|
1086
|
+
* IMPORTANT — dynamic imports
|
|
1087
|
+
*
|
|
1088
|
+
* Garaga ships a ~1.2MB WASM bundle. We dynamic-import it so callers
|
|
1089
|
+
* who never touch Ed25519 don't pay the bundle cost. The first call
|
|
1090
|
+
* to `buildEd25519Envelope` will lazy-load + initialize Garaga; this
|
|
1091
|
+
* is irreversible and cached for the process lifetime. Cold-start
|
|
1092
|
+
* cost is ~200-400ms on Android, ~80-150ms on iPhone 13+.
|
|
1093
|
+
*
|
|
1094
|
+
* `@noble/ed25519` is also dynamic-imported for symmetry, though it
|
|
1095
|
+
* tree-shakes cleanly as a static import too.
|
|
1096
|
+
*/
|
|
1097
|
+
/**
|
|
1098
|
+
* The Cairo Ed25519 verifier reads the SNIP-12 hash via its hex-ASCII
|
|
1099
|
+
* 64-byte encoding so the signature commits to a string that Phantom
|
|
1100
|
+
* (and any "sign-this-string" UX) can display. This helper is exposed
|
|
1101
|
+
* here for parity with the Cairo-side decoder; it's the same function
|
|
1102
|
+
* as `snip12HashToHexAsciiBytes` in snip12-hash.ts, re-exported under
|
|
1103
|
+
* a kind-specific name for discoverability.
|
|
1104
|
+
*/
|
|
1105
|
+
declare function ed25519SignedBytes(messageHash: bigint): Uint8Array;
|
|
1106
|
+
/**
|
|
1107
|
+
* Compute the V8.4 owner_id for an Ed25519-kind owner from its public
|
|
1108
|
+
* key. owner_id = owner_commitment(ED25519_tag, [pub_low, pub_high])
|
|
1109
|
+
* — the same shape as `src/signer/ed25519/verifier.cairo` expects in
|
|
1110
|
+
* `validate_pubkey`.
|
|
1111
|
+
*/
|
|
1112
|
+
declare function ed25519OwnerId(publicKey: Uint8Array): bigint;
|
|
1113
|
+
/**
|
|
1114
|
+
* Ed25519 pubkey felt layout for the V8.4 constructor / propose_add_owner.
|
|
1115
|
+
* Returns the low/high u128 halves of the 32-byte public key in that
|
|
1116
|
+
* order — what the Cairo `kind == ED25519` branch reads.
|
|
1117
|
+
*/
|
|
1118
|
+
declare function ed25519PubkeyFelts(publicKey: Uint8Array): bigint[];
|
|
1119
|
+
/**
|
|
1120
|
+
* Build a V2_SNIP12 envelope for the Ed25519 kind. Async because the
|
|
1121
|
+
* underlying Garaga WASM must be initialized; subsequent calls reuse
|
|
1122
|
+
* the cached init.
|
|
1123
|
+
*
|
|
1124
|
+
* Inputs:
|
|
1125
|
+
* privateKey — 32-byte Ed25519 secret (Uint8Array, NOT hex/bigint:
|
|
1126
|
+
* the noble lib refuses anything else).
|
|
1127
|
+
* messageHash — the SNIP-12 hash of the OE (see oe.ts::computeOeHash).
|
|
1128
|
+
*
|
|
1129
|
+
* Output: the `signature` span you pass to
|
|
1130
|
+
* `paymaster_executeSponsoredRaw.calldata`.
|
|
1131
|
+
*/
|
|
1132
|
+
declare function buildEd25519Envelope(params: {
|
|
1133
|
+
privateKey: Uint8Array;
|
|
1134
|
+
messageHash: bigint;
|
|
1135
|
+
/**
|
|
1136
|
+
* Owner index in the wallet's owner_set. **Defaults to 0n (primary).**
|
|
1137
|
+
* Override for additional owners added via `propose_add_owner`.
|
|
1138
|
+
* MUST fit in u32 (i.e., < 2^32). Passing the poseidon
|
|
1139
|
+
* `owner_commitment` (e.g. via `ed25519OwnerId`) here will revert
|
|
1140
|
+
* on chain with `'SHHH: bad owner_id'` — the bug Shhh team caught
|
|
1141
|
+
* 2026-05-27 + audit-trail 2026-05-28.
|
|
1142
|
+
*/
|
|
1143
|
+
ownerIndex?: bigint;
|
|
1144
|
+
}): Promise<bigint[]>;
|
|
1145
|
+
/**
|
|
1146
|
+
* Build a V2_SNIP12 Ed25519 envelope from an **externally-produced**
|
|
1147
|
+
* signature — the Phantom / Solana flow. Phantom (and every external
|
|
1148
|
+
* Ed25519 wallet) never exposes the private key: the user signs via
|
|
1149
|
+
* `window.solana.signMessage(...)`, which returns a 64-byte signature
|
|
1150
|
+
* (R‖s). This is the from-signature counterpart of
|
|
1151
|
+
* `buildEd25519Envelope`, mirroring `buildEip191EnvelopeFromSignature`
|
|
1152
|
+
* for MetaMask.
|
|
1153
|
+
*
|
|
1154
|
+
* IMPORTANT — what the wallet MUST sign:
|
|
1155
|
+
* The wallet must sign the EXACT bytes returned by
|
|
1156
|
+
* `ed25519SignedBytes(messageHash)` — the 64-byte hex-ASCII encoding
|
|
1157
|
+
* of the SNIP-12 OE hash — NOT the raw 32-byte `messageHash`. The
|
|
1158
|
+
* Cairo verifier reconstructs those same bytes from `messageHash`, so
|
|
1159
|
+
* a mismatch fails on chain. The dashboard adapter does:
|
|
1160
|
+
* await window.solana.connect();
|
|
1161
|
+
* // Phantom returns the 32-byte Ed25519 pubkey as a Solana PublicKey;
|
|
1162
|
+
* // `.toBytes()` is the Uint8Array this API expects.
|
|
1163
|
+
* const publicKey = window.solana.publicKey.toBytes();
|
|
1164
|
+
* const msg = ed25519SignedBytes(messageHash);
|
|
1165
|
+
* // Phantom's signMessage returns { signature: Uint8Array (64B), publicKey }.
|
|
1166
|
+
* const { signature } = await window.solana.signMessage(msg);
|
|
1167
|
+
* const env = await buildEd25519EnvelopeFromSignature({
|
|
1168
|
+
* signature, publicKey, messageHash,
|
|
1169
|
+
* });
|
|
1170
|
+
*
|
|
1171
|
+
* Async (unlike the synchronous EIP-191 from-signature path): the
|
|
1172
|
+
* Garaga WASM hint builder must be initialized to produce the EC hints
|
|
1173
|
+
* the Cairo Ed25519 verifier needs. The issue sketch (#311) showed a
|
|
1174
|
+
* sync return; Ed25519 cannot avoid the WASM init that secp256k1 does.
|
|
1175
|
+
*/
|
|
1176
|
+
declare function buildEd25519EnvelopeFromSignature(params: {
|
|
1177
|
+
/** 64-byte Ed25519 signature R(32)‖s(32) from the wallet's signMessage. */
|
|
1178
|
+
signature: Uint8Array;
|
|
1179
|
+
/** 32-byte Ed25519 public key (as Phantom returns it). */
|
|
1180
|
+
publicKey: Uint8Array;
|
|
1181
|
+
/** SNIP-12 hash of the OutsideExecution — see oe.ts::computeOeHash. */
|
|
1182
|
+
messageHash: bigint;
|
|
1183
|
+
/**
|
|
1184
|
+
* Owner index in the wallet's owner_set. **Defaults to 0n (primary).**
|
|
1185
|
+
* MUST fit in u32. Same semantics + footgun as `buildEd25519Envelope`:
|
|
1186
|
+
* pass the INDEX, never an `ed25519OwnerId` commitment.
|
|
1187
|
+
*/
|
|
1188
|
+
ownerIndex?: bigint;
|
|
1189
|
+
}): Promise<bigint[]>;
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* EIP-191 secp256k1 signer for SHHH V8.4 ShhhAccount.
|
|
1193
|
+
*
|
|
1194
|
+
* The signer kind for MetaMask + any EVM wallet that exposes the
|
|
1195
|
+
* `personal_sign` RPC. The wallet computes
|
|
1196
|
+
* keccak256("\x19Ethereum Signed Message:\n32" || msg_be32)
|
|
1197
|
+
* internally and signs with secp256k1; the V8.4 EIP191Secp256k1Verifier
|
|
1198
|
+
* (class `0x03a75997…084e6`) reproduces the same prefix-and-hash and
|
|
1199
|
+
* recovers the signing key from (r, s, y_parity) to compare to the
|
|
1200
|
+
* stored pubkey.
|
|
1201
|
+
*
|
|
1202
|
+
* Reference: shhh-wallet-cairo/scripts/ts/gen-eip191-fixture.mjs
|
|
1203
|
+
*
|
|
1204
|
+
* Verifier overhead: ~10M l2_gas (per SHHH_KIND_GAS_OVERHEAD).
|
|
1205
|
+
*
|
|
1206
|
+
* Two builder entry points:
|
|
1207
|
+
* - buildEip191EnvelopeFromSignature: production / MetaMask flow.
|
|
1208
|
+
* Caller already has the 65-byte signature back from `personal_sign`
|
|
1209
|
+
* and the wallet's pubkey (either pre-known or recovered).
|
|
1210
|
+
* - buildEip191EnvelopeFromPrivateKey: test / Chipi-held-key flow.
|
|
1211
|
+
* Sign locally with a known secp256k1 private key.
|
|
1212
|
+
*
|
|
1213
|
+
* Both produce the identical V2_SNIP12 envelope shape:
|
|
1214
|
+
* [V2_SNIP12, owner_id, EIP191_SECP256K1_tag,
|
|
1215
|
+
* r.low, r.high, s.low, s.high, y_parity]
|
|
1216
|
+
*/
|
|
1217
|
+
/**
|
|
1218
|
+
* Uncompressed secp256k1 public key, split into 4 felt252-sized halves
|
|
1219
|
+
* matching what Cairo's EIP191 verifier expects in
|
|
1220
|
+
* `validate_pubkey` — `[x.low, x.high, y.low, y.high]` (big-endian
|
|
1221
|
+
* scalar, low_u128 first).
|
|
1222
|
+
*/
|
|
1223
|
+
interface Eip191Pubkey {
|
|
1224
|
+
/** 32-byte big-endian X coordinate. */
|
|
1225
|
+
x: Uint8Array;
|
|
1226
|
+
/** 32-byte big-endian Y coordinate. */
|
|
1227
|
+
y: Uint8Array;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Pubkey felt layout for the V8.4 constructor / propose_add_owner.
|
|
1231
|
+
* Returns `[x.low, x.high, y.low, y.high]` (each u128).
|
|
1232
|
+
*/
|
|
1233
|
+
declare function eip191PubkeyFelts(pubkey: Eip191Pubkey): bigint[];
|
|
1234
|
+
/**
|
|
1235
|
+
* Compute the V8.4 owner_id for an EIP-191 owner.
|
|
1236
|
+
* owner_id = owner_commitment(EIP191_tag, [x.low, x.high, y.low, y.high]).
|
|
1237
|
+
*/
|
|
1238
|
+
declare function eip191OwnerId(pubkey: Eip191Pubkey): bigint;
|
|
1239
|
+
/**
|
|
1240
|
+
* Parse a 65-byte `personal_sign`/`eth_sign` signature hex string into
|
|
1241
|
+
* the Cairo-shaped envelope payload `[r.low, r.high, s.low, s.high, y_parity]`.
|
|
1242
|
+
*
|
|
1243
|
+
* Accepts both `v ∈ {27, 28}` (EIP-191 wallet convention) and
|
|
1244
|
+
* `y_parity ∈ {0, 1}` raw forms.
|
|
1245
|
+
*/
|
|
1246
|
+
declare function parseEip191Signature(signatureHex: string): bigint[];
|
|
1247
|
+
/**
|
|
1248
|
+
* Production / MetaMask flow. Caller already has the 65-byte signature
|
|
1249
|
+
* from `personal_sign` (e.g., `await provider.send("personal_sign", [...])`)
|
|
1250
|
+
* and the wallet's public key.
|
|
1251
|
+
*/
|
|
1252
|
+
declare function buildEip191EnvelopeFromSignature(params: {
|
|
1253
|
+
/** 65-byte `personal_sign` signature, 0x-prefixed hex. */
|
|
1254
|
+
signatureHex: string;
|
|
1255
|
+
/** Uncompressed secp256k1 pubkey (x, y) as 32 BE bytes each. */
|
|
1256
|
+
pubkey: Eip191Pubkey;
|
|
1257
|
+
/**
|
|
1258
|
+
* Owner index in the wallet's owner_set. **Defaults to 0n (primary).**
|
|
1259
|
+
* MUST fit in u32. Passing `eip191OwnerId(pubkey)` (the commitment)
|
|
1260
|
+
* here will revert on chain with `'SHHH: bad owner_id'`.
|
|
1261
|
+
*/
|
|
1262
|
+
ownerIndex?: bigint;
|
|
1263
|
+
}): bigint[];
|
|
1264
|
+
/**
|
|
1265
|
+
* Test / Chipi-held-key flow. Sign locally with a known secp256k1
|
|
1266
|
+
* private key using the exact `personal_sign` recipe MetaMask uses
|
|
1267
|
+
* (keccak256("\x19Ethereum Signed Message:\n32" || msg_be32),
|
|
1268
|
+
* secp256k1 sign of that hash).
|
|
1269
|
+
*
|
|
1270
|
+
* Async because @noble/secp256k1 v3 ships ESM and we dynamic-import.
|
|
1271
|
+
*/
|
|
1272
|
+
declare function buildEip191EnvelopeFromPrivateKey(params: {
|
|
1273
|
+
/** 32-byte secp256k1 private key (Uint8Array OR 0x-prefixed hex). */
|
|
1274
|
+
privateKey: Uint8Array | string;
|
|
1275
|
+
/** SNIP-12 hash of the OutsideExecution — see oe.ts::computeOeHash. */
|
|
1276
|
+
messageHash: bigint;
|
|
1277
|
+
/**
|
|
1278
|
+
* Owner index in the wallet's owner_set. **Defaults to 0n (primary).**
|
|
1279
|
+
* MUST fit in u32. Same semantics as `buildEip191EnvelopeFromSignature`.
|
|
1280
|
+
*/
|
|
1281
|
+
ownerIndex?: bigint;
|
|
1282
|
+
}): Promise<bigint[]>;
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* WebAuthn P-256 signer for SHHH V8.4 ShhhAccount.
|
|
1286
|
+
*
|
|
1287
|
+
* The SHHH default signer kind in the browser. Wraps a WebAuthn
|
|
1288
|
+
* `navigator.credentials.get()` assertion (or a synthesized equivalent
|
|
1289
|
+
* in tests) into the V2_SNIP12 envelope the on-chain
|
|
1290
|
+
* WebAuthnP256Verifier (class `0x074f6efd…44657`) consumes.
|
|
1291
|
+
*
|
|
1292
|
+
* The verifier reproduces the WebAuthn assertion check on-chain:
|
|
1293
|
+
* 1. Parse `authData` (rpIdHash 32B || flags 1B || signCount 4B || …).
|
|
1294
|
+
* Require UP flag (bit 0) set.
|
|
1295
|
+
* 2. Compute `outer = sha256(authData ‖ sha256(clientDataJSON))`.
|
|
1296
|
+
* 3. Verify P-256 ECDSA `(r, s, y_parity)` against `outer` and the
|
|
1297
|
+
* stored pubkey.
|
|
1298
|
+
* 4. At `challenge_offset` in `clientDataJSON`, base64url-decode the
|
|
1299
|
+
* next 43 chars to 32 bytes and compare equal to the SNIP-12 hash.
|
|
1300
|
+
* 5. Sanity-check the `clientDataJSON` prefix
|
|
1301
|
+
* `{"type":"webauthn.get",…` (rejects webauthn.create / missing
|
|
1302
|
+
* type).
|
|
1303
|
+
*
|
|
1304
|
+
* Reference: shhh-wallet-cairo/scripts/ts/gen-webauthn-fixture.mjs
|
|
1305
|
+
* and tests/signer_webauthn_fixture.cairo
|
|
1306
|
+
*
|
|
1307
|
+
* Verifier overhead: ~35M l2_gas (per SHHH_KIND_GAS_OVERHEAD).
|
|
1308
|
+
*
|
|
1309
|
+
* Envelope shape (after the V2_SNIP12 + owner_id + kind_tag wrap):
|
|
1310
|
+
* ByteArray(authData)
|
|
1311
|
+
* ByteArray(clientDataJSON)
|
|
1312
|
+
* challenge_offset: u32
|
|
1313
|
+
* r.low, r.high, s.low, s.high, y_parity
|
|
1314
|
+
*
|
|
1315
|
+
* Cairo ByteArray serde:
|
|
1316
|
+
* num_full_words: u32
|
|
1317
|
+
* full_words: felt252[num_full_words] // 31 bytes packed MSB-first
|
|
1318
|
+
* pending_word: felt252 // <31 remaining bytes
|
|
1319
|
+
* pending_word_len: u32
|
|
1320
|
+
*/
|
|
1321
|
+
interface WebAuthnPubkey {
|
|
1322
|
+
/** 32-byte big-endian P-256 X coordinate. */
|
|
1323
|
+
x: Uint8Array;
|
|
1324
|
+
/** 32-byte big-endian P-256 Y coordinate. */
|
|
1325
|
+
y: Uint8Array;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Serialize a byte string as Cairo's `ByteArray` serde — must match
|
|
1329
|
+
* what the Cairo runtime would produce when this same bytes value is
|
|
1330
|
+
* passed as a `ByteArray` parameter.
|
|
1331
|
+
*
|
|
1332
|
+
* Layout: `[num_full_words, ...full_words, pending_word, pending_word_len]`.
|
|
1333
|
+
* Each full word packs 31 bytes MSB-first (network order) into a felt252.
|
|
1334
|
+
*/
|
|
1335
|
+
declare function byteArrayToFelts(bytes: Uint8Array): bigint[];
|
|
1336
|
+
/**
|
|
1337
|
+
* Parse a DER-encoded ECDSA signature into raw (r, s) 32-byte BE values.
|
|
1338
|
+
* WebAuthn assertions return DER per spec; we strip it to the raw
|
|
1339
|
+
* P-256 components the Cairo verifier expects.
|
|
1340
|
+
*/
|
|
1341
|
+
declare function parseDerEcdsaSignature(der: Uint8Array): {
|
|
1342
|
+
r: Uint8Array<ArrayBuffer>;
|
|
1343
|
+
s: Uint8Array<ArrayBuffer>;
|
|
1344
|
+
};
|
|
1345
|
+
declare function webauthnPubkeyFelts(pub: WebAuthnPubkey): bigint[];
|
|
1346
|
+
declare function webauthnOwnerId(pub: WebAuthnPubkey): bigint;
|
|
1347
|
+
/**
|
|
1348
|
+
* Production / browser flow. The caller has run
|
|
1349
|
+
* `navigator.credentials.get({ publicKey: { challenge: msgBe32, ... } })`
|
|
1350
|
+
* and now has the assertion's `authenticatorData`, `clientDataJSON`,
|
|
1351
|
+
* and DER-encoded `signature` (plus the wallet's stored P-256 pubkey).
|
|
1352
|
+
*
|
|
1353
|
+
* This builder:
|
|
1354
|
+
* 1. Parses the DER signature into raw r, s.
|
|
1355
|
+
* 2. Recovers y_parity from the signature (always 0; WebAuthn does
|
|
1356
|
+
* not expose recovery — the on-chain verifier compares the recovered
|
|
1357
|
+
* pubkey against the stored one and tries both parities if needed).
|
|
1358
|
+
* 3. Locates the challenge in clientDataJSON to compute challenge_offset.
|
|
1359
|
+
* 4. Encodes authData + clientDataJSON as Cairo ByteArrays.
|
|
1360
|
+
* 5. Wraps everything in the V2_SNIP12 envelope.
|
|
1361
|
+
*/
|
|
1362
|
+
declare function buildWebAuthnEnvelopeFromAssertion(params: {
|
|
1363
|
+
authenticatorData: Uint8Array;
|
|
1364
|
+
clientDataJSON: Uint8Array;
|
|
1365
|
+
/** DER-encoded ECDSA signature from `assertion.response.signature`. */
|
|
1366
|
+
signatureDer: Uint8Array;
|
|
1367
|
+
/** P-256 pubkey stored at registration time. */
|
|
1368
|
+
pubkey: WebAuthnPubkey;
|
|
1369
|
+
/** SNIP-12 hash the challenge was constructed from. */
|
|
1370
|
+
messageHash: bigint;
|
|
1371
|
+
/**
|
|
1372
|
+
* y_parity recovery bit. WebAuthn assertions don't include this — pass
|
|
1373
|
+
* 0 (the verifier tries both parities internally when needed). Override
|
|
1374
|
+
* only if you know the bit independently.
|
|
1375
|
+
*/
|
|
1376
|
+
yParity?: 0n | 1n;
|
|
1377
|
+
/**
|
|
1378
|
+
* Owner index in the wallet's owner_set. Defaults to 0n (primary).
|
|
1379
|
+
* MUST fit in u32. Passing `webauthnOwnerId(pubkey)` (the commitment)
|
|
1380
|
+
* here will revert on chain with `'SHHH: bad owner_id'`.
|
|
1381
|
+
*/
|
|
1382
|
+
ownerIndex?: bigint;
|
|
1383
|
+
}): bigint[];
|
|
1384
|
+
/**
|
|
1385
|
+
* Test / Chipi-held-key flow. Synthesizes a WebAuthn assertion under
|
|
1386
|
+
* a known P-256 private key, then builds the envelope. Matches the
|
|
1387
|
+
* exact recipe in `gen-webauthn-fixture.mjs` so the resulting envelope
|
|
1388
|
+
* byte-for-byte matches `signer_webauthn_fixture.cairo`.
|
|
1389
|
+
*
|
|
1390
|
+
* Defaults match the canonical Shhh fixture: rpId="cifra.mx",
|
|
1391
|
+
* origin="https://cifra.mx", flags=0x05 (UP+UV), signCount=42.
|
|
1392
|
+
*/
|
|
1393
|
+
declare function buildWebAuthnEnvelopeFromPrivateKey(params: {
|
|
1394
|
+
/** 32-byte P-256 private key (Uint8Array or 0x-prefixed hex). */
|
|
1395
|
+
privateKey: Uint8Array | string;
|
|
1396
|
+
/** SNIP-12 hash of the OutsideExecution. */
|
|
1397
|
+
messageHash: bigint;
|
|
1398
|
+
/** WebAuthn relying-party ID (the rpId field). Default: "cifra.mx". */
|
|
1399
|
+
rpId?: string;
|
|
1400
|
+
/** WebAuthn origin string. Default: "https://cifra.mx". */
|
|
1401
|
+
origin?: string;
|
|
1402
|
+
/** WebAuthn authData flags byte. Default 0x05 (UP+UV). */
|
|
1403
|
+
flags?: number;
|
|
1404
|
+
/** 32-bit sign count. Default 42. */
|
|
1405
|
+
signCount?: number;
|
|
1406
|
+
/**
|
|
1407
|
+
* Owner index in the wallet's owner_set. Defaults to 0n (primary).
|
|
1408
|
+
* MUST fit in u32. Same semantics as `buildWebAuthnEnvelopeFromAssertion`.
|
|
1409
|
+
*/
|
|
1410
|
+
ownerIndex?: bigint;
|
|
1411
|
+
}): Promise<bigint[]>;
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* JWT ES256 "Sign in with Apple" sub-bound signer for SHHH V8.4.
|
|
1415
|
+
*
|
|
1416
|
+
* Wave B PR B3. Adds the Apple Sign In signer kind
|
|
1417
|
+
* (`JWT_ES256_APPLE_SUB`, verifier class `0x06b67762...`). The headline
|
|
1418
|
+
* UX value is iOS-native onboarding: users without MetaMask but with
|
|
1419
|
+
* an iPhone authenticate with Apple, and the resulting JWT becomes a
|
|
1420
|
+
* SHHH owner. Multi-tenant safe via the `sub` claim binding.
|
|
1421
|
+
*
|
|
1422
|
+
* Cairo source of truth:
|
|
1423
|
+
* shhh-wallet-cairo/src/signer/jwt_es256_apple_sub/verifier.cairo
|
|
1424
|
+
*
|
|
1425
|
+
* Envelope layout (Cairo Serde over Span<felt252>):
|
|
1426
|
+
* header_b64 ByteArray // base64url JOSE header bytes
|
|
1427
|
+
* payload_decoded ByteArray // raw JSON claims (NOT base64url)
|
|
1428
|
+
* nonce_offset u32 // index of the 43-byte b64url nonce
|
|
1429
|
+
* iss_offset u32 // index of "https://appleid.apple.com"
|
|
1430
|
+
* sub_offset u32 // index of the sub claim VALUE
|
|
1431
|
+
* sub_len u32 // byte length of the sub claim VALUE
|
|
1432
|
+
* r u256 // low | high
|
|
1433
|
+
* s u256 // low | high
|
|
1434
|
+
* y_parity felt252 // 0 | 1
|
|
1435
|
+
*
|
|
1436
|
+
* Pubkey layout (5 felts):
|
|
1437
|
+
* [x_low, x_high, y_low, y_high, sub_hash]
|
|
1438
|
+
*
|
|
1439
|
+
* where sub_hash = poseidon_hash_span over the user's stable Apple
|
|
1440
|
+
* `sub` identifier, one felt per byte. The 5th felt binds the
|
|
1441
|
+
* on-chain owner to a SPECIFIC end-user — so multiple Chipi accounts
|
|
1442
|
+
* sharing Apple's same signing key can't be co-controlled by an
|
|
1443
|
+
* arbitrary Apple-authenticated user.
|
|
1444
|
+
*
|
|
1445
|
+
* Two flows:
|
|
1446
|
+
* - `buildJwtAppleSubEnvelopeFromTokenResponse` — production. Caller
|
|
1447
|
+
* drops in the Apple-issued `id_token` + the SNIP-12 message hash
|
|
1448
|
+
* that was committed to via the OE nonce.
|
|
1449
|
+
* - `buildJwtAppleSubEnvelopeFromTestKey` — synthesized test path.
|
|
1450
|
+
* Generates an ES256 keypair, constructs a JWT shaped like
|
|
1451
|
+
* Apple's, and signs it locally. Used for unit tests + the Wave
|
|
1452
|
+
* B B9 mainnet smoke. Real Apple-issued fixture upgrade comes
|
|
1453
|
+
* post-Wave-B (per plan D18).
|
|
1454
|
+
*/
|
|
1455
|
+
/** The hardcoded `iss` claim every Apple JWT carries. 25 ASCII bytes. */
|
|
1456
|
+
declare const APPLE_ISSUER = "https://appleid.apple.com";
|
|
1457
|
+
/**
|
|
1458
|
+
* Uncompressed P-256 public key for Apple's current ES256 signing key.
|
|
1459
|
+
* Apple rotates this periodically — see the JWKS endpoint at
|
|
1460
|
+
* `https://appleid.apple.com/auth/keys`.
|
|
1461
|
+
*/
|
|
1462
|
+
interface JwtAppleSubPubkey {
|
|
1463
|
+
/** 32-byte big-endian X coordinate. */
|
|
1464
|
+
x: Uint8Array;
|
|
1465
|
+
/** 32-byte big-endian Y coordinate. */
|
|
1466
|
+
y: Uint8Array;
|
|
1467
|
+
/** The user's stable Apple `sub` identifier (ASCII), e.g. "001234.abc.5678". */
|
|
1468
|
+
sub: string;
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* What you get back from `https://appleid.apple.com/auth/token` after
|
|
1472
|
+
* a Sign-in-with-Apple OAuth flow. Only `id_token` is signature-bearing;
|
|
1473
|
+
* `access_token` and friends are out of scope here.
|
|
1474
|
+
*/
|
|
1475
|
+
interface AppleTokenResponse {
|
|
1476
|
+
id_token: string;
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Compute the sub_hash that the Cairo verifier checks against.
|
|
1480
|
+
* Equals `poseidon_hash_span` over the bytes of the sub string, one
|
|
1481
|
+
* felt per byte. The SDK and the verifier must agree byte-for-byte.
|
|
1482
|
+
*/
|
|
1483
|
+
declare function jwtAppleSubHash(sub: string): bigint;
|
|
1484
|
+
/**
|
|
1485
|
+
* Pubkey felt layout for the V8.4 constructor / propose_add_owner.
|
|
1486
|
+
* Returns `[x.low, x.high, y.low, y.high, sub_hash]` (5 felts).
|
|
1487
|
+
*/
|
|
1488
|
+
declare function jwtAppleSubPubkeyFelts(pubkey: JwtAppleSubPubkey): bigint[];
|
|
1489
|
+
/** owner_commitment for an Apple JWT owner. */
|
|
1490
|
+
declare function jwtAppleSubOwnerId(pubkey: JwtAppleSubPubkey): bigint;
|
|
1491
|
+
/**
|
|
1492
|
+
* base64url-no-padding encoding of a 32 BE-byte SNIP-12 hash. The
|
|
1493
|
+
* Cairo verifier asserts the 43 ASCII bytes at
|
|
1494
|
+
* `payload_decoded[nonce_offset .. nonce_offset+43]` equal exactly
|
|
1495
|
+
* this string. Format must match RFC 4648 §5 (no padding `=`).
|
|
1496
|
+
*/
|
|
1497
|
+
declare function messageHashToNonceB64url(messageHash: bigint): string;
|
|
1498
|
+
/**
|
|
1499
|
+
* Split an Apple-issued `id_token` into its three base64url parts.
|
|
1500
|
+
* Returns the decoded payload bytes + the header b64url + the
|
|
1501
|
+
* 64-byte raw ECDSA signature (r || s).
|
|
1502
|
+
*/
|
|
1503
|
+
declare function parseAppleIdToken(idToken: string): {
|
|
1504
|
+
headerB64: string;
|
|
1505
|
+
payloadDecoded: Uint8Array;
|
|
1506
|
+
rawSignature: Uint8Array;
|
|
1507
|
+
};
|
|
1508
|
+
/**
|
|
1509
|
+
* Locate the sub claim's VALUE bytes in a decoded JSON payload.
|
|
1510
|
+
* Searches for the literal `"sub":"<value>"` pattern. JSON allows
|
|
1511
|
+
* whitespace, but Apple's id_token always uses canonical
|
|
1512
|
+
* no-whitespace form, so we don't try to handle pretty-printed JSON.
|
|
1513
|
+
*
|
|
1514
|
+
* Returns `{ offset, length }` of the value bytes (not including the
|
|
1515
|
+
* surrounding quotes), suitable for `sub_offset` + `sub_len` in the
|
|
1516
|
+
* envelope.
|
|
1517
|
+
*/
|
|
1518
|
+
declare function locateSubClaim(payload: Uint8Array): {
|
|
1519
|
+
offset: number;
|
|
1520
|
+
length: number;
|
|
1521
|
+
};
|
|
1522
|
+
/**
|
|
1523
|
+
* Production flow. Build a V2_SNIP12 envelope wrapping an actual
|
|
1524
|
+
* Apple-issued JWT. Caller has invoked the OAuth flow client-side
|
|
1525
|
+
* (e.g. via `appleid.com` JS SDK or AuthenticationServices on iOS)
|
|
1526
|
+
* and received an `id_token` whose `nonce` claim equals
|
|
1527
|
+
* `messageHashToNonceB64url(oeMessageHash)`.
|
|
1528
|
+
*
|
|
1529
|
+
* Returns the `signature` felt span you pass to
|
|
1530
|
+
* `paymaster_executeSponsoredRaw.calldata`.
|
|
1531
|
+
*/
|
|
1532
|
+
declare function buildJwtAppleSubEnvelopeFromTokenResponse(params: {
|
|
1533
|
+
/** Apple OAuth token endpoint response (we only need `id_token`). */
|
|
1534
|
+
idToken: string;
|
|
1535
|
+
/** Owner's full pubkey including sub — same as registered on-chain. */
|
|
1536
|
+
pubkey: JwtAppleSubPubkey;
|
|
1537
|
+
/** SNIP-12 hash of the OE — the nonce in the id_token MUST encode this. */
|
|
1538
|
+
messageHash: bigint;
|
|
1539
|
+
/**
|
|
1540
|
+
* Owner index in the wallet's owner_set. Defaults to 0n (primary).
|
|
1541
|
+
* MUST fit in u32. Passing `jwtAppleSubOwnerId(pubkey)` (the commitment)
|
|
1542
|
+
* here will revert on chain with `'SHHH: bad owner_id'`.
|
|
1543
|
+
*/
|
|
1544
|
+
ownerIndex?: bigint;
|
|
1545
|
+
}): bigint[];
|
|
1546
|
+
/**
|
|
1547
|
+
* Test / synthesized-fixture flow. Generate an ES256 P-256 keypair,
|
|
1548
|
+
* construct a JWT shaped like an Apple-issued one, sign it locally,
|
|
1549
|
+
* and wrap into a SHHH envelope.
|
|
1550
|
+
*
|
|
1551
|
+
* Used by unit tests + the Wave B B9 mainnet smoke. The wallet
|
|
1552
|
+
* deployed for the smoke must be registered with the SAME synthesized
|
|
1553
|
+
* pubkey returned here (`{ x, y, sub }`); production wallets register
|
|
1554
|
+
* Apple's actual JWKS pubkey + the user's real sub.
|
|
1555
|
+
*
|
|
1556
|
+
* Async because `@noble/curves` ESM imports.
|
|
1557
|
+
*
|
|
1558
|
+
* Real-Apple-Developer-issued fixture upgrade is tracked as a separate
|
|
1559
|
+
* post-Wave-B item (plan D18).
|
|
1560
|
+
*/
|
|
1561
|
+
declare function buildJwtAppleSubEnvelopeFromTestKey(params: {
|
|
1562
|
+
/** 32-byte raw P-256 private key. */
|
|
1563
|
+
privateKey: Uint8Array;
|
|
1564
|
+
/** Stable Apple-style sub identifier (e.g. "001234.synthesized.5678"). */
|
|
1565
|
+
sub: string;
|
|
1566
|
+
/** Audience claim — typically the integrator's Apple Developer team ID + bundle ID. */
|
|
1567
|
+
aud: string;
|
|
1568
|
+
/** SNIP-12 hash of the OE; encoded as the nonce claim. */
|
|
1569
|
+
messageHash: bigint;
|
|
1570
|
+
/**
|
|
1571
|
+
* Owner index in the wallet's owner_set. Defaults to 0n (primary).
|
|
1572
|
+
* MUST fit in u32. Same semantics as `buildJwtAppleSubEnvelopeFromTokenResponse`.
|
|
1573
|
+
*/
|
|
1574
|
+
ownerIndex?: bigint;
|
|
1575
|
+
}): Promise<{
|
|
1576
|
+
envelope: bigint[];
|
|
1577
|
+
pubkey: JwtAppleSubPubkey;
|
|
1578
|
+
idToken: string;
|
|
1579
|
+
}>;
|
|
1580
|
+
|
|
1581
|
+
/**
|
|
1582
|
+
* SHHH V8.4 governance + recovery call builders.
|
|
1583
|
+
*
|
|
1584
|
+
* Wave B PR B1. The V8.4 ShhhAccount gates every owner-set mutation
|
|
1585
|
+
* through a propose → wait-timelock → execute flow (`@external` fns
|
|
1586
|
+
* in `src/account.cairo`). Each entrypoint is `_assert_self_call`-gated,
|
|
1587
|
+
* which means callers wrap these `Call`s inside an OutsideExecution and
|
|
1588
|
+
* submit through the paymaster.
|
|
1589
|
+
*
|
|
1590
|
+
* This module produces the `Call` (`{ contractAddress, entrypoint,
|
|
1591
|
+
* calldata }`) shape that `oe.ts:buildOutsideExecution` expects. It
|
|
1592
|
+
* does NOT sign — that's the integrator's choice of signer kind. For
|
|
1593
|
+
* recovery initiation specifically, the signer MUST be a guardian owner
|
|
1594
|
+
* (`role == ROLE_GUARDIAN` on-chain).
|
|
1595
|
+
*
|
|
1596
|
+
* Sources of truth on the Cairo side:
|
|
1597
|
+
*
|
|
1598
|
+
* src/account.cairo:616 propose_add_owner
|
|
1599
|
+
* src/account.cairo:634 propose_remove_owner
|
|
1600
|
+
* src/account.cairo:649 propose_rotate_owner
|
|
1601
|
+
* src/account.cairo:670 propose_set_threshold
|
|
1602
|
+
* src/account.cairo:731 execute_add_owner
|
|
1603
|
+
* src/account.cairo:760 execute_remove_owner
|
|
1604
|
+
* src/account.cairo:769 execute_rotate_owner
|
|
1605
|
+
* src/account.cairo:786 execute_set_threshold
|
|
1606
|
+
* src/account.cairo:828 cancel_pending_op
|
|
1607
|
+
* src/account.cairo:864 initiate_recovery
|
|
1608
|
+
* src/account.cairo:888 cancel_recovery
|
|
1609
|
+
* src/account.cairo:897 finalize_recovery
|
|
1610
|
+
* src/governance/pending_ops.cairo OP_* + TIMELOCK_* felt constants
|
|
1611
|
+
* src/owner_set/interface.cairo ROLE_* felt constants
|
|
1612
|
+
*
|
|
1613
|
+
* Timelock semantics
|
|
1614
|
+
|
|
1615
|
+
* propose_add_owner 48h
|
|
1616
|
+
* propose_remove_owner 24h
|
|
1617
|
+
* propose_rotate_owner 24h
|
|
1618
|
+
* propose_set_threshold 48h
|
|
1619
|
+
* initiate_recovery 7d (longest — the headline UX gate)
|
|
1620
|
+
*
|
|
1621
|
+
* Recovery is ADDITIVE on V8.4: `finalize_recovery` adds the new owner
|
|
1622
|
+
* but does not remove existing ones. The integrator's UI is expected
|
|
1623
|
+
* to follow with `propose_remove_owner` for stale devices.
|
|
1624
|
+
*
|
|
1625
|
+
* finalize_recovery is the only entrypoint here that is NOT
|
|
1626
|
+
* `_assert_self_call`-gated — it's permissionless after the 7d window,
|
|
1627
|
+
* so anyone can call it directly without an OE wrapper. The function
|
|
1628
|
+
* still returns a Call so the integrator can dispatch via their
|
|
1629
|
+
* normal account abstraction layer.
|
|
1630
|
+
*/
|
|
1631
|
+
|
|
1632
|
+
/** `src/owner_set/interface.cairo:10` — `pub const ROLE_OWNER: felt252 = 'OWNER'`. */
|
|
1633
|
+
declare const ROLE_OWNER: bigint;
|
|
1634
|
+
/** `src/owner_set/interface.cairo:11` — `pub const ROLE_GUARDIAN: felt252 = 'GUARDIAN'`. */
|
|
1635
|
+
declare const ROLE_GUARDIAN: bigint;
|
|
1636
|
+
/** `src/governance/pending_ops.cairo:19` — `OP_ADD_OWNER: felt252 = 'ADD_OWNER'`. */
|
|
1637
|
+
declare const OP_ADD_OWNER: bigint;
|
|
1638
|
+
declare const OP_REMOVE_OWNER: bigint;
|
|
1639
|
+
declare const OP_ROTATE_OWNER: bigint;
|
|
1640
|
+
declare const OP_SET_THRESHOLD: bigint;
|
|
1641
|
+
/**
|
|
1642
|
+
* Per-op timelocks in seconds. Match `src/governance/pending_ops.cairo:42-50`
|
|
1643
|
+
* exactly. Exposed so SDK consumers can predict timelock expiry without
|
|
1644
|
+
* an extra on-chain call after `proposeAddOwner` succeeds.
|
|
1645
|
+
*/
|
|
1646
|
+
declare const TIMELOCK_SECONDS: {
|
|
1647
|
+
readonly ADD_OWNER: 172800;
|
|
1648
|
+
readonly REMOVE_OWNER: 86400;
|
|
1649
|
+
readonly ROTATE_OWNER: 86400;
|
|
1650
|
+
readonly SET_THRESHOLD: 172800;
|
|
1651
|
+
readonly ADD_VERIFIER: 172800;
|
|
1652
|
+
readonly REMOVE_VERIFIER: 86400;
|
|
1653
|
+
readonly ADD_GUARDIAN: 86400;
|
|
1654
|
+
readonly REMOVE_GUARDIAN: 86400;
|
|
1655
|
+
readonly RECOVERY: 604800;
|
|
1656
|
+
};
|
|
1657
|
+
/** `DEFAULT_OP_EXPIRY_SECONDS` from `pending_ops.cairo:78`. After this
|
|
1658
|
+
* window from propose-time, the pending op auto-expires even if no one
|
|
1659
|
+
* calls cancel. UI should reflect this so users don't sit on a stale op. */
|
|
1660
|
+
declare const DEFAULT_OP_EXPIRY_SECONDS = 1209600;
|
|
1661
|
+
/** Owner role on-chain. ROLE_OWNER can sign OEs; ROLE_GUARDIAN can
|
|
1662
|
+
* only `initiate_recovery` (no general signing power). */
|
|
1663
|
+
type OwnerRole = "OWNER" | "GUARDIAN";
|
|
1664
|
+
/**
|
|
1665
|
+
* Inputs shared across add-owner / rotate / initiate-recovery — they
|
|
1666
|
+
* all encode a (kind, pubkey, role, weight, label) tuple.
|
|
1667
|
+
*
|
|
1668
|
+
* `pubkeyBytes` is the per-kind felt-array layout exposed by the
|
|
1669
|
+
* matching `*PubkeyFelts` helper in `signers/{stark,ed25519,eip191,
|
|
1670
|
+
* webauthn}.ts`. The Cairo verifier calls `owner_commitment(kind,
|
|
1671
|
+
* pubkeyBytes)` to derive the on-chain owner_id; if the bytes
|
|
1672
|
+
* disagree with the kind's verifier expectation,
|
|
1673
|
+
* `_validate_pubkey_via_verifier` panics at execute time (audit M-1).
|
|
1674
|
+
*/
|
|
1675
|
+
interface NewOwnerInput {
|
|
1676
|
+
kind: string;
|
|
1677
|
+
pubkeyBytes: bigint[];
|
|
1678
|
+
role: OwnerRole;
|
|
1679
|
+
weight: number;
|
|
1680
|
+
label: string;
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Build a `propose_add_owner(proposer, kind, pubkey_bytes, role,
|
|
1684
|
+
* weight, label)` call. Wrap in an OE signed by `proposer` and submit
|
|
1685
|
+
* through the paymaster. After confirmation, watch for the
|
|
1686
|
+
* `OpProposed` event to learn the `op_id`; then wait
|
|
1687
|
+
* `TIMELOCK_SECONDS.ADD_OWNER` and call `buildExecuteAddOwnerCall`.
|
|
1688
|
+
*/
|
|
1689
|
+
declare function buildProposeAddOwnerCall(args: {
|
|
1690
|
+
walletAddress: bigint | string;
|
|
1691
|
+
proposer: number;
|
|
1692
|
+
newOwner: NewOwnerInput;
|
|
1693
|
+
}): Call;
|
|
1694
|
+
/**
|
|
1695
|
+
* Build an `execute_add_owner(op_id, kind, pubkey_bytes, role, weight,
|
|
1696
|
+
* label)` call. MUST be called after the 48h timelock from the matching
|
|
1697
|
+
* propose. The wallet recomputes the payload and asserts equality
|
|
1698
|
+
* before mutating `owner_set`.
|
|
1699
|
+
*/
|
|
1700
|
+
declare function buildExecuteAddOwnerCall(args: {
|
|
1701
|
+
walletAddress: bigint | string;
|
|
1702
|
+
opId: bigint | string;
|
|
1703
|
+
newOwner: NewOwnerInput;
|
|
1704
|
+
}): Call;
|
|
1705
|
+
declare function buildProposeRemoveOwnerCall(args: {
|
|
1706
|
+
walletAddress: bigint | string;
|
|
1707
|
+
proposer: number;
|
|
1708
|
+
ownerId: number;
|
|
1709
|
+
}): Call;
|
|
1710
|
+
declare function buildExecuteRemoveOwnerCall(args: {
|
|
1711
|
+
walletAddress: bigint | string;
|
|
1712
|
+
opId: bigint | string;
|
|
1713
|
+
ownerId: number;
|
|
1714
|
+
}): Call;
|
|
1715
|
+
declare function buildProposeRotateOwnerCall(args: {
|
|
1716
|
+
walletAddress: bigint | string;
|
|
1717
|
+
proposer: number;
|
|
1718
|
+
ownerId: number;
|
|
1719
|
+
newPubkeyBytes: bigint[];
|
|
1720
|
+
}): Call;
|
|
1721
|
+
declare function buildExecuteRotateOwnerCall(args: {
|
|
1722
|
+
walletAddress: bigint | string;
|
|
1723
|
+
opId: bigint | string;
|
|
1724
|
+
ownerId: number;
|
|
1725
|
+
newPubkeyBytes: bigint[];
|
|
1726
|
+
}): Call;
|
|
1727
|
+
declare function buildProposeSetThresholdCall(args: {
|
|
1728
|
+
walletAddress: bigint | string;
|
|
1729
|
+
proposer: number;
|
|
1730
|
+
newThreshold: number;
|
|
1731
|
+
}): Call;
|
|
1732
|
+
declare function buildExecuteSetThresholdCall(args: {
|
|
1733
|
+
walletAddress: bigint | string;
|
|
1734
|
+
opId: bigint | string;
|
|
1735
|
+
newThreshold: number;
|
|
1736
|
+
}): Call;
|
|
1737
|
+
/**
|
|
1738
|
+
* Build a `cancel_pending_op(op_id)` call. Valid any time before the
|
|
1739
|
+
* op is executed; signed by any current owner via OE.
|
|
1740
|
+
*/
|
|
1741
|
+
declare function buildCancelPendingOpCall(args: {
|
|
1742
|
+
walletAddress: bigint | string;
|
|
1743
|
+
opId: bigint | string;
|
|
1744
|
+
}): Call;
|
|
1745
|
+
/**
|
|
1746
|
+
* Build an `initiate_recovery(proposer, new_owner_kind, new_pubkey_bytes,
|
|
1747
|
+
* new_role, new_weight, new_label)` call.
|
|
1748
|
+
*
|
|
1749
|
+
* IMPORTANT: `proposer` MUST be an owner with `role == ROLE_GUARDIAN`.
|
|
1750
|
+
* If it's a regular owner, the wallet panics with
|
|
1751
|
+
* `'RECOVERY: not a guardian'`. The new owner being recovered to is
|
|
1752
|
+
* typically `role: "OWNER"` so it can sign after the timelock.
|
|
1753
|
+
*
|
|
1754
|
+
* After confirmation, the wallet starts a 7-day timelock
|
|
1755
|
+
* (`TIMELOCK_SECONDS.RECOVERY`). During the window:
|
|
1756
|
+
* - Any current ROLE_OWNER can call `cancel_recovery` to abort.
|
|
1757
|
+
* - After the window, anyone can call `finalize_recovery`.
|
|
1758
|
+
*/
|
|
1759
|
+
declare function buildInitiateRecoveryCall(args: {
|
|
1760
|
+
walletAddress: bigint | string;
|
|
1761
|
+
/** owner_id of the guardian that's initiating */
|
|
1762
|
+
proposer: number;
|
|
1763
|
+
newOwner: NewOwnerInput;
|
|
1764
|
+
}): Call;
|
|
1765
|
+
/**
|
|
1766
|
+
* Build a `cancel_recovery(owner_id)` call. Signed by any current
|
|
1767
|
+
* ROLE_OWNER via OE — the canceler must hold a working signing key.
|
|
1768
|
+
* The owner_id arg identifies which owner is canceling (used by the
|
|
1769
|
+
* wallet for event attribution and the not-revoked / role-OWNER check).
|
|
1770
|
+
*/
|
|
1771
|
+
declare function buildCancelRecoveryCall(args: {
|
|
1772
|
+
walletAddress: bigint | string;
|
|
1773
|
+
ownerId: number;
|
|
1774
|
+
}): Call;
|
|
1775
|
+
/**
|
|
1776
|
+
* Build a `finalize_recovery(new_owner_kind, new_pubkey_bytes, new_role,
|
|
1777
|
+
* new_weight, new_label)` call.
|
|
1778
|
+
*
|
|
1779
|
+
* Permissionless after the 7-day timelock. The wallet recomputes the
|
|
1780
|
+
* pubkey commitment and asserts equality against the stored value
|
|
1781
|
+
* from `initiate_recovery`. Per audit H-1, the recovery path also
|
|
1782
|
+
* library-calls the per-kind verifier so a poison-pill pubkey from a
|
|
1783
|
+
* compromised guardian still fails at finalize time, not silently.
|
|
1784
|
+
*
|
|
1785
|
+
* Recovery is additive — existing owners stay. Follow up with
|
|
1786
|
+
* `buildProposeRemoveOwnerCall` for the stale devices.
|
|
1787
|
+
*
|
|
1788
|
+
* Because this call is NOT `_assert_self_call`-gated, the integrator
|
|
1789
|
+
* can either wrap it in an OE (signed by anyone) or dispatch directly
|
|
1790
|
+
* via a regular invoke. The Call shape is the same.
|
|
1791
|
+
*/
|
|
1792
|
+
declare function buildFinalizeRecoveryCall(args: {
|
|
1793
|
+
walletAddress: bigint | string;
|
|
1794
|
+
newOwner: NewOwnerInput;
|
|
1795
|
+
}): Call;
|
|
1796
|
+
/**
|
|
1797
|
+
* Compute the selector for a view; convenient for `provider.callContract`.
|
|
1798
|
+
* Exposed so SDK consumers can build their own `{ contractAddress,
|
|
1799
|
+
* entryPointSelector, calldata }` `Call`-shaped views without
|
|
1800
|
+
* re-deriving selectors.
|
|
1801
|
+
*/
|
|
1802
|
+
declare const VIEW_SELECTORS: {
|
|
1803
|
+
readonly get_pending_op: string;
|
|
1804
|
+
readonly get_pending_recovery: string;
|
|
1805
|
+
readonly get_owner: string;
|
|
1806
|
+
};
|
|
1807
|
+
/**
|
|
1808
|
+
* Given a `valid_after` block timestamp (seconds) returned from
|
|
1809
|
+
* `get_pending_op` or `get_pending_recovery`, compute whether the
|
|
1810
|
+
* timelock has elapsed against a given clock reading. Cheap UI helper
|
|
1811
|
+
* so consumers don't reach for a date library. Accepts fractional
|
|
1812
|
+
* `number` seconds (e.g., `Date.now() / 1000`) — truncates via floor.
|
|
1813
|
+
*/
|
|
1814
|
+
declare function isReadyToExecute(validAfterSecs: number | bigint, nowSecs: number): boolean;
|
|
1815
|
+
/**
|
|
1816
|
+
* Seconds remaining until a pending op / recovery becomes executable.
|
|
1817
|
+
* Returns 0 when the timelock has already elapsed. Useful for showing
|
|
1818
|
+
* a countdown in UI without a separate clock import. Accepts fractional
|
|
1819
|
+
* `number` seconds — truncates via floor.
|
|
1820
|
+
*/
|
|
1821
|
+
declare function secondsUntilExecutable(validAfterSecs: number | bigint, nowSecs: number): number;
|
|
1822
|
+
|
|
1823
|
+
/**
|
|
1824
|
+
* SHHH V8.4 threshold envelope assembly + gas estimation.
|
|
1825
|
+
*
|
|
1826
|
+
* Wave B PR B2. The single-owner OE flow already ships via
|
|
1827
|
+
* `wrap_v2_snip12_envelope` in `oe.ts`. This module adds the
|
|
1828
|
+
* multi-owner threshold flow: N owners (possibly with mixed signer
|
|
1829
|
+
* kinds) co-sign a single OE, and the wallet's
|
|
1830
|
+
* `execute_from_outside_v2` walks the envelope to verify each owner
|
|
1831
|
+
* independently before checking total weight against the configured
|
|
1832
|
+
* threshold.
|
|
1833
|
+
*
|
|
1834
|
+
* Cairo dispatch shape (`src/account.cairo:418-460`):
|
|
1835
|
+
*
|
|
1836
|
+
* signature = [
|
|
1837
|
+
* V2_THRESHOLD, // tag
|
|
1838
|
+
* n, // u32, MUST be >= 2 and <= owner_count
|
|
1839
|
+
* env_1_len, // u32
|
|
1840
|
+
* ...env_1 (env_1_len felts),
|
|
1841
|
+
* env_2_len,
|
|
1842
|
+
* ...env_2,
|
|
1843
|
+
* ...
|
|
1844
|
+
* env_n_len,
|
|
1845
|
+
* ...env_n,
|
|
1846
|
+
* ]
|
|
1847
|
+
*
|
|
1848
|
+
* Each inner envelope is a V2_SNIP12 single-owner envelope
|
|
1849
|
+
* (`wrap_v2_snip12_envelope` output) from a different owner. Nested
|
|
1850
|
+
* threshold envelopes are NOT permitted — the Cairo `_verify_sub_envelope`
|
|
1851
|
+
* recursively checks the inner tag and rejects anything except
|
|
1852
|
+
* V2_SNIP12.
|
|
1853
|
+
*
|
|
1854
|
+
* Cairo-enforced invariants this module replicates client-side so
|
|
1855
|
+
* mis-shaped envelopes fail at the SDK boundary with a clear message
|
|
1856
|
+
* instead of with an opaque "THRESH: …" Starknet revert:
|
|
1857
|
+
*
|
|
1858
|
+
* - signature.len() >= 2
|
|
1859
|
+
* - n >= 2 (n=1 is invalid — use V2_SNIP12 directly for single-owner)
|
|
1860
|
+
* - each inner env_len >= 2
|
|
1861
|
+
* - no buffer overflow / truncation
|
|
1862
|
+
* - no trailing bytes after the last inner envelope
|
|
1863
|
+
* - no duplicate owner_id across inner envelopes (extracted from
|
|
1864
|
+
* envelope[1] of each inner)
|
|
1865
|
+
*
|
|
1866
|
+
* Total-weight vs threshold is enforced ON-CHAIN (the SDK can't know
|
|
1867
|
+
* the configured threshold without reading on-chain state). Use
|
|
1868
|
+
* `estimateThresholdGasOverhead` to predict paymaster fees.
|
|
1869
|
+
*/
|
|
1870
|
+
|
|
1871
|
+
/** Minimum number of inner envelopes in a threshold (Cairo enforces >= 2). */
|
|
1872
|
+
declare const MIN_THRESHOLD_OWNERS = 2;
|
|
1873
|
+
/**
|
|
1874
|
+
* Validate that an envelope is a well-formed V2_SNIP12 single-owner
|
|
1875
|
+
* envelope suitable for inclusion in a threshold envelope. Returns
|
|
1876
|
+
* the extracted `owner_id` (envelope[1]) so the caller can dedupe.
|
|
1877
|
+
*
|
|
1878
|
+
* Rejects nested threshold envelopes (`signature[0] == V2_THRESHOLD`)
|
|
1879
|
+
* — V8.4 does not allow nesting; the Cairo `_verify_sub_envelope`
|
|
1880
|
+
* would revert with `'SRC9: bad tag'`.
|
|
1881
|
+
*/
|
|
1882
|
+
declare function assertValidV2Snip12Envelope(envelope: bigint[]): bigint;
|
|
1883
|
+
/**
|
|
1884
|
+
* Build a V2_THRESHOLD envelope from a list of pre-signed V2_SNIP12
|
|
1885
|
+
* inner envelopes. Each inner envelope must come from a DIFFERENT
|
|
1886
|
+
* owner — duplicates fail loudly here instead of on-chain.
|
|
1887
|
+
*
|
|
1888
|
+
* The typical UI flow:
|
|
1889
|
+
* 1. Compute the OE message hash once (computeOeHash from oe.ts)
|
|
1890
|
+
* 2. Distribute the hash to N owners (push notification, QR, etc.)
|
|
1891
|
+
* 3. Each owner signs with their kind's envelope builder and
|
|
1892
|
+
* returns the resulting `bigint[]`
|
|
1893
|
+
* 4. UI collects the envelopes and calls this function
|
|
1894
|
+
* 5. UI submits the resulting threshold envelope as the
|
|
1895
|
+
* `signature` field of `paymaster_executeSponsoredRaw.calldata`
|
|
1896
|
+
*
|
|
1897
|
+
* `buildThresholdEnvelope` does NOT submit anything. It returns the
|
|
1898
|
+
* felt span that goes into the OE calldata.
|
|
1899
|
+
*/
|
|
1900
|
+
declare function buildThresholdEnvelope(args: {
|
|
1901
|
+
innerEnvelopes: bigint[][];
|
|
1902
|
+
}): bigint[];
|
|
1903
|
+
/**
|
|
1904
|
+
* Split a V2_THRESHOLD envelope back into its constituent inner
|
|
1905
|
+
* V2_SNIP12 envelopes. Inverse of `buildThresholdEnvelope`.
|
|
1906
|
+
*
|
|
1907
|
+
* Useful for UI flows that need to display "N of M signers signed
|
|
1908
|
+
* so far" status without re-deriving from individual envelopes. Also
|
|
1909
|
+
* useful in tests for round-trip validation.
|
|
1910
|
+
*
|
|
1911
|
+
* Throws if the envelope is malformed (tag mismatch, n < 2, length
|
|
1912
|
+
* mismatch, trailing bytes). Mirrors the Cairo dispatch's structural
|
|
1913
|
+
* checks (`src/account.cairo:418-456`) so the same envelope that
|
|
1914
|
+
* would parse-fail here would also revert on-chain.
|
|
1915
|
+
*/
|
|
1916
|
+
declare function parseThresholdEnvelope(envelope: bigint[]): {
|
|
1917
|
+
innerEnvelopes: bigint[][];
|
|
1918
|
+
};
|
|
1919
|
+
/**
|
|
1920
|
+
* Predict the on-chain L2 gas overhead a threshold envelope will
|
|
1921
|
+
* consume. Each inner envelope dispatches to its kind's verifier
|
|
1922
|
+
* class via `library_call`, so total overhead is the sum of
|
|
1923
|
+
* per-kind overheads.
|
|
1924
|
+
*
|
|
1925
|
+
* Returned value is the same unit as `SHHH_KIND_GAS_OVERHEAD` in
|
|
1926
|
+
* `@chipi-stack/shared` (raw L2 gas units). Use to predict paymaster
|
|
1927
|
+
* fees:
|
|
1928
|
+
* estimatedFee = baselineOEGas + estimateThresholdGasOverhead(owners)
|
|
1929
|
+
*
|
|
1930
|
+
* Does NOT include the wallet's threshold-validation loop overhead
|
|
1931
|
+
* (the deduplication walk and weight summation) — that's a constant
|
|
1932
|
+
* ~50K L2 gas regardless of N, and the paymaster's fee predictor
|
|
1933
|
+
* already accounts for it.
|
|
1934
|
+
*/
|
|
1935
|
+
declare function estimateThresholdGasOverhead(owners: Array<{
|
|
1936
|
+
signerKind: ShhhSignerKind;
|
|
1937
|
+
}>): number;
|
|
1938
|
+
|
|
1939
|
+
/**
|
|
1940
|
+
* SHHH V8.4 migration builders — sessions / OZ → V8.4.
|
|
1941
|
+
*
|
|
1942
|
+
* Wave B PR B4. Covers two paths:
|
|
1943
|
+
*
|
|
1944
|
+
* 1. Happy-path atomic multicall (`buildMigrationCalls`)
|
|
1945
|
+
* Used when the legacy wallet is still operational and can sign
|
|
1946
|
+
* an OutsideExecution. The wallet emits one OE containing two
|
|
1947
|
+
* calls in order:
|
|
1948
|
+
*
|
|
1949
|
+
* upgrade(SHHH_ACCOUNT_CLASS_HASH)
|
|
1950
|
+
* bootstrap_from_sessions(public_key, STARK_VERIFIER_CLASS_HASH, label)
|
|
1951
|
+
*
|
|
1952
|
+
* Atomicity is the security property — between `upgrade` and
|
|
1953
|
+
* `bootstrap_from_sessions` an attacker watching the mempool
|
|
1954
|
+
* could call `bootstrap_from_sessions(attacker_pk, ...)` first
|
|
1955
|
+
* and seize the account. The OE bundles them so that race
|
|
1956
|
+
* window doesn't exist.
|
|
1957
|
+
*
|
|
1958
|
+
* 2. Stranded-state recovery (`buildBootstrapSignedCall` +
|
|
1959
|
+
* `computeBootstrapMessage` + `signBootstrapMessage`)
|
|
1960
|
+
* Used when the legacy multicall ran but `bootstrap_from_sessions`
|
|
1961
|
+
* silently reverted (the old chipi-pay/sessions-smart-contract's
|
|
1962
|
+
* `_execute_calls` swallowed subcall errors at a
|
|
1963
|
+
* `Result::Err(_) => res.append(array![].span())` site). The
|
|
1964
|
+
* account is at V8.4 class but `primary_kind == 0`.
|
|
1965
|
+
*
|
|
1966
|
+
* Recovery: anyone calls
|
|
1967
|
+
*
|
|
1968
|
+
* bootstrap_from_sessions_signed(
|
|
1969
|
+
* public_key, stark_verifier_class, label, signature_r, signature_s
|
|
1970
|
+
* )
|
|
1971
|
+
*
|
|
1972
|
+
* where (r, s) is a STARK ECDSA signature over
|
|
1973
|
+
*
|
|
1974
|
+
* bootstrap_msg = poseidon([
|
|
1975
|
+
* 'SHHH_BOOTSTRAP_V8_4',
|
|
1976
|
+
* account_address,
|
|
1977
|
+
* public_key,
|
|
1978
|
+
* stark_verifier_class,
|
|
1979
|
+
* label,
|
|
1980
|
+
* ])
|
|
1981
|
+
*
|
|
1982
|
+
* Authorization comes from two independent checks on-chain:
|
|
1983
|
+
* - the preserved `LEGACY_OZ_ACCOUNT_PUBKEY_SLOT` storage slot
|
|
1984
|
+
* must equal `public_key` (audit C-1 binding to the original
|
|
1985
|
+
* owner)
|
|
1986
|
+
* - the signature must verify under `public_key`
|
|
1987
|
+
*
|
|
1988
|
+
* So this entrypoint is callable from any sponsor / relayer
|
|
1989
|
+
* without a self-call channel, but is non-transferable: only the
|
|
1990
|
+
* original owner who controls the STARK private key can produce
|
|
1991
|
+
* a valid signature.
|
|
1992
|
+
*
|
|
1993
|
+
* Cairo source of truth:
|
|
1994
|
+
* shhh-wallet-cairo/src/account.cairo:1024 bootstrap_from_sessions
|
|
1995
|
+
* shhh-wallet-cairo/src/account.cairo:1113 bootstrap_from_sessions_signed
|
|
1996
|
+
* shhh-wallet-cairo/scripts/ts/migrate-sessions-wallet.ts (happy-path ref)
|
|
1997
|
+
*
|
|
1998
|
+
* What this module does NOT cover (out of scope for B4):
|
|
1999
|
+
* - The upstream OE-build + sign step. Caller wraps these `Call`s
|
|
2000
|
+
* with `buildOutsideExecution` + `buildStarkEnvelope` and submits
|
|
2001
|
+
* via the paymaster. Same flow as B1's governance builders.
|
|
2002
|
+
* - Verifying on-chain whether a wallet is stranded. Done by the
|
|
2003
|
+
* dashboard / UI; here we just produce the recovery call.
|
|
2004
|
+
*/
|
|
2005
|
+
|
|
2006
|
+
/**
|
|
2007
|
+
* Domain separator the on-chain signed-bootstrap message uses as its
|
|
2008
|
+
* first felt. Mirrors `'SHHH_BOOTSTRAP_V8_4'` literal at
|
|
2009
|
+
* `src/account.cairo:1170`.
|
|
2010
|
+
*/
|
|
2011
|
+
declare const BOOTSTRAP_DOMAIN_SEPARATOR: bigint;
|
|
2012
|
+
/**
|
|
2013
|
+
* Encode a label string as a Starknet short-string felt252.
|
|
2014
|
+
*
|
|
2015
|
+
* starknet.js `shortString.encodeShortString` accepts only ASCII
|
|
2016
|
+
* characters and rejects strings longer than 31 characters (it
|
|
2017
|
+
* stuffs one character per byte, and felt252 has 251 useful bits).
|
|
2018
|
+
* We pre-validate here so callers get a clear error instead of the
|
|
2019
|
+
* generic encoder revert; the previous "≤31 bytes" message was
|
|
2020
|
+
* misleading because non-ASCII strings would silently slip past a
|
|
2021
|
+
* UTF-16 `.length` check.
|
|
2022
|
+
*/
|
|
2023
|
+
declare function labelToFelt(label: string): bigint;
|
|
2024
|
+
interface BuildMigrationCallsParams {
|
|
2025
|
+
/** Address of the legacy CHIPI v29 / sessions wallet being migrated. */
|
|
2026
|
+
legacyAccountAddress: bigint | string;
|
|
2027
|
+
/** STARK public key currently stored in the legacy wallet's OZ slot. */
|
|
2028
|
+
legacyPublicKey: bigint | string;
|
|
2029
|
+
/** Target SHHH ShhhAccount class hash (e.g. `WALLET_CLASS_HASHES.SHHH`). */
|
|
2030
|
+
shhhAccountClassHash: bigint | string;
|
|
2031
|
+
/** STARK verifier class hash (`SHHH_VERIFIER_CLASS_HASHES.STARK`). */
|
|
2032
|
+
starkVerifierClassHash: bigint | string;
|
|
2033
|
+
/** Optional user-facing tag bound into the V8.4 owner record (≤31 ASCII characters). */
|
|
2034
|
+
label?: string;
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Build the two-call atomic multicall that migrates a legacy
|
|
2038
|
+
* CHIPI v29 (chipi-pay/sessions-smart-contract) wallet to SHHH V8.4.
|
|
2039
|
+
*
|
|
2040
|
+
* Caller's responsibility:
|
|
2041
|
+
* 1. Wrap the returned `Call[]` in a single `OutsideExecution`
|
|
2042
|
+
* against the legacy wallet's address.
|
|
2043
|
+
* 2. Sign the OE with the legacy wallet's STARK key (the SDK's
|
|
2044
|
+
* legacy CHIPI flow already does this; use that signer here).
|
|
2045
|
+
* 3. Submit via the paymaster.
|
|
2046
|
+
*
|
|
2047
|
+
* The multicall MUST be atomic — if any call between the two could
|
|
2048
|
+
* land independently on chain (e.g. via mempool front-running), the
|
|
2049
|
+
* resulting `upgrade`-but-not-`bootstrap` state is exploitable.
|
|
2050
|
+
* Bundling them in a single OE makes both calls land atomically.
|
|
2051
|
+
*
|
|
2052
|
+
* Returns the calls in order: [upgrade, bootstrap_from_sessions].
|
|
2053
|
+
*/
|
|
2054
|
+
declare function buildMigrationCalls(params: BuildMigrationCallsParams): Call[];
|
|
2055
|
+
interface BootstrapMessageParams {
|
|
2056
|
+
/** The (already-upgraded) SHHH wallet's contract address. */
|
|
2057
|
+
accountAddress: bigint | string;
|
|
2058
|
+
/** STARK public key originally stored in the legacy OZ slot. */
|
|
2059
|
+
publicKey: bigint | string;
|
|
2060
|
+
/** STARK verifier class hash to set as primary. */
|
|
2061
|
+
starkVerifierClassHash: bigint | string;
|
|
2062
|
+
/** User-facing tag bound into the V8.4 owner record (≤31 ASCII characters). */
|
|
2063
|
+
label?: string;
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Compute the canonical bootstrap message the on-chain
|
|
2067
|
+
* `bootstrap_from_sessions_signed` verifier will check the STARK
|
|
2068
|
+
* ECDSA signature against:
|
|
2069
|
+
*
|
|
2070
|
+
* poseidon([
|
|
2071
|
+
* 'SHHH_BOOTSTRAP_V8_4',
|
|
2072
|
+
* account_address,
|
|
2073
|
+
* public_key,
|
|
2074
|
+
* stark_verifier_class,
|
|
2075
|
+
* label,
|
|
2076
|
+
* ])
|
|
2077
|
+
*
|
|
2078
|
+
* Domain separation via the literal prefix means a signature produced
|
|
2079
|
+
* for this message can never be replayed as a different intent
|
|
2080
|
+
* (audit point — covered in `audit_poc_attacker_can_seize_any_stranded_wallet`).
|
|
2081
|
+
*/
|
|
2082
|
+
declare function computeBootstrapMessage(params: BootstrapMessageParams): bigint;
|
|
2083
|
+
/**
|
|
2084
|
+
* Sign the canonical bootstrap message with a STARK private key.
|
|
2085
|
+
*
|
|
2086
|
+
* Used in two situations:
|
|
2087
|
+
* - Server-side test fixtures + the Wave B B9 mainnet smoke
|
|
2088
|
+
* - "Recovery as a service" flows where the SDK is allowed to hold
|
|
2089
|
+
* a temporary STARK key custodied for the user
|
|
2090
|
+
*
|
|
2091
|
+
* Most production flows will instead sign via the user's wallet
|
|
2092
|
+
* (e.g. by exposing the message hash to the legacy wallet's STARK
|
|
2093
|
+
* signing endpoint) and pass `(r, s)` directly to
|
|
2094
|
+
* `buildBootstrapSignedCall`.
|
|
2095
|
+
*/
|
|
2096
|
+
declare function signBootstrapMessage(params: {
|
|
2097
|
+
privateKey: bigint | string;
|
|
2098
|
+
message: BootstrapMessageParams;
|
|
2099
|
+
}): {
|
|
2100
|
+
r: bigint;
|
|
2101
|
+
s: bigint;
|
|
2102
|
+
};
|
|
2103
|
+
interface BuildBootstrapSignedCallParams {
|
|
2104
|
+
/** The stranded SHHH wallet's contract address. */
|
|
2105
|
+
accountAddress: bigint | string;
|
|
2106
|
+
/** STARK public key originally stored in the legacy OZ slot. */
|
|
2107
|
+
publicKey: bigint | string;
|
|
2108
|
+
/** STARK verifier class hash. Must match the value the original migration intended. */
|
|
2109
|
+
starkVerifierClassHash: bigint | string;
|
|
2110
|
+
/** User-facing tag (≤31 ASCII characters). Must match the original migration intent. */
|
|
2111
|
+
label?: string;
|
|
2112
|
+
/** STARK ECDSA signature r component over `computeBootstrapMessage(...)`. */
|
|
2113
|
+
signatureR: bigint | string;
|
|
2114
|
+
/** STARK ECDSA signature s component. */
|
|
2115
|
+
signatureS: bigint | string;
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* Build a `bootstrap_from_sessions_signed(public_key, stark_verifier_class,
|
|
2119
|
+
* label, signature_r, signature_s)` call against a stranded SHHH wallet.
|
|
2120
|
+
*
|
|
2121
|
+
* Permissionless on-chain — anyone (typically a sponsor / relayer)
|
|
2122
|
+
* can dispatch this call. The signature is the auth. So the integrator
|
|
2123
|
+
* does NOT need to wrap it in an OE from the wallet itself; it goes
|
|
2124
|
+
* out as a regular invoke from any STARK account.
|
|
2125
|
+
*
|
|
2126
|
+
* Returns the `Call` for the integrator's account-abstraction layer
|
|
2127
|
+
* (e.g. a chipi-back service account, or a one-off operator wallet).
|
|
2128
|
+
*/
|
|
2129
|
+
declare function buildBootstrapSignedCall(params: BuildBootstrapSignedCallParams): Call;
|
|
2130
|
+
|
|
2131
|
+
/**
|
|
2132
|
+
* High-level SHHH V8.4 OE execution path for the backend SDK.
|
|
2133
|
+
*
|
|
2134
|
+
* This module is the glue between the low-level OE builders in
|
|
2135
|
+
* `oe.ts` + `signers/*.ts` and chipi-back's
|
|
2136
|
+
* `POST /transactions/execute-sponsored-raw` endpoint.
|
|
2137
|
+
*
|
|
2138
|
+
* Why this exists separately from the legacy
|
|
2139
|
+
* `executePaymasterTransaction` flow:
|
|
2140
|
+
*
|
|
2141
|
+
* - The Chipi paymaster cannot synthesize SHHH envelopes server-side
|
|
2142
|
+
* (each of the 10 V8.4 signer kinds has a different envelope
|
|
2143
|
+
* shape; the paymaster's design — see chipi-pay/paymaster#16 — is
|
|
2144
|
+
* to pass the signature span verbatim to `execute_from_outside_v2`).
|
|
2145
|
+
* - Therefore SHHH clients MUST build + sign the OE locally and
|
|
2146
|
+
* submit it pre-signed to a forwarding endpoint
|
|
2147
|
+
* (`/transactions/execute-sponsored-raw`).
|
|
2148
|
+
* - The legacy flow (`prepare-typed-data` → sign → `execute-sponsored-
|
|
2149
|
+
* transaction`) is unchanged and continues to serve CHIPI / READY
|
|
2150
|
+
* wallets.
|
|
2151
|
+
*
|
|
2152
|
+
* Scope (Wave B, this PR): STARK + ED25519 signer kinds.
|
|
2153
|
+
* EIP-191 / WEBAUTHN_P256 / JWT_ES256_APPLE_SUB still throw the
|
|
2154
|
+
* deferred-feature error until the external-rooted wallet adapter
|
|
2155
|
+
* flow lands.
|
|
2156
|
+
*/
|
|
2157
|
+
|
|
2158
|
+
/**
|
|
2159
|
+
* Pick the chain ID for SNIP-12 hashing. If the caller did not provide
|
|
2160
|
+
* an explicit override, infer from the chipi-back base URL (sepolia
|
|
2161
|
+
* hostnames map to SN_SEPOLIA; everything else maps to SN_MAIN per
|
|
2162
|
+
* the project's mainnet-only invariant).
|
|
2163
|
+
*
|
|
2164
|
+
* Exported for unit-testing the resolver in isolation.
|
|
2165
|
+
*/
|
|
2166
|
+
declare function resolveShhhChainId(params: {
|
|
2167
|
+
override?: bigint | string;
|
|
2168
|
+
baseUrl?: string;
|
|
2169
|
+
}): string;
|
|
2170
|
+
/**
|
|
2171
|
+
* Serialize an OutsideExecution struct + signature span into the
|
|
2172
|
+
* flat felt-array calldata that `execute_from_outside_v2` expects.
|
|
2173
|
+
*
|
|
2174
|
+
* Cairo's serde order (per shhh-wallet-cairo v8.4
|
|
2175
|
+
* `src/account.cairo::execute_from_outside_v2`):
|
|
2176
|
+
*
|
|
2177
|
+
* caller
|
|
2178
|
+
* nonce
|
|
2179
|
+
* execute_after (u64, 1 felt)
|
|
2180
|
+
* execute_before (u64, 1 felt)
|
|
2181
|
+
* calls.len()
|
|
2182
|
+
* for each Call:
|
|
2183
|
+
* to (felt)
|
|
2184
|
+
* selector (felt)
|
|
2185
|
+
* calldata.len()
|
|
2186
|
+
* ...calldata felts
|
|
2187
|
+
* signature.len()
|
|
2188
|
+
* ...signature felts
|
|
2189
|
+
*
|
|
2190
|
+
* Returns 0x-prefixed lowercase hex strings ready for direct
|
|
2191
|
+
* JSON-RPC submission via the chipi-back forwarder.
|
|
2192
|
+
*/
|
|
2193
|
+
declare function serializeExecuteFromOutsideV2Calldata(params: {
|
|
2194
|
+
outsideExecution: {
|
|
2195
|
+
caller: bigint | string;
|
|
2196
|
+
nonce: bigint | string;
|
|
2197
|
+
execute_after: bigint;
|
|
2198
|
+
execute_before: bigint;
|
|
2199
|
+
calls: Call[];
|
|
2200
|
+
};
|
|
2201
|
+
signature: bigint[];
|
|
2202
|
+
}): string[];
|
|
2203
|
+
/**
|
|
2204
|
+
* Decrypt the wallet's STARK private key from its
|
|
2205
|
+
* `encryptedPrivateKey` ciphertext + the per-wallet `encryptKey` (PIN
|
|
2206
|
+
* or passkey-derived secret), build a SHHH V8.4 OE around the given
|
|
2207
|
+
* inner calls, sign it with the wallet's STARK key, serialize the
|
|
2208
|
+
* calldata for `execute_from_outside_v2`, and forward to chipi-back's
|
|
2209
|
+
* `/transactions/execute-sponsored-raw` endpoint.
|
|
2210
|
+
*
|
|
2211
|
+
* Returns the on-chain transaction hash.
|
|
2212
|
+
*
|
|
2213
|
+
* Pre-conditions:
|
|
2214
|
+
* - `wallet.walletType === "SHHH"`.
|
|
2215
|
+
* - `wallet.publicKey` is the deployed account address (NOT the raw
|
|
2216
|
+
* STARK pubkey; V8.4 deployment derives address from kind-bound
|
|
2217
|
+
* salt + verifier).
|
|
2218
|
+
* - `wallet.encryptedPrivateKey` decrypts cleanly with `encryptKey`.
|
|
2219
|
+
* - The wallet is already deployed (no deploy_and_invoke variant
|
|
2220
|
+
* here; that flow stays inside `createWallet`).
|
|
2221
|
+
*/
|
|
2222
|
+
declare function executeShhhSponsoredRaw(params: {
|
|
2223
|
+
client: ChipiClient;
|
|
2224
|
+
bearerToken: string;
|
|
2225
|
+
wallet: {
|
|
2226
|
+
publicKey: string;
|
|
2227
|
+
encryptedPrivateKey: string;
|
|
2228
|
+
walletType?: string;
|
|
2229
|
+
};
|
|
2230
|
+
encryptKey: string;
|
|
2231
|
+
calls: Call[];
|
|
2232
|
+
/**
|
|
2233
|
+
* Defaults to `"STARK"`. Wave B ships STARK + ED25519. EIP-191 /
|
|
2234
|
+
* WEBAUTHN_P256 / JWT_ES256_APPLE_SUB are deferred to the
|
|
2235
|
+
* external-rooted wallet adapter PRs.
|
|
2236
|
+
*/
|
|
2237
|
+
signerKind?: "STARK" | "ED25519";
|
|
2238
|
+
/** Override the OE nonce; default = `Date.now() << 24 | random24bit`. */
|
|
2239
|
+
nonce?: bigint;
|
|
2240
|
+
/** Optional externalUserId for the audit-log hint. */
|
|
2241
|
+
externalUserId?: string;
|
|
2242
|
+
/**
|
|
2243
|
+
* Override the chain ID used for SNIP-12 hashing. Defaults to
|
|
2244
|
+
* mainnet per CLAUDE.md's mainnet-only invariant; sepolia is auto-
|
|
2245
|
+
* inferred from the chipi-back base URL when present. Drift here
|
|
2246
|
+
* = the V8.4 account rejects the signature on chain.
|
|
2247
|
+
*/
|
|
2248
|
+
chainId?: bigint | string;
|
|
2249
|
+
}): Promise<string>;
|
|
2250
|
+
|
|
2251
|
+
export { ANY_CALLER_FELT, APPLE_ISSUER, type AppleTokenResponse, BOOTSTRAP_DOMAIN_SEPARATOR, type BootstrapMessageParams, type BuildBootstrapSignedCallParams, type BuildMigrationCallsParams, ChipiBrowserSDK, ChipiClient, ChipiPaymasterAdapter, ChipiSDK, ChipiServerSDK, ChipiSessions, ChipiSkuPurchases, ChipiSkus, ChipiTransactions, ChipiWallets, type ComputeAddressInput, DEFAULT_OP_EXPIRY_SECONDS, type Eip191Pubkey, ISIGNER_CANONICAL_LABEL, ISIGNER_ID, ISRC9_V2_ID, type JwtAppleSubPubkey, MAX_OE_VALIDITY_WINDOW_SECONDS, MIN_THRESHOLD_OWNERS, type NewOwnerInput, OE_DOMAIN_NAME, OE_DOMAIN_REVISION, OE_DOMAIN_VERSION, OP_ADD_OWNER, OP_REMOVE_OWNER, OP_ROTATE_OWNER, OP_SET_THRESHOLD, type OutsideExecution, type OwnerRole, ROLE_GUARDIAN, ROLE_OWNER, SIG_VERSION_V1_HEX_ASCII, SIG_VERSION_V2_SNIP12, SIG_VERSION_V2_THRESHOLD, STARKNET_MESSAGE_PREFIX, type ShhhX402Wallet, TIMELOCK_SECONDS, VIEW_SELECTORS, type WebAuthnPubkey, X402Facilitator, type X402FacilitatorCreateOptions, type X402MiddlewareConfig, type X402MiddlewareFlatConfig, X402ShhhClient, type X402ShhhClientConfig, assertValidV2Snip12Envelope, buildBootstrapSignedCall, buildCancelPendingOpCall, buildCancelRecoveryCall, buildEd25519Envelope, buildEd25519EnvelopeFromSignature, buildEip191EnvelopeFromPrivateKey, buildEip191EnvelopeFromSignature, buildExecuteAddOwnerCall, buildExecuteRemoveOwnerCall, buildExecuteRotateOwnerCall, buildExecuteSetThresholdCall, buildFinalizeRecoveryCall, buildInitiateRecoveryCall, buildJwtAppleSubEnvelopeFromTestKey, buildJwtAppleSubEnvelopeFromTokenResponse, buildMigrationCalls, buildOutsideExecution, buildProposeAddOwnerCall, buildProposeRemoveOwnerCall, buildProposeRotateOwnerCall, buildProposeSetThresholdCall, buildStarkEnvelope, buildThresholdEnvelope, buildWebAuthnEnvelopeFromAssertion, buildWebAuthnEnvelopeFromPrivateKey, byteArrayToFelts, computeAddressSalt, computeBootstrapMessage, computeISignerId, computeOeHash, computeShhhAddress, computeSnip12Hash, decryptPrivateKey, ed25519OwnerId, ed25519PubkeyFelts, ed25519SignedBytes, eip191OwnerId, eip191PubkeyFelts, encryptPrivateKey, estimateThresholdGasOverhead, executeShhhSponsoredRaw, hashCall, hashCalls, hashOutsideExecutionStruct, hashStarknetDomain, isReadyToExecute, jwtAppleSubHash, jwtAppleSubOwnerId, jwtAppleSubPubkeyFelts, labelToFelt, locateSubClaim, messageHashToNonceB64url, ownerCommitment, parseAppleIdToken, parseDerEcdsaSignature, parseEip191Signature, parseThresholdEnvelope, resolveShhhChainId, secondsUntilExecutable, serializeExecuteFromOutsideV2Calldata, signBootstrapMessage, signTypedData, snip12HashToHexAsciiBytes, starkOwnerId, starkPubkeyFelts, webauthnOwnerId, webauthnPubkeyFelts, wrapV2Snip12Envelope, wrapV2ThresholdEnvelope, x402Middleware };
|