@inco/shield-js 0.1.0 → 0.2.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.
Files changed (74) hide show
  1. package/README.md +52 -308
  2. package/dist/contracts/abi.d.ts +2000 -4513
  3. package/dist/contracts/abi.js +2400 -5651
  4. package/dist/contracts/index.d.ts +2 -2
  5. package/dist/contracts/index.js +2 -2
  6. package/dist/contracts/utils.d.ts +5 -24
  7. package/dist/contracts/utils.js +13 -24
  8. package/dist/errors.d.ts +16 -47
  9. package/dist/errors.js +6 -53
  10. package/dist/generated/inco/shield/v2/conductor_pb.d.ts +77 -0
  11. package/dist/generated/inco/shield/v2/conductor_pb.js +6 -0
  12. package/dist/generated/inco/shield/v2/deposit_pb.d.ts +12 -103
  13. package/dist/generated/inco/shield/v2/deposit_pb.js +4 -141
  14. package/dist/generated/inco/shield/v2/drafting_pb.d.ts +45 -269
  15. package/dist/generated/inco/shield/v2/drafting_pb.js +8 -361
  16. package/dist/generated/inco/shield/v2/types_pb.d.ts +47 -166
  17. package/dist/generated/inco/shield/v2/types_pb.js +12 -261
  18. package/dist/index.d.ts +1 -7
  19. package/dist/index.js +1 -14
  20. package/dist/shield/abi.d.ts +5 -0
  21. package/dist/shield/abi.js +28 -0
  22. package/dist/shield/client.d.ts +27 -51
  23. package/dist/shield/client.js +73 -65
  24. package/dist/shield/convert.d.ts +2 -14
  25. package/dist/shield/convert.js +39 -115
  26. package/dist/shield/encryption.d.ts +0 -67
  27. package/dist/shield/encryption.js +0 -67
  28. package/dist/shield/envelope.d.ts +10 -11
  29. package/dist/shield/envelope.js +15 -14
  30. package/dist/shield/fee.d.ts +24 -0
  31. package/dist/shield/fee.js +151 -0
  32. package/dist/shield/index.d.ts +9 -3
  33. package/dist/shield/index.js +7 -2
  34. package/dist/shield/intent.d.ts +20 -933
  35. package/dist/shield/intent.js +92 -112
  36. package/dist/shield/permission-status.d.ts +9 -0
  37. package/dist/shield/permission-status.js +8 -0
  38. package/dist/shield/rpc.d.ts +50 -49
  39. package/dist/shield/rpc.js +207 -354
  40. package/dist/shield/shield.eip712.gen.d.ts +1096 -43
  41. package/dist/shield/shield.eip712.gen.js +572 -62
  42. package/dist/shield/types.d.ts +7 -102
  43. package/dist/shield/types.js +1 -4
  44. package/dist/shield/userop/gas-estimation.d.ts +7 -22
  45. package/dist/shield/userop/gas-estimation.js +100 -137
  46. package/dist/shield/userop/paymaster.d.ts +15 -0
  47. package/dist/shield/userop/paymaster.js +17 -0
  48. package/dist/shield/userop/token-exchange.d.ts +5 -52
  49. package/dist/shield/userop/token-exchange.js +41 -149
  50. package/dist/shield/userop/types.d.ts +2 -27
  51. package/dist/shield/userop/types.js +0 -3
  52. package/dist/shield/userop/userOp.d.ts +0 -19
  53. package/dist/shield/userop/userOp.js +4 -22
  54. package/dist/shield/utils/chain.d.ts +1 -0
  55. package/dist/shield/utils/chain.js +3 -0
  56. package/dist/shield/validate-withdrawal-split.d.ts +13 -0
  57. package/dist/shield/validate-withdrawal-split.js +93 -0
  58. package/package.json +18 -27
  59. package/dist/generated/inco/shield/v2/conductor_connect.d.ts +0 -137
  60. package/dist/generated/inco/shield/v2/conductor_connect.js +0 -141
  61. package/dist/generated/inco/shield/v2/permission_pb.d.ts +0 -443
  62. package/dist/generated/inco/shield/v2/permission_pb.js +0 -639
  63. package/dist/generated/inco/shield/v2/query_pb.d.ts +0 -103
  64. package/dist/generated/inco/shield/v2/query_pb.js +0 -141
  65. package/dist/shield/userop/index.d.ts +0 -1
  66. package/dist/shield/userop/index.js +0 -1
  67. package/dist/uniswap-adapter/config.d.ts +0 -34
  68. package/dist/uniswap-adapter/config.js +0 -45
  69. package/dist/uniswap-adapter/find-pools.d.ts +0 -91
  70. package/dist/uniswap-adapter/find-pools.js +0 -108
  71. package/dist/uniswap-adapter/index.d.ts +0 -10
  72. package/dist/uniswap-adapter/index.js +0 -13
  73. package/dist/uniswap-adapter/swap.d.ts +0 -344
  74. package/dist/uniswap-adapter/swap.js +0 -309
@@ -1,119 +1,24 @@
1
1
  import type { Transport as ConnectTransport } from '@connectrpc/connect';
2
- import type { Hex, Transport as ViemTransport, WalletClient } from 'viem';
3
- /** Minimal signer interface, structurally compatible with viem LocalAccount. */
2
+ import type { Hex, TypedDataDefinition, Transport as ViemTransport, WalletClient } from 'viem';
4
3
  export interface ShieldSigner {
5
4
  address: Hex;
6
5
  sign(params: {
7
6
  hash: Hex;
8
7
  }): Promise<Hex>;
9
8
  }
10
- /**
11
- * Accepted signer types. Either:
12
- * - A viem `WalletClient` (browser wallets or backend with privateKeyToAccount)
13
- * - A `ShieldSigner` (custom signer with raw hash signing)
14
- */
9
+ export interface ResolvedSigner {
10
+ address: Hex;
11
+ signTypedData(typedData: TypedDataDefinition): Promise<Hex>;
12
+ }
15
13
  export type ShieldSignerInput = WalletClient | ShieldSigner;
16
- /** Configuration for creating a ShieldClient. */
17
14
  export interface ShieldConfig {
18
15
  grpcTransport: ConnectTransport;
19
- /**
20
- * RPC transport for Ethereum-compatible chains.
21
- */
22
16
  viemTransport?: ViemTransport;
23
17
  chainId: bigint;
24
18
  shieldAddress: Hex;
25
19
  signer: ShieldSignerInput;
26
- /**
27
- * Serialized TEE public key for encrypting envelopes (known out of band).
28
- * TODO: SDK should fetch this from the IncoShield contract rather than
29
- * requiring it as config. Tracked separately.
30
- */
31
20
  teePublicKey: Uint8Array;
32
- /** Bundler URL. Defaults to the Pimlico public bundler if not provided. */
33
21
  bundlerUrl?: string;
22
+ feeAccrualAdapter: Hex;
23
+ protocolFeeTreasury: Hex;
34
24
  }
35
- /** Parameters for the `getBalance` RPC call. */
36
- export interface GetBalanceParams {
37
- address: Hex;
38
- assetId: Hex;
39
- /** Query balance at a specific block. Defaults to latest. */
40
- atBlock?: bigint;
41
- }
42
- /** Balance breakdown for an account and asset. */
43
- export interface Balance {
44
- confirmed: bigint;
45
- pending: bigint;
46
- available: bigint;
47
- }
48
- /**
49
- * Per-issuance salt — 32-byte hex. Distinguishes intents that are otherwise
50
- * field-identical and gives `permissionId` cryptographic unguessability against
51
- * outside observers of revocation events.
52
- */
53
- export type Salt = Hex;
54
- /**
55
- * Owner's terms for a delegated-spend grant. Owner signs this struct (EIP-712)
56
- * once and submits it to the TEE during issuance; the TEE returns a
57
- * `PermissionAttestation` and the spender carries that attestation thereafter.
58
- *
59
- * `permissionId` is recomputed from these fields by both sides; it is not
60
- * carried in the intent itself. The TEE's `draftingHandle` anchor is chosen
61
- * at issuance from enclave state, not supplied by the user.
62
- *
63
- * @deprecated
64
- * TODO: Remove as part of https://github.com/Inco-fhevm/shield/issues/499
65
- */
66
- export interface PermissionIntent {
67
- owner: Hex;
68
- spender: Hex;
69
- assetId: Hex;
70
- allowance: bigint;
71
- start: bigint;
72
- end: bigint;
73
- period: bigint;
74
- agreementHash: Hex;
75
- salt: Salt;
76
- intentExpiry: bigint;
77
- }
78
- /** Parameters for the `requestDepositAttestation` RPC call. */
79
- export interface RequestDepositAttestationParams {
80
- recipient: Hex;
81
- assetAdapter: Hex;
82
- assetIdentificationArgs: Hex;
83
- memo: Hex;
84
- escapeHatch: Hex;
85
- }
86
- /** TEE-issued deposit attestation: ciphertext for the recipient and signature over it. */
87
- export interface DepositAttestation {
88
- toCiphertext: Hex;
89
- teeSignature: Hex;
90
- }
91
- export interface GetPermissionsByOwnerParams {
92
- includeExpired?: boolean;
93
- }
94
- export interface GetPermissionsBySpenderParams {
95
- includeExpired?: boolean;
96
- }
97
- export type PermissionStatusType = 'unspecified' | 'active' | 'not_yet_active' | 'expired' | 'revoked' | 'generation_revoked';
98
- export interface PermissionInfo {
99
- permissionId: Hex;
100
- owner: Hex;
101
- spender: Hex;
102
- assetId: Hex;
103
- allowance: bigint;
104
- start: bigint;
105
- end: bigint;
106
- period: bigint;
107
- agreementHash: Hex;
108
- spentInCurrentPeriod: bigint;
109
- availableAllowance: bigint;
110
- status: PermissionStatusType;
111
- }
112
- export interface GetPermissionDetailsParams {
113
- permissionId: Hex;
114
- }
115
- export interface PermissionDetails {
116
- permission: PermissionInfo;
117
- found: boolean;
118
- }
119
- export { ShieldError } from '../errors.js';
@@ -1,4 +1 @@
1
- // ---------------------------------------------------------------------------
2
- // Errors (re-export — concrete classes live in `../errors.ts`)
3
- // ---------------------------------------------------------------------------
4
- export { ShieldError } from '../errors.js';
1
+ export {};
@@ -1,21 +1,14 @@
1
- /**
2
- * Constitution: Creates an intent with gas estimation.
3
- *
4
- * The flow is:
5
- * 1. Create a placeholder UserOp from the user's intent op
6
- * 2. Get the gas estimate from the bundler using the placeholder UserOp
7
- * 3. Assemble the final intent with the gas estimate
8
- */
9
1
  import { type Address, type Hex, type PublicClient } from 'viem';
10
2
  import { type UserOperation } from 'viem/account-abstraction';
11
3
  import type { IntentOp, UserGasParams } from '../intent.js';
12
- import type { ShieldConfig, ShieldSigner } from '../types.js';
4
+ import type { ShieldConfig } from '../types.js';
13
5
  declare const ENTRYPOINT_VERSION = "0.7";
14
- /** Placeholder `abi.encode(bytes[] signers, bytes[] sigs)`. Real signer addresses
15
- * required: OZ's `isSigner` check runs before any `ecrecover`, so zero-address
16
- * signers short-circuit the validation loop and under-quote gas. */
6
+ export interface EstimationPlaceholder {
7
+ readonly userOp: UserOperation<typeof ENTRYPOINT_VERSION>;
8
+ readonly signerCount: number;
9
+ }
10
+ export declare function reserveUnmeasuredSignatureGas(verificationGasLimit: bigint, signerCount: number): bigint;
17
11
  export declare function placeholderMultisig(signers: readonly Address[]): Hex;
18
- /** Exported for tests that pin the expected placeholder byte counts. */
19
12
  export declare const PLACEHOLDER_SIZES: {
20
13
  readonly transferCiphertext: number;
21
14
  readonly withdrawalCiphertext: number;
@@ -24,13 +17,5 @@ export declare const PLACEHOLDER_SIZES: {
24
17
  readonly erc20AssetIdentificationArgs: 32;
25
18
  readonly erc20MovementArgs: 32;
26
19
  };
27
- /**
28
- * Build an unsigned UserOp for bundler gas estimation.
29
- *
30
- * Invariant: every wire-shape field matches the size of what the conductor will
31
- * submit. Cheque fields are sized against worst-case plaintexts (over-estimate,
32
- * never under); adapter args are ERC-20-sized — new adapters need entries in
33
- * `PLACEHOLDER_SIZES`.
34
- */
35
- export declare function userOpFromIntentForGasEstimation(intentOp: IntentOp, signer: ShieldSigner, shieldConfig: ShieldConfig, publicClient: PublicClient, gas: UserGasParams): Promise<UserOperation<typeof ENTRYPOINT_VERSION>>;
20
+ export declare function userOpFromIntentForGasEstimation(intentOp: IntentOp, shieldConfig: ShieldConfig, publicClient: PublicClient, gas: UserGasParams, paymaster?: Address): Promise<EstimationPlaceholder>;
36
21
  export {};
@@ -1,110 +1,83 @@
1
- /**
2
- * Constitution: Creates an intent with gas estimation.
3
- *
4
- * The flow is:
5
- * 1. Create a placeholder UserOp from the user's intent op
6
- * 2. Get the gas estimate from the bundler using the placeholder UserOp
7
- * 3. Assemble the final intent with the gas estimate
8
- */
9
- import { bytesToHex, concat, encodeAbiParameters, isAddress, isHex, maxUint64, maxUint256, zeroAddress, zeroHash, } from 'viem';
1
+ import { bytesToHex, concat, encodeAbiParameters, isAddress, maxUint64, maxUint256, zeroAddress, zeroHash, } from 'viem';
10
2
  import { entryPoint07Abi, entryPoint07Address, } from 'viem/account-abstraction';
11
- import { incoShieldAbi, shieldAbiStubAbi, teeSmartAccountAbi, } from '../../contracts/abi.js';
12
- import { getAbiFromUnitFunction } from '../../contracts/utils.js';
3
+ import { incoShieldAbi, teeSmartAccountAbi } from '../../contracts/abi.js';
4
+ import { erc20Asset, NATIVE_ASSET } from '../../contracts/utils.js';
13
5
  import { InvalidContractReturnType } from '../../errors.js';
14
6
  import { IntentKind } from '../../generated/inco/shield/v2/drafting_pb.js';
7
+ import { encodeStruct } from '../abi.js';
15
8
  import { chequeBlobSize } from '../encryption.js';
16
9
  import { assembleUserOp, buildUnsignedUserOp } from './userOp.js';
10
+ const ESTIMATION_STUB_PAYMASTER_DATA = encodeAbiParameters([{ type: 'bytes' }, { type: 'bytes' }], ['0x', '0x']);
17
11
  const ENTRYPOINT_VERSION = '0.7';
18
- /** Pimlico-style dummy ECDSA sig: EIP-2 canonical `s` keeps OZ's
19
- * `ECDSA.tryRecover` on the `ecrecover` path (else verification gas under-quotes). */
12
+ const GENESIS_HANDLE = zeroHash;
13
+ const PER_SIGNATURE_VERIFICATION_GAS = 4000n;
14
+ export function reserveUnmeasuredSignatureGas(verificationGasLimit, signerCount) {
15
+ const unmeasuredSignatures = BigInt(Math.max(0, signerCount - 1));
16
+ return (verificationGasLimit + unmeasuredSignatures * PER_SIGNATURE_VERIFICATION_GAS);
17
+ }
20
18
  const PLACEHOLDER_ECDSA_SIG = concat([
21
- bytesToHex(new Uint8Array(32).fill(0xff)), // r
22
- `0x7${'a'.repeat(63)}`, // s (top nibble 0x7 → s < n/2)
23
- '0x1c', // v
19
+ bytesToHex(new Uint8Array(32).fill(0xff)),
20
+ `0x7${'a'.repeat(63)}`,
21
+ '0x1c',
24
22
  ]);
25
- /** Placeholder `abi.encode(bytes[] signers, bytes[] sigs)`. Real signer addresses
26
- * required: OZ's `isSigner` check runs before any `ecrecover`, so zero-address
27
- * signers short-circuit the validation loop and under-quote gas. */
28
23
  export function placeholderMultisig(signers) {
29
24
  const sigs = signers.map(() => PLACEHOLDER_ECDSA_SIG);
30
25
  return encodeAbiParameters([{ type: 'bytes[]' }, { type: 'bytes[]' }], [signers, sigs]);
31
26
  }
32
- /**
33
- * Given a plaintext encoded as a 0x-prefixed hex string, return the ciphertext size.
34
- */
35
27
  function ptToCtSize(pt) {
36
- // TODO FIXME https://github.com/Inco-fhevm/shield/issues/496
37
- // We should use ciphertext size.
38
- return chequeBlobSize(pt.length / 2 - 1); // Hex to bytes length
28
+ return chequeBlobSize(pt.length / 2 - 1);
39
29
  }
40
- const TRANSFER_PLAINTEXT = encodeAbiParameters([getAbiFromUnitFunction(shieldAbiStubAbi, 'transferPlaintext')], [
41
- {
42
- sender: zeroAddress,
43
- receiver: zeroAddress,
44
- amount: maxUint256,
45
- assetId: zeroHash,
46
- },
47
- ]);
48
- const WITHDRAWAL_PLAINTEXT = encodeAbiParameters([getAbiFromUnitFunction(shieldAbiStubAbi, 'withdrawalPlaintext')], [{ sender: zeroAddress }]);
49
- const SENDER_ESCAPE_PLAINTEXT = encodeAbiParameters([getAbiFromUnitFunction(shieldAbiStubAbi, 'senderEscapePlaintext')], [
50
- {
51
- sender: zeroAddress,
52
- assetId: zeroHash,
53
- draftingHandle: zeroHash,
54
- spendAmount: maxUint256,
55
- balanceSnapshot: maxUint256,
56
- chequeNumber: maxUint64,
57
- oldestPossiblyOutstanding: maxUint64,
58
- },
59
- ]);
60
- const RECEIVER_ESCAPE_PLAINTEXT = encodeAbiParameters([getAbiFromUnitFunction(shieldAbiStubAbi, 'receiverEscapePlaintext')], [
61
- {
62
- receiver: zeroAddress,
63
- assetId: zeroHash,
64
- draftingHandle: zeroHash,
65
- receiveAmount: maxUint256,
66
- balanceSnapshot: maxUint256,
67
- chequeNullifier: zeroHash,
68
- senderChequeNumber: maxUint64,
69
- oldestPossiblyOutstandingSend: maxUint64,
70
- },
71
- ]);
72
- const INDIVIDUAL_REVOCATION_PLAINTEXT = encodeAbiParameters([getAbiFromUnitFunction(shieldAbiStubAbi, 'individualRevocationPlaintext')], [{ owner: zeroAddress, permissionId: zeroHash }]);
73
- const USER_WIDE_REVOCATION_PLAINTEXT = encodeAbiParameters([getAbiFromUnitFunction(shieldAbiStubAbi, 'userWideRevocationPlaintext')], [{ owner: zeroAddress }]);
74
- /** `abi.encode(address)` is one ABI word (32 bytes) regardless of adapter. */
30
+ const TRANSFER_PLAINTEXT = encodeStruct('TransferPlaintext', {
31
+ sender: zeroAddress,
32
+ receiver: zeroAddress,
33
+ amount: maxUint256,
34
+ assetId: zeroHash,
35
+ });
36
+ const WITHDRAWAL_PLAINTEXT = encodeStruct('WithdrawalPlaintext', {
37
+ sender: zeroAddress,
38
+ });
39
+ const SENDER_ESCAPE_PLAINTEXT = encodeStruct('SenderEscapePlaintext', {
40
+ sender: zeroAddress,
41
+ assetId: zeroHash,
42
+ draftingHandle: zeroHash,
43
+ spendAmount: maxUint256,
44
+ balanceSnapshot: maxUint256,
45
+ chequeNumber: maxUint64,
46
+ oldestPossiblyOutstanding: maxUint64,
47
+ });
48
+ const RECEIVER_ESCAPE_PLAINTEXT = encodeStruct('ReceiverEscapePlaintext', {
49
+ receiver: zeroAddress,
50
+ assetId: zeroHash,
51
+ draftingHandle: zeroHash,
52
+ receiveAmount: maxUint256,
53
+ balanceSnapshot: maxUint256,
54
+ chequeNullifier: zeroHash,
55
+ senderChequeNumber: maxUint64,
56
+ oldestPossiblyOutstandingSend: maxUint64,
57
+ });
58
+ const INDIVIDUAL_REVOCATION_PLAINTEXT = encodeStruct('IndividualRevocationPlaintext', { owner: zeroAddress, permissionId: zeroHash });
59
+ const USER_WIDE_REVOCATION_PLAINTEXT = encodeStruct('UserWideRevocationPlaintext', {
60
+ owner: zeroAddress,
61
+ });
75
62
  const ERC20_ASSET_IDENTIFICATION_ARGS_SIZE = 32;
76
- /** `abi.encode(uint256)` is one ABI word (32 bytes). */
77
63
  const ERC20_MOVEMENT_ARGS_SIZE = 32;
78
- /** Non-zero hex of `byteLength` bytes. Zero bytes cost 4 gas vs 16 for non-zero,
79
- * so `zeroHash`-style placeholders under-quote calldata gas by ~4×. */
80
64
  function nonZeroPlaceholder(byteLength) {
81
65
  return bytesToHex(new Uint8Array(byteLength).fill(0xaa));
82
66
  }
83
- /** Build a placeholder {@link SettlementOp} from an `IntentOp`: intent-supplied fields
84
- * (memo, recipient, asset movement, …) are real; enclave-derived fields use
85
- * worst-case-sized placeholders so the estimate never under-quotes. Per-kind
86
- * cases keep TS narrowing intact across the discriminated union.
87
- *
88
- * `draftingHandle` must be a handle known on-chain — `IncoShield.transfer` and
89
- * `processWithdrawalCore` revert with `PastShieldHandleNotFound` against an
90
- * unknown handle. Caller reads the live handle via `IncoShield.getHandle()` and
91
- * threads it in, though it need not be the handle the enclave ultimately drafts
92
- * against. `bytes32` slots use a nonzero placeholder so calldata gas (16/byte)
93
- * matches the eventual real values rather than zero bytes (4/byte). */
94
- function toPlaceholderSettlementOp(intentOp, currentHandle) {
67
+ function toPlaceholderSettlementOp(intentOp) {
95
68
  switch (intentOp.kind) {
96
69
  case IntentKind.TRANSFER:
97
70
  case IntentKind.DELEGATED_TRANSFER:
98
71
  return {
99
72
  kind: intentOp.kind,
100
- op: transferPlaceholder(intentOp.op.memo, currentHandle),
73
+ op: transferPlaceholder(intentOp.op.memo),
101
74
  };
102
75
  case IntentKind.WITHDRAW:
103
76
  case IntentKind.DELEGATED_WITHDRAW:
104
77
  return {
105
78
  kind: intentOp.kind,
106
79
  op: {
107
- core: withdrawalCorePlaceholder(intentOp.op.core, currentHandle),
80
+ core: withdrawalCorePlaceholder(intentOp.op.core),
108
81
  memo: intentOp.op.memo,
109
82
  },
110
83
  };
@@ -113,7 +86,7 @@ function toPlaceholderSettlementOp(intentOp, currentHandle) {
113
86
  return {
114
87
  kind: intentOp.kind,
115
88
  op: {
116
- withdrawal: withdrawalCorePlaceholder(intentOp.op.withdrawal, currentHandle),
89
+ withdrawal: withdrawalCorePlaceholder(intentOp.op.withdrawal),
117
90
  defiAdapter: intentOp.op.defiAdapter,
118
91
  adapterArgs: intentOp.op.adapterArgs,
119
92
  memo: intentOp.op.memo,
@@ -139,9 +112,9 @@ function toPlaceholderSettlementOp(intentOp, currentHandle) {
139
112
  };
140
113
  }
141
114
  }
142
- function transferPlaceholder(memo, currentHandle) {
115
+ function transferPlaceholder(memo) {
143
116
  return {
144
- draftingHandle: currentHandle,
117
+ draftingHandle: GENESIS_HANDLE,
145
118
  draftingTimestamp: maxUint64,
146
119
  nullifier: nonZeroPlaceholder(32),
147
120
  senderEscapeCommitment: nonZeroPlaceholder(32),
@@ -152,20 +125,18 @@ function transferPlaceholder(memo, currentHandle) {
152
125
  memo,
153
126
  };
154
127
  }
155
- function withdrawalCorePlaceholder(core, currentHandle) {
128
+ function withdrawalCorePlaceholder(core) {
156
129
  return {
157
- to: core.to,
130
+ asset: core.asset,
131
+ assetMovements: core.assetMovements,
158
132
  nullifier: nonZeroPlaceholder(32),
159
- amount: maxUint256,
160
- draftingHandle: currentHandle,
133
+ draftingHandle: GENESIS_HANDLE,
161
134
  draftingTimestamp: maxUint64,
162
- assetMovement: core.assetMovement,
163
135
  ciphertext: nonZeroPlaceholder(ptToCtSize(WITHDRAWAL_PLAINTEXT)),
164
136
  senderEscapeCommitment: nonZeroPlaceholder(32),
165
137
  senderEscapeData: nonZeroPlaceholder(ptToCtSize(SENDER_ESCAPE_PLAINTEXT)),
166
138
  };
167
139
  }
168
- /** Exported for tests that pin the expected placeholder byte counts. */
169
140
  export const PLACEHOLDER_SIZES = {
170
141
  transferCiphertext: ptToCtSize(TRANSFER_PLAINTEXT),
171
142
  withdrawalCiphertext: ptToCtSize(WITHDRAWAL_PLAINTEXT),
@@ -174,23 +145,22 @@ export const PLACEHOLDER_SIZES = {
174
145
  erc20AssetIdentificationArgs: ERC20_ASSET_IDENTIFICATION_ARGS_SIZE,
175
146
  erc20MovementArgs: ERC20_MOVEMENT_ARGS_SIZE,
176
147
  };
177
- /**
178
- * Build an unsigned UserOp for bundler gas estimation.
179
- *
180
- * Invariant: every wire-shape field matches the size of what the conductor will
181
- * submit. Cheque fields are sized against worst-case plaintexts (over-estimate,
182
- * never under); adapter args are ERC-20-sized — new adapters need entries in
183
- * `PLACEHOLDER_SIZES`.
184
- */
185
- export async function userOpFromIntentForGasEstimation(intentOp, signer, shieldConfig, publicClient, gas) {
186
- const [nonce, signersForPlaceholder, currentHandle, paymasterGasLimits] = await Promise.all([
187
- getEntryPointNonce(publicClient, signer.address),
188
- getQuorumSignersForPlaceholder(publicClient, signer.address),
189
- getCurrentHandle(publicClient, shieldConfig.shieldAddress),
148
+ export async function userOpFromIntentForGasEstimation(intentOp, shieldConfig, publicClient, gas, paymaster) {
149
+ const teeSmartAccount = await getTeeSmartAccount(publicClient, shieldConfig.shieldAddress);
150
+ const [nonce, signersForPlaceholder, paymasterGasLimits] = await Promise.all([
151
+ getEntryPointNonce(publicClient, teeSmartAccount),
152
+ getQuorumSignersForPlaceholder(publicClient, teeSmartAccount),
190
153
  getPaymasterGasLimits(),
191
154
  ]);
192
155
  const placeholderGas = {
193
- ...gas,
156
+ asset: gas.feeToken === undefined ? NATIVE_ASSET : erc20Asset(gas.feeToken),
157
+ assetMovement: {
158
+ account: zeroAddress,
159
+ assetAdapter: zeroAddress,
160
+ movementArgs: '0x',
161
+ },
162
+ maxFeePerGas: gas.maxFeePerGas,
163
+ maxPriorityFeePerGas: gas.maxPriorityFeePerGas,
194
164
  callGasLimit: 0n,
195
165
  verificationGasLimit: 0n,
196
166
  preVerificationGas: 0n,
@@ -198,16 +168,26 @@ export async function userOpFromIntentForGasEstimation(intentOp, signer, shieldC
198
168
  paymasterPostOpGasLimit: paymasterGasLimits.paymasterPostOpGasLimit,
199
169
  maxAmount: 0n,
200
170
  };
171
+ const paymasterFields = paymaster === undefined
172
+ ? undefined
173
+ : {
174
+ paymaster,
175
+ paymasterVerificationGasLimit: paymasterGasLimits.paymasterVerificationGasLimit,
176
+ paymasterPostOpGasLimit: paymasterGasLimits.paymasterPostOpGasLimit,
177
+ paymasterData: ESTIMATION_STUB_PAYMASTER_DATA,
178
+ };
201
179
  const unsigned = buildUnsignedUserOp({
202
- settlementOp: toPlaceholderSettlementOp(intentOp, currentHandle),
203
- sender: signer.address,
180
+ settlementOp: toPlaceholderSettlementOp(intentOp),
181
+ sender: teeSmartAccount,
204
182
  nonce,
205
183
  shieldAddress: shieldConfig.shieldAddress,
206
184
  gas: placeholderGas,
185
+ paymaster: paymasterFields,
207
186
  });
208
- return assembleUserOp(unsigned, placeholderMultisig(signersForPlaceholder));
209
- /** Read the v0.7 EntryPoint sequential nonce for `sender` under default key 0. */
210
- // TODO: implement of parrallelised (2D nonce behaviour) using some random key dont use 0 @chiranjeev13
187
+ return {
188
+ userOp: assembleUserOp(unsigned, placeholderMultisig(signersForPlaceholder)),
189
+ signerCount: signersForPlaceholder.length,
190
+ };
211
191
  async function getEntryPointNonce(publicClient, sender) {
212
192
  const nonce = await publicClient.readContract({
213
193
  address: entryPoint07Address,
@@ -224,23 +204,13 @@ export async function userOpFromIntentForGasEstimation(intentOp, signer, shieldC
224
204
  }
225
205
  return nonce;
226
206
  }
227
- /** Read the TEE quorum signers from `TeeSmartAccount` and slice to threshold T.
228
- * Real addresses let the bundler's `isSigner` loop traverse fully before
229
- * short-circuiting on `DUMMY_ECDSA_SIG`. */
230
207
  async function getQuorumSignersForPlaceholder(publicClient, account) {
231
- const [signersRaw, thresholdRaw] = await Promise.all([
232
- publicClient.readContract({
233
- address: account,
234
- abi: teeSmartAccountAbi,
235
- functionName: 'getSigners',
236
- args: [0n, maxUint64],
237
- }),
238
- publicClient.readContract({
239
- address: account,
240
- abi: teeSmartAccountAbi,
241
- functionName: 'threshold',
242
- }),
243
- ]);
208
+ const signersRaw = await publicClient.readContract({
209
+ address: account,
210
+ abi: teeSmartAccountAbi,
211
+ functionName: 'getSigners',
212
+ args: [0n, maxUint64],
213
+ });
244
214
  if (!Array.isArray(signersRaw) ||
245
215
  !signersRaw.every((s) => isAddress(s))) {
246
216
  throw new InvalidContractReturnType({
@@ -249,29 +219,22 @@ export async function userOpFromIntentForGasEstimation(intentOp, signer, shieldC
249
219
  expectedType: 'readonly Address[]',
250
220
  });
251
221
  }
252
- if (typeof thresholdRaw !== 'bigint') {
253
- throw new InvalidContractReturnType({
254
- message: `TeeSmartAccount.threshold returned non-bigint: ${typeof thresholdRaw}`,
255
- functionName: 'TeeSmartAccount.threshold',
256
- expectedType: 'bigint',
257
- });
258
- }
259
- return signersRaw.slice(0, Number(thresholdRaw));
222
+ return signersRaw;
260
223
  }
261
- async function getCurrentHandle(publicClient, shieldAddress) {
262
- const handle = await publicClient.readContract({
224
+ async function getTeeSmartAccount(publicClient, shieldAddress) {
225
+ const account = await publicClient.readContract({
263
226
  address: shieldAddress,
264
227
  abi: incoShieldAbi,
265
- functionName: 'getHandle',
228
+ functionName: 'teeSmartAccount',
266
229
  });
267
- if (!isHex(handle) || handle.length !== 66) {
230
+ if (!isAddress(account)) {
268
231
  throw new InvalidContractReturnType({
269
- message: `IncoShield.getHandle returned non-bytes32: ${String(handle)}`,
270
- functionName: 'IncoShield.getHandle',
271
- expectedType: 'Hex (bytes32)',
232
+ message: `IncoShield.teeSmartAccount returned a non-address: ${String(account)}`,
233
+ functionName: 'IncoShield.teeSmartAccount',
234
+ expectedType: 'Address',
272
235
  });
273
236
  }
274
- return handle;
237
+ return account;
275
238
  }
276
239
  async function getPaymasterGasLimits() {
277
240
  const defaultPMVerificationGasLimit = 100000n;
@@ -0,0 +1,15 @@
1
+ import { type Address, type Hex } from 'viem';
2
+ import type { Gas, Withdrawal } from '../shield.eip712.gen.js';
3
+ export type BuildPaymasterDataParams = {
4
+ paymaster: Address;
5
+ gas: Gas;
6
+ withdrawal: Withdrawal;
7
+ teeSignature?: Hex;
8
+ };
9
+ export type PaymasterFields = {
10
+ paymaster: Address;
11
+ paymasterVerificationGasLimit: bigint;
12
+ paymasterPostOpGasLimit: bigint;
13
+ paymasterData: Hex;
14
+ };
15
+ export declare function buildPaymasterData(params: BuildPaymasterDataParams): PaymasterFields;
@@ -0,0 +1,17 @@
1
+ import { encodeAbiParameters } from 'viem';
2
+ import { encodeStruct } from '../abi.js';
3
+ const paymasterDataAbiParams = [{ type: 'bytes' }, { type: 'bytes' }];
4
+ export function buildPaymasterData(params) {
5
+ return {
6
+ paymaster: params.paymaster,
7
+ paymasterVerificationGasLimit: params.gas.paymasterVerificationGasLimit,
8
+ paymasterPostOpGasLimit: params.gas.paymasterPostOpGasLimit,
9
+ paymasterData: encodeAbiParameters(paymasterDataAbiParams, [
10
+ encodeWithdrawal(params.withdrawal),
11
+ params.teeSignature ?? '0x',
12
+ ]),
13
+ };
14
+ }
15
+ function encodeWithdrawal(withdrawal) {
16
+ return encodeStruct('Withdrawal', withdrawal);
17
+ }
@@ -1,54 +1,7 @@
1
- import { type Address, type Hex, type PublicClient } from 'viem';
2
- export type AggregatorRate = {
3
- readonly answer: bigint;
4
- readonly decimals: number;
5
- readonly updatedAt: bigint;
6
- readonly description: string;
7
- };
8
- /**
9
- * Fetch the latest price from a single Chainlink aggregator contract.
10
- *
11
- * The aggregator's `description` (e.g. "ETH / USD") encodes the pair —
12
- * there is no on-chain way to enforce that callers passed the right address.
13
- */
14
- export declare function getAggregatorRate(publicClient: PublicClient, aggregator: Address): Promise<AggregatorRate>;
15
- declare const UnknownAssetFeed_base: new <Fields extends Record<string, unknown>>(fields: Fields) => Error & {
16
- readonly _tag: "UnknownAssetFeed";
17
- } & Readonly<Fields>;
18
- export declare class UnknownAssetFeed extends UnknownAssetFeed_base<{
19
- readonly assetId: Hex;
20
- }> {
21
- }
22
- /**
23
- * Resolve the Chainlink aggregator for a Shield `assetId` (= ERC-20 token
24
- * address padded to bytes32) and return its latest rate.
25
- */
26
- export declare function getAssetRate(publicClient: PublicClient, assetId: Hex): Promise<AggregatorRate>;
27
- /** RAY = 1e27, the fixed-point scale used by the Shield paymaster. */
1
+ import { type Address, type PublicClient } from 'viem';
2
+ export declare function getPaymasterAssetAdapter(publicClient: PublicClient, paymaster: Address): Promise<Address>;
3
+ export declare function assertFeeTokenSupported(publicClient: PublicClient, paymaster: Address, token: Address): Promise<void>;
28
4
  export declare const RAY: bigint;
29
- /**
30
- * Compute the RAY-precision exchange rate (ETH priced in token units)
31
- * that the Shield paymaster consumes. Mirrors the on-chain convention
32
- * from SHIELD_PAYMASTER.md:
33
- *
34
- * tokenAmount = ceil(gasCostWei * exchangeRate / 1e27)
35
- *
36
- * Worked example: if 1 ETH = 2000 USDC and USDC has 6 decimals,
37
- * exchangeRate = 2000 * 1e6 * 1e27 / 1e18 = 2e18
38
- *
39
- * Derivation here uses two Chainlink USD feeds:
40
- * ethInToken (real) = ethUsd / tokenUsd
41
- * rate (RAY) = ethInToken * 10^tokenDec * 1e27 / 1e18
42
- * = ethUsd * 10^tokenDec * 10^tokenFeedDec * RAY
43
- * / (tokenUsd * 10^ethFeedDec * 1e18)
44
- */
45
- export declare function getEthExchangeRateRay(publicClient: PublicClient, assetId: Hex): Promise<bigint>;
46
- /** `ceil(ethWei * exchangeRate / RAY)` — matches the paymaster's rounding. */
5
+ export declare function getEthExchangeRateRay(publicClient: PublicClient, paymaster: Address, token: Address): Promise<bigint>;
47
6
  export declare function applyExchangeRateRayCeil(ethWei: bigint, exchangeRateRay: bigint): bigint;
48
- /**
49
- * Convenience: fetch the RAY rate for `assetId` and apply it to `ethWei`.
50
- * Use `getEthExchangeRateRay` directly when the caller also needs to
51
- * forward the rate to the paymaster signer.
52
- */
53
- export declare function convertEthWeiToTokenUnits(publicClient: PublicClient, assetId: Hex, ethWei: bigint): Promise<bigint>;
54
- export {};
7
+ export declare function convertEthWeiToTokenUnits(publicClient: PublicClient, paymaster: Address, token: Address, ethWei: bigint): Promise<bigint>;