@xyyz1207/uptick-aawallet-mpc-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -0
- package/package.json +18 -0
- package/src/core/chain-presets.js +70 -0
- package/src/core/config.js +159 -0
- package/src/index.js +32 -0
- package/src/sdk.js +89 -0
- package/src/services/transaction-service.js +17 -0
- package/src/services/wallet-service.js +65 -0
- package/src/utils/aa-call.js +159 -0
- package/src/utils/aa-deploy.js +699 -0
- package/src/utils/aa-mpc.js +123 -0
- package/src/utils/aa-wallet.js +193 -0
- package/src/utils/mpc-client.js +145 -0
- package/src/utils/runtime-config.js +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# AA Wallet SDK
|
|
2
|
+
|
|
3
|
+
An ERC-4337 Account Abstraction wallet SDK with a minimal public API.
|
|
4
|
+
This SDK is **only designed for the Uptick Network chain**.
|
|
5
|
+
|
|
6
|
+
Common methods:
|
|
7
|
+
|
|
8
|
+
- `init`: Initialize chain and sponsor configuration (bundler / paymaster / chainId / rpc / apiKey, etc.)
|
|
9
|
+
- `createWallet`: Create and return the AA wallet address (auto-sponsored deployment when needed)
|
|
10
|
+
- `sendTransaction`: Send a transaction with the AA wallet (single call or batched calls)
|
|
11
|
+
|
|
12
|
+
The internal design follows a layered architecture:
|
|
13
|
+
|
|
14
|
+
- `core`: Parameter normalization and input validation
|
|
15
|
+
- `services`: Wallet and transaction flow orchestration
|
|
16
|
+
- `sdk`: External SDK instance API (while keeping default singleton compatibility)
|
|
17
|
+
|
|
18
|
+
## Installation & Import
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @uptickjs/aa-wallet-sdk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import { init, createWallet, sendTransaction } from '@uptickjs/aa-wallet-sdk';
|
|
26
|
+
import { encodeFunctionData } from 'viem';
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## API
|
|
30
|
+
|
|
31
|
+
### 1) `init(config)`
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
init({
|
|
35
|
+
chainId: 9000,
|
|
36
|
+
bundlerUrl: 'https://...',
|
|
37
|
+
paymasterServiceUrl: 'https://...',
|
|
38
|
+
bundlerApiKey: '',
|
|
39
|
+
paymasterApiKey: '',
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2) `createWallet(params)`
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
const wallet = await createWallet({
|
|
47
|
+
ownerPrivateKey: '0x...',
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Return example:
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
{
|
|
55
|
+
ownerAddress: "0x...",
|
|
56
|
+
aaWalletAddress: "0x...",
|
|
57
|
+
deployedOnChain: true,
|
|
58
|
+
deployTxHash: "0x..." // null if deployment is not triggered
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3) `sendTransaction(params)`
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
const tx = await sendTransaction({
|
|
66
|
+
ownerPrivateKey: '0x...',
|
|
67
|
+
to: '0x...',
|
|
68
|
+
value: 0n,
|
|
69
|
+
data: '0x',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const data = encodeFunctionData({
|
|
73
|
+
abi: loadAbi,
|
|
74
|
+
functionName: 'transfer',
|
|
75
|
+
args: [params1,params2...],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const tx = await sendTransaction({
|
|
79
|
+
ownerPrivateKey: '0x...',
|
|
80
|
+
to: contractAddress,
|
|
81
|
+
value: 0n,
|
|
82
|
+
data: data,
|
|
83
|
+
});
|
|
84
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xyyz1207/uptick-aawallet-mpc-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"description": "",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"dotenv": "^17.4.2",
|
|
15
|
+
"permissionless": "^0.3.5",
|
|
16
|
+
"viem": "^2.48.1"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const COMMON_AA_PRESET = {
|
|
2
|
+
accountType: "safe",
|
|
3
|
+
safeVersion: "1.5.0",
|
|
4
|
+
entryPointVersion: "0.7",
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 按 chainId 维护默认参数,调用方可在 init(config) 里覆盖。
|
|
9
|
+
*/
|
|
10
|
+
export const CHAIN_PRESETS = {
|
|
11
|
+
9000: {
|
|
12
|
+
chain: {
|
|
13
|
+
id: 9000,
|
|
14
|
+
name: "Uptick test",
|
|
15
|
+
rpcUrl: "http://54.254.15.166:10545",
|
|
16
|
+
},
|
|
17
|
+
aa: {
|
|
18
|
+
...COMMON_AA_PRESET,
|
|
19
|
+
entryPointAddress: "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
|
|
20
|
+
factoryAddress: "0x91E60e0613810449d098b0b5Ec8b51A0FE8c8985",
|
|
21
|
+
safeProxyFactoryAddress: "0x34aeE8688B23516f9cD8F145D52D5a13080028D2",
|
|
22
|
+
safeSingletonAddress: "0x59CAB03C911eF5Ab4590Bb6c4F00B768C10F09D8",
|
|
23
|
+
safe4337ModuleAddress: "0x75cf11467937ce3F2f357CE24ffc3DBF8fD5c226",
|
|
24
|
+
safeModuleSetupAddress: "0x2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47",
|
|
25
|
+
multiSendAddress: "0x4faF5C1F98B09F1494bDaf93c85E2A05FbC1e1Bd",
|
|
26
|
+
multiSendCallOnlyAddress: "0x756E377D1dcDC33bD973216E64D32bec6aB4b569",
|
|
27
|
+
},
|
|
28
|
+
deployment: {
|
|
29
|
+
enabled: true,
|
|
30
|
+
paymasterAddress: "0x80b00Dce53C4F717aECEb84881F4f8beA099189C",
|
|
31
|
+
paymasterVerificationGasLimit: "100000",
|
|
32
|
+
paymasterPostOpGasLimit: "50000",
|
|
33
|
+
gasLimitMultiplier: 120,
|
|
34
|
+
feeMultiplier: 130,
|
|
35
|
+
debug: false,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
1170: {
|
|
39
|
+
chain: {
|
|
40
|
+
id: 1170,
|
|
41
|
+
name: "Uptick test",
|
|
42
|
+
rpcUrl: "https://json-rpc.origin.uptick.network",
|
|
43
|
+
},
|
|
44
|
+
aa: {
|
|
45
|
+
...COMMON_AA_PRESET,
|
|
46
|
+
entryPointAddress: "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
|
|
47
|
+
factoryAddress: "0x91E60e0613810449d098b0b5Ec8b51A0FE8c8985",
|
|
48
|
+
safeProxyFactoryAddress: "0x34aeE8688B23516f9cD8F145D52D5a13080028D2",
|
|
49
|
+
safeSingletonAddress: "0x59CAB03C911eF5Ab4590Bb6c4F00B768C10F09D8",
|
|
50
|
+
safe4337ModuleAddress: "0x75cf11467937ce3F2f357CE24ffc3DBF8fD5c226",
|
|
51
|
+
safeModuleSetupAddress: "0x2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47",
|
|
52
|
+
multiSendAddress: "0x4faF5C1F98B09F1494bDaf93c85E2A05FbC1e1Bd",
|
|
53
|
+
multiSendCallOnlyAddress: "0x756E377D1dcDC33bD973216E64D32bec6aB4b569",
|
|
54
|
+
},
|
|
55
|
+
deployment: {
|
|
56
|
+
enabled: true,
|
|
57
|
+
paymasterAddress: "0x450AeF871cc771D4F0B7813f2BBef9a8c77611F7",
|
|
58
|
+
paymasterVerificationGasLimit: "100000",
|
|
59
|
+
paymasterPostOpGasLimit: "50000",
|
|
60
|
+
gasLimitMultiplier: 120,
|
|
61
|
+
feeMultiplier: 130,
|
|
62
|
+
debug: false,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export function getChainPreset(chainId) {
|
|
68
|
+
if (chainId == null) return null;
|
|
69
|
+
return CHAIN_PRESETS[Number(chainId)] ?? null;
|
|
70
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { getChainPreset } from "./chain-presets.js";
|
|
2
|
+
|
|
3
|
+
function pickDefined(value, fallback) {
|
|
4
|
+
return value === undefined ? fallback : value;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function normalizeSdkConfig(config) {
|
|
8
|
+
if (!config) throw new Error("init(config) 参数不能为空");
|
|
9
|
+
if (!config.chainId) throw new Error("缺少必填参数 chainId");
|
|
10
|
+
|
|
11
|
+
const preset = getChainPreset(config.chainId);
|
|
12
|
+
const chainId = Number(config.chainId);
|
|
13
|
+
|
|
14
|
+
const chain = {
|
|
15
|
+
id: chainId,
|
|
16
|
+
name: pickDefined(config.chainName, preset?.chain?.name ?? "AA Chain"),
|
|
17
|
+
rpcUrl: pickDefined(config.rpcUrl, preset?.chain?.rpcUrl),
|
|
18
|
+
};
|
|
19
|
+
if (!chain.rpcUrl) throw new Error(`chainId=${chainId} 缺少 rpcUrl 配置`);
|
|
20
|
+
|
|
21
|
+
const aa = {
|
|
22
|
+
...(preset?.aa ?? {}),
|
|
23
|
+
...(config.aa ?? {}),
|
|
24
|
+
accountType: pickDefined(
|
|
25
|
+
config.accountType,
|
|
26
|
+
pickDefined(config.aa?.accountType, preset?.aa?.accountType)
|
|
27
|
+
),
|
|
28
|
+
safeVersion: pickDefined(
|
|
29
|
+
config.safeVersion,
|
|
30
|
+
pickDefined(config.aa?.safeVersion, preset?.aa?.safeVersion)
|
|
31
|
+
),
|
|
32
|
+
entryPointVersion: pickDefined(
|
|
33
|
+
config.entryPointVersion,
|
|
34
|
+
pickDefined(config.aa?.entryPointVersion, preset?.aa?.entryPointVersion)
|
|
35
|
+
),
|
|
36
|
+
entryPointAddress: pickDefined(
|
|
37
|
+
config.entryPointAddress,
|
|
38
|
+
pickDefined(config.aa?.entryPointAddress, preset?.aa?.entryPointAddress)
|
|
39
|
+
),
|
|
40
|
+
factoryAddress: pickDefined(
|
|
41
|
+
config.factoryAddress,
|
|
42
|
+
pickDefined(config.aa?.factoryAddress, preset?.aa?.factoryAddress)
|
|
43
|
+
),
|
|
44
|
+
safeProxyFactoryAddress: pickDefined(
|
|
45
|
+
config.safeProxyFactoryAddress,
|
|
46
|
+
pickDefined(
|
|
47
|
+
config.aa?.safeProxyFactoryAddress,
|
|
48
|
+
preset?.aa?.safeProxyFactoryAddress
|
|
49
|
+
)
|
|
50
|
+
),
|
|
51
|
+
safeSingletonAddress: pickDefined(
|
|
52
|
+
config.safeSingletonAddress,
|
|
53
|
+
pickDefined(config.aa?.safeSingletonAddress, preset?.aa?.safeSingletonAddress)
|
|
54
|
+
),
|
|
55
|
+
safe4337ModuleAddress: pickDefined(
|
|
56
|
+
config.safe4337ModuleAddress,
|
|
57
|
+
pickDefined(
|
|
58
|
+
config.aa?.safe4337ModuleAddress,
|
|
59
|
+
preset?.aa?.safe4337ModuleAddress
|
|
60
|
+
)
|
|
61
|
+
),
|
|
62
|
+
safeModuleSetupAddress: pickDefined(
|
|
63
|
+
config.safeModuleSetupAddress,
|
|
64
|
+
pickDefined(config.aa?.safeModuleSetupAddress, preset?.aa?.safeModuleSetupAddress)
|
|
65
|
+
),
|
|
66
|
+
multiSendAddress: pickDefined(
|
|
67
|
+
config.multiSendAddress,
|
|
68
|
+
pickDefined(config.aa?.multiSendAddress, preset?.aa?.multiSendAddress)
|
|
69
|
+
),
|
|
70
|
+
multiSendCallOnlyAddress: pickDefined(
|
|
71
|
+
config.multiSendCallOnlyAddress,
|
|
72
|
+
pickDefined(
|
|
73
|
+
config.aa?.multiSendCallOnlyAddress,
|
|
74
|
+
preset?.aa?.multiSendCallOnlyAddress
|
|
75
|
+
)
|
|
76
|
+
),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const deployment = {
|
|
80
|
+
...(preset?.deployment ?? {}),
|
|
81
|
+
...(config.deployment ?? {}),
|
|
82
|
+
enabled: pickDefined(
|
|
83
|
+
config.deploymentEnabled,
|
|
84
|
+
pickDefined(config.deployment?.enabled, preset?.deployment?.enabled ?? true)
|
|
85
|
+
),
|
|
86
|
+
bundlerUrl: pickDefined(
|
|
87
|
+
config.bundlerUrl,
|
|
88
|
+
config.deployment?.bundlerUrl
|
|
89
|
+
),
|
|
90
|
+
bundlerApiKey: pickDefined(
|
|
91
|
+
config.bundlerApiKey,
|
|
92
|
+
config.deployment?.bundlerApiKey
|
|
93
|
+
),
|
|
94
|
+
paymasterServiceUrl: pickDefined(
|
|
95
|
+
config.paymasterServiceUrl,
|
|
96
|
+
config.deployment?.paymasterServiceUrl
|
|
97
|
+
),
|
|
98
|
+
paymasterAddress: pickDefined(
|
|
99
|
+
config.paymasterAddress,
|
|
100
|
+
pickDefined(config.deployment?.paymasterAddress, preset?.deployment?.paymasterAddress)
|
|
101
|
+
),
|
|
102
|
+
paymasterApiKey: pickDefined(
|
|
103
|
+
config.paymasterApiKey,
|
|
104
|
+
config.deployment?.paymasterApiKey
|
|
105
|
+
),
|
|
106
|
+
paymasterVerificationGasLimit: pickDefined(
|
|
107
|
+
config.paymasterVerificationGasLimit,
|
|
108
|
+
pickDefined(
|
|
109
|
+
config.deployment?.paymasterVerificationGasLimit,
|
|
110
|
+
preset?.deployment?.paymasterVerificationGasLimit
|
|
111
|
+
)
|
|
112
|
+
),
|
|
113
|
+
paymasterPostOpGasLimit: pickDefined(
|
|
114
|
+
config.paymasterPostOpGasLimit,
|
|
115
|
+
pickDefined(
|
|
116
|
+
config.deployment?.paymasterPostOpGasLimit,
|
|
117
|
+
preset?.deployment?.paymasterPostOpGasLimit
|
|
118
|
+
)
|
|
119
|
+
),
|
|
120
|
+
gasLimitMultiplier: pickDefined(
|
|
121
|
+
config.gasLimitMultiplier,
|
|
122
|
+
pickDefined(
|
|
123
|
+
config.deployment?.gasLimitMultiplier,
|
|
124
|
+
preset?.deployment?.gasLimitMultiplier
|
|
125
|
+
)
|
|
126
|
+
),
|
|
127
|
+
feeMultiplier: pickDefined(
|
|
128
|
+
config.feeMultiplier,
|
|
129
|
+
pickDefined(config.deployment?.feeMultiplier, preset?.deployment?.feeMultiplier)
|
|
130
|
+
),
|
|
131
|
+
debug: pickDefined(
|
|
132
|
+
config.deployDebug,
|
|
133
|
+
pickDefined(config.deployment?.debug, preset?.deployment?.debug ?? false)
|
|
134
|
+
),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
if (!deployment.bundlerUrl) {
|
|
138
|
+
throw new Error("缺少必填参数 bundlerUrl");
|
|
139
|
+
}
|
|
140
|
+
if (!deployment.paymasterServiceUrl) {
|
|
141
|
+
throw new Error("缺少必填参数 paymasterServiceUrl");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { chain, aa, deployment };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function normalizeCalls({ to, value = 0n, data = '0x', calls }) {
|
|
148
|
+
const normalizedCalls =
|
|
149
|
+
calls && calls.length
|
|
150
|
+
? calls
|
|
151
|
+
: to
|
|
152
|
+
? [{ to, value: BigInt(value), data }]
|
|
153
|
+
: null;
|
|
154
|
+
|
|
155
|
+
if (!normalizedCalls) {
|
|
156
|
+
throw new Error('sendTransaction 需要传入 calls 或 to');
|
|
157
|
+
}
|
|
158
|
+
return normalizedCalls;
|
|
159
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createAaWalletSdk } from "./sdk.js";
|
|
2
|
+
|
|
3
|
+
export { AaWalletSdk, createAaWalletSdk } from "./sdk.js";
|
|
4
|
+
export {
|
|
5
|
+
createMpcViemLocalAccount,
|
|
6
|
+
createMpcOwnerFromRest,
|
|
7
|
+
} from "./utils/aa-mpc.js";
|
|
8
|
+
export { mpcGetWallet, mpcSignHash } from "./utils/mpc-client.js";
|
|
9
|
+
export { createAaSmartAccountFromOwner } from "./utils/aa-wallet.js";
|
|
10
|
+
|
|
11
|
+
const defaultSdk = createAaWalletSdk();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 兼容模式:默认单例 init
|
|
15
|
+
*/
|
|
16
|
+
export function init(config) {
|
|
17
|
+
defaultSdk.init(config);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 兼容模式:默认单例 createWallet
|
|
22
|
+
*/
|
|
23
|
+
export async function createWallet(params) {
|
|
24
|
+
return defaultSdk.createWallet(params);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 兼容模式:默认单例 sendTransaction
|
|
29
|
+
*/
|
|
30
|
+
export async function sendTransaction(params) {
|
|
31
|
+
return defaultSdk.sendTransaction(params);
|
|
32
|
+
}
|
package/src/sdk.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { normalizeCalls, normalizeSdkConfig } from './core/config.js';
|
|
2
|
+
import {
|
|
3
|
+
buildWalletContext,
|
|
4
|
+
ensureWalletDeployed,
|
|
5
|
+
} from './services/wallet-service.js';
|
|
6
|
+
import { sendCallsBySmartAccount } from './services/transaction-service.js';
|
|
7
|
+
import { shouldAttemptSponsoredDeploy } from './utils/aa-deploy.js';
|
|
8
|
+
|
|
9
|
+
export class AaWalletSdk {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.config = null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
init(config) {
|
|
15
|
+
console.log('init 15', config);
|
|
16
|
+
this.config = normalizeSdkConfig(config);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getConfig() {
|
|
20
|
+
if (!this.config) {
|
|
21
|
+
throw new Error('SDK 未初始化,请先调用 init(config)');
|
|
22
|
+
}
|
|
23
|
+
return this.config;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async createWallet({ ownerPrivateKey, owner, index = 0 } = {}) {
|
|
27
|
+
const config = this.getConfig();
|
|
28
|
+
const {
|
|
29
|
+
publicClient,
|
|
30
|
+
owner: resolvedOwner,
|
|
31
|
+
smartAccount,
|
|
32
|
+
} = await buildWalletContext(config, { ownerPrivateKey, owner, index });
|
|
33
|
+
const { deployedOnChain, deployTxHash } = await ensureWalletDeployed(
|
|
34
|
+
config,
|
|
35
|
+
publicClient,
|
|
36
|
+
smartAccount,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
ownerAddress: resolvedOwner.address,
|
|
41
|
+
aaWalletAddress: smartAccount.address,
|
|
42
|
+
deployedOnChain,
|
|
43
|
+
deployTxHash,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async sendTransaction({
|
|
48
|
+
ownerPrivateKey,
|
|
49
|
+
owner,
|
|
50
|
+
index = 0,
|
|
51
|
+
to,
|
|
52
|
+
value = 0n,
|
|
53
|
+
data = '0x',
|
|
54
|
+
calls,
|
|
55
|
+
} = {}) {
|
|
56
|
+
const config = this.getConfig();
|
|
57
|
+
if (!shouldAttemptSponsoredDeploy(config.deployment)) {
|
|
58
|
+
throw new Error('缺少 bundler/paymaster 配置,无法发送赞助交易');
|
|
59
|
+
}
|
|
60
|
+
const normalizedCalls = normalizeCalls({ to, value, data, calls });
|
|
61
|
+
const {
|
|
62
|
+
publicClient,
|
|
63
|
+
owner: resolvedOwner,
|
|
64
|
+
smartAccount,
|
|
65
|
+
} = await buildWalletContext(config, { ownerPrivateKey, owner, index });
|
|
66
|
+
const { deployedOnChain } = await ensureWalletDeployed(
|
|
67
|
+
config,
|
|
68
|
+
publicClient,
|
|
69
|
+
smartAccount,
|
|
70
|
+
);
|
|
71
|
+
const txHash = await sendCallsBySmartAccount({
|
|
72
|
+
config,
|
|
73
|
+
publicClient,
|
|
74
|
+
smartAccount,
|
|
75
|
+
calls: normalizedCalls,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
ownerAddress: resolvedOwner.address,
|
|
80
|
+
aaWalletAddress: smartAccount.address,
|
|
81
|
+
deployedOnChain,
|
|
82
|
+
txHash,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function createAaWalletSdk() {
|
|
88
|
+
return new AaWalletSdk();
|
|
89
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { sendSponsoredAaCallViaPaymaster } from "../utils/aa-deploy.js";
|
|
2
|
+
|
|
3
|
+
export async function sendCallsBySmartAccount({
|
|
4
|
+
config,
|
|
5
|
+
publicClient,
|
|
6
|
+
smartAccount,
|
|
7
|
+
calls,
|
|
8
|
+
}) {
|
|
9
|
+
const callData = await smartAccount.encodeCalls(calls);
|
|
10
|
+
const txHash = await sendSponsoredAaCallViaPaymaster({
|
|
11
|
+
publicClient,
|
|
12
|
+
smartAccount,
|
|
13
|
+
deployment: config.deployment,
|
|
14
|
+
callData,
|
|
15
|
+
});
|
|
16
|
+
return txHash;
|
|
17
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAaPublicClient,
|
|
3
|
+
createAaSmartAccountFromOwner,
|
|
4
|
+
createAaWallet,
|
|
5
|
+
createChainFromConfig,
|
|
6
|
+
} from "../utils/aa-wallet.js";
|
|
7
|
+
import {
|
|
8
|
+
deployAaWalletViaPaymaster,
|
|
9
|
+
shouldAttemptSponsoredDeploy,
|
|
10
|
+
} from "../utils/aa-deploy.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} config
|
|
14
|
+
* @param {{ ownerPrivateKey?: `0x${string}`; owner?: import("viem/accounts").LocalAccount; index?: number }} options
|
|
15
|
+
*/
|
|
16
|
+
export async function buildWalletContext(config, options) {
|
|
17
|
+
const index = options.index ?? 0;
|
|
18
|
+
const { ownerPrivateKey, owner } = options;
|
|
19
|
+
|
|
20
|
+
if (!ownerPrivateKey && !owner) {
|
|
21
|
+
throw new Error("缺少 ownerPrivateKey 或 owner(例如 MPC LocalAccount)");
|
|
22
|
+
}
|
|
23
|
+
if (ownerPrivateKey && owner) {
|
|
24
|
+
throw new Error("请勿同时传入 ownerPrivateKey 与 owner");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const publicClient = createAaPublicClient(config.chain);
|
|
28
|
+
const { owner: resolvedOwner, smartAccount } = owner
|
|
29
|
+
? await createAaSmartAccountFromOwner({
|
|
30
|
+
publicClient,
|
|
31
|
+
owner,
|
|
32
|
+
index,
|
|
33
|
+
aa: config.aa,
|
|
34
|
+
})
|
|
35
|
+
: await createAaWallet({
|
|
36
|
+
publicClient,
|
|
37
|
+
privateKey: /** @type {`0x${string}`} */ (ownerPrivateKey),
|
|
38
|
+
index,
|
|
39
|
+
aa: config.aa,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return { publicClient, owner: resolvedOwner, smartAccount };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function ensureWalletDeployed(
|
|
46
|
+
config,
|
|
47
|
+
publicClient,
|
|
48
|
+
smartAccount
|
|
49
|
+
) {
|
|
50
|
+
let deployedOnChain = await smartAccount.isDeployed();
|
|
51
|
+
let deployTxHash = null;
|
|
52
|
+
|
|
53
|
+
if (!deployedOnChain && shouldAttemptSponsoredDeploy(config.deployment)) {
|
|
54
|
+
const chain = createChainFromConfig(config.chain);
|
|
55
|
+
deployTxHash = await deployAaWalletViaPaymaster({
|
|
56
|
+
publicClient,
|
|
57
|
+
chain,
|
|
58
|
+
smartAccount,
|
|
59
|
+
deployment: config.deployment,
|
|
60
|
+
});
|
|
61
|
+
deployedOnChain = await smartAccount.isDeployed();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { deployedOnChain, deployTxHash };
|
|
65
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { encodeFunctionData } from "viem";
|
|
2
|
+
import { createAaWallet, createAaPublicClient } from "./aa-wallet.js";
|
|
3
|
+
import {
|
|
4
|
+
deployAaWalletViaPaymaster,
|
|
5
|
+
sendSponsoredAaCallViaPaymaster,
|
|
6
|
+
shouldAttemptSponsoredDeploy,
|
|
7
|
+
} from "./aa-deploy.js";
|
|
8
|
+
|
|
9
|
+
const erc20Abi = [
|
|
10
|
+
{
|
|
11
|
+
type: "function",
|
|
12
|
+
name: "approve",
|
|
13
|
+
stateMutability: "nonpayable",
|
|
14
|
+
inputs: [
|
|
15
|
+
{ name: "spender", type: "address" },
|
|
16
|
+
{ name: "value", type: "uint256" },
|
|
17
|
+
],
|
|
18
|
+
outputs: [{ name: "", type: "bool" }],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: "function",
|
|
22
|
+
name: "transfer",
|
|
23
|
+
stateMutability: "nonpayable",
|
|
24
|
+
inputs: [
|
|
25
|
+
{ name: "to", type: "address" },
|
|
26
|
+
{ name: "value", type: "uint256" },
|
|
27
|
+
],
|
|
28
|
+
outputs: [{ name: "", type: "bool" }],
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 通过 AA 钱包调用目标合约(默认通过 paymaster 代付)。
|
|
34
|
+
*
|
|
35
|
+
* @param {object} params
|
|
36
|
+
* @param {object} params.config 与运行时配置结构一致
|
|
37
|
+
* @param {number} [params.accountIndex=0] 使用第几个 owner 账户
|
|
38
|
+
* @param {{ to: `0x${string}`; value?: bigint; data?: `0x${string}` }[]} params.calls
|
|
39
|
+
* @returns {Promise<{ ownerAddress: `0x${string}`; aaWalletAddress: `0x${string}`; txHash: `0x${string}`; deployedOnChain: boolean }>}
|
|
40
|
+
*/
|
|
41
|
+
export async function callContractByAaWallet({
|
|
42
|
+
config,
|
|
43
|
+
accountIndex = 0,
|
|
44
|
+
calls,
|
|
45
|
+
}) {
|
|
46
|
+
const account = config.accounts?.[accountIndex];
|
|
47
|
+
if (!account) {
|
|
48
|
+
throw new Error(`accounts[${accountIndex}] 不存在`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!shouldAttemptSponsoredDeploy(config.deployment)) {
|
|
52
|
+
throw new Error("deployment 未启用或缺少 bundler/paymaster 配置");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const publicClient = createAaPublicClient(config.chain);
|
|
56
|
+
const { owner, smartAccount } = await createAaWallet({
|
|
57
|
+
publicClient,
|
|
58
|
+
privateKey: account.privateKey,
|
|
59
|
+
index: account.index,
|
|
60
|
+
aa: config.aa,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
let deployedOnChain = await smartAccount.isDeployed();
|
|
64
|
+
if (!deployedOnChain) {
|
|
65
|
+
await deployAaWalletViaPaymaster({
|
|
66
|
+
publicClient,
|
|
67
|
+
smartAccount,
|
|
68
|
+
deployment: config.deployment,
|
|
69
|
+
});
|
|
70
|
+
deployedOnChain = await smartAccount.isDeployed();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const callData = await smartAccount.encodeCalls(calls);
|
|
74
|
+
const txHash = await sendSponsoredAaCallViaPaymaster({
|
|
75
|
+
publicClient,
|
|
76
|
+
smartAccount,
|
|
77
|
+
deployment: config.deployment,
|
|
78
|
+
callData,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
ownerAddress: owner.address,
|
|
83
|
+
aaWalletAddress: smartAccount.address,
|
|
84
|
+
txHash,
|
|
85
|
+
deployedOnChain,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* AA 钱包执行 ERC20 transfer。
|
|
91
|
+
*
|
|
92
|
+
* @param {object} params
|
|
93
|
+
* @param {object} params.config 与运行时配置结构一致
|
|
94
|
+
* @param {`0x${string}`} params.tokenAddress
|
|
95
|
+
* @param {`0x${string}`} params.to
|
|
96
|
+
* @param {bigint} params.amount
|
|
97
|
+
* @param {number} [params.accountIndex=0]
|
|
98
|
+
*/
|
|
99
|
+
export async function transferErc20ByAaWallet({
|
|
100
|
+
config,
|
|
101
|
+
tokenAddress,
|
|
102
|
+
to,
|
|
103
|
+
amount,
|
|
104
|
+
accountIndex = 0,
|
|
105
|
+
}) {
|
|
106
|
+
const data = encodeFunctionData({
|
|
107
|
+
abi: erc20Abi,
|
|
108
|
+
functionName: "transfer",
|
|
109
|
+
args: [to, amount],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return callContractByAaWallet({
|
|
113
|
+
config,
|
|
114
|
+
accountIndex,
|
|
115
|
+
calls: [
|
|
116
|
+
{
|
|
117
|
+
to: tokenAddress,
|
|
118
|
+
value: 0n,
|
|
119
|
+
data,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* AA 钱包执行 ERC20 approve。
|
|
127
|
+
*
|
|
128
|
+
* @param {object} params
|
|
129
|
+
* @param {object} params.config 与运行时配置结构一致
|
|
130
|
+
* @param {`0x${string}`} params.tokenAddress
|
|
131
|
+
* @param {`0x${string}`} params.spender
|
|
132
|
+
* @param {bigint} params.amount
|
|
133
|
+
* @param {number} [params.accountIndex=0]
|
|
134
|
+
*/
|
|
135
|
+
export async function approveErc20ByAaWallet({
|
|
136
|
+
config,
|
|
137
|
+
tokenAddress,
|
|
138
|
+
spender,
|
|
139
|
+
amount,
|
|
140
|
+
accountIndex = 0,
|
|
141
|
+
}) {
|
|
142
|
+
const data = encodeFunctionData({
|
|
143
|
+
abi: erc20Abi,
|
|
144
|
+
functionName: "approve",
|
|
145
|
+
args: [spender, amount],
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return callContractByAaWallet({
|
|
149
|
+
config,
|
|
150
|
+
accountIndex,
|
|
151
|
+
calls: [
|
|
152
|
+
{
|
|
153
|
+
to: tokenAddress,
|
|
154
|
+
value: 0n,
|
|
155
|
+
data,
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
}
|