@baliola/smart-account-sdk 0.3.1
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/LICENSE +37 -0
- package/README.md +374 -0
- package/dist/abi-ziHt832n.js +1470 -0
- package/dist/accounts/index.d.ts +20 -0
- package/dist/accounts/index.js +2 -0
- package/dist/accounts-Bw0IrOJ1.js +229 -0
- package/dist/actions/index.d.ts +37 -0
- package/dist/actions/index.js +2 -0
- package/dist/auth/index.d.ts +35 -0
- package/dist/auth/index.js +2 -0
- package/dist/base-v7RyiDFz.js +104 -0
- package/dist/chains/index.d.ts +2 -0
- package/dist/chains/index.js +33 -0
- package/dist/classifyBundlerError-BqFLORNt.js +161 -0
- package/dist/clients/index.d.ts +86 -0
- package/dist/clients/index.js +3 -0
- package/dist/clients-B17dl_fh.js +134 -0
- package/dist/deployments-D4U_osAQ.js +10 -0
- package/dist/errors/index.d.ts +136 -0
- package/dist/errors/index.js +3 -0
- package/dist/index-BVqNo3O0.d.ts +222 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +12 -0
- package/dist/receipt-Ceeclfnv.d.ts +24 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +1 -0
- package/dist/types-BcsdeCby.d.ts +25 -0
- package/dist/types-CD0TvpY4.d.ts +60 -0
- package/dist/validateApiKey-lUfEM5W0.js +68 -0
- package/dist/writeContract-AyQox2dQ.js +371 -0
- package/dist/writeContract-CdcmYmx0.d.ts +184 -0
- package/package.json +85 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/auth/types.d.ts
|
|
2
|
+
/** Rejection reasons returned by baliola-auth `/api/api-keys/validate` when `valid: false`. */
|
|
3
|
+
type ApiKeyInvalidReason = "not_found" | "revoked" | "expired" | "wrong_module" | "module_inactive" | "origin_not_allowed" | "quota_exceeded" | "rate_limited";
|
|
4
|
+
interface ApiKeyQuota {
|
|
5
|
+
limitType: "periodic" | "lifetime";
|
|
6
|
+
period: "day" | "month" | "year";
|
|
7
|
+
/** Per-period (or lifetime) limit. `null` when uncapped. */
|
|
8
|
+
callLimit: number | null;
|
|
9
|
+
/** Remaining calls in the current period (or total). `null` when uncapped. */
|
|
10
|
+
callsRemaining: number | null;
|
|
11
|
+
/** ISO 8601 timestamp when the current period resets. */
|
|
12
|
+
periodResetsAt: string;
|
|
13
|
+
}
|
|
14
|
+
/** Validated key info — what the SDK exposes on `client.apiKey` after construction. */
|
|
15
|
+
interface ApiKeyInfo {
|
|
16
|
+
/** UUID of the API key record itself. */
|
|
17
|
+
id: string;
|
|
18
|
+
/** UUID of the baliola account that owns this key. */
|
|
19
|
+
accountId: string;
|
|
20
|
+
/** Module slug the key is minted against (always `"smart-account"` for this SDK). */
|
|
21
|
+
module: string;
|
|
22
|
+
quota: ApiKeyQuota;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { ApiKeyInvalidReason as n, ApiKeyQuota as r, ApiKeyInfo as t };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Account, Address, Hex, PublicClient } from "viem";
|
|
2
|
+
|
|
3
|
+
//#region ../shared/src/user-op/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* IUserOperation in the "unpacked" JSON-RPC wire form (ERC-4337 v0.7).
|
|
6
|
+
* This is what clients submit via eth_sendUserOperation.
|
|
7
|
+
*/
|
|
8
|
+
interface IUserOperation {
|
|
9
|
+
sender: Address;
|
|
10
|
+
nonce: bigint;
|
|
11
|
+
factory?: Address;
|
|
12
|
+
factoryData?: Hex;
|
|
13
|
+
callData: Hex;
|
|
14
|
+
callGasLimit: bigint;
|
|
15
|
+
verificationGasLimit: bigint;
|
|
16
|
+
preVerificationGas: bigint;
|
|
17
|
+
maxFeePerGas: bigint;
|
|
18
|
+
maxPriorityFeePerGas: bigint;
|
|
19
|
+
paymaster?: Address;
|
|
20
|
+
paymasterVerificationGasLimit?: bigint;
|
|
21
|
+
paymasterPostOpGasLimit?: bigint;
|
|
22
|
+
paymasterData?: Hex;
|
|
23
|
+
signature: Hex;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/accounts/types.d.ts
|
|
27
|
+
/**
|
|
28
|
+
* Raw call tuple — identical to viem's `sendTransaction` input.
|
|
29
|
+
* `value` defaults to `0n` when omitted. `data` defaults to `"0x"`.
|
|
30
|
+
*/
|
|
31
|
+
type Call = {
|
|
32
|
+
to: Address;
|
|
33
|
+
value?: bigint;
|
|
34
|
+
data?: Hex;
|
|
35
|
+
};
|
|
36
|
+
/** Viem `Account` narrowed to require `signMessage` (EIP-191 signing). */
|
|
37
|
+
type SigningAccount = Account & {
|
|
38
|
+
signMessage: NonNullable<Account["signMessage"]>;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A Smart Account instance bundles the counterfactual `sender` address,
|
|
42
|
+
* the first-use `factory` + `factoryData`, and the EIP-191 signing closure
|
|
43
|
+
* over `userOpHash`.
|
|
44
|
+
*/
|
|
45
|
+
interface ISmartAccount {
|
|
46
|
+
readonly address: Address;
|
|
47
|
+
readonly owner: SigningAccount;
|
|
48
|
+
readonly factory: Address;
|
|
49
|
+
readonly factoryData: Hex;
|
|
50
|
+
readonly entryPoint: Address;
|
|
51
|
+
readonly nonceKey: bigint;
|
|
52
|
+
readonly chainId: number;
|
|
53
|
+
isDeployed(publicClient: PublicClient): Promise<boolean>;
|
|
54
|
+
markDeployed(): void;
|
|
55
|
+
signUserOperation(op: IUserOperation): Promise<Hex>;
|
|
56
|
+
encodeCalls(calls: Call[]): Hex;
|
|
57
|
+
getNonce(publicClient: PublicClient): Promise<bigint>;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { IUserOperation as i, ISmartAccount as n, SigningAccount as r, Call as t };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { n as BaliolaAuthUnreachableError, t as ApiKeyInvalidError } from "./base-v7RyiDFz.js";
|
|
2
|
+
//#region src/auth/authUrls.ts
|
|
3
|
+
/**
|
|
4
|
+
* Default baliola-auth base URLs per MAC chain. The SDK appends
|
|
5
|
+
* `/api/api-keys/validate` to whatever URL is configured.
|
|
6
|
+
*/
|
|
7
|
+
const DEFAULT_AUTH_URLS = {
|
|
8
|
+
macTestnet: "https://baliola-auth.baliola.dev",
|
|
9
|
+
macMainnet: "https://auth.baliola.io"
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Resolve the auth base URL for a chain, honoring an optional override
|
|
13
|
+
* (e.g. `http://localhost:8000` during dev). Trailing slashes are stripped
|
|
14
|
+
* so the caller can append a path safely.
|
|
15
|
+
*/
|
|
16
|
+
function resolveAuthUrl(chainName, override) {
|
|
17
|
+
return (override ?? DEFAULT_AUTH_URLS[chainName]).replace(/\/+$/, "");
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/auth/validateApiKey.ts
|
|
21
|
+
const SDK_SERVICE_LABEL = "smart-account-sdk";
|
|
22
|
+
const MODULE = "smart-account";
|
|
23
|
+
/**
|
|
24
|
+
* Validate a customer API key against `baliola-auth` and return the resolved
|
|
25
|
+
* key info on success. Throws `ApiKeyInvalidError` on a structured rejection
|
|
26
|
+
* (`valid: false` or `401`), and `BaliolaAuthUnreachableError` on network /
|
|
27
|
+
* transport failures (fetch rejection, non-2xx 5xx, unparseable body).
|
|
28
|
+
*
|
|
29
|
+
* One successful call charges one quota unit against the key — callers should
|
|
30
|
+
* cache the resulting client rather than re-invoking the factory.
|
|
31
|
+
*/
|
|
32
|
+
async function validateApiKey(params) {
|
|
33
|
+
const url = `${params.authUrl}/api/api-keys/validate`;
|
|
34
|
+
let res;
|
|
35
|
+
try {
|
|
36
|
+
res = await fetch(url, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: {
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
Authorization: `Bearer ${params.apiKey}`
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
requiredModule: MODULE,
|
|
44
|
+
service: SDK_SERVICE_LABEL
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
} catch (cause) {
|
|
48
|
+
throw new BaliolaAuthUnreachableError(url, cause);
|
|
49
|
+
}
|
|
50
|
+
if (res.status === 401) throw new ApiKeyInvalidError("not_found");
|
|
51
|
+
if (!res.ok) throw new BaliolaAuthUnreachableError(url, /* @__PURE__ */ new Error(`HTTP ${res.status}`));
|
|
52
|
+
let body;
|
|
53
|
+
try {
|
|
54
|
+
body = await res.json();
|
|
55
|
+
} catch (cause) {
|
|
56
|
+
throw new BaliolaAuthUnreachableError(url, cause);
|
|
57
|
+
}
|
|
58
|
+
const data = body.data;
|
|
59
|
+
if (!data.valid) throw new ApiKeyInvalidError(data.reason, data.retryAfterSeconds);
|
|
60
|
+
return {
|
|
61
|
+
id: data.apiKeyId,
|
|
62
|
+
accountId: data.accountId,
|
|
63
|
+
module: data.module,
|
|
64
|
+
quota: data.quota
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { DEFAULT_AUTH_URLS as n, resolveAuthUrl as r, validateApiKey as t };
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { t as entryPointAbi } from "./abi-ziHt832n.js";
|
|
2
|
+
import { a as UserOperationReceiptTimeoutError } from "./base-v7RyiDFz.js";
|
|
3
|
+
import { decodeEventLog, defineChain, encodeFunctionData, getAbiItem, hexToBigInt, numberToHex, toEventSelector } from "viem";
|
|
4
|
+
import "viem/accounts";
|
|
5
|
+
//#region src/actions/buildDraft.ts
|
|
6
|
+
/**
|
|
7
|
+
* Draft gas anchors. MAC's Substrate Frontier EVM runs the bundler's
|
|
8
|
+
* `eth_estimateUserOperationGas` path through `EntryPointSimulations` via
|
|
9
|
+
* `eth_call`, and the simulation fails if the packed `accountGasLimits` are
|
|
10
|
+
* zero. Seeding the draft with generous values lets the simulation complete
|
|
11
|
+
* and come back with precise estimates that replace these on submit.
|
|
12
|
+
*
|
|
13
|
+
* Values mirror `bundler/scripts/demo-userop.ts`, which has validated
|
|
14
|
+
* against live MAC Testnet end-to-end.
|
|
15
|
+
*/
|
|
16
|
+
const DRAFT_CALL_GAS_LIMIT = 200000n;
|
|
17
|
+
const DRAFT_VERIFICATION_GAS_LIMIT = 900000n;
|
|
18
|
+
const DRAFT_PRE_VERIFICATION_GAS = 150000n;
|
|
19
|
+
const DRAFT_GAS_ANCHORS = {
|
|
20
|
+
callGasLimit: DRAFT_CALL_GAS_LIMIT,
|
|
21
|
+
verificationGasLimit: DRAFT_VERIFICATION_GAS_LIMIT,
|
|
22
|
+
preVerificationGas: DRAFT_PRE_VERIFICATION_GAS,
|
|
23
|
+
paymasterVerificationGasLimit: 200000n,
|
|
24
|
+
paymasterPostOpGasLimit: 100000n
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* 65-byte dummy signature used during `eth_estimateUserOperationGas`.
|
|
28
|
+
*
|
|
29
|
+
* SimpleAccount's `_validateSignature` calls OpenZeppelin 5.0.2's
|
|
30
|
+
* `ECDSA.recover(hash, signature)`, which enforces EIP-2 low-`s` (reverts
|
|
31
|
+
* when `s > n/2`) and rejects zero-recovery. A naive dummy like `0xfa * 64`
|
|
32
|
+
* has a high `s` and causes the simulation to revert before the account
|
|
33
|
+
* can return `SIG_VALIDATION_FAILED`.
|
|
34
|
+
*
|
|
35
|
+
* This dummy uses `r = 1`, `s = 1`, `v = 27` — all within OZ's accepted
|
|
36
|
+
* ranges. `ecrecover` returns a valid, non-zero address that almost
|
|
37
|
+
* certainly is not the owner, so the account returns
|
|
38
|
+
* `SIG_VALIDATION_FAILED` cleanly and the simulator produces gas numbers.
|
|
39
|
+
*/
|
|
40
|
+
const DUMMY_SIGNATURE = "0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000011b";
|
|
41
|
+
/**
|
|
42
|
+
* Build an unsigned, un-paymastered, pre-estimate UserOperation from raw
|
|
43
|
+
* calls. Gas limits are seeded to generous defaults so the bundler's
|
|
44
|
+
* estimator can simulate through MAC's Frontier EVM and return precise
|
|
45
|
+
* values; fee fields default to the chain's `gasPrice` (MAC reports legacy
|
|
46
|
+
* pricing, so `maxFee` and `maxPriorityFee` are equal).
|
|
47
|
+
*/
|
|
48
|
+
async function buildDraftUserOp(ctx, calls, overrides = {}) {
|
|
49
|
+
const callData = overrides.callData ?? ctx.account.encodeCalls(calls);
|
|
50
|
+
const [nonce, deployed] = await Promise.all([overrides.nonce !== void 0 ? Promise.resolve(overrides.nonce) : ctx.account.getNonce(ctx.publicClient), ctx.account.isDeployed(ctx.publicClient)]);
|
|
51
|
+
const needsFactory = overrides.factory === void 0 ? !deployed : true;
|
|
52
|
+
const factory = overrides.factory ?? (needsFactory ? ctx.account.factory : void 0);
|
|
53
|
+
const factoryData = factory ? overrides.factoryData ?? ctx.account.factoryData : void 0;
|
|
54
|
+
const gasPrice = overrides.maxFeePerGas ?? overrides.maxPriorityFeePerGas ?? await ctx.publicClient.getGasPrice();
|
|
55
|
+
const draft = {
|
|
56
|
+
sender: ctx.account.address,
|
|
57
|
+
nonce,
|
|
58
|
+
callData,
|
|
59
|
+
callGasLimit: overrides.callGasLimit ?? DRAFT_CALL_GAS_LIMIT,
|
|
60
|
+
verificationGasLimit: overrides.verificationGasLimit ?? DRAFT_VERIFICATION_GAS_LIMIT,
|
|
61
|
+
preVerificationGas: overrides.preVerificationGas ?? DRAFT_PRE_VERIFICATION_GAS,
|
|
62
|
+
maxFeePerGas: overrides.maxFeePerGas ?? gasPrice,
|
|
63
|
+
maxPriorityFeePerGas: overrides.maxPriorityFeePerGas ?? gasPrice,
|
|
64
|
+
signature: overrides.signature ?? "0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000011b"
|
|
65
|
+
};
|
|
66
|
+
if (factory) {
|
|
67
|
+
draft.factory = factory;
|
|
68
|
+
draft.factoryData = factoryData ?? "0x";
|
|
69
|
+
}
|
|
70
|
+
return draft;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/clients/rpcCodec.ts
|
|
74
|
+
function toRpcUserOp(op) {
|
|
75
|
+
const rpc = {
|
|
76
|
+
sender: op.sender,
|
|
77
|
+
nonce: numberToHex(op.nonce),
|
|
78
|
+
callData: op.callData,
|
|
79
|
+
callGasLimit: numberToHex(op.callGasLimit),
|
|
80
|
+
verificationGasLimit: numberToHex(op.verificationGasLimit),
|
|
81
|
+
preVerificationGas: numberToHex(op.preVerificationGas),
|
|
82
|
+
maxFeePerGas: numberToHex(op.maxFeePerGas),
|
|
83
|
+
maxPriorityFeePerGas: numberToHex(op.maxPriorityFeePerGas),
|
|
84
|
+
signature: op.signature
|
|
85
|
+
};
|
|
86
|
+
if (op.factory) {
|
|
87
|
+
rpc.factory = op.factory;
|
|
88
|
+
rpc.factoryData = op.factoryData ?? "0x";
|
|
89
|
+
}
|
|
90
|
+
if (op.paymaster) {
|
|
91
|
+
rpc.paymaster = op.paymaster;
|
|
92
|
+
rpc.paymasterVerificationGasLimit = numberToHex(op.paymasterVerificationGasLimit ?? 0n);
|
|
93
|
+
rpc.paymasterPostOpGasLimit = numberToHex(op.paymasterPostOpGasLimit ?? 0n);
|
|
94
|
+
rpc.paymasterData = op.paymasterData ?? "0x";
|
|
95
|
+
}
|
|
96
|
+
return rpc;
|
|
97
|
+
}
|
|
98
|
+
function fromRpcUserOp(rpc) {
|
|
99
|
+
const op = {
|
|
100
|
+
sender: rpc.sender,
|
|
101
|
+
nonce: hexToBigInt(rpc.nonce),
|
|
102
|
+
callData: rpc.callData,
|
|
103
|
+
callGasLimit: hexToBigInt(rpc.callGasLimit),
|
|
104
|
+
verificationGasLimit: hexToBigInt(rpc.verificationGasLimit),
|
|
105
|
+
preVerificationGas: hexToBigInt(rpc.preVerificationGas),
|
|
106
|
+
maxFeePerGas: hexToBigInt(rpc.maxFeePerGas),
|
|
107
|
+
maxPriorityFeePerGas: hexToBigInt(rpc.maxPriorityFeePerGas),
|
|
108
|
+
signature: rpc.signature
|
|
109
|
+
};
|
|
110
|
+
if (rpc.factory) {
|
|
111
|
+
op.factory = rpc.factory;
|
|
112
|
+
op.factoryData = rpc.factoryData ?? "0x";
|
|
113
|
+
}
|
|
114
|
+
if (rpc.paymaster) {
|
|
115
|
+
op.paymaster = rpc.paymaster;
|
|
116
|
+
op.paymasterVerificationGasLimit = hexToBigInt(rpc.paymasterVerificationGasLimit ?? "0x0");
|
|
117
|
+
op.paymasterPostOpGasLimit = hexToBigInt(rpc.paymasterPostOpGasLimit ?? "0x0");
|
|
118
|
+
op.paymasterData = rpc.paymasterData ?? "0x";
|
|
119
|
+
}
|
|
120
|
+
return op;
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/actions/estimateUserOperationGas.ts
|
|
124
|
+
/**
|
|
125
|
+
* Low-level estimate that skips paymaster resolution — callers that want
|
|
126
|
+
* the full `{ calls, overrides }` surface should use the higher-level
|
|
127
|
+
* `estimateUserOperationGas` action below.
|
|
128
|
+
*/
|
|
129
|
+
async function estimateUserOpGasRaw(ctx, op) {
|
|
130
|
+
const rpc = toRpcUserOp(op);
|
|
131
|
+
const raw = await ctx.bundler.request("eth_estimateUserOperationGas", [rpc, ctx.entryPoint]);
|
|
132
|
+
const estimate = {
|
|
133
|
+
callGasLimit: hexToBigInt(raw.callGasLimit),
|
|
134
|
+
verificationGasLimit: hexToBigInt(raw.verificationGasLimit),
|
|
135
|
+
preVerificationGas: hexToBigInt(raw.preVerificationGas)
|
|
136
|
+
};
|
|
137
|
+
if (raw.paymasterVerificationGasLimit !== void 0) estimate.paymasterVerificationGasLimit = hexToBigInt(raw.paymasterVerificationGasLimit);
|
|
138
|
+
if (raw.paymasterPostOpGasLimit !== void 0) estimate.paymasterPostOpGasLimit = hexToBigInt(raw.paymasterPostOpGasLimit);
|
|
139
|
+
return estimate;
|
|
140
|
+
}
|
|
141
|
+
async function estimateUserOperationGas(ctx, params) {
|
|
142
|
+
const draft = await buildDraftUserOp(ctx, params.calls, params.overrides);
|
|
143
|
+
return estimateUserOpGasRaw(ctx, params.paymasterFields ? {
|
|
144
|
+
...draft,
|
|
145
|
+
...params.paymasterFields
|
|
146
|
+
} : draft);
|
|
147
|
+
}
|
|
148
|
+
defineChain({
|
|
149
|
+
id: 20017,
|
|
150
|
+
name: "MAC Testnet",
|
|
151
|
+
nativeCurrency: {
|
|
152
|
+
name: "Kepeng Testnet",
|
|
153
|
+
symbol: "KPGBT",
|
|
154
|
+
decimals: 18
|
|
155
|
+
},
|
|
156
|
+
rpcUrls: { default: { http: ["https://collator1.baliola.dev"] } },
|
|
157
|
+
testnet: true
|
|
158
|
+
});
|
|
159
|
+
defineChain({
|
|
160
|
+
id: 20016,
|
|
161
|
+
name: "MAC",
|
|
162
|
+
nativeCurrency: {
|
|
163
|
+
name: "Kepeng",
|
|
164
|
+
symbol: "KPGB",
|
|
165
|
+
decimals: 18
|
|
166
|
+
},
|
|
167
|
+
rpcUrls: { default: { http: ["https://collator4-mac.baliola.io"] } },
|
|
168
|
+
testnet: false
|
|
169
|
+
});
|
|
170
|
+
getAbiItem({
|
|
171
|
+
abi: entryPointAbi,
|
|
172
|
+
name: "UserOperationEvent"
|
|
173
|
+
});
|
|
174
|
+
/**
|
|
175
|
+
* Slice receipt logs for one userop's execution frame: all logs between the
|
|
176
|
+
* BeforeExecution marker (or the prior UserOperationEvent) and this op's
|
|
177
|
+
* UserOperationEvent.
|
|
178
|
+
*/
|
|
179
|
+
function sliceUserOpLogs(receiptLogs, userOpHash) {
|
|
180
|
+
const beforeExecutionTopic = toEventSelector("BeforeExecution()");
|
|
181
|
+
const userOpEventTopic = toEventSelector("UserOperationEvent(bytes32,address,address,uint256,bool,uint256,uint256)");
|
|
182
|
+
let startIdx = -1;
|
|
183
|
+
for (let i = 0; i < receiptLogs.length; i++) {
|
|
184
|
+
const log = receiptLogs[i];
|
|
185
|
+
if (!log) continue;
|
|
186
|
+
if (log.topics[0] === beforeExecutionTopic) {
|
|
187
|
+
startIdx = i;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (log.topics[0] === userOpEventTopic) {
|
|
191
|
+
if (log.topics[1] === userOpHash) return receiptLogs.slice(startIdx + 1, i);
|
|
192
|
+
startIdx = i;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/actions/getUserOperationReceipt.ts
|
|
199
|
+
async function getUserOperationReceipt(ctx, { userOpHash }) {
|
|
200
|
+
const raw = await ctx.bundler.request("eth_getUserOperationReceipt", [userOpHash]);
|
|
201
|
+
if (!raw) return null;
|
|
202
|
+
return decodeReceipt(raw, userOpHash);
|
|
203
|
+
}
|
|
204
|
+
function decodeReceipt(raw, userOpHash) {
|
|
205
|
+
const tx = raw.receipt;
|
|
206
|
+
const bundleLogs = tx.logs ?? [];
|
|
207
|
+
const preSliced = raw.logs ?? [];
|
|
208
|
+
const logs = preSliced.length > 0 ? preSliced : sliceUserOpLogs(bundleLogs, userOpHash);
|
|
209
|
+
const receipt = {
|
|
210
|
+
userOpHash: raw.userOpHash,
|
|
211
|
+
sender: raw.sender,
|
|
212
|
+
nonce: hexToBigInt(raw.nonce),
|
|
213
|
+
success: raw.success,
|
|
214
|
+
actualGasUsed: hexToBigInt(raw.actualGasUsed),
|
|
215
|
+
actualGasCost: hexToBigInt(raw.actualGasCost),
|
|
216
|
+
txHash: tx.transactionHash,
|
|
217
|
+
blockNumber: typeof tx.blockNumber === "bigint" ? tx.blockNumber : hexToBigInt(tx.blockNumber),
|
|
218
|
+
blockHash: tx.blockHash,
|
|
219
|
+
logs
|
|
220
|
+
};
|
|
221
|
+
if (raw.paymaster) receipt.paymaster = raw.paymaster;
|
|
222
|
+
if (raw.reason) receipt.reason = raw.reason;
|
|
223
|
+
return receipt;
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/actions/waitForUserOperationReceipt.ts
|
|
227
|
+
const DEFAULT_TIMEOUT_MS = 6e4;
|
|
228
|
+
const DEFAULT_POLL_MS = 1e3;
|
|
229
|
+
async function waitForUserOperationReceipt(ctx, params) {
|
|
230
|
+
const timeout = params.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
231
|
+
const pollingInterval = params.pollingInterval ?? DEFAULT_POLL_MS;
|
|
232
|
+
const deadline = Date.now() + timeout;
|
|
233
|
+
while (true) {
|
|
234
|
+
if (params.signal?.aborted) throw params.signal.reason ?? /* @__PURE__ */ new Error("aborted");
|
|
235
|
+
const receipt = await getUserOperationReceipt(ctx, { userOpHash: params.userOpHash });
|
|
236
|
+
if (receipt) return receipt;
|
|
237
|
+
if (Date.now() >= deadline) throw new UserOperationReceiptTimeoutError(params.userOpHash, timeout);
|
|
238
|
+
await sleep(pollingInterval, params.signal);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function sleep(ms, signal) {
|
|
242
|
+
return new Promise((resolve, reject) => {
|
|
243
|
+
const timer = setTimeout(() => {
|
|
244
|
+
signal?.removeEventListener("abort", onAbort);
|
|
245
|
+
resolve();
|
|
246
|
+
}, ms);
|
|
247
|
+
const onAbort = () => {
|
|
248
|
+
clearTimeout(timer);
|
|
249
|
+
reject(signal?.reason ?? /* @__PURE__ */ new Error("aborted"));
|
|
250
|
+
};
|
|
251
|
+
if (signal) signal.addEventListener("abort", onAbort, { once: true });
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/actions/watchUserOperations.ts
|
|
256
|
+
const userOperationEventAbiItem = getAbiItem({
|
|
257
|
+
abi: entryPointAbi,
|
|
258
|
+
name: "UserOperationEvent"
|
|
259
|
+
});
|
|
260
|
+
function watchUserOperations(ctx, params) {
|
|
261
|
+
const sender = params.sender ?? (params.userOpHash ? void 0 : ctx.account.address);
|
|
262
|
+
const args = {};
|
|
263
|
+
if (sender) args.sender = sender;
|
|
264
|
+
if (params.userOpHash) args.userOpHash = params.userOpHash;
|
|
265
|
+
return ctx.publicClient.watchEvent({
|
|
266
|
+
address: ctx.entryPoint,
|
|
267
|
+
event: userOperationEventAbiItem,
|
|
268
|
+
args,
|
|
269
|
+
onLogs(logs) {
|
|
270
|
+
for (const log of logs) try {
|
|
271
|
+
const match = decodeMatch(log);
|
|
272
|
+
params.onUserOp(match);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
params.onError?.(err);
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
...params.onError ? { onError: params.onError } : {}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function decodeMatch(log) {
|
|
281
|
+
const args = decodeEventLog({
|
|
282
|
+
abi: entryPointAbi,
|
|
283
|
+
eventName: "UserOperationEvent",
|
|
284
|
+
data: log.data,
|
|
285
|
+
topics: log.topics
|
|
286
|
+
}).args;
|
|
287
|
+
return {
|
|
288
|
+
userOpHash: args.userOpHash,
|
|
289
|
+
sender: args.sender,
|
|
290
|
+
paymaster: args.paymaster,
|
|
291
|
+
nonce: args.nonce,
|
|
292
|
+
success: args.success,
|
|
293
|
+
actualGasCost: args.actualGasCost,
|
|
294
|
+
actualGasUsed: args.actualGasUsed,
|
|
295
|
+
log
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/actions/sendUserOperation.ts
|
|
300
|
+
/**
|
|
301
|
+
* Internal engine: build draft → resolve paymaster → estimate gas → sign → submit.
|
|
302
|
+
* Public callers use `writeContract`; this stays internal so we own the lifecycle.
|
|
303
|
+
*/
|
|
304
|
+
async function sendUserOperation(ctx, { calls, overrides }) {
|
|
305
|
+
const draft = await buildDraftUserOp(ctx, calls, overrides);
|
|
306
|
+
const paymasterFields = overrides?.paymasterFields ?? await ctx.paymaster({
|
|
307
|
+
userOp: draft,
|
|
308
|
+
entryPoint: ctx.entryPoint,
|
|
309
|
+
chainId: ctx.account.chainId
|
|
310
|
+
});
|
|
311
|
+
const withPaymaster = {
|
|
312
|
+
...draft,
|
|
313
|
+
...paymasterFields,
|
|
314
|
+
paymasterVerificationGasLimit: paymasterFields.paymasterVerificationGasLimit ?? DRAFT_GAS_ANCHORS.paymasterVerificationGasLimit,
|
|
315
|
+
paymasterPostOpGasLimit: paymasterFields.paymasterPostOpGasLimit ?? DRAFT_GAS_ANCHORS.paymasterPostOpGasLimit
|
|
316
|
+
};
|
|
317
|
+
const estimate = await estimateUserOpGasRaw(ctx, withPaymaster);
|
|
318
|
+
const final = {
|
|
319
|
+
...withPaymaster,
|
|
320
|
+
callGasLimit: overrides?.callGasLimit ?? estimate.callGasLimit,
|
|
321
|
+
verificationGasLimit: overrides?.verificationGasLimit ?? estimate.verificationGasLimit,
|
|
322
|
+
preVerificationGas: overrides?.preVerificationGas ?? estimate.preVerificationGas,
|
|
323
|
+
paymasterVerificationGasLimit: overrides?.paymasterVerificationGasLimit ?? paymasterFields.paymasterVerificationGasLimit ?? estimate.paymasterVerificationGasLimit ?? DRAFT_GAS_ANCHORS.paymasterVerificationGasLimit,
|
|
324
|
+
paymasterPostOpGasLimit: overrides?.paymasterPostOpGasLimit ?? paymasterFields.paymasterPostOpGasLimit ?? estimate.paymasterPostOpGasLimit ?? DRAFT_GAS_ANCHORS.paymasterPostOpGasLimit
|
|
325
|
+
};
|
|
326
|
+
final.signature = await ctx.account.signUserOperation(final);
|
|
327
|
+
return ctx.bundler.request("eth_sendUserOperation", [toRpcUserOp(final), ctx.entryPoint]);
|
|
328
|
+
}
|
|
329
|
+
//#endregion
|
|
330
|
+
//#region src/actions/writeContract.ts
|
|
331
|
+
function isTypedCall(entry) {
|
|
332
|
+
return "abi" in entry;
|
|
333
|
+
}
|
|
334
|
+
function normalizeEntry(entry) {
|
|
335
|
+
if (isTypedCall(entry)) {
|
|
336
|
+
const data = encodeFunctionData({
|
|
337
|
+
abi: entry.abi,
|
|
338
|
+
functionName: entry.functionName,
|
|
339
|
+
args: entry.args
|
|
340
|
+
});
|
|
341
|
+
return entry.value !== void 0 ? {
|
|
342
|
+
to: entry.address,
|
|
343
|
+
value: entry.value,
|
|
344
|
+
data
|
|
345
|
+
} : {
|
|
346
|
+
to: entry.address,
|
|
347
|
+
data
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return entry;
|
|
351
|
+
}
|
|
352
|
+
async function writeContractImpl(ctx, input, overrides) {
|
|
353
|
+
const entries = Array.isArray(input) ? input : [input];
|
|
354
|
+
if (entries.length === 0) throw new Error("writeContract: input array must not be empty");
|
|
355
|
+
return sendUserOperation(ctx, {
|
|
356
|
+
calls: entries.map(normalizeEntry),
|
|
357
|
+
overrides
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
function writeContract(ctx, input, overrides) {
|
|
361
|
+
return writeContractImpl(ctx, input, overrides);
|
|
362
|
+
}
|
|
363
|
+
/** Build a context-bound `writeContract` for use on `SmartAccountClient`. */
|
|
364
|
+
function bindWriteContract(ctx) {
|
|
365
|
+
function bound(input, overrides) {
|
|
366
|
+
return writeContractImpl(ctx, input, overrides);
|
|
367
|
+
}
|
|
368
|
+
return bound;
|
|
369
|
+
}
|
|
370
|
+
//#endregion
|
|
371
|
+
export { getUserOperationReceipt as a, estimateUserOperationGas as c, DRAFT_GAS_ANCHORS as d, DUMMY_SIGNATURE as f, waitForUserOperationReceipt as i, fromRpcUserOp as l, writeContract as n, sliceUserOpLogs as o, buildDraftUserOp as p, watchUserOperations as r, estimateUserOpGasRaw as s, bindWriteContract as t, toRpcUserOp as u };
|