@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.
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 +8 -23
  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 +3 -28
  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,39 +1,37 @@
1
- import { decodeAbiParameters, encodeAbiParameters, keccak256, toHex, } from 'viem';
1
+ import { create, fromBinary, toBinary } from '@bufbuild/protobuf';
2
+ import { encodeAbiParameters, keccak256, zeroHash, } from 'viem';
2
3
  import { bytesToHex, hexToBytes } from '../binary.js';
3
- import { shieldAbiStubAbi as incoShieldAbi } from '../contracts/abi.js';
4
- import { computeErc20AssetId, getAbiFromUnitFunction, } from '../contracts/utils.js';
5
- import { CovalidatorError, ShieldError } from '../errors.js';
6
- import { RequestDepositAttestationRequest, RequestDepositAttestationResponse, } from '../generated/inco/shield/v2/deposit_pb.js';
7
- import { DraftRequest, DraftResponse, IntentKind, } from '../generated/inco/shield/v2/drafting_pb.js';
8
- import { GetOwnerGenerationRequest, GetOwnerGenerationResponse, GetPermissionDetailsRequest, GetPermissionDetailsResponse, GetPermissionsByOwnerRequest, GetPermissionsBySpenderRequest, GetPermissionsResponse, RequestPermissionAttestationRequest, RequestPermissionAttestationResponse, } from '../generated/inco/shield/v2/permission_pb.js';
9
- import { GetBalanceRequest, GetBalanceResponse, } from '../generated/inco/shield/v2/query_pb.js';
10
- import { Address as ProtoAddress, AssetId as ProtoAssetId, } from '../generated/inco/shield/v2/types_pb.js';
11
- import { parseBalance, parseOwnerGeneration, parsePermissionDetails, parsePermissions, parseUserOp07, } from './convert.js';
12
- import { decryptResponse, generateRandomSalt, generateResponseKey, makeEnvelope, } from './envelope.js';
13
- import { encodeIntent } from './intent.js';
14
- import { balanceQueryDigest, delegatedTransferIntentDigest, delegatedWithdrawalIntentDigest, depositAttestationRequestDigest, getOwnerGenerationDigest, getPermissionDetailsDigest, getPermissionsByOwnerDigest, getPermissionsBySpenderDigest, individualRevocationIntentDigest, permissionAttestationRequestDigest, transferIntentDigest, userWideRevocationIntentDigest, withdrawalIntentDigest, } from './shield.eip712.gen.js';
15
- /**
16
- * `permissionId = keccak256(abi.encode(...))` over the 9 identity fields. Must
17
- * match the umbo helper bit-for-bit; cross-checked in `sdk/test/permission-id.test.ts`.
18
- *
19
- * Off-chain only — no on-chain code recomputes this.
20
- */
4
+ import { InvalidWithdrawalIntentOp, ShieldError } from '../errors.js';
5
+ import { GetLatestHandleRequestSchema, } from '../generated/inco/shield/v2/conductor_pb.js';
6
+ import { DepositAttestationRequestSchema } from '../generated/inco/shield/v2/deposit_pb.js';
7
+ import { DraftRequestSchema, DraftResponseSchema, IntentKind, } from '../generated/inco/shield/v2/drafting_pb.js';
8
+ import { AddressSchema, } from '../generated/inco/shield/v2/types_pb.js';
9
+ import { decodeStruct, encodeStruct } from './abi.js';
10
+ import { parseUserOp07 } from './convert.js';
11
+ import { openAead } from './encryption.js';
12
+ import { generateRandomSalt, generateResponseKey, makeEnvelope, openSignedResponse, } from './envelope.js';
13
+ import { withProtocolFeeLeg } from './fee.js';
14
+ import { createDelegatedTransferIntent, createDelegatedWithdrawIntent, createIndividualRevocationIntent, createTransferIntent, createUserWideRevocationIntent, createWithdrawIntent, encodeIntent, } from './intent.js';
15
+ import { EIP712_TYPES, } from './shield.eip712.gen.js';
16
+ import { assertWithdrawalSplitMatchesIntent } from './validate-withdrawal-split.js';
21
17
  export function computePermissionId(input) {
22
18
  return keccak256(encodeAbiParameters([
23
- { type: 'address' }, // owner
24
- { type: 'address' }, // spender
25
- { type: 'bytes32' }, // assetId
26
- { type: 'uint256' }, // allowance
27
- { type: 'uint256' }, // start
28
- { type: 'uint256' }, // end
29
- { type: 'uint256' }, // period
30
- { type: 'bytes32' }, // agreementHash
31
- { type: 'bytes32' }, // salt
19
+ { type: 'address' },
20
+ { type: 'address' },
21
+ { type: 'bytes32' },
22
+ { type: 'uint256' },
23
+ { type: 'address[]' },
24
+ { type: 'uint256' },
25
+ { type: 'uint256' },
26
+ { type: 'uint256' },
27
+ { type: 'bytes32' },
28
+ { type: 'bytes32' },
32
29
  ], [
33
30
  input.owner,
34
31
  input.spender,
35
32
  input.assetId,
36
33
  input.allowance,
34
+ input.to,
37
35
  input.start,
38
36
  input.end,
39
37
  input.period,
@@ -41,383 +39,238 @@ export function computePermissionId(input) {
41
39
  input.salt,
42
40
  ]));
43
41
  }
44
- // ---------------------------------------------------------------------------
45
- // Issue (transfer)
46
- // ---------------------------------------------------------------------------
47
- // TODO: replace with real gas estimation
48
- // https://github.com/Inco-fhevm/shield/issues/669
49
- const TODO_GAS = {
50
- assetId: '0x0000000000000000000000000000000000000000000000000000000000000000',
51
- maxAmount: 0n,
52
- callGasLimit: 2000000n,
53
- verificationGasLimit: 1000000n,
54
- preVerificationGas: 100000n,
55
- paymasterVerificationGasLimit: 0n,
56
- paymasterPostOpGasLimit: 0n,
57
- maxFeePerGas: 30000000000n, // 30 gwei
58
- maxPriorityFeePerGas: 1000000000n, // 1 gwei
59
- };
60
- export async function issueTransfer(ctx, intentOp) {
61
- const intent = {
62
- kind: IntentKind.TRANSFER,
63
- op: intentOp,
64
- signer: ctx.signer.address,
65
- gas: TODO_GAS,
66
- salt: generateRandomSalt(),
67
- };
68
- const digest = transferIntentDigest(intent, ctx.domainSeparator);
69
- const signature = await ctx.signer.sign({ hash: digest });
70
- const responseKey = generateResponseKey();
71
- const draftRequest = new DraftRequest({
72
- intent: new Uint8Array(hexToBytes(encodeIntent(IntentKind.TRANSFER, intent))),
73
- intentKind: IntentKind.TRANSFER,
74
- eip712Signature: new Uint8Array(hexToBytes(signature)),
75
- responseKey,
42
+ function signTypedIntent(ctx, primaryType, message) {
43
+ return ctx.signer.signTypedData({
44
+ domain: ctx.domain,
45
+ types: EIP712_TYPES,
46
+ primaryType,
47
+ message,
76
48
  });
77
- const envelope = await makeEnvelope(ctx.teePublicKey, draftRequest);
78
- const encResponse = await ctx.rpc.draft(envelope);
79
- const response = await decryptResponse(encResponse, responseKey, DraftResponse);
80
- return { userOp: parseUserOp07(response.userOp) };
81
49
  }
82
- // ---------------------------------------------------------------------------
83
- // IssueWithdraw
84
- // ---------------------------------------------------------------------------
85
- export async function issueWithdraw(ctx, intentOp) {
86
- const intent = {
87
- kind: IntentKind.WITHDRAW,
88
- op: intentOp,
89
- signer: ctx.signer.address,
90
- gas: TODO_GAS,
91
- salt: generateRandomSalt(),
92
- };
93
- const digest = withdrawalIntentDigest(intent, ctx.domainSeparator);
94
- const signature = await ctx.signer.sign({ hash: digest });
50
+ async function draft(ctx, intentKind, intent, signature) {
95
51
  const responseKey = generateResponseKey();
96
- const draftRequest = new DraftRequest({
97
- intent: new Uint8Array(hexToBytes(encodeIntent(IntentKind.WITHDRAW, intent))),
98
- intentKind: IntentKind.WITHDRAW,
99
- eip712Signature: new Uint8Array(hexToBytes(signature)),
100
- responseKey,
101
- });
102
- const envelope = await makeEnvelope(ctx.teePublicKey, draftRequest);
103
- const encResponse = await ctx.rpc.draft(envelope);
104
- const response = await decryptResponse(encResponse, responseKey, DraftResponse);
105
- return { userOp: parseUserOp07(response.userOp) };
52
+ const body = toBinary(DraftRequestSchema, create(DraftRequestSchema, {
53
+ intent: hexToBytes(encodeIntent(intentKind, intent)),
54
+ intentKind,
55
+ }));
56
+ const envelope = await makeEnvelope(ctx.teePublicKey, { eip712Signature: hexToBytes(signature), responseKey }, body);
57
+ const resp = await ctx.rpc.draft(envelope);
58
+ const plaintext = await openAead(responseKey, resp.nonce, resp.ciphertext);
59
+ return parseUserOp07(fromBinary(DraftResponseSchema, plaintext).userOp);
60
+ }
61
+ const DEFAULT_USER_GAS = {
62
+ maxFeePerGas: 30000000000n,
63
+ maxPriorityFeePerGas: 1000000000n,
64
+ };
65
+ function feeContextOf(config) {
66
+ return {
67
+ adapter: config.feeAccrualAdapter,
68
+ treasury: config.protocolFeeTreasury,
69
+ };
70
+ }
71
+ export async function issueTransfer(ctx, intentOp, gas = DEFAULT_USER_GAS) {
72
+ const intent = await createTransferIntent(intentOp, generateRandomSalt(), ctx.signer, ctx.shieldConfig, ctx.publicClient, gas, ctx.paymasterContext);
73
+ const signature = await signTypedIntent(ctx, 'TransferIntent', intent);
74
+ return { userOp: await draft(ctx, IntentKind.TRANSFER, intent, signature) };
75
+ }
76
+ export async function issueWithdraw(ctx, intentOp, gas = DEFAULT_USER_GAS) {
77
+ const intent = await createWithdrawIntent(withProtocolFeeLeg(intentOp, feeContextOf(ctx.shieldConfig)), generateRandomSalt(), ctx.signer, ctx.shieldConfig, ctx.publicClient, gas, ctx.paymasterContext);
78
+ const signature = await signTypedIntent(ctx, 'WithdrawalIntent', intent);
79
+ const userOp = await draft(ctx, IntentKind.WITHDRAW, intent, signature);
80
+ assertWithdrawalSplitMatchesIntent(userOp, intent, ctx.shieldConfig.shieldAddress);
81
+ return { userOp };
106
82
  }
107
- // ---------------------------------------------------------------------------
108
- // RequestPermissionAttestation
109
- // ---------------------------------------------------------------------------
110
- /**
111
- * Owner-side issuance flow: sign the intent, hand it to the TEE, receive a
112
- * long-lived `PermissionAttestation` for the spender. The spender presents
113
- * the attestation on every subsequent draft.
114
- *
115
- * Doc-mandated client cross-check: recompute `permissionId` locally from
116
- * intent terms and verify the TEE's returned attestation matches. A mismatch
117
- * means the TEE substituted a different identity — fail closed.
118
- */
119
83
  export async function requestPermissionAttestation(ctx, req) {
120
84
  const { permission } = req;
121
85
  if (ctx.signer.address.toLowerCase() !== permission.owner.toLowerCase()) {
122
86
  throw new ShieldError({
123
- message: 'requestPermissionAttestation: signer.address must match intent.owner',
87
+ message: 'requestPermissionAttestation: signer.address must match permission.owner',
124
88
  });
125
89
  }
126
90
  const expectedPid = computePermissionId(permission);
127
- const digest = permissionAttestationRequestDigest(req, ctx.domainSeparator);
128
- const ownerSignature = await ctx.signer.sign({ hash: digest });
91
+ const ownerSignature = await signTypedIntent(ctx, 'PermissionRequest', req);
129
92
  const responseKey = generateResponseKey();
130
- const permissionAttestationRequestBytes = encodeAbiParameters([getAbiFromUnitFunction(incoShieldAbi, 'permissionAttestationRequest')], [req]);
131
- const request = new RequestPermissionAttestationRequest({
132
- permissionAttestationRequestBytes: new Uint8Array(hexToBytes(permissionAttestationRequestBytes)),
133
- eip712Signature: hexToBytes(ownerSignature),
134
- responseKey,
135
- });
136
- const envelope = await makeEnvelope(ctx.teePublicKey, request);
137
- const encResponse = await ctx.rpc.requestPermissionAttestation(envelope);
138
- const response = await decryptResponse(encResponse, responseKey, RequestPermissionAttestationResponse);
139
- const [attestation] = decodeAbiParameters([getAbiFromUnitFunction(incoShieldAbi, 'permissionAttestation')], toHex(response.permissionAttestationBytes));
140
- if (attestation.payload.permissionId !== expectedPid) {
93
+ const envelope = await makeEnvelope(ctx.teePublicKey, { eip712Signature: hexToBytes(ownerSignature), responseKey }, hexToBytes(encodeStruct('PermissionRequest', req)));
94
+ const { body, teeSignature } = await openSignedResponse(await ctx.rpc.permit(envelope), responseKey);
95
+ const payload = decodeStruct('PermissionAttestationPayload', bytesToHex(body));
96
+ if (payload.permissionId !== expectedPid) {
141
97
  throw new ShieldError({
142
- message: `requestPermissionAttestation: TEE returned mismatched permissionId (expected ${expectedPid}, got ${attestation.payload.permissionId})`,
98
+ message: `requestPermissionAttestation: TEE returned mismatched permissionId (expected ${expectedPid}, got ${payload.permissionId})`,
143
99
  });
144
100
  }
145
- return attestation;
101
+ return { payload, signature: bytesToHex(teeSignature) };
146
102
  }
147
- // ---------------------------------------------------------------------------
148
- // IssueDelegatedTransfer
149
- // ---------------------------------------------------------------------------
150
- /**
151
- * Spender-side delegated transfer draft: sign the transfer intent, present the
152
- * owner's `PermissionAttestation` to the TEE, receive a spending note. The TEE
153
- * verifies the attestation signature and runs all 7 validation steps before
154
- * drafting the spend.
155
- */
156
- export async function issueDelegatedTransfer(ctx, intentOp, attestation) {
157
- const intent = {
158
- kind: IntentKind.DELEGATED_TRANSFER,
159
- intent: {
160
- op: intentOp,
161
- signer: ctx.signer.address,
162
- gas: TODO_GAS,
163
- salt: generateRandomSalt(),
164
- },
165
- spender: ctx.signer.address,
166
- attestation,
103
+ export async function issueDelegatedTransfer(ctx, intentOp, permissionId, gas = DEFAULT_USER_GAS) {
104
+ const intent = await createDelegatedTransferIntent(intentOp, generateRandomSalt(), ctx.signer.address, permissionId, ctx.signer, ctx.shieldConfig, ctx.publicClient, gas, ctx.paymasterContext);
105
+ const spenderSignature = await signTypedIntent(ctx, 'DelegatedTransferIntent', intent);
106
+ return {
107
+ userOp: await draft(ctx, IntentKind.DELEGATED_TRANSFER, intent, spenderSignature),
167
108
  };
168
- const digest = delegatedTransferIntentDigest(intent, ctx.domainSeparator);
169
- const spenderSignature = await ctx.signer.sign({ hash: digest });
170
- const responseKey = generateResponseKey();
171
- const draftRequest = new DraftRequest({
172
- intent: new Uint8Array(hexToBytes(encodeIntent(IntentKind.DELEGATED_TRANSFER, intent))),
173
- intentKind: IntentKind.DELEGATED_TRANSFER,
174
- eip712Signature: new Uint8Array(hexToBytes(spenderSignature)),
175
- responseKey,
176
- });
177
- const envelope = await makeEnvelope(ctx.teePublicKey, draftRequest);
178
- const encResponse = await ctx.rpc.draft(envelope);
179
- const response = await decryptResponse(encResponse, responseKey, DraftResponse);
180
- return { userOp: parseUserOp07(response.userOp) };
181
109
  }
182
- // ---------------------------------------------------------------------------
183
- // IssueDelegatedWithdraw
184
- // ---------------------------------------------------------------------------
185
- /**
186
- * The resulting cheque settles byte-identically to a direct withdraw —
187
- * observers cannot distinguish delegated from direct.
188
- */
189
- export async function issueDelegatedWithdraw(ctx, intentOp, attestation) {
190
- const intent = {
191
- kind: IntentKind.DELEGATED_WITHDRAW,
192
- intent: {
193
- op: intentOp,
194
- signer: ctx.signer.address,
195
- gas: TODO_GAS,
196
- salt: generateRandomSalt(),
197
- },
198
- spender: ctx.signer.address,
199
- attestation,
110
+ export async function issueDelegatedWithdraw(ctx, intentOp, permissionId, gas = DEFAULT_USER_GAS) {
111
+ if (intentOp.core.assetMovements.length !== 1) {
112
+ throw new InvalidWithdrawalIntentOp({
113
+ message: `delegated withdrawal must carry exactly one recipient movement, got ${intentOp.core.assetMovements.length}`,
114
+ });
115
+ }
116
+ const intent = await createDelegatedWithdrawIntent(withProtocolFeeLeg(intentOp, feeContextOf(ctx.shieldConfig)), generateRandomSalt(), ctx.signer.address, permissionId, ctx.signer, ctx.shieldConfig, ctx.publicClient, gas, ctx.paymasterContext);
117
+ const spenderSignature = await signTypedIntent(ctx, 'DelegatedWithdrawalIntent', intent);
118
+ const userOp = await draft(ctx, IntentKind.DELEGATED_WITHDRAW, intent, spenderSignature);
119
+ assertWithdrawalSplitMatchesIntent(userOp, intent.intent, ctx.shieldConfig.shieldAddress);
120
+ return { userOp };
121
+ }
122
+ export async function revokeOne(ctx, params, gas = DEFAULT_USER_GAS) {
123
+ const intent = await createIndividualRevocationIntent({ permissionId: params.permissionId }, generateRandomSalt(), ctx.signer, ctx.shieldConfig, ctx.publicClient, gas, ctx.paymasterContext);
124
+ const signature = await signTypedIntent(ctx, 'IndividualRevocationIntent', intent);
125
+ return {
126
+ userOp: await draft(ctx, IntentKind.INDIVIDUAL_REVOCATION, intent, signature),
200
127
  };
201
- const digest = delegatedWithdrawalIntentDigest(intent, ctx.domainSeparator);
202
- const spenderSignature = await ctx.signer.sign({ hash: digest });
203
- const responseKey = generateResponseKey();
204
- const draftRequest = new DraftRequest({
205
- intent: new Uint8Array(hexToBytes(encodeIntent(IntentKind.DELEGATED_WITHDRAW, intent))),
206
- intentKind: IntentKind.DELEGATED_WITHDRAW,
207
- eip712Signature: new Uint8Array(hexToBytes(spenderSignature)),
208
- responseKey,
209
- });
210
- const envelope = await makeEnvelope(ctx.teePublicKey, draftRequest);
211
- const encResponse = await ctx.rpc.draft(envelope);
212
- const response = await decryptResponse(encResponse, responseKey, DraftResponse);
213
- return { userOp: parseUserOp07(response.userOp) };
214
128
  }
215
- export async function revokeOne(ctx, params) {
216
- const intent = {
217
- kind: IntentKind.INDIVIDUAL_REVOCATION,
218
- op: { permissionId: params.permissionId },
219
- signer: ctx.signer.address,
220
- gas: TODO_GAS,
221
- salt: generateRandomSalt(),
129
+ export async function revokeAll(ctx, gas = DEFAULT_USER_GAS) {
130
+ const intent = await createUserWideRevocationIntent({ empty: '0x' }, generateRandomSalt(), ctx.signer, ctx.shieldConfig, ctx.publicClient, gas, ctx.paymasterContext);
131
+ const signature = await signTypedIntent(ctx, 'UserWideRevocationIntent', intent);
132
+ return {
133
+ userOp: await draft(ctx, IntentKind.USER_WIDE_REVOCATION, intent, signature),
222
134
  };
223
- const digest = individualRevocationIntentDigest(intent, ctx.domainSeparator);
224
- const signature = await ctx.signer.sign({ hash: digest });
135
+ }
136
+ const ZERO_BYTES32 = zeroHash;
137
+ const FIRST_PAGE_CURSOR = {
138
+ anchorHandle: ZERO_BYTES32,
139
+ afterTimestamp: 0n,
140
+ afterOpIndex: 0n,
141
+ };
142
+ export async function createViewingSession(ctx, params) {
225
143
  const responseKey = generateResponseKey();
226
- const draftRequest = new DraftRequest({
227
- intent: new Uint8Array(hexToBytes(encodeIntent(IntentKind.INDIVIDUAL_REVOCATION, intent))),
228
- intentKind: IntentKind.INDIVIDUAL_REVOCATION,
229
- eip712Signature: new Uint8Array(hexToBytes(signature)),
230
- responseKey,
144
+ const session = {
145
+ owner: params.owner ?? ctx.signer.address,
146
+ issuedAt: BigInt(Math.floor(Date.now() / 1000)),
147
+ expiresAt: params.expiresAt,
148
+ periodStart: params.periodStart ?? 0n,
149
+ periodEnd: params.periodEnd ?? 0n,
150
+ responseKey: bytesToHex(responseKey),
151
+ salt: generateRandomSalt(),
152
+ };
153
+ const signature = await signTypedIntent(ctx, 'ViewingSession', session);
154
+ return { session, signature };
155
+ }
156
+ async function sessionQuery(ctx, rpcMethod, viewing, name, query) {
157
+ const responseKey = hexToBytes(viewing.session.responseKey);
158
+ const envelope = await makeEnvelope(ctx.teePublicKey, { eip712Signature: hexToBytes(viewing.signature), responseKey }, hexToBytes(encodeStruct(name, query)));
159
+ const { body } = await openSignedResponse(await rpcMethod(envelope), responseKey);
160
+ return body;
161
+ }
162
+ export async function getBalance(ctx, viewing, params) {
163
+ const body = await sessionQuery(ctx, (e) => ctx.rpc.getBalance(e), viewing, 'BalanceQuery', {
164
+ session: viewing.session,
165
+ assetId: params.assetId,
231
166
  });
232
- const envelope = await makeEnvelope(ctx.teePublicKey, draftRequest);
233
- const encResponse = await ctx.rpc.draft(envelope);
234
- const response = await decryptResponse(encResponse, responseKey, DraftResponse);
235
- return { userOp: parseUserOp07(response.userOp) };
167
+ return decodeStruct('BalanceResponse', bytesToHex(body));
236
168
  }
237
- export async function revokeAll(ctx) {
238
- const intent = {
239
- kind: IntentKind.USER_WIDE_REVOCATION,
240
- op: { empty: '0x' },
241
- signer: ctx.signer.address,
242
- gas: TODO_GAS,
243
- salt: generateRandomSalt(),
169
+ export async function getPortfolio(ctx, viewing) {
170
+ const body = await sessionQuery(ctx, (e) => ctx.rpc.getPortfolio(e), viewing, 'PortfolioQuery', { session: viewing.session });
171
+ return decodeStruct('PortfolioResponse', bytesToHex(body));
172
+ }
173
+ export function nextHistoryCursor(response) {
174
+ if (!response.hasMore)
175
+ return undefined;
176
+ return {
177
+ anchorHandle: response.anchorHandle,
178
+ afterTimestamp: response.nextTimestamp,
179
+ afterOpIndex: response.nextOpIndex,
244
180
  };
245
- const digest = userWideRevocationIntentDigest(intent, ctx.domainSeparator);
246
- const signature = await ctx.signer.sign({ hash: digest });
247
- const responseKey = generateResponseKey();
248
- const draftRequest = new DraftRequest({
249
- intent: new Uint8Array(hexToBytes(encodeIntent(IntentKind.USER_WIDE_REVOCATION, intent))),
250
- intentKind: IntentKind.USER_WIDE_REVOCATION,
251
- eip712Signature: new Uint8Array(hexToBytes(signature)),
252
- responseKey,
181
+ }
182
+ function historyPagination(options) {
183
+ return {
184
+ cursor: options?.cursor ?? FIRST_PAGE_CURSOR,
185
+ limit: options?.limit ?? 0,
186
+ };
187
+ }
188
+ export async function getBalanceHistory(ctx, viewing, assetId, options) {
189
+ if (assetId === ZERO_BYTES32) {
190
+ throw new ShieldError({
191
+ message: 'getBalanceHistory requires a nonzero assetId; use getPortfolioHistory',
192
+ });
193
+ }
194
+ const body = await sessionQuery(ctx, (e) => ctx.rpc.getBalanceHistory(e), viewing, 'BalanceHistoryQuery', {
195
+ session: viewing.session,
196
+ assetId,
197
+ pagination: historyPagination(options),
253
198
  });
254
- const envelope = await makeEnvelope(ctx.teePublicKey, draftRequest);
255
- const encResponse = await ctx.rpc.draft(envelope);
256
- const response = await decryptResponse(encResponse, responseKey, DraftResponse);
257
- return { userOp: parseUserOp07(response.userOp) };
199
+ return decodeStruct('BalanceHistoryResponse', bytesToHex(body));
258
200
  }
259
- // ---------------------------------------------------------------------------
260
- // GetBalance
261
- // ---------------------------------------------------------------------------
262
- export async function getBalance(ctx, params) {
263
- const queryTimestamp = BigInt(Math.floor(Date.now() / 1000));
264
- const digest = balanceQueryDigest({
265
- owner: params.address,
266
- assetId: params.assetId,
267
- requester: ctx.signer.address,
268
- queryTimestamp,
269
- }, ctx.domainSeparator);
270
- const signature = await ctx.signer.sign({ hash: digest });
271
- const responseKey = generateResponseKey();
272
- const envelope = await makeEnvelope(ctx.teePublicKey, new GetBalanceRequest({
273
- address: new ProtoAddress({ value: hexToBytes(params.address) }),
274
- assetId: new ProtoAssetId({ value: hexToBytes(params.assetId) }),
275
- atBlock: params.atBlock ?? 0n,
276
- eip712Signature: hexToBytes(signature),
277
- responseKey,
278
- queryTimestamp,
279
- }));
280
- const response = await ctx.rpc.getBalance(envelope);
281
- return parseBalance(await decryptResponse(response, responseKey, GetBalanceResponse));
201
+ export async function getPortfolioHistory(ctx, viewing, options) {
202
+ const body = await sessionQuery(ctx, (e) => ctx.rpc.getPortfolioHistory(e), viewing, 'PortfolioHistoryQuery', {
203
+ session: viewing.session,
204
+ pagination: historyPagination(options),
205
+ });
206
+ return decodeStruct('PortfolioHistoryResponse', bytesToHex(body));
282
207
  }
283
- // ---------------------------------------------------------------------------
284
- // RequestDepositAttestation
285
- // ---------------------------------------------------------------------------
286
208
  export async function requestDepositAttestation(ctx, params) {
287
- const depositor = ctx.signer.address;
288
- const [tokenAddress] = decodeAbiParameters([{ type: 'address' }], params.assetIdentificationArgs);
289
- const assetId = computeErc20AssetId(tokenAddress);
290
- const digest = depositAttestationRequestDigest({
291
- recipient: params.recipient,
292
- assetId,
293
- }, ctx.domainSeparator);
294
- const signature = await ctx.signer.sign({ hash: digest });
295
- const responseKey = generateResponseKey();
296
- const envelope = await makeEnvelope(ctx.teePublicKey, new RequestDepositAttestationRequest({
297
- recipient: new ProtoAddress({
209
+ const body = toBinary(DepositAttestationRequestSchema, create(DepositAttestationRequestSchema, {
210
+ recipient: create(AddressSchema, {
298
211
  value: hexToBytes(params.recipient),
299
212
  }),
300
- assetAdapter: new ProtoAddress({
213
+ assetAdapter: create(AddressSchema, {
301
214
  value: hexToBytes(params.assetAdapter),
302
215
  }),
303
216
  assetIdentificationArgs: hexToBytes(params.assetIdentificationArgs),
304
217
  memo: hexToBytes(params.memo),
305
218
  escapeHatch: hexToBytes(params.escapeHatch),
306
- eip712Signature: hexToBytes(signature),
307
- responseKey,
308
- depositor: new ProtoAddress({ value: hexToBytes(depositor) }),
309
219
  }));
310
- const response = await ctx.rpc.requestDepositAttestation(envelope);
311
- const decrypted = await decryptResponse(response, responseKey, RequestDepositAttestationResponse);
312
- if (decrypted.errorMessage) {
313
- throw new CovalidatorError({ message: decrypted.errorMessage });
314
- }
315
- return {
316
- toCiphertext: bytesToHex(decrypted.toCiphertext),
317
- teeSignature: bytesToHex(decrypted.teeSignature),
318
- };
319
- }
320
- // ---------------------------------------------------------------------------
321
- // Permission query helpers
322
- // ---------------------------------------------------------------------------
323
- // Each query signs its own Solidity struct so every field is authenticated.
324
- // Self-query: `requester` is always the signer.
325
- async function signGetPermissionsByOwner(ctx, includeExpired) {
326
- const query = {
327
- requester: ctx.signer.address,
328
- includeExpired,
329
- timestamp: BigInt(Math.floor(Date.now() / 1000)),
330
- };
331
- const digest = getPermissionsByOwnerDigest(query, ctx.domainSeparator);
332
- const sig = await ctx.signer.sign({ hash: digest });
333
- const encodedQuery = encodeAbiParameters([getAbiFromUnitFunction(incoShieldAbi, 'getPermissionsByOwner')], [query]);
334
- return { encodedQuery: hexToBytes(encodedQuery), signature: hexToBytes(sig) };
220
+ const responseKey = generateResponseKey();
221
+ const envelope = await makeEnvelope(ctx.teePublicKey, { responseKey }, body);
222
+ const { body: responseBody, teeSignature } = await openSignedResponse(await ctx.rpc.requestDepositAttestation(envelope), responseKey);
223
+ const payload = decodeStruct('DepositAttestationPayload', bytesToHex(responseBody));
224
+ return { payload, signature: bytesToHex(teeSignature) };
335
225
  }
336
- async function signGetPermissionsBySpender(ctx, includeExpired) {
226
+ export async function getPermissionsByOwner(ctx, params = {}) {
337
227
  const query = {
338
228
  requester: ctx.signer.address,
339
- includeExpired,
340
- timestamp: BigInt(Math.floor(Date.now() / 1000)),
229
+ includeExpired: params.includeExpired ?? false,
341
230
  };
342
- const digest = getPermissionsBySpenderDigest(query, ctx.domainSeparator);
343
- const sig = await ctx.signer.sign({ hash: digest });
344
- const encodedQuery = encodeAbiParameters([getAbiFromUnitFunction(incoShieldAbi, 'getPermissionsBySpender')], [query]);
345
- return { encodedQuery: hexToBytes(encodedQuery), signature: hexToBytes(sig) };
231
+ const signature = await signTypedIntent(ctx, 'GetPermissionsByOwner', query);
232
+ const responseKey = generateResponseKey();
233
+ const envelope = await makeEnvelope(ctx.teePublicKey, { eip712Signature: hexToBytes(signature), responseKey }, hexToBytes(encodeStruct('GetPermissionsByOwner', query)));
234
+ const { body } = await openSignedResponse(await ctx.rpc.getPermissionsByOwner(envelope), responseKey);
235
+ return decodeStruct('PermissionListResponse', bytesToHex(body));
346
236
  }
347
- async function signGetPermissionDetails(ctx, permissionId) {
237
+ export async function getPermissionsBySpender(ctx, params = {}) {
348
238
  const query = {
349
239
  requester: ctx.signer.address,
350
- permissionId,
351
- timestamp: BigInt(Math.floor(Date.now() / 1000)),
240
+ includeExpired: params.includeExpired ?? false,
352
241
  };
353
- const digest = getPermissionDetailsDigest(query, ctx.domainSeparator);
354
- const sig = await ctx.signer.sign({ hash: digest });
355
- const encodedQuery = encodeAbiParameters([getAbiFromUnitFunction(incoShieldAbi, 'getPermissionDetails')], [query]);
356
- return { encodedQuery: hexToBytes(encodedQuery), signature: hexToBytes(sig) };
242
+ const signature = await signTypedIntent(ctx, 'GetPermissionsBySpender', query);
243
+ const responseKey = generateResponseKey();
244
+ const envelope = await makeEnvelope(ctx.teePublicKey, { eip712Signature: hexToBytes(signature), responseKey }, hexToBytes(encodeStruct('GetPermissionsBySpender', query)));
245
+ const { body } = await openSignedResponse(await ctx.rpc.getPermissionsBySpender(envelope), responseKey);
246
+ return decodeStruct('PermissionListResponse', bytesToHex(body));
357
247
  }
358
- async function signGetOwnerGeneration(ctx) {
248
+ export async function getPermissionDetails(ctx, params) {
359
249
  const query = {
360
250
  requester: ctx.signer.address,
361
- timestamp: BigInt(Math.floor(Date.now() / 1000)),
251
+ permissionId: params.permissionId,
362
252
  };
363
- const digest = getOwnerGenerationDigest(query, ctx.domainSeparator);
364
- const sig = await ctx.signer.sign({ hash: digest });
365
- const encodedQuery = encodeAbiParameters([getAbiFromUnitFunction(incoShieldAbi, 'getOwnerGeneration')], [query]);
366
- return { encodedQuery: hexToBytes(encodedQuery), signature: hexToBytes(sig) };
367
- }
368
- // ---------------------------------------------------------------------------
369
- // GetPermissionsByOwner
370
- // ---------------------------------------------------------------------------
371
- export async function getPermissionsByOwner(ctx, params) {
372
- const { encodedQuery, signature } = await signGetPermissionsByOwner(ctx, params.includeExpired ?? false);
253
+ const signature = await signTypedIntent(ctx, 'GetPermissionDetails', query);
373
254
  const responseKey = generateResponseKey();
374
- const envelope = await makeEnvelope(ctx.teePublicKey, new GetPermissionsByOwnerRequest({
375
- getPermissionsByOwnerBytes: encodedQuery,
376
- eip712Signature: signature,
377
- responseKey,
378
- }));
379
- const response = await ctx.rpc.getPermissionsByOwner(envelope);
380
- return parsePermissions(await decryptResponse(response, responseKey, GetPermissionsResponse));
381
- }
382
- // ---------------------------------------------------------------------------
383
- // GetPermissionsBySpender
384
- // ---------------------------------------------------------------------------
385
- export async function getPermissionsBySpender(ctx, params) {
386
- const { encodedQuery, signature } = await signGetPermissionsBySpender(ctx, params.includeExpired ?? false);
387
- const responseKey = generateResponseKey();
388
- const envelope = await makeEnvelope(ctx.teePublicKey, new GetPermissionsBySpenderRequest({
389
- getPermissionsBySpenderBytes: encodedQuery,
390
- eip712Signature: signature,
391
- responseKey,
392
- }));
393
- const response = await ctx.rpc.getPermissionsBySpender(envelope);
394
- return parsePermissions(await decryptResponse(response, responseKey, GetPermissionsResponse));
255
+ const envelope = await makeEnvelope(ctx.teePublicKey, { eip712Signature: hexToBytes(signature), responseKey }, hexToBytes(encodeStruct('GetPermissionDetails', query)));
256
+ const { body } = await openSignedResponse(await ctx.rpc.getPermissionDetails(envelope), responseKey);
257
+ return decodeStruct('PermissionDetailsResponse', bytesToHex(body));
395
258
  }
396
- // ---------------------------------------------------------------------------
397
- // GetPermissionDetails
398
- // ---------------------------------------------------------------------------
399
- export async function getPermissionDetails(ctx, params) {
400
- const { encodedQuery, signature } = await signGetPermissionDetails(ctx, params.permissionId);
401
- const responseKey = generateResponseKey();
402
- const envelope = await makeEnvelope(ctx.teePublicKey, new GetPermissionDetailsRequest({
403
- getPermissionDetailsBytes: encodedQuery,
404
- eip712Signature: signature,
405
- responseKey,
406
- }));
407
- const response = await ctx.rpc.getPermissionDetails(envelope);
408
- return parsePermissionDetails(await decryptResponse(response, responseKey, GetPermissionDetailsResponse));
409
- }
410
- // ---------------------------------------------------------------------------
411
- // GetOwnerGeneration
412
- // ---------------------------------------------------------------------------
413
259
  export async function getOwnerGeneration(ctx) {
414
- const { encodedQuery, signature } = await signGetOwnerGeneration(ctx);
260
+ const query = { requester: ctx.signer.address };
261
+ const signature = await signTypedIntent(ctx, 'GetOwnerGeneration', query);
415
262
  const responseKey = generateResponseKey();
416
- const envelope = await makeEnvelope(ctx.teePublicKey, new GetOwnerGenerationRequest({
417
- getOwnerGenerationBytes: encodedQuery,
418
- eip712Signature: signature,
419
- responseKey,
420
- }));
421
- const response = await ctx.rpc.getOwnerGeneration(envelope);
422
- return parseOwnerGeneration(await decryptResponse(response, responseKey, GetOwnerGenerationResponse));
263
+ const envelope = await makeEnvelope(ctx.teePublicKey, { eip712Signature: hexToBytes(signature), responseKey }, hexToBytes(encodeStruct('GetOwnerGeneration', query)));
264
+ const { body } = await openSignedResponse(await ctx.rpc.getOwnerGeneration(envelope), responseKey);
265
+ return decodeStruct('OwnerGenerationResponse', bytesToHex(body));
266
+ }
267
+ export async function getLatestHandle(ctx) {
268
+ const response = await ctx.rpc.getLatestHandle(create(GetLatestHandleRequestSchema));
269
+ const value = response.handle?.value;
270
+ if (value === undefined || value.length === 0) {
271
+ throw new ShieldError({
272
+ message: 'GetLatestHandle response is missing the handle',
273
+ });
274
+ }
275
+ return bytesToHex(value);
423
276
  }