@inco/shield-js 0.0.0-bootstrap.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.
- package/README.md +52 -308
- package/dist/contracts/abi.d.ts +2000 -4513
- package/dist/contracts/abi.js +2400 -5651
- package/dist/contracts/index.d.ts +2 -2
- package/dist/contracts/index.js +2 -2
- package/dist/contracts/utils.d.ts +5 -24
- package/dist/contracts/utils.js +13 -24
- package/dist/errors.d.ts +16 -47
- package/dist/errors.js +6 -53
- package/dist/generated/inco/shield/v2/conductor_pb.d.ts +77 -0
- package/dist/generated/inco/shield/v2/conductor_pb.js +6 -0
- package/dist/generated/inco/shield/v2/deposit_pb.d.ts +12 -103
- package/dist/generated/inco/shield/v2/deposit_pb.js +4 -141
- package/dist/generated/inco/shield/v2/drafting_pb.d.ts +45 -269
- package/dist/generated/inco/shield/v2/drafting_pb.js +8 -361
- package/dist/generated/inco/shield/v2/types_pb.d.ts +47 -166
- package/dist/generated/inco/shield/v2/types_pb.js +12 -261
- package/dist/index.d.ts +1 -7
- package/dist/index.js +1 -14
- package/dist/shield/abi.d.ts +5 -0
- package/dist/shield/abi.js +28 -0
- package/dist/shield/client.d.ts +27 -51
- package/dist/shield/client.js +73 -65
- package/dist/shield/convert.d.ts +2 -14
- package/dist/shield/convert.js +39 -115
- package/dist/shield/encryption.d.ts +0 -67
- package/dist/shield/encryption.js +0 -67
- package/dist/shield/envelope.d.ts +10 -11
- package/dist/shield/envelope.js +15 -14
- package/dist/shield/fee.d.ts +24 -0
- package/dist/shield/fee.js +151 -0
- package/dist/shield/index.d.ts +9 -3
- package/dist/shield/index.js +7 -2
- package/dist/shield/intent.d.ts +20 -933
- package/dist/shield/intent.js +92 -112
- package/dist/shield/permission-status.d.ts +9 -0
- package/dist/shield/permission-status.js +8 -0
- package/dist/shield/rpc.d.ts +50 -49
- package/dist/shield/rpc.js +207 -354
- package/dist/shield/shield.eip712.gen.d.ts +1096 -43
- package/dist/shield/shield.eip712.gen.js +572 -62
- package/dist/shield/types.d.ts +7 -102
- package/dist/shield/types.js +1 -4
- package/dist/shield/userop/gas-estimation.d.ts +8 -23
- package/dist/shield/userop/gas-estimation.js +100 -137
- package/dist/shield/userop/paymaster.d.ts +15 -0
- package/dist/shield/userop/paymaster.js +17 -0
- package/dist/shield/userop/token-exchange.d.ts +5 -52
- package/dist/shield/userop/token-exchange.js +41 -149
- package/dist/shield/userop/types.d.ts +3 -28
- package/dist/shield/userop/types.js +0 -3
- package/dist/shield/userop/userOp.d.ts +0 -19
- package/dist/shield/userop/userOp.js +4 -22
- package/dist/shield/utils/chain.d.ts +1 -0
- package/dist/shield/utils/chain.js +3 -0
- package/dist/shield/validate-withdrawal-split.d.ts +13 -0
- package/dist/shield/validate-withdrawal-split.js +93 -0
- package/package.json +18 -27
- package/dist/generated/inco/shield/v2/conductor_connect.d.ts +0 -137
- package/dist/generated/inco/shield/v2/conductor_connect.js +0 -141
- package/dist/generated/inco/shield/v2/permission_pb.d.ts +0 -443
- package/dist/generated/inco/shield/v2/permission_pb.js +0 -639
- package/dist/generated/inco/shield/v2/query_pb.d.ts +0 -103
- package/dist/generated/inco/shield/v2/query_pb.js +0 -141
- package/dist/shield/userop/index.d.ts +0 -1
- package/dist/shield/userop/index.js +0 -1
- package/dist/uniswap-adapter/config.d.ts +0 -34
- package/dist/uniswap-adapter/config.js +0 -45
- package/dist/uniswap-adapter/find-pools.d.ts +0 -91
- package/dist/uniswap-adapter/find-pools.js +0 -108
- package/dist/uniswap-adapter/index.d.ts +0 -10
- package/dist/uniswap-adapter/index.js +0 -13
- package/dist/uniswap-adapter/swap.d.ts +0 -344
- package/dist/uniswap-adapter/swap.js +0 -309
|
@@ -1,165 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Fetch the latest price from a single Chainlink aggregator contract.
|
|
13
|
-
*
|
|
14
|
-
* The aggregator's `description` (e.g. "ETH / USD") encodes the pair —
|
|
15
|
-
* there is no on-chain way to enforce that callers passed the right address.
|
|
16
|
-
*/
|
|
17
|
-
export async function getAggregatorRate(publicClient, aggregator) {
|
|
18
|
-
const [roundDataRaw, decimalsRaw, descriptionRaw] = await Promise.all([
|
|
19
|
-
publicClient.readContract({
|
|
20
|
-
address: aggregator,
|
|
21
|
-
abi: aggregatorAbi,
|
|
22
|
-
functionName: 'latestRoundData',
|
|
23
|
-
}),
|
|
24
|
-
publicClient.readContract({
|
|
25
|
-
address: aggregator,
|
|
26
|
-
abi: aggregatorAbi,
|
|
27
|
-
functionName: 'decimals',
|
|
28
|
-
}),
|
|
29
|
-
publicClient.readContract({
|
|
30
|
-
address: aggregator,
|
|
31
|
-
abi: aggregatorAbi,
|
|
32
|
-
functionName: 'description',
|
|
33
|
-
}),
|
|
34
|
-
]);
|
|
35
|
-
const { answer, updatedAt } = parseLatestRoundData(roundDataRaw);
|
|
36
|
-
const decimals = parseDecimals(decimalsRaw);
|
|
37
|
-
if (typeof descriptionRaw !== 'string') {
|
|
1
|
+
import { zeroAddress } from 'viem';
|
|
2
|
+
import { shieldPaymasterAbi } from '../../contracts/abi.js';
|
|
3
|
+
import { InvalidContractReturnType, UnsupportedFeeToken, } from '../../errors.js';
|
|
4
|
+
export async function getPaymasterAssetAdapter(publicClient, paymaster) {
|
|
5
|
+
const adapter = await publicClient.readContract({
|
|
6
|
+
address: paymaster,
|
|
7
|
+
abi: shieldPaymasterAbi,
|
|
8
|
+
functionName: 'ALLOWED_ASSET_ADAPTER',
|
|
9
|
+
});
|
|
10
|
+
if (typeof adapter !== 'string') {
|
|
38
11
|
throw new InvalidContractReturnType({
|
|
39
|
-
message: `
|
|
40
|
-
functionName: '
|
|
41
|
-
expectedType: '
|
|
12
|
+
message: `ShieldPaymaster.ALLOWED_ASSET_ADAPTER returned non-address: ${typeof adapter}`,
|
|
13
|
+
functionName: 'ShieldPaymaster.ALLOWED_ASSET_ADAPTER',
|
|
14
|
+
expectedType: 'address',
|
|
42
15
|
});
|
|
43
16
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
17
|
+
return adapter;
|
|
18
|
+
}
|
|
19
|
+
export async function assertFeeTokenSupported(publicClient, paymaster, token) {
|
|
20
|
+
const [wrapper] = await publicClient.readContract({
|
|
21
|
+
address: paymaster,
|
|
22
|
+
abi: shieldPaymasterAbi,
|
|
23
|
+
functionName: 'priceFeeds',
|
|
24
|
+
args: [token],
|
|
25
|
+
});
|
|
26
|
+
if (wrapper === zeroAddress) {
|
|
27
|
+
throw new UnsupportedFeeToken({
|
|
28
|
+
message: `Fee token ${token} has no price feed on ShieldPaymaster ${paymaster}; it cannot be used to pay gas`,
|
|
29
|
+
feeToken: token,
|
|
49
30
|
});
|
|
50
31
|
}
|
|
51
|
-
return { answer, decimals, updatedAt, description: descriptionRaw };
|
|
52
32
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
33
|
+
export const RAY = 10n ** 27n;
|
|
34
|
+
export async function getEthExchangeRateRay(publicClient, paymaster, token) {
|
|
35
|
+
const rate = await publicClient.readContract({
|
|
36
|
+
address: paymaster,
|
|
37
|
+
abi: shieldPaymasterAbi,
|
|
38
|
+
functionName: 'exchangeRateRay',
|
|
39
|
+
args: [token],
|
|
40
|
+
});
|
|
41
|
+
if (typeof rate !== 'bigint') {
|
|
58
42
|
throw new InvalidContractReturnType({
|
|
59
|
-
message:
|
|
60
|
-
functionName: '
|
|
61
|
-
expectedType: '
|
|
43
|
+
message: `ShieldPaymaster.exchangeRateRay returned non-bigint: ${typeof rate}`,
|
|
44
|
+
functionName: 'ShieldPaymaster.exchangeRateRay',
|
|
45
|
+
expectedType: 'uint256',
|
|
62
46
|
});
|
|
63
47
|
}
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Chainlink aggregators for fee-payment assets on Base Sepolia, keyed by
|
|
68
|
-
* the ERC-20 token address (Shield's `assetId` is just the token address
|
|
69
|
-
* left-padded to bytes32). Each feed is **TOKEN / USD** — direction matters
|
|
70
|
-
* when converting gas cost from ETH to token units downstream.
|
|
71
|
-
*/
|
|
72
|
-
const BASE_SEPOLIA_TOKEN_FEEDS = {
|
|
73
|
-
// Circle native USDC on Base Sepolia → USDC/USD aggregator
|
|
74
|
-
'0x036CbD53842c5426634e7929541eC2318f3dCF7e': '0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165',
|
|
75
|
-
};
|
|
76
|
-
export class UnknownAssetFeed extends TaggedError('UnknownAssetFeed') {
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Resolve the Chainlink aggregator for a Shield `assetId` (= ERC-20 token
|
|
80
|
-
* address padded to bytes32) and return its latest rate.
|
|
81
|
-
*/
|
|
82
|
-
export async function getAssetRate(publicClient, assetId) {
|
|
83
|
-
const tokenAddress = tokenAddressFromAssetId(assetId);
|
|
84
|
-
const aggregator = lookupFeed(tokenAddress);
|
|
85
|
-
if (aggregator === undefined) {
|
|
86
|
-
throw new UnknownAssetFeed({ assetId });
|
|
87
|
-
}
|
|
88
|
-
return getAggregatorRate(publicClient, aggregator);
|
|
89
|
-
}
|
|
90
|
-
/** Extract the trailing 20-byte ERC-20 address from a bytes32 assetId. */
|
|
91
|
-
function tokenAddressFromAssetId(assetId) {
|
|
92
|
-
// bytes32 hex = "0x" + 64 chars; last 40 are the address.
|
|
93
|
-
const hex = assetId.startsWith('0x') ? assetId.slice(2) : assetId;
|
|
94
|
-
return `0x${hex.slice(-40)}`;
|
|
95
|
-
}
|
|
96
|
-
/** Case-insensitive lookup against the feed registry. */
|
|
97
|
-
function lookupFeed(token) {
|
|
98
|
-
const target = token.toLowerCase();
|
|
99
|
-
for (const [key, aggregator] of Object.entries(BASE_SEPOLIA_TOKEN_FEEDS)) {
|
|
100
|
-
if (key.toLowerCase() === target)
|
|
101
|
-
return aggregator;
|
|
102
|
-
}
|
|
103
|
-
return undefined;
|
|
104
|
-
}
|
|
105
|
-
/** RAY = 1e27, the fixed-point scale used by the Shield paymaster. */
|
|
106
|
-
export const RAY = 10n ** 27n;
|
|
107
|
-
/**
|
|
108
|
-
* Compute the RAY-precision exchange rate (ETH priced in token units)
|
|
109
|
-
* that the Shield paymaster consumes. Mirrors the on-chain convention
|
|
110
|
-
* from SHIELD_PAYMASTER.md:
|
|
111
|
-
*
|
|
112
|
-
* tokenAmount = ceil(gasCostWei * exchangeRate / 1e27)
|
|
113
|
-
*
|
|
114
|
-
* Worked example: if 1 ETH = 2000 USDC and USDC has 6 decimals,
|
|
115
|
-
* exchangeRate = 2000 * 1e6 * 1e27 / 1e18 = 2e18
|
|
116
|
-
*
|
|
117
|
-
* Derivation here uses two Chainlink USD feeds:
|
|
118
|
-
* ethInToken (real) = ethUsd / tokenUsd
|
|
119
|
-
* rate (RAY) = ethInToken * 10^tokenDec * 1e27 / 1e18
|
|
120
|
-
* = ethUsd * 10^tokenDec * 10^tokenFeedDec * RAY
|
|
121
|
-
* / (tokenUsd * 10^ethFeedDec * 1e18)
|
|
122
|
-
*/
|
|
123
|
-
export async function getEthExchangeRateRay(publicClient, assetId) {
|
|
124
|
-
const tokenAddress = tokenAddressFromAssetId(assetId);
|
|
125
|
-
const [tokenRate, ethRate, tokenDecimalsRaw] = await Promise.all([
|
|
126
|
-
getAssetRate(publicClient, assetId),
|
|
127
|
-
getAggregatorRate(publicClient, BASE_SEPOLIA_ETH_USD_FEED),
|
|
128
|
-
publicClient.readContract({
|
|
129
|
-
address: tokenAddress,
|
|
130
|
-
abi: erc20DecimalsAbi,
|
|
131
|
-
functionName: 'decimals',
|
|
132
|
-
}),
|
|
133
|
-
]);
|
|
134
|
-
const tokenDecimals = parseDecimals(tokenDecimalsRaw);
|
|
135
|
-
const numerator = ethRate.answer *
|
|
136
|
-
10n ** BigInt(tokenDecimals) *
|
|
137
|
-
10n ** BigInt(tokenRate.decimals) *
|
|
138
|
-
RAY;
|
|
139
|
-
const denominator = tokenRate.answer * 10n ** BigInt(ethRate.decimals) * 10n ** 18n;
|
|
140
|
-
return numerator / denominator;
|
|
48
|
+
return rate;
|
|
141
49
|
}
|
|
142
|
-
/** `ceil(ethWei * exchangeRate / RAY)` — matches the paymaster's rounding. */
|
|
143
50
|
export function applyExchangeRateRayCeil(ethWei, exchangeRateRay) {
|
|
144
51
|
const product = ethWei * exchangeRateRay;
|
|
145
52
|
return (product + RAY - 1n) / RAY;
|
|
146
53
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
* Use `getEthExchangeRateRay` directly when the caller also needs to
|
|
150
|
-
* forward the rate to the paymaster signer.
|
|
151
|
-
*/
|
|
152
|
-
export async function convertEthWeiToTokenUnits(publicClient, assetId, ethWei) {
|
|
153
|
-
const rate = await getEthExchangeRateRay(publicClient, assetId);
|
|
54
|
+
export async function convertEthWeiToTokenUnits(publicClient, paymaster, token, ethWei) {
|
|
55
|
+
const rate = await getEthExchangeRateRay(publicClient, paymaster, token);
|
|
154
56
|
return applyExchangeRateRayCeil(ethWei, rate);
|
|
155
57
|
}
|
|
156
|
-
function parseDecimals(value) {
|
|
157
|
-
if (typeof value !== 'number') {
|
|
158
|
-
throw new InvalidContractReturnType({
|
|
159
|
-
message: `Aggregator.decimals returned non-number: ${typeof value}`,
|
|
160
|
-
functionName: 'Aggregator.decimals',
|
|
161
|
-
expectedType: 'uint8',
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
return value;
|
|
165
|
-
}
|
|
@@ -1,22 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type-only surface for the UserOp builder. Runtime code lives in `userOp.ts`.
|
|
3
|
-
*/
|
|
4
1
|
import type { Address } from 'viem';
|
|
5
2
|
import type { UserOperation } from 'viem/account-abstraction';
|
|
6
3
|
import type { IntentKind } from '../../generated/inco/shield/v2/drafting_pb.js';
|
|
7
4
|
import type * as abi from '../shield.eip712.gen.js';
|
|
8
5
|
import type { DefiInteraction, IndividualRevocation, Transfer, UserWideRevocation, Withdrawal } from '../shield.eip712.gen.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* A drafted op — the full on-chain settlement struct the enclave produces from
|
|
12
|
-
* an intent via `draft()`, tagged by {@link IntentKind}. This is the "cheque":
|
|
13
|
-
* an intent is what the user signs *before* `draft()`; the cheque is what comes
|
|
14
|
-
* *after*, and is exactly what `IncoShield.{transfer,withdraw,...}` settles.
|
|
15
|
-
*
|
|
16
|
-
* Delegated kinds settle through the same on-chain function — and thus the same
|
|
17
|
-
* struct — as their non-delegated counterparts; the delegation context rides
|
|
18
|
-
* inside the intent, not in a separate selector.
|
|
19
|
-
*/
|
|
6
|
+
import type { PaymasterFields } from './paymaster.js';
|
|
7
|
+
export declare const ENTRYPOINT_VERSION = "0.7";
|
|
20
8
|
export type SettlementOp = {
|
|
21
9
|
kind: IntentKind.TRANSFER;
|
|
22
10
|
op: Transfer;
|
|
@@ -42,13 +30,6 @@ export type SettlementOp = {
|
|
|
42
30
|
kind: IntentKind.USER_WIDE_REVOCATION;
|
|
43
31
|
op: UserWideRevocation;
|
|
44
32
|
};
|
|
45
|
-
/**
|
|
46
|
-
* Domain Intent: a signed EIP-712 intent struct tagged by its {@link IntentKind}
|
|
47
|
-
* discriminant. This is what the user signs *before* `draft()` — the enclave
|
|
48
|
-
* processes an `Intent` and produces the {@link Cheque}. Each variant is the
|
|
49
|
-
* generated `abi.{*}Intent` struct plus a `kind` tag; delegated kinds wrap
|
|
50
|
-
* their non-delegated inner intent inside the generated `Delegated*Intent` shape.
|
|
51
|
-
*/
|
|
52
33
|
export type Intent = TransferIntent | WithdrawalIntent | DefiInteractionIntent | IndividualRevocationIntent | UserWideRevocationIntent | DelegatedTransferIntent | DelegatedWithdrawalIntent | DelegatedDefiInteractionIntent;
|
|
53
34
|
export type TransferIntent = {
|
|
54
35
|
readonly kind: IntentKind.TRANSFER;
|
|
@@ -74,18 +55,12 @@ export type DelegatedWithdrawalIntent = {
|
|
|
74
55
|
export type DelegatedDefiInteractionIntent = {
|
|
75
56
|
readonly kind: IntentKind.DELEGATED_DEFI_INTERACTION;
|
|
76
57
|
} & abi.DelegatedDefiInteractionIntent;
|
|
77
|
-
/**
|
|
78
|
-
* A v0.7 UserOperation before the quorum signature is attached. Quorum signing
|
|
79
|
-
* happens enclave-side over {@link userOpHash}; the SDK only builds and assembles.
|
|
80
|
-
*/
|
|
81
58
|
export type UnsignedUserOperation = Omit<UserOperation<typeof ENTRYPOINT_VERSION>, 'signature'>;
|
|
82
59
|
export type BuildUnsignedUserOpParams = {
|
|
83
|
-
/** The drafted on-chain struct to settle, tagged by intent kind. */
|
|
84
60
|
settlementOp: SettlementOp;
|
|
85
|
-
/** AA account submitting the op — the TeeSmartAccount (sole TEE_ROLE holder). */
|
|
86
61
|
sender: Address;
|
|
87
62
|
nonce: bigint;
|
|
88
63
|
shieldAddress: Address;
|
|
89
|
-
/** Bundler-estimated limits — everything except the signed fee fields. */
|
|
90
64
|
gas: abi.Gas;
|
|
65
|
+
paymaster?: PaymasterFields;
|
|
91
66
|
};
|
|
@@ -1,26 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Constitution: builds a v0.7 UserOperation from a drafted cheque in three pure
|
|
3
|
-
* stages — (1) assemble the unsigned op, (2) compute the hash the quorum signs,
|
|
4
|
-
* (3) attach a signature. Every stage is deterministic: identical inputs produce
|
|
5
|
-
* identical bytes across all quorum members. That is the property the on-chain
|
|
6
|
-
* TEE smart account relies on when it verifies that every quorum signature is
|
|
7
|
-
* over the same userOpHash. Quorum signing itself is enclave-side and
|
|
8
|
-
* intentionally absent here — the SDK builds and assembles, it does not sign.
|
|
9
|
-
*/
|
|
10
1
|
import { type Address, type Hex } from 'viem';
|
|
11
2
|
import { type UserOperation } from 'viem/account-abstraction';
|
|
12
3
|
import { type BuildUnsignedUserOpParams, ENTRYPOINT_VERSION, type SettlementOp, type UnsignedUserOperation } from './types.js';
|
|
13
4
|
export declare function buildUnsignedUserOp(params: BuildUnsignedUserOpParams): UnsignedUserOperation;
|
|
14
5
|
export declare function userOpHash(unsignedUserOp: UnsignedUserOperation, chainId: number, entryPointAddress?: Address): Hex;
|
|
15
6
|
export declare function assembleUserOp(unsigned: UnsignedUserOperation, signature: Hex): UserOperation<typeof ENTRYPOINT_VERSION>;
|
|
16
|
-
/**
|
|
17
|
-
* Encode the shield settlement call for a drafted {@link OnchainCheque}: the
|
|
18
|
-
* `kind` selects the on-chain function and `op` is its sole argument. This is
|
|
19
|
-
* the inner call wrapped by {@link buildUnsignedUserOp} inside `execute`, and is
|
|
20
|
-
* exported so the enclave can reuse the canonical encoding when it assembles
|
|
21
|
-
* the cheque calldata itself.
|
|
22
|
-
*
|
|
23
|
-
* TODO(#639): the enclave OnChainOp path ABI-encodes only the struct while this
|
|
24
|
-
* emits selector + struct. Harmonize so both produce identical calldata.
|
|
25
|
-
*/
|
|
26
7
|
export declare function encodeSettlementOpCall(settlementOp: SettlementOp): Hex;
|
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Constitution: builds a v0.7 UserOperation from a drafted cheque in three pure
|
|
3
|
-
* stages — (1) assemble the unsigned op, (2) compute the hash the quorum signs,
|
|
4
|
-
* (3) attach a signature. Every stage is deterministic: identical inputs produce
|
|
5
|
-
* identical bytes across all quorum members. That is the property the on-chain
|
|
6
|
-
* TEE smart account relies on when it verifies that every quorum signature is
|
|
7
|
-
* over the same userOpHash. Quorum signing itself is enclave-side and
|
|
8
|
-
* intentionally absent here — the SDK builds and assembles, it does not sign.
|
|
9
|
-
*/
|
|
10
1
|
import { encodeFunctionData } from 'viem';
|
|
11
2
|
import { entryPoint07Address, getUserOperationHash, } from 'viem/account-abstraction';
|
|
12
|
-
import {
|
|
3
|
+
import { incoShieldAbi, teeSmartAccountAbi } from '../../contracts/abi.js';
|
|
13
4
|
import { IntentKind } from '../../generated/inco/shield/v2/drafting_pb.js';
|
|
14
5
|
import { ENTRYPOINT_VERSION, } from './types.js';
|
|
15
6
|
export function buildUnsignedUserOp(params) {
|
|
@@ -21,7 +12,6 @@ export function buildUnsignedUserOp(params) {
|
|
|
21
12
|
functionName: 'execute',
|
|
22
13
|
args: [
|
|
23
14
|
params.shieldAddress,
|
|
24
|
-
// value: shield ops carry no native ETH
|
|
25
15
|
0n,
|
|
26
16
|
encodeSettlementOpCall(params.settlementOp),
|
|
27
17
|
],
|
|
@@ -33,6 +23,8 @@ export function buildUnsignedUserOp(params) {
|
|
|
33
23
|
maxPriorityFeePerGas: params.gas.maxPriorityFeePerGas,
|
|
34
24
|
paymasterVerificationGasLimit: params.gas.paymasterVerificationGasLimit,
|
|
35
25
|
paymasterPostOpGasLimit: params.gas.paymasterPostOpGasLimit,
|
|
26
|
+
paymaster: params.paymaster?.paymaster,
|
|
27
|
+
paymasterData: params.paymaster?.paymasterData,
|
|
36
28
|
};
|
|
37
29
|
}
|
|
38
30
|
export function userOpHash(unsignedUserOp, chainId, entryPointAddress = entryPoint07Address) {
|
|
@@ -46,18 +38,8 @@ export function userOpHash(unsignedUserOp, chainId, entryPointAddress = entryPoi
|
|
|
46
38
|
export function assembleUserOp(unsigned, signature) {
|
|
47
39
|
return { ...unsigned, signature };
|
|
48
40
|
}
|
|
49
|
-
/**
|
|
50
|
-
* Encode the shield settlement call for a drafted {@link OnchainCheque}: the
|
|
51
|
-
* `kind` selects the on-chain function and `op` is its sole argument. This is
|
|
52
|
-
* the inner call wrapped by {@link buildUnsignedUserOp} inside `execute`, and is
|
|
53
|
-
* exported so the enclave can reuse the canonical encoding when it assembles
|
|
54
|
-
* the cheque calldata itself.
|
|
55
|
-
*
|
|
56
|
-
* TODO(#639): the enclave OnChainOp path ABI-encodes only the struct while this
|
|
57
|
-
* emits selector + struct. Harmonize so both produce identical calldata.
|
|
58
|
-
*/
|
|
59
41
|
export function encodeSettlementOpCall(settlementOp) {
|
|
60
|
-
const abi =
|
|
42
|
+
const abi = incoShieldAbi;
|
|
61
43
|
switch (settlementOp.kind) {
|
|
62
44
|
case IntentKind.TRANSFER:
|
|
63
45
|
case IntentKind.DELEGATED_TRANSFER:
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Address } from 'viem';
|
|
2
|
+
import type { UserOperation } from 'viem/account-abstraction';
|
|
3
|
+
import type { WithdrawalIntent } from './shield.eip712.gen.js';
|
|
4
|
+
import type { ENTRYPOINT_VERSION } from './userop/types.js';
|
|
5
|
+
declare const WithdrawalSplitMismatch_base: new <Fields extends Record<string, unknown>>(fields: Fields) => Error & {
|
|
6
|
+
readonly _tag: "WithdrawalSplitMismatch";
|
|
7
|
+
} & Readonly<Fields>;
|
|
8
|
+
export declare class WithdrawalSplitMismatch extends WithdrawalSplitMismatch_base<{
|
|
9
|
+
readonly message: string;
|
|
10
|
+
}> {
|
|
11
|
+
}
|
|
12
|
+
export declare function assertWithdrawalSplitMatchesIntent(userOp: UserOperation<typeof ENTRYPOINT_VERSION>, signed: WithdrawalIntent, shieldAddress: Address): void;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { decodeFunctionData, isAddressEqual } from 'viem';
|
|
2
|
+
import { incoShieldAbi, teeSmartAccountAbi } from '../contracts/abi.js';
|
|
3
|
+
import { TaggedError } from '../errors.js';
|
|
4
|
+
export class WithdrawalSplitMismatch extends TaggedError('WithdrawalSplitMismatch') {
|
|
5
|
+
}
|
|
6
|
+
export function assertWithdrawalSplitMatchesIntent(userOp, signed, shieldAddress) {
|
|
7
|
+
const outer = tryDecode(() => decodeFunctionData({ abi: teeSmartAccountAbi, data: userOp.callData }), 'settlement callData');
|
|
8
|
+
if (outer.functionName !== 'execute') {
|
|
9
|
+
throw mismatch(`settlement call is '${outer.functionName}', expected 'execute'`);
|
|
10
|
+
}
|
|
11
|
+
const [target, value, inner] = outer.args;
|
|
12
|
+
if (!isAddressEqual(target, shieldAddress)) {
|
|
13
|
+
throw mismatch(`execute target ${target} is not the shield ${shieldAddress}`);
|
|
14
|
+
}
|
|
15
|
+
if (value !== 0n) {
|
|
16
|
+
throw mismatch(`execute carries non-zero value ${value}`);
|
|
17
|
+
}
|
|
18
|
+
const call = tryDecode(() => decodeFunctionData({ abi: incoShieldAbi, data: inner }), 'inner withdraw call');
|
|
19
|
+
if (call.functionName !== 'withdraw') {
|
|
20
|
+
throw mismatch(`inner call is '${call.functionName}', expected 'withdraw'`);
|
|
21
|
+
}
|
|
22
|
+
const core = call.args[0].core;
|
|
23
|
+
const signedAsset = signed.op.core.asset;
|
|
24
|
+
if (core.asset.standard !== signedAsset.standard ||
|
|
25
|
+
!isAddressEqual(core.asset.token, signedAsset.token) ||
|
|
26
|
+
core.asset.tokenId !== signedAsset.tokenId) {
|
|
27
|
+
throw mismatch('withdrawal asset does not match the signed asset');
|
|
28
|
+
}
|
|
29
|
+
const signedMovements = signed.op.core.assetMovements;
|
|
30
|
+
const movements = core.assetMovements;
|
|
31
|
+
if (movements.length !== signedMovements.length) {
|
|
32
|
+
throw mismatch(`expected the ${signedMovements.length} signed movement(s), got ${movements.length}`);
|
|
33
|
+
}
|
|
34
|
+
signedMovements.forEach((signedMovement, i) => {
|
|
35
|
+
const movement = movements[i];
|
|
36
|
+
if (!isAddressEqual(movement.account, signedMovement.account)) {
|
|
37
|
+
throw mismatch(`movement ${i} account was changed`);
|
|
38
|
+
}
|
|
39
|
+
if (!isAddressEqual(movement.assetAdapter, signedMovement.assetAdapter)) {
|
|
40
|
+
throw mismatch(`movement ${i} asset adapter was changed`);
|
|
41
|
+
}
|
|
42
|
+
if (movement.movementArgs.toLowerCase() !==
|
|
43
|
+
signedMovement.movementArgs.toLowerCase()) {
|
|
44
|
+
throw mismatch(`movement ${i} args were changed`);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
assertGasWithinSigned(userOp, signed.gas);
|
|
48
|
+
}
|
|
49
|
+
function assertGasWithinSigned(userOp, gas) {
|
|
50
|
+
const limits = [
|
|
51
|
+
['callGasLimit', userOp.callGasLimit, gas.callGasLimit],
|
|
52
|
+
[
|
|
53
|
+
'verificationGasLimit',
|
|
54
|
+
userOp.verificationGasLimit,
|
|
55
|
+
gas.verificationGasLimit,
|
|
56
|
+
],
|
|
57
|
+
['preVerificationGas', userOp.preVerificationGas, gas.preVerificationGas],
|
|
58
|
+
['maxFeePerGas', userOp.maxFeePerGas, gas.maxFeePerGas],
|
|
59
|
+
[
|
|
60
|
+
'maxPriorityFeePerGas',
|
|
61
|
+
userOp.maxPriorityFeePerGas,
|
|
62
|
+
gas.maxPriorityFeePerGas,
|
|
63
|
+
],
|
|
64
|
+
[
|
|
65
|
+
'paymasterVerificationGasLimit',
|
|
66
|
+
userOp.paymasterVerificationGasLimit ?? 0n,
|
|
67
|
+
gas.paymasterVerificationGasLimit,
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
'paymasterPostOpGasLimit',
|
|
71
|
+
userOp.paymasterPostOpGasLimit ?? 0n,
|
|
72
|
+
gas.paymasterPostOpGasLimit,
|
|
73
|
+
],
|
|
74
|
+
];
|
|
75
|
+
for (const [name, actual, limit] of limits) {
|
|
76
|
+
if (actual > limit) {
|
|
77
|
+
throw mismatch(`${name} ${actual} exceeds the signed limit ${limit}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function tryDecode(decode, what) {
|
|
82
|
+
try {
|
|
83
|
+
return decode();
|
|
84
|
+
}
|
|
85
|
+
catch (cause) {
|
|
86
|
+
throw mismatch(`could not decode ${what}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function mismatch(detail) {
|
|
90
|
+
return new WithdrawalSplitMismatch({
|
|
91
|
+
message: `enclave withdrawal UserOp does not match the signed intent: ${detail}`,
|
|
92
|
+
});
|
|
93
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inco/shield-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "TypeScript SDK for interacting with Inco Shield.",
|
|
4
5
|
"license": "Apache-2.0",
|
|
5
|
-
"repository":
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Inco-fhevm/shield.git",
|
|
9
|
+
"directory": "sdk"
|
|
10
|
+
},
|
|
6
11
|
"type": "module",
|
|
7
12
|
"files": [
|
|
8
13
|
"dist"
|
|
@@ -19,48 +24,34 @@
|
|
|
19
24
|
".": {
|
|
20
25
|
"types": "./dist/index.d.ts",
|
|
21
26
|
"import": "./dist/index.js"
|
|
22
|
-
},
|
|
23
|
-
"./uniswap": {
|
|
24
|
-
"types": "./dist/uniswap-adapter/index.d.ts",
|
|
25
|
-
"import": "./dist/uniswap-adapter/index.js"
|
|
26
27
|
}
|
|
27
28
|
},
|
|
28
29
|
"scripts": {
|
|
29
30
|
"generate:proto": "rm -rf src/generated && buf generate",
|
|
30
31
|
"generate:abi": "cd ../contracts && bun run generate && cd ../sdk && bun run scripts/extract-abi.ts",
|
|
31
32
|
"generate": "bun run generate:proto && bun run generate:abi",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
33
|
+
"clean": "rm -rf dist",
|
|
34
|
+
"compile": "bun run clean && tsc -p tsconfig.dist.json",
|
|
35
|
+
"build": "bun run generate && bun run compile",
|
|
36
|
+
"typecheck": "tsc --noEmit -p tsconfig.dist.json",
|
|
37
|
+
"test": "bun run generate && bun run test:unit",
|
|
38
|
+
"test:unit": "vitest run",
|
|
35
39
|
"lint": "biome check --write . && eslint .",
|
|
36
40
|
"test:lint": "biome check . && eslint .",
|
|
37
|
-
"
|
|
41
|
+
"publish:npm": "npm publish"
|
|
38
42
|
},
|
|
39
43
|
"dependencies": {
|
|
40
|
-
"@bufbuild/protobuf": "^
|
|
41
|
-
"@connectrpc/connect": "^1.
|
|
44
|
+
"@bufbuild/protobuf": "^2.12.0",
|
|
45
|
+
"@connectrpc/connect": "^2.1.2",
|
|
42
46
|
"@hpke/chacha20poly1305": "^1.8.0",
|
|
43
47
|
"@hpke/core": "^1.9.0",
|
|
44
48
|
"@hpke/hybridkem-x-wing": "^0.7.0",
|
|
45
49
|
"@noble/ciphers": "^1.3.0",
|
|
46
50
|
"viem": "^2.30.0"
|
|
47
51
|
},
|
|
48
|
-
"peerDependencies": {
|
|
49
|
-
"@uniswap/v4-sdk": "^1.22.0",
|
|
50
|
-
"ethers": "^5"
|
|
51
|
-
},
|
|
52
|
-
"peerDependenciesMeta": {
|
|
53
|
-
"@uniswap/v4-sdk": {
|
|
54
|
-
"optional": true
|
|
55
|
-
},
|
|
56
|
-
"ethers": {
|
|
57
|
-
"optional": true
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
52
|
"devDependencies": {
|
|
61
|
-
"@bufbuild/buf": "^1.
|
|
62
|
-
"@bufbuild/protoc-gen-es": "^
|
|
63
|
-
"@connectrpc/protoc-gen-connect-es": "^1.7.0",
|
|
53
|
+
"@bufbuild/buf": "^1.70.0",
|
|
54
|
+
"@bufbuild/protoc-gen-es": "^2.12.0",
|
|
64
55
|
"@types/node": "^22.8.5",
|
|
65
56
|
"@uniswap/v4-sdk": "^1.22.0",
|
|
66
57
|
"bun-types": "^1.3.8",
|