@waiaas/daemon 2.10.0-rc → 2.10.0-rc.2
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 +1 -0
- package/dist/api/routes/actions.d.ts +4 -0
- package/dist/api/routes/actions.d.ts.map +1 -1
- package/dist/api/routes/actions.js +94 -1
- package/dist/api/routes/actions.js.map +1 -1
- package/dist/api/routes/connect-info.d.ts +5 -0
- package/dist/api/routes/connect-info.d.ts.map +1 -1
- package/dist/api/routes/connect-info.js +46 -1
- package/dist/api/routes/connect-info.js.map +1 -1
- package/dist/api/routes/erc8004.d.ts +28 -0
- package/dist/api/routes/erc8004.d.ts.map +1 -0
- package/dist/api/routes/erc8004.js +325 -0
- package/dist/api/routes/erc8004.js.map +1 -0
- package/dist/api/routes/index.d.ts +1 -0
- package/dist/api/routes/index.d.ts.map +1 -1
- package/dist/api/routes/index.js +1 -0
- package/dist/api/routes/index.js.map +1 -1
- package/dist/api/routes/openapi-schemas.d.ts +180 -5
- package/dist/api/routes/openapi-schemas.d.ts.map +1 -1
- package/dist/api/routes/openapi-schemas.js +43 -0
- package/dist/api/routes/openapi-schemas.js.map +1 -1
- package/dist/api/routes/transactions.d.ts +1 -0
- package/dist/api/routes/transactions.d.ts.map +1 -1
- package/dist/api/routes/transactions.js +6 -0
- package/dist/api/routes/transactions.js.map +1 -1
- package/dist/api/routes/wallets.d.ts +3 -0
- package/dist/api/routes/wallets.d.ts.map +1 -1
- package/dist/api/routes/wallets.js +78 -3
- package/dist/api/routes/wallets.js.map +1 -1
- package/dist/api/server.d.ts +4 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +16 -0
- package/dist/api/server.js.map +1 -1
- package/dist/infrastructure/database/index.d.ts +1 -1
- package/dist/infrastructure/database/index.d.ts.map +1 -1
- package/dist/infrastructure/database/index.js +1 -1
- package/dist/infrastructure/database/index.js.map +1 -1
- package/dist/infrastructure/database/migrate.d.ts +1 -1
- package/dist/infrastructure/database/migrate.d.ts.map +1 -1
- package/dist/infrastructure/database/migrate.js +124 -5
- package/dist/infrastructure/database/migrate.js.map +1 -1
- package/dist/infrastructure/database/schema.d.ts +455 -1
- package/dist/infrastructure/database/schema.d.ts.map +1 -1
- package/dist/infrastructure/database/schema.js +44 -3
- package/dist/infrastructure/database/schema.js.map +1 -1
- package/dist/infrastructure/settings/hot-reload.d.ts.map +1 -1
- package/dist/infrastructure/settings/hot-reload.js +9 -0
- package/dist/infrastructure/settings/hot-reload.js.map +1 -1
- package/dist/infrastructure/settings/setting-keys.d.ts +1 -1
- package/dist/infrastructure/settings/setting-keys.d.ts.map +1 -1
- package/dist/infrastructure/settings/setting-keys.js +39 -0
- package/dist/infrastructure/settings/setting-keys.js.map +1 -1
- package/dist/infrastructure/smart-account/index.d.ts +5 -0
- package/dist/infrastructure/smart-account/index.d.ts.map +1 -0
- package/dist/infrastructure/smart-account/index.js +3 -0
- package/dist/infrastructure/smart-account/index.js.map +1 -0
- package/dist/infrastructure/smart-account/smart-account-clients.d.ts +40 -0
- package/dist/infrastructure/smart-account/smart-account-clients.d.ts.map +1 -0
- package/dist/infrastructure/smart-account/smart-account-clients.js +103 -0
- package/dist/infrastructure/smart-account/smart-account-clients.js.map +1 -0
- package/dist/infrastructure/smart-account/smart-account-service.d.ts +35 -0
- package/dist/infrastructure/smart-account/smart-account-service.d.ts.map +1 -0
- package/dist/infrastructure/smart-account/smart-account-service.js +45 -0
- package/dist/infrastructure/smart-account/smart-account-service.js.map +1 -0
- package/dist/lifecycle/daemon.d.ts.map +1 -1
- package/dist/lifecycle/daemon.js +35 -2
- package/dist/lifecycle/daemon.js.map +1 -1
- package/dist/pipeline/database-policy-engine.d.ts +45 -3
- package/dist/pipeline/database-policy-engine.d.ts.map +1 -1
- package/dist/pipeline/database-policy-engine.js +161 -12
- package/dist/pipeline/database-policy-engine.js.map +1 -1
- package/dist/pipeline/pipeline.d.ts.map +1 -1
- package/dist/pipeline/pipeline.js +1 -0
- package/dist/pipeline/pipeline.js.map +1 -1
- package/dist/pipeline/stages.d.ts +24 -0
- package/dist/pipeline/stages.d.ts.map +1 -1
- package/dist/pipeline/stages.js +479 -2
- package/dist/pipeline/stages.js.map +1 -1
- package/dist/services/erc8004/eip712-typed-data.d.ts +66 -0
- package/dist/services/erc8004/eip712-typed-data.d.ts.map +1 -0
- package/dist/services/erc8004/eip712-typed-data.js +53 -0
- package/dist/services/erc8004/eip712-typed-data.js.map +1 -0
- package/dist/services/erc8004/index.d.ts +8 -0
- package/dist/services/erc8004/index.d.ts.map +1 -0
- package/dist/services/erc8004/index.js +8 -0
- package/dist/services/erc8004/index.js.map +1 -0
- package/dist/services/erc8004/reputation-cache-service.d.ts +70 -0
- package/dist/services/erc8004/reputation-cache-service.d.ts.map +1 -0
- package/dist/services/erc8004/reputation-cache-service.js +221 -0
- package/dist/services/erc8004/reputation-cache-service.js.map +1 -0
- package/dist/services/signing-sdk/approval-channel-router.d.ts.map +1 -1
- package/dist/services/signing-sdk/approval-channel-router.js +9 -0
- package/dist/services/signing-sdk/approval-channel-router.js.map +1 -1
- package/dist/services/signing-sdk/sign-request-builder.d.ts +2 -0
- package/dist/services/signing-sdk/sign-request-builder.d.ts.map +1 -1
- package/dist/services/signing-sdk/sign-request-builder.js.map +1 -1
- package/dist/services/wc-signing-bridge.d.ts +8 -0
- package/dist/services/wc-signing-bridge.d.ts.map +1 -1
- package/dist/services/wc-signing-bridge.js +65 -1
- package/dist/services/wc-signing-bridge.js.map +1 -1
- package/dist/workflow/approval-workflow.d.ts +17 -0
- package/dist/workflow/approval-workflow.d.ts.map +1 -1
- package/dist/workflow/approval-workflow.js +31 -4
- package/dist/workflow/approval-workflow.js.map +1 -1
- package/package.json +5 -5
- package/public/admin/assets/index-C1XAaEL3.js +3 -0
- package/public/admin/index.html +1 -1
- package/public/admin/assets/index-RagPnRU1.js +0 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stages.d.ts","sourceRoot":"","sources":["../../src/pipeline/stages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EAKxB,MAAM,cAAc,CAAC;AAItB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,KAAK,KAAK,MAAM,MAAM,sCAAsC,CAAC;AAGpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAC;AACtF,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAgB,eAAe,EAAE,MAAM,cAAc,CAAC;AACnG,OAAO,EAAuC,KAAK,QAAQ,EAAkB,MAAM,cAAc,CAAC;AAClG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oDAAoD,CAAC;AAMhG,QAAA,MAAM,YAAY,aAAoB,CAAC;AAGvC,OAAO,EAAE,YAAY,EAAE,CAAC;AAMxB,MAAM,WAAW,eAAe;IAE9B,EAAE,EAAE,qBAAqB,CAAC,OAAO,MAAM,CAAC,CAAC;IACzC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,aAAa,CAAC;IACxB,YAAY,EAAE,aAAa,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IAEvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"stages.d.ts","sourceRoot":"","sources":["../../src/pipeline/stages.ts"],"names":[],"mappings":"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"}
|
package/dist/pipeline/stages.js
CHANGED
|
@@ -21,6 +21,11 @@ import { formatDisplayCurrency, formatAmount } from '@waiaas/core';
|
|
|
21
21
|
import { resolveEffectiveAmountUsd } from './resolve-effective-amount-usd.js';
|
|
22
22
|
import { sleep } from './sleep.js';
|
|
23
23
|
import { rpcConfigKey } from '../infrastructure/adapter-pool.js';
|
|
24
|
+
// v30.6: ERC-4337 smart account imports
|
|
25
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
26
|
+
import { createPublicClient, http, encodeFunctionData, toHex } from 'viem';
|
|
27
|
+
import { SmartAccountService } from '../infrastructure/smart-account/smart-account-service.js';
|
|
28
|
+
import { createSmartAccountBundlerClient } from '../infrastructure/smart-account/smart-account-clients.js';
|
|
24
29
|
// v1.5: CoinGecko 키 안내 힌트 최초 1회 추적 (데몬 재시작 시 리셋 OK)
|
|
25
30
|
const hintedTokens = new Set();
|
|
26
31
|
// Exported for test cleanup
|
|
@@ -293,9 +298,25 @@ export async function stage3Policy(ctx) {
|
|
|
293
298
|
else {
|
|
294
299
|
// evaluateAndReserve에 usdAmount 전달 (Phase 127)
|
|
295
300
|
const usdAmount = priceResult?.type === 'success' ? priceResult.usdAmount : undefined;
|
|
301
|
+
// [Phase 320] Pre-fetch reputation floor tier (async, before IMMEDIATE txn)
|
|
302
|
+
let reputationFloorTier;
|
|
303
|
+
if (ctx.reputationCache && ctx.policyEngine instanceof DatabasePolicyEngine) {
|
|
304
|
+
const prefetchResult = await ctx.policyEngine.prefetchReputationTier(ctx.walletId, txParam, ctx.reputationCache);
|
|
305
|
+
reputationFloorTier = prefetchResult?.tier;
|
|
306
|
+
// INT-01 fix: Emit REPUTATION_THRESHOLD_TRIGGERED when tier is escalated.
|
|
307
|
+
// This fires before the evaluation result is used, so the notification goes out
|
|
308
|
+
// regardless of whether the transaction is ultimately allowed or denied.
|
|
309
|
+
if (prefetchResult) {
|
|
310
|
+
void ctx.notificationService?.notify('REPUTATION_THRESHOLD_TRIGGERED', ctx.walletId, {
|
|
311
|
+
tier: prefetchResult.tier,
|
|
312
|
+
score: prefetchResult.score ?? '',
|
|
313
|
+
threshold: prefetchResult.threshold ?? '',
|
|
314
|
+
}, { txId: ctx.txId });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
296
317
|
// Use evaluateAndReserve for TOCTOU-safe evaluation when DatabasePolicyEngine + sqlite available
|
|
297
318
|
if (ctx.policyEngine instanceof DatabasePolicyEngine && ctx.sqlite) {
|
|
298
|
-
evaluation = ctx.policyEngine.evaluateAndReserve(ctx.walletId, txParam, ctx.txId, usdAmount);
|
|
319
|
+
evaluation = ctx.policyEngine.evaluateAndReserve(ctx.walletId, txParam, ctx.txId, usdAmount, reputationFloorTier);
|
|
299
320
|
}
|
|
300
321
|
else {
|
|
301
322
|
evaluation = await ctx.policyEngine.evaluate(ctx.walletId, txParam);
|
|
@@ -622,7 +643,11 @@ export async function stage4Wait(ctx) {
|
|
|
622
643
|
// Fallback: if no ApprovalWorkflow, treat as INSTANT (backward compat)
|
|
623
644
|
return;
|
|
624
645
|
}
|
|
625
|
-
|
|
646
|
+
// Pass EIP-712 metadata to requestApproval if present (Phase 321)
|
|
647
|
+
ctx.approvalWorkflow.requestApproval(ctx.txId, ctx.eip712Metadata ? {
|
|
648
|
+
approvalType: ctx.eip712Metadata.approvalType,
|
|
649
|
+
typedDataJson: ctx.eip712Metadata.typedDataJson,
|
|
650
|
+
} : undefined);
|
|
626
651
|
// Route approval to the correct signing channel
|
|
627
652
|
if (ctx.approvalChannelRouter) {
|
|
628
653
|
// v2.6.1+: use ApprovalChannelRouter to determine the correct channel
|
|
@@ -638,6 +663,7 @@ export async function stage4Wait(ctx) {
|
|
|
638
663
|
to: getRequestTo(ctx.request),
|
|
639
664
|
amount: getRequestAmount(ctx.request),
|
|
640
665
|
policyTier: 'APPROVAL',
|
|
666
|
+
approvalType: ctx.eip712Metadata?.approvalType,
|
|
641
667
|
});
|
|
642
668
|
// Only invoke WC bridge when the router selects walletconnect
|
|
643
669
|
if (result.method === 'walletconnect' && ctx.wcSigningBridge) {
|
|
@@ -768,11 +794,456 @@ export async function buildByType(adapter, request, walletPublicKey) {
|
|
|
768
794
|
}
|
|
769
795
|
}
|
|
770
796
|
// ---------------------------------------------------------------------------
|
|
797
|
+
// Stage 5: Smart account ERC-4337 UserOperation helpers
|
|
798
|
+
// ---------------------------------------------------------------------------
|
|
799
|
+
/**
|
|
800
|
+
* Minimal ERC-20 ABI for transfer/approve encoding in UserOperation calls.
|
|
801
|
+
* Inline to avoid cross-package import from @waiaas/adapters-evm.
|
|
802
|
+
*/
|
|
803
|
+
const ERC20_USEROP_ABI = [
|
|
804
|
+
{ type: 'function', name: 'transfer', inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] },
|
|
805
|
+
{ type: 'function', name: 'approve', inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] },
|
|
806
|
+
];
|
|
807
|
+
/**
|
|
808
|
+
* Convert a TransactionRequest to viem's calls[] format for UserOperation submission.
|
|
809
|
+
* Each call is { to, value, data } for the smart account to execute.
|
|
810
|
+
*/
|
|
811
|
+
export function buildUserOpCalls(request) {
|
|
812
|
+
const type = ('type' in request && request.type) || 'TRANSFER';
|
|
813
|
+
switch (type) {
|
|
814
|
+
case 'TRANSFER': {
|
|
815
|
+
return [{
|
|
816
|
+
to: getRequestTo(request),
|
|
817
|
+
value: BigInt(getRequestAmount(request)),
|
|
818
|
+
data: '0x',
|
|
819
|
+
}];
|
|
820
|
+
}
|
|
821
|
+
case 'TOKEN_TRANSFER': {
|
|
822
|
+
const req = request;
|
|
823
|
+
return [{
|
|
824
|
+
to: req.token.address,
|
|
825
|
+
value: 0n,
|
|
826
|
+
data: encodeFunctionData({
|
|
827
|
+
abi: ERC20_USEROP_ABI,
|
|
828
|
+
functionName: 'transfer',
|
|
829
|
+
args: [req.to, BigInt(req.amount)],
|
|
830
|
+
}),
|
|
831
|
+
}];
|
|
832
|
+
}
|
|
833
|
+
case 'CONTRACT_CALL': {
|
|
834
|
+
const req = request;
|
|
835
|
+
return [{
|
|
836
|
+
to: req.to,
|
|
837
|
+
value: BigInt(req.value ?? '0'),
|
|
838
|
+
data: (req.calldata || '0x'),
|
|
839
|
+
}];
|
|
840
|
+
}
|
|
841
|
+
case 'APPROVE': {
|
|
842
|
+
const req = request;
|
|
843
|
+
return [{
|
|
844
|
+
to: req.token.address,
|
|
845
|
+
value: 0n,
|
|
846
|
+
data: encodeFunctionData({
|
|
847
|
+
abi: ERC20_USEROP_ABI,
|
|
848
|
+
functionName: 'approve',
|
|
849
|
+
args: [req.spender, BigInt(req.amount)],
|
|
850
|
+
}),
|
|
851
|
+
}];
|
|
852
|
+
}
|
|
853
|
+
case 'BATCH': {
|
|
854
|
+
const req = request;
|
|
855
|
+
return req.instructions.map((instr) => {
|
|
856
|
+
if ('spender' in instr) {
|
|
857
|
+
// APPROVE instruction
|
|
858
|
+
const a = instr;
|
|
859
|
+
return {
|
|
860
|
+
to: a.token.address,
|
|
861
|
+
value: 0n,
|
|
862
|
+
data: encodeFunctionData({
|
|
863
|
+
abi: ERC20_USEROP_ABI,
|
|
864
|
+
functionName: 'approve',
|
|
865
|
+
args: [a.spender, BigInt(a.amount)],
|
|
866
|
+
}),
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
if ('token' in instr) {
|
|
870
|
+
// TOKEN_TRANSFER instruction
|
|
871
|
+
const t = instr;
|
|
872
|
+
return {
|
|
873
|
+
to: t.token.address,
|
|
874
|
+
value: 0n,
|
|
875
|
+
data: encodeFunctionData({
|
|
876
|
+
abi: ERC20_USEROP_ABI,
|
|
877
|
+
functionName: 'transfer',
|
|
878
|
+
args: [t.to, BigInt(t.amount)],
|
|
879
|
+
}),
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
if ('calldata' in instr) {
|
|
883
|
+
// CONTRACT_CALL instruction (also used by ActionProvider resolve() output)
|
|
884
|
+
const c = instr;
|
|
885
|
+
return {
|
|
886
|
+
to: c.to,
|
|
887
|
+
value: BigInt(c.value ?? '0'),
|
|
888
|
+
data: (c.calldata || '0x'),
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
// TRANSFER instruction (native transfer)
|
|
892
|
+
const t = instr;
|
|
893
|
+
return {
|
|
894
|
+
to: t.to,
|
|
895
|
+
value: BigInt(t.amount),
|
|
896
|
+
data: '0x',
|
|
897
|
+
};
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
default:
|
|
901
|
+
throw new WAIaaSError('CHAIN_ERROR', {
|
|
902
|
+
message: `Unknown transaction type for UserOp: ${type}`,
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Stage 5 smart account path: execute via UserOperation through BundlerClient.
|
|
908
|
+
*
|
|
909
|
+
* Flow:
|
|
910
|
+
* 1. Decrypt signer key -> create LocalAccount via viem's privateKeyToAccount
|
|
911
|
+
* 2. Create SmartAccount instance via SmartAccountService
|
|
912
|
+
* 3. Create BundlerClient via createSmartAccountBundlerClient
|
|
913
|
+
* 4. Build calls[] from request via buildUserOpCalls
|
|
914
|
+
* 5. prepareUserOperation -> apply 120% gas safety margin
|
|
915
|
+
* 6. sendUserOperation -> waitForUserOperationReceipt
|
|
916
|
+
* 7. Update DB: SUBMITTED -> CONFIRMED, update deployed status if needed
|
|
917
|
+
*
|
|
918
|
+
* Error mapping:
|
|
919
|
+
* - Paymaster rejection (message contains 'paymaster'/'PM_') -> PAYMASTER_REJECTED
|
|
920
|
+
* - UserOperationReverted -> TRANSACTION_REVERTED
|
|
921
|
+
* - Receipt timeout -> TRANSACTION_TIMEOUT
|
|
922
|
+
* - Other -> CHAIN_ERROR
|
|
923
|
+
*/
|
|
924
|
+
async function stage5ExecuteSmartAccount(ctx) {
|
|
925
|
+
const reqAmount = formatNotificationAmount(ctx.request, ctx.wallet.chain);
|
|
926
|
+
const reqTo = getRequestTo(ctx.request);
|
|
927
|
+
const displayAmount = await resolveDisplayAmount(ctx.amountUsd ?? null, ctx.settingsService, ctx.forexRateService);
|
|
928
|
+
// Build calls[] from request
|
|
929
|
+
const calls = buildUserOpCalls(ctx.request);
|
|
930
|
+
// CRITICAL: key MUST be released in finally block
|
|
931
|
+
let privateKey = null;
|
|
932
|
+
try {
|
|
933
|
+
// Step 1: Decrypt signer key
|
|
934
|
+
privateKey = await ctx.keyStore.decryptPrivateKey(ctx.walletId, ctx.masterPassword);
|
|
935
|
+
const hexKey = toHex(privateKey);
|
|
936
|
+
const localAccount = privateKeyToAccount(hexKey);
|
|
937
|
+
// Step 2: Create SmartAccount via SmartAccountService
|
|
938
|
+
const smartAccountService = new SmartAccountService();
|
|
939
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
940
|
+
const publicClient = createPublicClient({
|
|
941
|
+
chain: ctx.adapter.viemChain ?? undefined,
|
|
942
|
+
transport: http(ctx.adapter.rpcUrl ?? undefined),
|
|
943
|
+
});
|
|
944
|
+
const smartAccountInfo = await smartAccountService.createSmartAccount({
|
|
945
|
+
owner: localAccount,
|
|
946
|
+
client: publicClient,
|
|
947
|
+
});
|
|
948
|
+
// Step 3: Create BundlerClient (includes optional PaymasterClient)
|
|
949
|
+
if (!ctx.settingsService) {
|
|
950
|
+
throw new WAIaaSError('CHAIN_ERROR', {
|
|
951
|
+
message: 'SettingsService required for smart account transactions',
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
const bundlerClient = createSmartAccountBundlerClient({
|
|
955
|
+
client: publicClient,
|
|
956
|
+
account: smartAccountInfo.account,
|
|
957
|
+
networkId: ctx.resolvedNetwork,
|
|
958
|
+
settingsService: ctx.settingsService,
|
|
959
|
+
});
|
|
960
|
+
// Step 4: Prepare UserOperation to get gas estimates
|
|
961
|
+
const prepared = await bundlerClient.prepareUserOperation({ calls });
|
|
962
|
+
// Step 5: Apply 120% gas safety margin per CLAUDE.md rule
|
|
963
|
+
const safeCallGasLimit = (BigInt(prepared.callGasLimit) * 120n) / 100n;
|
|
964
|
+
const safeVerificationGasLimit = (BigInt(prepared.verificationGasLimit) * 120n) / 100n;
|
|
965
|
+
const safePreVerificationGas = (BigInt(prepared.preVerificationGas) * 120n) / 100n;
|
|
966
|
+
// Step 6: Submit UserOperation with overridden gas limits
|
|
967
|
+
ctx.metricsCounter?.increment('rpc.calls', { network: ctx.resolvedNetwork });
|
|
968
|
+
const userOpHash = await bundlerClient.sendUserOperation({
|
|
969
|
+
calls,
|
|
970
|
+
userOperation: {
|
|
971
|
+
callGasLimit: safeCallGasLimit,
|
|
972
|
+
verificationGasLimit: safeVerificationGasLimit,
|
|
973
|
+
preVerificationGas: safePreVerificationGas,
|
|
974
|
+
},
|
|
975
|
+
});
|
|
976
|
+
ctx.metricsCounter?.increment('tx.submitted', { network: ctx.resolvedNetwork });
|
|
977
|
+
// Update DB: SUBMITTED with userOpHash
|
|
978
|
+
await ctx.db
|
|
979
|
+
.update(transactions)
|
|
980
|
+
.set({ status: 'SUBMITTED', txHash: userOpHash })
|
|
981
|
+
.where(eq(transactions.id, ctx.txId));
|
|
982
|
+
// Audit log: TX_SUBMITTED
|
|
983
|
+
if (ctx.sqlite) {
|
|
984
|
+
insertAuditLog(ctx.sqlite, {
|
|
985
|
+
eventType: 'TX_SUBMITTED',
|
|
986
|
+
actor: ctx.sessionId ?? 'system',
|
|
987
|
+
walletId: ctx.walletId,
|
|
988
|
+
txId: ctx.txId,
|
|
989
|
+
details: {
|
|
990
|
+
txHash: userOpHash,
|
|
991
|
+
chain: ctx.wallet.chain,
|
|
992
|
+
network: ctx.resolvedNetwork,
|
|
993
|
+
type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
|
|
994
|
+
accountType: 'smart',
|
|
995
|
+
},
|
|
996
|
+
severity: 'info',
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
// Notify TX_SUBMITTED
|
|
1000
|
+
void ctx.notificationService?.notify('TX_SUBMITTED', ctx.walletId, {
|
|
1001
|
+
txId: ctx.txId,
|
|
1002
|
+
txHash: userOpHash,
|
|
1003
|
+
amount: reqAmount,
|
|
1004
|
+
to: reqTo,
|
|
1005
|
+
display_amount: displayAmount,
|
|
1006
|
+
network: ctx.resolvedNetwork,
|
|
1007
|
+
}, { txId: ctx.txId });
|
|
1008
|
+
// Emit wallet:activity
|
|
1009
|
+
ctx.eventBus?.emit('wallet:activity', {
|
|
1010
|
+
walletId: ctx.walletId,
|
|
1011
|
+
activity: 'TX_SUBMITTED',
|
|
1012
|
+
details: { txId: ctx.txId, txHash: userOpHash },
|
|
1013
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
1014
|
+
});
|
|
1015
|
+
// Step 7: Wait for UserOperation receipt (120s timeout)
|
|
1016
|
+
const receipt = await bundlerClient.waitForUserOperationReceipt({
|
|
1017
|
+
hash: userOpHash,
|
|
1018
|
+
timeout: 120_000,
|
|
1019
|
+
});
|
|
1020
|
+
const txHash = receipt?.receipt?.transactionHash ?? userOpHash;
|
|
1021
|
+
// Update DB: CONFIRMED with actual txHash
|
|
1022
|
+
await ctx.db
|
|
1023
|
+
.update(transactions)
|
|
1024
|
+
.set({ status: 'CONFIRMED', txHash })
|
|
1025
|
+
.where(eq(transactions.id, ctx.txId));
|
|
1026
|
+
// Update deployed status if this was first UserOp (lazy deployment)
|
|
1027
|
+
const walletRow = ctx.db.select().from(wallets).where(eq(wallets.id, ctx.walletId)).get();
|
|
1028
|
+
if (walletRow && !walletRow.deployed) {
|
|
1029
|
+
ctx.db.update(wallets).set({ deployed: true }).where(eq(wallets.id, ctx.walletId)).run();
|
|
1030
|
+
}
|
|
1031
|
+
ctx.metricsCounter?.increment('tx.confirmed', { network: ctx.resolvedNetwork });
|
|
1032
|
+
// Store submitResult for Stage 6
|
|
1033
|
+
ctx.submitResult = { txHash, status: 'confirmed' };
|
|
1034
|
+
// Audit log: TX_CONFIRMED
|
|
1035
|
+
if (ctx.sqlite) {
|
|
1036
|
+
insertAuditLog(ctx.sqlite, {
|
|
1037
|
+
eventType: 'TX_CONFIRMED',
|
|
1038
|
+
actor: ctx.sessionId ?? 'system',
|
|
1039
|
+
walletId: ctx.walletId,
|
|
1040
|
+
txId: ctx.txId,
|
|
1041
|
+
details: {
|
|
1042
|
+
txHash,
|
|
1043
|
+
chain: ctx.wallet.chain,
|
|
1044
|
+
network: ctx.resolvedNetwork,
|
|
1045
|
+
accountType: 'smart',
|
|
1046
|
+
},
|
|
1047
|
+
severity: 'info',
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
// Notify TX_CONFIRMED
|
|
1051
|
+
void ctx.notificationService?.notify('TX_CONFIRMED', ctx.walletId, {
|
|
1052
|
+
txId: ctx.txId,
|
|
1053
|
+
txHash,
|
|
1054
|
+
amount: reqAmount,
|
|
1055
|
+
to: reqTo,
|
|
1056
|
+
display_amount: displayAmount,
|
|
1057
|
+
network: ctx.resolvedNetwork,
|
|
1058
|
+
}, { txId: ctx.txId });
|
|
1059
|
+
// Emit transaction:completed event
|
|
1060
|
+
ctx.eventBus?.emit('transaction:completed', {
|
|
1061
|
+
walletId: ctx.walletId,
|
|
1062
|
+
txId: ctx.txId,
|
|
1063
|
+
txHash,
|
|
1064
|
+
network: ctx.resolvedNetwork,
|
|
1065
|
+
type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
|
|
1066
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
catch (err) {
|
|
1070
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1071
|
+
const errName = err instanceof Error ? err.name : '';
|
|
1072
|
+
// Already a WAIaaSError? (e.g., CHAIN_ERROR from bundler URL not configured)
|
|
1073
|
+
if (err instanceof WAIaaSError) {
|
|
1074
|
+
// Update DB to FAILED
|
|
1075
|
+
await ctx.db
|
|
1076
|
+
.update(transactions)
|
|
1077
|
+
.set({ status: 'FAILED', error: errMsg })
|
|
1078
|
+
.where(eq(transactions.id, ctx.txId));
|
|
1079
|
+
ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
|
|
1080
|
+
void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
|
|
1081
|
+
txId: ctx.txId,
|
|
1082
|
+
error: errMsg,
|
|
1083
|
+
amount: reqAmount,
|
|
1084
|
+
display_amount: displayAmount,
|
|
1085
|
+
network: ctx.resolvedNetwork,
|
|
1086
|
+
}, { txId: ctx.txId });
|
|
1087
|
+
ctx.eventBus?.emit('transaction:failed', {
|
|
1088
|
+
walletId: ctx.walletId,
|
|
1089
|
+
txId: ctx.txId,
|
|
1090
|
+
error: errMsg,
|
|
1091
|
+
network: ctx.resolvedNetwork,
|
|
1092
|
+
type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
|
|
1093
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
1094
|
+
});
|
|
1095
|
+
throw err;
|
|
1096
|
+
}
|
|
1097
|
+
// Paymaster rejection detection
|
|
1098
|
+
if (errMsg.toLowerCase().includes('paymaster') ||
|
|
1099
|
+
errMsg.includes('PM_') ||
|
|
1100
|
+
errName.includes('Paymaster')) {
|
|
1101
|
+
await ctx.db
|
|
1102
|
+
.update(transactions)
|
|
1103
|
+
.set({ status: 'FAILED', error: `Paymaster rejected: ${errMsg}` })
|
|
1104
|
+
.where(eq(transactions.id, ctx.txId));
|
|
1105
|
+
ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
|
|
1106
|
+
if (ctx.sqlite) {
|
|
1107
|
+
insertAuditLog(ctx.sqlite, {
|
|
1108
|
+
eventType: 'TX_FAILED',
|
|
1109
|
+
actor: ctx.sessionId ?? 'system',
|
|
1110
|
+
walletId: ctx.walletId,
|
|
1111
|
+
txId: ctx.txId,
|
|
1112
|
+
details: { error: errMsg, stage: 5, reason: 'paymaster_rejected' },
|
|
1113
|
+
severity: 'warning',
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
|
|
1117
|
+
txId: ctx.txId,
|
|
1118
|
+
error: `Paymaster rejected: ${errMsg}`,
|
|
1119
|
+
amount: reqAmount,
|
|
1120
|
+
display_amount: displayAmount,
|
|
1121
|
+
network: ctx.resolvedNetwork,
|
|
1122
|
+
}, { txId: ctx.txId });
|
|
1123
|
+
ctx.eventBus?.emit('transaction:failed', {
|
|
1124
|
+
walletId: ctx.walletId,
|
|
1125
|
+
txId: ctx.txId,
|
|
1126
|
+
error: `Paymaster rejected: ${errMsg}`,
|
|
1127
|
+
network: ctx.resolvedNetwork,
|
|
1128
|
+
type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
|
|
1129
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
1130
|
+
});
|
|
1131
|
+
throw new WAIaaSError('PAYMASTER_REJECTED', {
|
|
1132
|
+
message: `Paymaster rejected the UserOperation: ${errMsg}`,
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
// UserOperationReverted
|
|
1136
|
+
if (errName === 'UserOperationReverted' || errMsg.includes('UserOperation reverted')) {
|
|
1137
|
+
await ctx.db
|
|
1138
|
+
.update(transactions)
|
|
1139
|
+
.set({ status: 'FAILED', error: errMsg })
|
|
1140
|
+
.where(eq(transactions.id, ctx.txId));
|
|
1141
|
+
ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
|
|
1142
|
+
if (ctx.sqlite) {
|
|
1143
|
+
insertAuditLog(ctx.sqlite, {
|
|
1144
|
+
eventType: 'TX_FAILED',
|
|
1145
|
+
actor: ctx.sessionId ?? 'system',
|
|
1146
|
+
walletId: ctx.walletId,
|
|
1147
|
+
txId: ctx.txId,
|
|
1148
|
+
details: { error: errMsg, stage: 5, reason: 'user_op_reverted' },
|
|
1149
|
+
severity: 'warning',
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
|
|
1153
|
+
txId: ctx.txId,
|
|
1154
|
+
error: errMsg,
|
|
1155
|
+
amount: reqAmount,
|
|
1156
|
+
display_amount: displayAmount,
|
|
1157
|
+
network: ctx.resolvedNetwork,
|
|
1158
|
+
}, { txId: ctx.txId });
|
|
1159
|
+
ctx.eventBus?.emit('transaction:failed', {
|
|
1160
|
+
walletId: ctx.walletId,
|
|
1161
|
+
txId: ctx.txId,
|
|
1162
|
+
error: errMsg,
|
|
1163
|
+
network: ctx.resolvedNetwork,
|
|
1164
|
+
type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
|
|
1165
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
1166
|
+
});
|
|
1167
|
+
throw new WAIaaSError('TRANSACTION_REVERTED', {
|
|
1168
|
+
message: errMsg,
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
// Receipt timeout
|
|
1172
|
+
if (errName === 'WaitForUserOperationReceiptTimeoutError' || errMsg.includes('timed out')) {
|
|
1173
|
+
await ctx.db
|
|
1174
|
+
.update(transactions)
|
|
1175
|
+
.set({ status: 'FAILED', error: `UserOp receipt timeout: ${errMsg}` })
|
|
1176
|
+
.where(eq(transactions.id, ctx.txId));
|
|
1177
|
+
ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
|
|
1178
|
+
if (ctx.sqlite) {
|
|
1179
|
+
insertAuditLog(ctx.sqlite, {
|
|
1180
|
+
eventType: 'TX_FAILED',
|
|
1181
|
+
actor: ctx.sessionId ?? 'system',
|
|
1182
|
+
walletId: ctx.walletId,
|
|
1183
|
+
txId: ctx.txId,
|
|
1184
|
+
details: { error: errMsg, stage: 5, reason: 'user_op_timeout' },
|
|
1185
|
+
severity: 'warning',
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
|
|
1189
|
+
txId: ctx.txId,
|
|
1190
|
+
error: `Receipt timeout: ${errMsg}`,
|
|
1191
|
+
amount: reqAmount,
|
|
1192
|
+
display_amount: displayAmount,
|
|
1193
|
+
network: ctx.resolvedNetwork,
|
|
1194
|
+
}, { txId: ctx.txId });
|
|
1195
|
+
ctx.eventBus?.emit('transaction:failed', {
|
|
1196
|
+
walletId: ctx.walletId,
|
|
1197
|
+
txId: ctx.txId,
|
|
1198
|
+
error: `Receipt timeout: ${errMsg}`,
|
|
1199
|
+
network: ctx.resolvedNetwork,
|
|
1200
|
+
type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
|
|
1201
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
1202
|
+
});
|
|
1203
|
+
throw new WAIaaSError('TRANSACTION_TIMEOUT', {
|
|
1204
|
+
message: `UserOperation receipt timed out: ${errMsg}`,
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
// Generic fallback
|
|
1208
|
+
await ctx.db
|
|
1209
|
+
.update(transactions)
|
|
1210
|
+
.set({ status: 'FAILED', error: errMsg })
|
|
1211
|
+
.where(eq(transactions.id, ctx.txId));
|
|
1212
|
+
ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
|
|
1213
|
+
void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
|
|
1214
|
+
txId: ctx.txId,
|
|
1215
|
+
error: errMsg,
|
|
1216
|
+
amount: reqAmount,
|
|
1217
|
+
display_amount: displayAmount,
|
|
1218
|
+
network: ctx.resolvedNetwork,
|
|
1219
|
+
}, { txId: ctx.txId });
|
|
1220
|
+
ctx.eventBus?.emit('transaction:failed', {
|
|
1221
|
+
walletId: ctx.walletId,
|
|
1222
|
+
txId: ctx.txId,
|
|
1223
|
+
error: errMsg,
|
|
1224
|
+
network: ctx.resolvedNetwork,
|
|
1225
|
+
type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
|
|
1226
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
1227
|
+
});
|
|
1228
|
+
throw new WAIaaSError('CHAIN_ERROR', { message: errMsg });
|
|
1229
|
+
}
|
|
1230
|
+
finally {
|
|
1231
|
+
if (privateKey) {
|
|
1232
|
+
ctx.keyStore.releaseKey(privateKey);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
// ---------------------------------------------------------------------------
|
|
771
1237
|
// Stage 5: On-chain execution (CONC-01 retry loop)
|
|
772
1238
|
// ---------------------------------------------------------------------------
|
|
773
1239
|
/**
|
|
774
1240
|
* Stage 5: Build -> Simulate -> Sign -> Submit with CONC-01 retry logic.
|
|
775
1241
|
*
|
|
1242
|
+
* For smart accounts (accountType === 'smart'), delegates to stage5ExecuteSmartAccount
|
|
1243
|
+
* which uses the UserOperation pipeline (BundlerClient + PaymasterClient).
|
|
1244
|
+
*
|
|
1245
|
+
* For EOA accounts, uses the existing buildByType -> simulate -> sign -> submit path.
|
|
1246
|
+
*
|
|
776
1247
|
* ChainError category-based retry:
|
|
777
1248
|
* - PERMANENT: immediate FAILED, no retry
|
|
778
1249
|
* - TRANSIENT: exponential backoff (1s, 2s, 4s), max 3 retries (retryCount >= 3 guard)
|
|
@@ -782,6 +1253,12 @@ export async function buildByType(adapter, request, walletPublicKey) {
|
|
|
782
1253
|
* Total attempts: initial 1 + up to 3 retries = 4 max.
|
|
783
1254
|
*/
|
|
784
1255
|
export async function stage5Execute(ctx) {
|
|
1256
|
+
// Smart account UserOperation path
|
|
1257
|
+
if (ctx.wallet.accountType === 'smart') {
|
|
1258
|
+
await stage5ExecuteSmartAccount(ctx);
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
// --- EOA execution path (unchanged) ---
|
|
785
1262
|
const reqAmount = formatNotificationAmount(ctx.request, ctx.wallet.chain);
|
|
786
1263
|
const reqTo = getRequestTo(ctx.request);
|
|
787
1264
|
// [Phase 139] Resolve display amount once for all Stage 5 notifications
|