@mycelium-sdk/core 0.1.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 +145 -0
- package/dist/index.cjs +2500 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1568 -0
- package/dist/index.d.ts +1568 -0
- package/dist/index.js +2481 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2500 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
DefaultSmartWallet: () => DefaultSmartWallet,
|
|
34
|
+
MyceliumSDK: () => MyceliumSDK
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/wallet/DefaultSmartWallet.ts
|
|
39
|
+
var import_viem3 = require("viem");
|
|
40
|
+
var import_account_abstraction = require("viem/account-abstraction");
|
|
41
|
+
|
|
42
|
+
// src/abis/smartWalletFactory.ts
|
|
43
|
+
var smartWalletFactoryAbi = [
|
|
44
|
+
{
|
|
45
|
+
type: "constructor",
|
|
46
|
+
inputs: [
|
|
47
|
+
{ name: "implementation_", type: "address", internalType: "address" }
|
|
48
|
+
],
|
|
49
|
+
stateMutability: "payable"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "function",
|
|
53
|
+
name: "createAccount",
|
|
54
|
+
inputs: [
|
|
55
|
+
{ name: "owners", type: "bytes[]", internalType: "bytes[]" },
|
|
56
|
+
{ name: "nonce", type: "uint256", internalType: "uint256" }
|
|
57
|
+
],
|
|
58
|
+
outputs: [
|
|
59
|
+
{
|
|
60
|
+
name: "account",
|
|
61
|
+
type: "address",
|
|
62
|
+
internalType: "contract CoinbaseSmartWallet"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
stateMutability: "payable"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: "function",
|
|
69
|
+
name: "getAddress",
|
|
70
|
+
inputs: [
|
|
71
|
+
{ name: "owners", type: "bytes[]", internalType: "bytes[]" },
|
|
72
|
+
{ name: "nonce", type: "uint256", internalType: "uint256" }
|
|
73
|
+
],
|
|
74
|
+
outputs: [{ name: "", type: "address", internalType: "address" }],
|
|
75
|
+
stateMutability: "view"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: "function",
|
|
79
|
+
name: "implementation",
|
|
80
|
+
inputs: [],
|
|
81
|
+
outputs: [{ name: "", type: "address", internalType: "address" }],
|
|
82
|
+
stateMutability: "view"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: "function",
|
|
86
|
+
name: "initCodeHash",
|
|
87
|
+
inputs: [],
|
|
88
|
+
outputs: [{ name: "", type: "bytes32", internalType: "bytes32" }],
|
|
89
|
+
stateMutability: "view"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: "event",
|
|
93
|
+
name: "AccountCreated",
|
|
94
|
+
inputs: [
|
|
95
|
+
{
|
|
96
|
+
name: "account",
|
|
97
|
+
type: "address",
|
|
98
|
+
indexed: true,
|
|
99
|
+
internalType: "address"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "owners",
|
|
103
|
+
type: "bytes[]",
|
|
104
|
+
indexed: false,
|
|
105
|
+
internalType: "bytes[]"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "nonce",
|
|
109
|
+
type: "uint256",
|
|
110
|
+
indexed: false,
|
|
111
|
+
internalType: "uint256"
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
anonymous: false
|
|
115
|
+
},
|
|
116
|
+
{ type: "error", name: "ImplementationUndeployed", inputs: [] },
|
|
117
|
+
{ type: "error", name: "OwnerRequired", inputs: [] }
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
// src/constants/addresses.ts
|
|
121
|
+
var smartWalletFactoryAddress = "0xBA5ED110eFDBa3D005bfC882d75358ACBbB85842";
|
|
122
|
+
|
|
123
|
+
// src/tools/TokenBalance.ts
|
|
124
|
+
var import_viem = require("viem");
|
|
125
|
+
|
|
126
|
+
// src/constants/tokens.ts
|
|
127
|
+
var import_chains = require("viem/chains");
|
|
128
|
+
var SUPPORTED_TOKENS = {
|
|
129
|
+
ETH: {
|
|
130
|
+
symbol: "ETH",
|
|
131
|
+
name: "Ethereum",
|
|
132
|
+
decimals: 18,
|
|
133
|
+
addresses: {
|
|
134
|
+
[import_chains.mainnet.id]: "0x0000000000000000000000000000000000000000",
|
|
135
|
+
[import_chains.unichain.id]: "0x0000000000000000000000000000000000000000",
|
|
136
|
+
[import_chains.base.id]: "0x0000000000000000000000000000000000000000",
|
|
137
|
+
[import_chains.baseSepolia.id]: "0x0000000000000000000000000000000000000000",
|
|
138
|
+
[import_chains.sepolia.id]: "0x0000000000000000000000000000000000000000"
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
USDC: {
|
|
142
|
+
symbol: "USDC",
|
|
143
|
+
name: "USDC",
|
|
144
|
+
decimals: 6,
|
|
145
|
+
addresses: {
|
|
146
|
+
[import_chains.mainnet.id]: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
|
147
|
+
[import_chains.unichain.id]: "0x078d782b760474a361dda0af3839290b0ef57ad6",
|
|
148
|
+
[import_chains.baseSepolia.id]: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
149
|
+
[import_chains.sepolia.id]: "0xf08A50178dfcDe18524640EA6618a1f965821715",
|
|
150
|
+
[import_chains.base.id]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/utils/tokens.ts
|
|
156
|
+
function getTokenAddress(symbol, chainId) {
|
|
157
|
+
const token = SUPPORTED_TOKENS[symbol];
|
|
158
|
+
return token?.addresses[chainId] || null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/tools/TokenBalance.ts
|
|
162
|
+
async function fetchETHBalance(chainManager, walletAddress) {
|
|
163
|
+
const supportedChain = chainManager.getSupportedChain();
|
|
164
|
+
const publicClient = chainManager.getPublicClient(supportedChain);
|
|
165
|
+
const balance = await publicClient.getBalance({
|
|
166
|
+
address: walletAddress
|
|
167
|
+
});
|
|
168
|
+
return {
|
|
169
|
+
symbol: "ETH",
|
|
170
|
+
totalBalance: balance,
|
|
171
|
+
totalFormattedBalance: (0, import_viem.formatEther)(balance),
|
|
172
|
+
chainBalances: [
|
|
173
|
+
{
|
|
174
|
+
chainId: supportedChain,
|
|
175
|
+
balance,
|
|
176
|
+
formattedBalance: (0, import_viem.formatEther)(balance)
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
async function fetchERC20Balance(chainManager, walletAddress, token) {
|
|
182
|
+
const supportedChain = chainManager.getSupportedChain();
|
|
183
|
+
const balance = await fetchERC20BalanceForChain(
|
|
184
|
+
token,
|
|
185
|
+
supportedChain,
|
|
186
|
+
walletAddress,
|
|
187
|
+
chainManager
|
|
188
|
+
);
|
|
189
|
+
return {
|
|
190
|
+
symbol: token.symbol,
|
|
191
|
+
totalBalance: balance,
|
|
192
|
+
totalFormattedBalance: (0, import_viem.formatUnits)(balance, token.decimals),
|
|
193
|
+
chainBalances: [
|
|
194
|
+
{
|
|
195
|
+
chainId: supportedChain,
|
|
196
|
+
balance,
|
|
197
|
+
formattedBalance: (0, import_viem.formatUnits)(balance, token.decimals)
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
async function fetchERC20BalanceForChain(token, chainId, walletAddress, chainManager) {
|
|
203
|
+
const tokenAddress = getTokenAddress(token.symbol, chainId);
|
|
204
|
+
if (!tokenAddress) {
|
|
205
|
+
throw new Error(`${token.symbol} not supported on chain ${chainId}`);
|
|
206
|
+
}
|
|
207
|
+
const publicClient = chainManager.getPublicClient(chainId);
|
|
208
|
+
if (token.symbol === "ETH") {
|
|
209
|
+
return publicClient.getBalance({
|
|
210
|
+
address: walletAddress
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return publicClient.readContract({
|
|
214
|
+
address: tokenAddress,
|
|
215
|
+
abi: import_viem.erc20Abi,
|
|
216
|
+
functionName: "balanceOf",
|
|
217
|
+
args: [walletAddress]
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/utils/assets.ts
|
|
222
|
+
var import_viem2 = require("viem");
|
|
223
|
+
function resolveAsset(asset, chainId) {
|
|
224
|
+
if (asset.startsWith("0x")) {
|
|
225
|
+
const address2 = asset;
|
|
226
|
+
for (const [, tokenInfo2] of Object.entries(SUPPORTED_TOKENS)) {
|
|
227
|
+
const tokenAddress = tokenInfo2.addresses[chainId];
|
|
228
|
+
if (tokenAddress && tokenAddress.toLowerCase() === address2.toLowerCase()) {
|
|
229
|
+
return {
|
|
230
|
+
address: tokenAddress,
|
|
231
|
+
symbol: tokenInfo2.symbol,
|
|
232
|
+
decimals: tokenInfo2.decimals
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
throw new Error(
|
|
237
|
+
`Unknown asset address: ${address2}. Please use a supported asset symbol like 'usdc' or add the token to SUPPORTED_TOKENS.`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
const normalizedSymbol = asset.toUpperCase();
|
|
241
|
+
const tokenInfo = SUPPORTED_TOKENS[normalizedSymbol];
|
|
242
|
+
if (!tokenInfo) {
|
|
243
|
+
const availableSymbols = Object.keys(SUPPORTED_TOKENS).join(", ");
|
|
244
|
+
throw new Error(`Unsupported asset symbol: ${asset}. Supported assets: ${availableSymbols}`);
|
|
245
|
+
}
|
|
246
|
+
const address = getTokenAddress(normalizedSymbol, chainId);
|
|
247
|
+
if (!address) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`Asset ${asset} is not supported on chain ${chainId}. Available chains: ${Object.keys(tokenInfo.addresses).join(", ")}`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
address,
|
|
254
|
+
symbol: tokenInfo.symbol,
|
|
255
|
+
decimals: tokenInfo.decimals
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function parseAssetAmount(amount, decimals) {
|
|
259
|
+
const amountStr = amount.toString();
|
|
260
|
+
return (0, import_viem2.parseUnits)(amountStr, decimals);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/wallet/base/wallets/SmartWallet.ts
|
|
264
|
+
var SmartWallet = class {
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// src/wallet/DefaultSmartWallet.ts
|
|
268
|
+
var DefaultSmartWallet = class extends SmartWallet {
|
|
269
|
+
/**
|
|
270
|
+
* Creates a smart wallet instance
|
|
271
|
+
*
|
|
272
|
+
* @internal
|
|
273
|
+
* @param owners Owners (addresses or WebAuthn accounts)
|
|
274
|
+
* @param signer Local account used to sign
|
|
275
|
+
* @param chainManager Chain/client manager
|
|
276
|
+
* @param protocolProvider Protocol provider instance (selected upstream)
|
|
277
|
+
* @param deploymentAddress Optional known deployment address
|
|
278
|
+
* @param signerOwnerIndex Optional index of `signer` in owners (default 0)
|
|
279
|
+
* @param nonce Optional salt for deterministic address calc (default 0)
|
|
280
|
+
*/
|
|
281
|
+
constructor(owners, signer, chainManager, protocolProvider, coinbaseCDP, deploymentAddress, signerOwnerIndex, nonce) {
|
|
282
|
+
super();
|
|
283
|
+
this.owners = owners;
|
|
284
|
+
this._signer = signer;
|
|
285
|
+
this.signerOwnerIndex = signerOwnerIndex;
|
|
286
|
+
this.deploymentAddress = deploymentAddress;
|
|
287
|
+
this.chainManager = chainManager;
|
|
288
|
+
this.nonce = nonce;
|
|
289
|
+
this.protocolProvider = protocolProvider;
|
|
290
|
+
this.coinbaseCDP = coinbaseCDP;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Returns the signer account for this smart wallet
|
|
294
|
+
*
|
|
295
|
+
* @public
|
|
296
|
+
* @category Account
|
|
297
|
+
* @remarks
|
|
298
|
+
* Used to authorize UserOperations and on-chain transactions
|
|
299
|
+
*/
|
|
300
|
+
get signer() {
|
|
301
|
+
return this._signer;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Resolves the smart wallet address
|
|
305
|
+
*
|
|
306
|
+
* @public
|
|
307
|
+
* @category Account
|
|
308
|
+
* @remarks
|
|
309
|
+
* If `deploymentAddress` is known, returns it. Otherwise derives a deterministic address
|
|
310
|
+
* via the factory (`getAddress`) using owners and `nonce` (CREATE2-style)
|
|
311
|
+
*
|
|
312
|
+
* @returns Promise that resolves to the wallet address
|
|
313
|
+
* @throws Error if no supported chains are configured
|
|
314
|
+
* @throws Error if an owner has an invalid type
|
|
315
|
+
*/
|
|
316
|
+
async getAddress() {
|
|
317
|
+
if (this.deploymentAddress) {
|
|
318
|
+
return this.deploymentAddress;
|
|
319
|
+
}
|
|
320
|
+
const owners_bytes = this.owners.map((owner) => {
|
|
321
|
+
if (typeof owner === "string") {
|
|
322
|
+
return (0, import_viem3.pad)(owner);
|
|
323
|
+
}
|
|
324
|
+
if (owner.type === "webAuthn") {
|
|
325
|
+
return owner.publicKey;
|
|
326
|
+
}
|
|
327
|
+
throw new Error("invalid owner type");
|
|
328
|
+
});
|
|
329
|
+
const supportedChain = this.chainManager.getSupportedChain();
|
|
330
|
+
if (!supportedChain) {
|
|
331
|
+
throw new Error("No supported chains configured");
|
|
332
|
+
}
|
|
333
|
+
const publicClient = this.chainManager.getPublicClient(supportedChain);
|
|
334
|
+
const smartWalletAddress = await publicClient.readContract({
|
|
335
|
+
abi: smartWalletFactoryAbi,
|
|
336
|
+
address: smartWalletFactoryAddress,
|
|
337
|
+
functionName: "getAddress",
|
|
338
|
+
args: [owners_bytes, this.nonce || 0n]
|
|
339
|
+
});
|
|
340
|
+
return smartWalletAddress;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Builds a Coinbase Smart Account for a specific chain
|
|
344
|
+
*
|
|
345
|
+
* @internal
|
|
346
|
+
* @category Account
|
|
347
|
+
* @param chainId Target chain ID
|
|
348
|
+
* @returns Viem Coinbase Smart Account for the given chain
|
|
349
|
+
*/
|
|
350
|
+
async getCoinbaseSmartAccount(chainId) {
|
|
351
|
+
return (0, import_account_abstraction.toCoinbaseSmartAccount)({
|
|
352
|
+
address: this.deploymentAddress,
|
|
353
|
+
ownerIndex: this.signerOwnerIndex,
|
|
354
|
+
client: this.chainManager.getPublicClient(chainId),
|
|
355
|
+
owners: [this.signer],
|
|
356
|
+
nonce: this.nonce,
|
|
357
|
+
version: "1.1"
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Fetches balances (ETH + ERC-20) for a smart account across supported chains
|
|
362
|
+
*
|
|
363
|
+
* @public
|
|
364
|
+
* @category Account
|
|
365
|
+
* @returns Promise resolving to a list of {@link TokenBalance}
|
|
366
|
+
*/
|
|
367
|
+
async getBalance() {
|
|
368
|
+
const address = await this.getAddress();
|
|
369
|
+
const tokenBalancePromises = Object.values(SUPPORTED_TOKENS).map(async (token) => {
|
|
370
|
+
return fetchERC20Balance(this.chainManager, address, token);
|
|
371
|
+
});
|
|
372
|
+
const ethBalancePromise = fetchETHBalance(this.chainManager, address);
|
|
373
|
+
return Promise.all([ethBalancePromise, ...tokenBalancePromises]);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Deposits into the selected protocol’s vault to start earning yield
|
|
377
|
+
* @public
|
|
378
|
+
* @category Earn
|
|
379
|
+
* @remarks
|
|
380
|
+
* The protocol is selected on the SDK initialization step
|
|
381
|
+
* @param amount Human-readable amount string
|
|
382
|
+
* @returns Transaction result for the deposit
|
|
383
|
+
*/
|
|
384
|
+
async earn(amount) {
|
|
385
|
+
this.chainManager.getSupportedChain();
|
|
386
|
+
const depositTransactionResult = this.protocolProvider.deposit(amount, this);
|
|
387
|
+
return depositTransactionResult;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Reads current deposit balance from the selected protocol’s vault
|
|
391
|
+
* Method to read the current deposit balance from the selected protocol’s vault for a smart account
|
|
392
|
+
*
|
|
393
|
+
* @public
|
|
394
|
+
* @category Earn
|
|
395
|
+
* @returns Vault balance or `null` if nothing deposited
|
|
396
|
+
*/
|
|
397
|
+
async getEarnBalance() {
|
|
398
|
+
const depositedVault = await this.protocolProvider.fetchDepositedVaults(this);
|
|
399
|
+
if (!depositedVault) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
const userAddress = await this.getAddress();
|
|
403
|
+
return this.protocolProvider.getBalance(depositedVault, userAddress);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Withdraws from the selected protocol’s vault
|
|
407
|
+
* @public
|
|
408
|
+
* @category Earn
|
|
409
|
+
* @param amount Human-readable amount string
|
|
410
|
+
* @returns Transaction result for the withdrawal
|
|
411
|
+
* @throws Error if the withdrawal fails
|
|
412
|
+
* @throws Error a user didn't deposit anything
|
|
413
|
+
*/
|
|
414
|
+
async withdraw(amount) {
|
|
415
|
+
const withdrawTransactionResult = await this.protocolProvider.withdraw(amount, this);
|
|
416
|
+
return withdrawTransactionResult;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Builds a UserOperation and submits via the bundler, then waits for inclusion
|
|
420
|
+
|
|
421
|
+
*
|
|
422
|
+
* @public
|
|
423
|
+
* @category Transactions
|
|
424
|
+
*
|
|
425
|
+
* @param transactionData Transaction details (`to`, `value`, `data`)
|
|
426
|
+
* @param chainId Target chain ID
|
|
427
|
+
* @returns Promise that resolves to the UserOperation hash
|
|
428
|
+
* @throws Error with a readable message if submission or inclusion fails
|
|
429
|
+
*/
|
|
430
|
+
async send(transactionData, chainId) {
|
|
431
|
+
try {
|
|
432
|
+
const account = await this.getCoinbaseSmartAccount(chainId);
|
|
433
|
+
const bundlerClient = this.chainManager.getBundlerClient(chainId, account);
|
|
434
|
+
const bump = (x, pct = 40n) => x + x * pct / 100n;
|
|
435
|
+
const gas = await bundlerClient.estimateUserOperationGas({
|
|
436
|
+
account,
|
|
437
|
+
calls: [transactionData]
|
|
438
|
+
});
|
|
439
|
+
const hash = await bundlerClient.sendUserOperation({
|
|
440
|
+
account,
|
|
441
|
+
calls: [transactionData],
|
|
442
|
+
callGasLimit: bump(gas.callGasLimit),
|
|
443
|
+
verificationGasLimit: bump(gas.verificationGasLimit),
|
|
444
|
+
preVerificationGas: bump(gas.preVerificationGas)
|
|
445
|
+
});
|
|
446
|
+
await bundlerClient.waitForUserOperationReceipt({
|
|
447
|
+
hash
|
|
448
|
+
});
|
|
449
|
+
return hash;
|
|
450
|
+
} catch (error) {
|
|
451
|
+
throw new Error(
|
|
452
|
+
`Failed to send transaction: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Builds a UserOperation from several onchain transactions and submits via the bundler, then waits for inclusion
|
|
458
|
+
*
|
|
459
|
+
* @public
|
|
460
|
+
* @category Transactions
|
|
461
|
+
*
|
|
462
|
+
* @param transactionData An array of calls to execute
|
|
463
|
+
* @param chainId Target chain ID
|
|
464
|
+
* @returns Promise that resolves to the UserOperation hash for the batch
|
|
465
|
+
* @throws Error with a readable message if submission or inclusion fails
|
|
466
|
+
*/
|
|
467
|
+
async sendBatch(transactionData, chainId) {
|
|
468
|
+
try {
|
|
469
|
+
const account = await this.getCoinbaseSmartAccount(chainId);
|
|
470
|
+
const bundlerClient = this.chainManager.getBundlerClient(chainId, account);
|
|
471
|
+
const bump = (x, pct = 40n) => x + x * pct / 100n;
|
|
472
|
+
const gas = await bundlerClient.estimateUserOperationGas({
|
|
473
|
+
account,
|
|
474
|
+
calls: transactionData
|
|
475
|
+
});
|
|
476
|
+
const hash = await bundlerClient.sendUserOperation({
|
|
477
|
+
account,
|
|
478
|
+
calls: transactionData,
|
|
479
|
+
callGasLimit: bump(gas.callGasLimit),
|
|
480
|
+
verificationGasLimit: bump(gas.verificationGasLimit),
|
|
481
|
+
preVerificationGas: bump(gas.preVerificationGas)
|
|
482
|
+
});
|
|
483
|
+
await bundlerClient.waitForUserOperationReceipt({
|
|
484
|
+
hash
|
|
485
|
+
});
|
|
486
|
+
return hash;
|
|
487
|
+
} catch (error) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
`Failed to send transaction: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Funds the smart wallet with the specified amount of the specified token via Coinbase CDP on-ramp service
|
|
495
|
+
*
|
|
496
|
+
* @public
|
|
497
|
+
* @category Ramp
|
|
498
|
+
*
|
|
499
|
+
* @remarks
|
|
500
|
+
* If Coinbase CDP is not initialized, the method will throw an error. For more details, visit @see {@link https://docs.cdp.coinbase.com/api-reference/v2/rest-api/onramp/create-an-onramp-session}
|
|
501
|
+
*
|
|
502
|
+
* @param amount Amount of token that a user wants to purchase and top up his account with (e.g., `"100"`, `"1.5"`)
|
|
503
|
+
* @param redirectUrl URL to redirect to after the on-ramp is complete. It's required to be a valid URL
|
|
504
|
+
* @param purchaseCurrency Purchase currency (e.g., `"USDC"`, `"ETH"`)
|
|
505
|
+
* @param paymentCurrency Payment currency (e.g., `"USD"`, `"EUR"`)
|
|
506
|
+
* @param paymentMethod Payment method (e.g., `"CARD"`)
|
|
507
|
+
* @param chain Chain name (e.g., `"base"`)
|
|
508
|
+
* @param country Country code (e.g., `"US"`)
|
|
509
|
+
*
|
|
510
|
+
*
|
|
511
|
+
* @returns @see {@link OnRampUrlResponse}
|
|
512
|
+
*/
|
|
513
|
+
async topUp(amount, redirectUrl, purchaseCurrency, paymentCurrency, paymentMethod, country) {
|
|
514
|
+
if (!this.coinbaseCDP) {
|
|
515
|
+
throw new Error(
|
|
516
|
+
"Coinbase CDP is not initialized. Please, provide the configuration in the SDK initialization"
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
const address = await this.getAddress();
|
|
520
|
+
const onRampLink = await this.coinbaseCDP.getOnRampLink(
|
|
521
|
+
address,
|
|
522
|
+
redirectUrl,
|
|
523
|
+
amount,
|
|
524
|
+
purchaseCurrency,
|
|
525
|
+
paymentCurrency,
|
|
526
|
+
paymentMethod,
|
|
527
|
+
country
|
|
528
|
+
);
|
|
529
|
+
return onRampLink;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Cashout token from smart wallet to fiat currency via Coinbase CDP off-ramp service
|
|
533
|
+
*
|
|
534
|
+
* @public
|
|
535
|
+
* @category Ramp
|
|
536
|
+
*
|
|
537
|
+
* @remarks
|
|
538
|
+
* If Coinbase CDP is not initialized, the method will throw an error. For more details, visit @see {@link https://docs.cdp.coinbase.com/api-reference/rest-api/onramp-offramp/create-sell-quote}
|
|
539
|
+
*
|
|
540
|
+
* @param country Country code (e.g., `"US"`)
|
|
541
|
+
* @param paymentMethod Payment method (e.g., `"CARD"`). To get the ful list, visit ""
|
|
542
|
+
* @param redirectUrl URL to redirect to after the off-ramp is complete. It's required to be a valid URL
|
|
543
|
+
* @param sellAmount Amount of token that a user wants to sell (e.g., `"100"`, `"1.5"`)
|
|
544
|
+
* @param cashoutCurrency Cashout currency (e.g., `"USD"`, `"EUR"`). To get the ful list, visit ""
|
|
545
|
+
* @param sellCurrency Sell currency (e.g., `"USDC"`, `"ETH"`). To get the ful list, visit ""
|
|
546
|
+
*
|
|
547
|
+
*
|
|
548
|
+
* @returns @see {@link OffRampUrlResponse}
|
|
549
|
+
*/
|
|
550
|
+
async cashOut(country, paymentMethod, redirectUrl, sellAmount, cashoutCurrency, sellCurrency) {
|
|
551
|
+
if (!this.coinbaseCDP) {
|
|
552
|
+
throw new Error(
|
|
553
|
+
"Coinbase CDP is not initialized. Please, provide the configuration in the SDK initialization"
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
const address = await this.getAddress();
|
|
557
|
+
const offRampLink = await this.coinbaseCDP.getOffRampLink(
|
|
558
|
+
address,
|
|
559
|
+
country,
|
|
560
|
+
paymentMethod,
|
|
561
|
+
redirectUrl,
|
|
562
|
+
sellAmount,
|
|
563
|
+
cashoutCurrency,
|
|
564
|
+
sellCurrency
|
|
565
|
+
);
|
|
566
|
+
return offRampLink;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Send tokens from a smart account to another address
|
|
570
|
+
*
|
|
571
|
+
* @public
|
|
572
|
+
* @category Transactions
|
|
573
|
+
*
|
|
574
|
+
* @param amount Human-readable amount (e.g., `1.5`)
|
|
575
|
+
* @param asset Asset symbol (e.g., `"usdc"`, `"eth"`) or token address
|
|
576
|
+
* @param recipientAddress Destination address
|
|
577
|
+
* @returns Transaction data suitable for inclusion in a UserOperation/call
|
|
578
|
+
* @throws Error if `recipientAddress` is missing, `amount` ≤ 0, or asset cannot be resolved
|
|
579
|
+
*/
|
|
580
|
+
async sendTokens(amount, asset, recipientAddress) {
|
|
581
|
+
if (!recipientAddress) {
|
|
582
|
+
throw new Error("Recipient address is required");
|
|
583
|
+
}
|
|
584
|
+
if (amount <= 0) {
|
|
585
|
+
throw new Error("Amount must be greater than 0");
|
|
586
|
+
}
|
|
587
|
+
const chainId = this.chainManager.getSupportedChain();
|
|
588
|
+
if (asset.toLowerCase() === "eth") {
|
|
589
|
+
const parsedAmount2 = parseAssetAmount(amount, 18);
|
|
590
|
+
return {
|
|
591
|
+
to: recipientAddress,
|
|
592
|
+
value: parsedAmount2,
|
|
593
|
+
data: "0x"
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
const resolvedAsset = resolveAsset(asset, chainId);
|
|
597
|
+
const parsedAmount = parseAssetAmount(amount, resolvedAsset.decimals);
|
|
598
|
+
const transferData = (0, import_viem3.encodeFunctionData)({
|
|
599
|
+
abi: import_viem3.erc20Abi,
|
|
600
|
+
functionName: "transfer",
|
|
601
|
+
args: [recipientAddress, parsedAmount]
|
|
602
|
+
});
|
|
603
|
+
return {
|
|
604
|
+
to: resolvedAsset.address,
|
|
605
|
+
value: 0n,
|
|
606
|
+
data: transferData
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
// src/tools/ChainManager.ts
|
|
612
|
+
var import_viem5 = require("viem");
|
|
613
|
+
var import_account_abstraction2 = require("viem/account-abstraction");
|
|
614
|
+
|
|
615
|
+
// src/constants/chains.ts
|
|
616
|
+
var import_chains2 = require("viem/chains");
|
|
617
|
+
var CHAINS_MAP = {
|
|
618
|
+
base: import_chains2.base,
|
|
619
|
+
baseSepolia: import_chains2.baseSepolia
|
|
620
|
+
};
|
|
621
|
+
var SUPPORTED_CHAIN_IDS = Object.values(CHAINS_MAP).map((c) => c.id);
|
|
622
|
+
|
|
623
|
+
// src/utils/chains.ts
|
|
624
|
+
var import_viem4 = require("viem");
|
|
625
|
+
var viemChains = __toESM(require("viem/chains"), 1);
|
|
626
|
+
var chainById = Object.values(viemChains).reduce(
|
|
627
|
+
(acc, maybeChain) => {
|
|
628
|
+
if (maybeChain && typeof maybeChain === "object" && "id" in maybeChain && typeof maybeChain.id === "number" && "name" in maybeChain) {
|
|
629
|
+
const chain = maybeChain;
|
|
630
|
+
acc[chain.id] = chain;
|
|
631
|
+
}
|
|
632
|
+
return acc;
|
|
633
|
+
},
|
|
634
|
+
{}
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
// src/types/logger.ts
|
|
638
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
639
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
640
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
641
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
642
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
643
|
+
return LogLevel2;
|
|
644
|
+
})(LogLevel || {});
|
|
645
|
+
|
|
646
|
+
// src/tools/Logger.ts
|
|
647
|
+
var Logger = class _Logger {
|
|
648
|
+
/**
|
|
649
|
+
* Create a new logger instance
|
|
650
|
+
* @param logLevel Initial log level, defaults to DEBUG
|
|
651
|
+
*/
|
|
652
|
+
constructor(logLevel = 0 /* DEBUG */) {
|
|
653
|
+
this.logs = [];
|
|
654
|
+
this.maxLogs = 1e3;
|
|
655
|
+
this.logLevel = logLevel;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Get singleton instance of the logger
|
|
659
|
+
* @param logLevel Optional log level to initialize if instance not yet created
|
|
660
|
+
* @returns Logger instance
|
|
661
|
+
*/
|
|
662
|
+
static getInstance(logLevel) {
|
|
663
|
+
if (!_Logger.instance) {
|
|
664
|
+
_Logger.instance = new _Logger(logLevel);
|
|
665
|
+
}
|
|
666
|
+
return _Logger.instance;
|
|
667
|
+
}
|
|
668
|
+
/** Set the log level */
|
|
669
|
+
setLogLevel(level) {
|
|
670
|
+
this.logLevel = level;
|
|
671
|
+
}
|
|
672
|
+
/** Get the current log level */
|
|
673
|
+
getLogLevel() {
|
|
674
|
+
return this.logLevel;
|
|
675
|
+
}
|
|
676
|
+
/** Internal check if a message should be logged */
|
|
677
|
+
shouldLog(level) {
|
|
678
|
+
return level >= this.logLevel;
|
|
679
|
+
}
|
|
680
|
+
/** Format log message into a readable string */
|
|
681
|
+
formatMessage(level, message, data, context) {
|
|
682
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
683
|
+
const levelName = LogLevel[level];
|
|
684
|
+
const contextStr = context ? `[${context}]` : "";
|
|
685
|
+
return `${timestamp} ${levelName}${contextStr}: ${message}`;
|
|
686
|
+
}
|
|
687
|
+
/** Add a log entry and output to console */
|
|
688
|
+
addLog(level, message, data, context) {
|
|
689
|
+
if (!this.shouldLog(level)) {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
const logEntry = {
|
|
693
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
694
|
+
level,
|
|
695
|
+
message,
|
|
696
|
+
data,
|
|
697
|
+
context
|
|
698
|
+
};
|
|
699
|
+
this.logs.push(logEntry);
|
|
700
|
+
if (this.logs.length > this.maxLogs) {
|
|
701
|
+
this.logs = this.logs.slice(-this.maxLogs);
|
|
702
|
+
}
|
|
703
|
+
const formattedMessage = this.formatMessage(level, message, data, context);
|
|
704
|
+
switch (level) {
|
|
705
|
+
case 0 /* DEBUG */:
|
|
706
|
+
console.debug(`\u{1F50D} ${formattedMessage}`, data || "");
|
|
707
|
+
break;
|
|
708
|
+
case 1 /* INFO */:
|
|
709
|
+
console.info(`\u2139\uFE0F ${formattedMessage}`, data || "");
|
|
710
|
+
break;
|
|
711
|
+
case 2 /* WARN */:
|
|
712
|
+
console.warn(`\u26A0\uFE0F ${formattedMessage}`, data || "");
|
|
713
|
+
break;
|
|
714
|
+
case 3 /* ERROR */:
|
|
715
|
+
console.error(`\u274C ${formattedMessage}`, data || "");
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
/** Log a debug message */
|
|
720
|
+
debug(message, data, context) {
|
|
721
|
+
this.addLog(0 /* DEBUG */, message, data, context);
|
|
722
|
+
}
|
|
723
|
+
/** Log an info message */
|
|
724
|
+
info(message, data, context) {
|
|
725
|
+
this.addLog(1 /* INFO */, message, data, context);
|
|
726
|
+
}
|
|
727
|
+
/** Log a warning message */
|
|
728
|
+
warn(message, data, context) {
|
|
729
|
+
this.addLog(2 /* WARN */, message, data, context);
|
|
730
|
+
}
|
|
731
|
+
/** Log an error message */
|
|
732
|
+
error(message, data, context) {
|
|
733
|
+
this.addLog(3 /* ERROR */, message, data, context);
|
|
734
|
+
}
|
|
735
|
+
/** Get all logs */
|
|
736
|
+
getLogs() {
|
|
737
|
+
return [...this.logs];
|
|
738
|
+
}
|
|
739
|
+
/** Get logs by level */
|
|
740
|
+
getLogsByLevel(level) {
|
|
741
|
+
return this.logs.filter((log) => log.level === level);
|
|
742
|
+
}
|
|
743
|
+
/** Clear all logs */
|
|
744
|
+
clearLogs() {
|
|
745
|
+
this.logs = [];
|
|
746
|
+
}
|
|
747
|
+
/** Export logs as a JSON string */
|
|
748
|
+
exportLogs() {
|
|
749
|
+
return JSON.stringify(this.logs, null, 2);
|
|
750
|
+
}
|
|
751
|
+
/** Set maximum number of logs to retain in memory */
|
|
752
|
+
setMaxLogs(max) {
|
|
753
|
+
this.maxLogs = max;
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
var logger = Logger.getInstance();
|
|
757
|
+
|
|
758
|
+
// src/tools/ChainManager.ts
|
|
759
|
+
var ChainManager = class {
|
|
760
|
+
/**
|
|
761
|
+
* Initializes the chain manager with the given configuration
|
|
762
|
+
*
|
|
763
|
+
* @internal
|
|
764
|
+
* @param chains Configuration object for a supported chain
|
|
765
|
+
*/
|
|
766
|
+
constructor(chains) {
|
|
767
|
+
this.chainConfigs = chains;
|
|
768
|
+
this.publicClient = this.createPublicClient(chains);
|
|
769
|
+
this.chainNames = CHAINS_MAP;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Utility to validate if a string is a valid HTTP(S) URL
|
|
773
|
+
*
|
|
774
|
+
* @internal
|
|
775
|
+
* @param url Candidate URL
|
|
776
|
+
* @returns True if valid, false otherwise
|
|
777
|
+
*/
|
|
778
|
+
isValidUrl(url) {
|
|
779
|
+
return /^https?:\/\/.+$/.test(url);
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Returns a {@link PublicClient} for the given chain ID
|
|
783
|
+
*
|
|
784
|
+
* @internal
|
|
785
|
+
* @category Clients
|
|
786
|
+
* @param chainId Target chain ID
|
|
787
|
+
* @returns {@link PublicClient} instance
|
|
788
|
+
* @throws Error if client is not configured
|
|
789
|
+
*/
|
|
790
|
+
getPublicClient(chainId) {
|
|
791
|
+
const client = this.publicClient;
|
|
792
|
+
if (!client) {
|
|
793
|
+
throw new Error(`No public client configured for chain ID: ${chainId}`);
|
|
794
|
+
}
|
|
795
|
+
return client;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Returns a {@link BundlerClient} for the given chain ID
|
|
799
|
+
*
|
|
800
|
+
* @internal
|
|
801
|
+
* @category Clients
|
|
802
|
+
* @param chainId Target chain ID
|
|
803
|
+
* @param account SmartAccount to bind to the bundler client
|
|
804
|
+
* @returns {@link BundlerClient} instance
|
|
805
|
+
* @throws Error if no bundler URL is configured
|
|
806
|
+
*/
|
|
807
|
+
getBundlerClient(chainId, account) {
|
|
808
|
+
const rpcUrl = this.getRpcUrl(chainId);
|
|
809
|
+
const bundlerUrl = this.getBundlerUrl(chainId);
|
|
810
|
+
if (!bundlerUrl) {
|
|
811
|
+
throw new Error(`No bundler URL configured for chain ID: ${chainId}`);
|
|
812
|
+
}
|
|
813
|
+
logger.info("Public client setup:", { bundlerUrl, chainId }, "ChainManager");
|
|
814
|
+
const client = (0, import_viem5.createPublicClient)({
|
|
815
|
+
chain: this.getChain(chainId),
|
|
816
|
+
transport: (0, import_viem5.http)(rpcUrl)
|
|
817
|
+
});
|
|
818
|
+
return (0, import_account_abstraction2.createBundlerClient)({
|
|
819
|
+
account,
|
|
820
|
+
client,
|
|
821
|
+
transport: (0, import_viem5.http)(bundlerUrl),
|
|
822
|
+
chain: this.getChain(chainId)
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Returns the RPC URL for the given chain ID
|
|
827
|
+
*
|
|
828
|
+
* @internal
|
|
829
|
+
* @category URLs
|
|
830
|
+
* @param chainId Target chain ID
|
|
831
|
+
* @returns RPC URL string
|
|
832
|
+
* @throws Error if chain config is missing or URL is invalid
|
|
833
|
+
*/
|
|
834
|
+
getRpcUrl(chainId) {
|
|
835
|
+
const chainConfig = this.chainConfigs;
|
|
836
|
+
if (!chainConfig) {
|
|
837
|
+
throw new Error(`No chain config found for chain ID: ${chainId}`);
|
|
838
|
+
}
|
|
839
|
+
if (!this.isValidUrl(chainConfig.rpcUrl)) {
|
|
840
|
+
throw new Error(`Invalid RPC URL for chain ID: ${chainId}`);
|
|
841
|
+
}
|
|
842
|
+
return chainConfig.rpcUrl;
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Returns the bundler URL for the given chain ID
|
|
846
|
+
*
|
|
847
|
+
* @internal
|
|
848
|
+
* @category URLs
|
|
849
|
+
* @param chainId Target chain ID
|
|
850
|
+
* @returns Bundler URL string
|
|
851
|
+
* @throws Error if chain config is missing or URL is invalid
|
|
852
|
+
*/
|
|
853
|
+
getBundlerUrl(chainId) {
|
|
854
|
+
const chainConfig = this.chainConfigs;
|
|
855
|
+
if (!chainConfig) {
|
|
856
|
+
throw new Error(`No chain config found for chain ID: ${chainId}`);
|
|
857
|
+
}
|
|
858
|
+
if (!this.isValidUrl(chainConfig.bundlerUrl)) {
|
|
859
|
+
throw new Error(`Invalid bundler URL for chain ID: ${chainId}`);
|
|
860
|
+
}
|
|
861
|
+
return chainConfig.bundlerUrl;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Returns the {@link Chain} object for the given chain ID
|
|
865
|
+
*
|
|
866
|
+
* @internal
|
|
867
|
+
* @category Info
|
|
868
|
+
* @param chainId Target chain ID
|
|
869
|
+
* @returns Chain metadata
|
|
870
|
+
* @throws Error if chain is not found
|
|
871
|
+
*/
|
|
872
|
+
getChain(chainId) {
|
|
873
|
+
const chain = chainById[chainId];
|
|
874
|
+
if (!chain) {
|
|
875
|
+
throw new Error(`Chain not found for ID: ${chainId}`);
|
|
876
|
+
}
|
|
877
|
+
return chain;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Returns the currently configured supported chain ID
|
|
881
|
+
*
|
|
882
|
+
* @internal
|
|
883
|
+
* @category Info
|
|
884
|
+
* @returns Supported chain ID
|
|
885
|
+
*/
|
|
886
|
+
getSupportedChain() {
|
|
887
|
+
return this.chainConfigs.chainId;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Creates a {@link PublicClient} for a chain
|
|
891
|
+
*
|
|
892
|
+
* @internal
|
|
893
|
+
* @category Clients
|
|
894
|
+
* @param chain Chain configuration
|
|
895
|
+
* @returns PublicClient instance
|
|
896
|
+
*/
|
|
897
|
+
createPublicClient(chain) {
|
|
898
|
+
const chainObject = chainById[chain.chainId];
|
|
899
|
+
const client = (0, import_viem5.createPublicClient)({
|
|
900
|
+
chain: chainObject,
|
|
901
|
+
transport: (0, import_viem5.http)(chain.rpcUrl)
|
|
902
|
+
});
|
|
903
|
+
return client;
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
// src/wallet/WalletNamespace.ts
|
|
908
|
+
var WalletNamespace = class {
|
|
909
|
+
/**
|
|
910
|
+
* Creates a wallet namespace to manage embedded and smart wallets
|
|
911
|
+
* @param provider Unified provider that composes embedded & smart providers
|
|
912
|
+
*/
|
|
913
|
+
constructor(provider) {
|
|
914
|
+
this.provider = provider;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Direct access to the underlying embedded wallet provider
|
|
918
|
+
*
|
|
919
|
+
* @public
|
|
920
|
+
* @category Providers
|
|
921
|
+
* @remarks
|
|
922
|
+
* Useful when you need advanced functionality beyond the unified namespace. By default, you should use the unified namespace
|
|
923
|
+
*
|
|
924
|
+
* @returns The configured embedded wallet provider instance
|
|
925
|
+
*/
|
|
926
|
+
get embeddedWalletProvider() {
|
|
927
|
+
return this.provider.embeddedWalletProvider;
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Direct access to the underlying smart wallet provider
|
|
931
|
+
*
|
|
932
|
+
* @public
|
|
933
|
+
* @category Providers
|
|
934
|
+
* @remarks
|
|
935
|
+
* Useful when you need advanced functionality beyond the unified namespace. By default, you should use the unified namespace
|
|
936
|
+
*
|
|
937
|
+
* @returns The configured smart wallet provider instance
|
|
938
|
+
*/
|
|
939
|
+
get smartWalletProvider() {
|
|
940
|
+
return this.provider.smartWalletProvider;
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Creates an embedded wallet
|
|
944
|
+
*
|
|
945
|
+
* @public
|
|
946
|
+
* @category Creation
|
|
947
|
+
* @remarks
|
|
948
|
+
* Thin wrapper around the embedded wallet provider’s `createWallet`
|
|
949
|
+
*
|
|
950
|
+
* @returns Promise that resolves to the newly created {@link EmbeddedWallet}
|
|
951
|
+
*/
|
|
952
|
+
async createEmbeddedWallet() {
|
|
953
|
+
return this.provider.createEmbeddedWallet();
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Creates a smart wallet (you provide signer and owners)
|
|
957
|
+
*
|
|
958
|
+
* @public
|
|
959
|
+
* @category Creation
|
|
960
|
+
* @remarks
|
|
961
|
+
* Use this when you already control a signer (e.g., `LocalAccount`) and want to
|
|
962
|
+
* create a smart wallet without creating an embedded wallet
|
|
963
|
+
*
|
|
964
|
+
* @param params Smart wallet creation parameters
|
|
965
|
+
* @param params.owners Owners for the smart wallet (addresses or WebAuthn public keys)
|
|
966
|
+
* @param params.signer Local account used for signing transactions
|
|
967
|
+
* @param params.nonce Optional nonce/salt for deterministic address generation (defaults to 0)
|
|
968
|
+
* @returns Promise that resolves to the created {@link SmartWallet}
|
|
969
|
+
*/
|
|
970
|
+
async createSmartWallet(params) {
|
|
971
|
+
return this.provider.createSmartWallet(params);
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Creates a smart wallet with an embedded wallet as signer
|
|
975
|
+
*
|
|
976
|
+
* @public
|
|
977
|
+
* @category Creation
|
|
978
|
+
* @remarks
|
|
979
|
+
* Creates an embedded wallet first, inserts its address into the owners array,
|
|
980
|
+
* and uses its account as the signer for the smart wallet
|
|
981
|
+
*
|
|
982
|
+
* @param params Optional creation parameters
|
|
983
|
+
* @param params.owners Optional additional owners. The embedded wallet address is inserted at the specified index
|
|
984
|
+
* @param params.embeddedWalletIndex Optional index at which to insert the embedded wallet address (defaults to end)
|
|
985
|
+
* @param params.nonce Optional nonce/salt for deterministic address generation (defaults to 0)
|
|
986
|
+
* @returns Promise that resolves to the created {@link SmartWallet}
|
|
987
|
+
*/
|
|
988
|
+
async createWalletWithEmbeddedSigner(params) {
|
|
989
|
+
return this.provider.createWalletWithEmbeddedSigner(params);
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Gets a smart wallet using an embedded wallet as the signer
|
|
993
|
+
*
|
|
994
|
+
* @public
|
|
995
|
+
* @category Retrieval
|
|
996
|
+
* @remarks
|
|
997
|
+
* Looks up an embedded wallet by `walletId` and uses it as signer
|
|
998
|
+
* If neither `walletAddress` nor `deploymentOwners` is provided, defaults to using
|
|
999
|
+
* the embedded wallet as the single owner for deterministic address calculation
|
|
1000
|
+
*
|
|
1001
|
+
* @param params Retrieval parameters
|
|
1002
|
+
* @param params.walletId Embedded wallet ID used to locate the signer wallet
|
|
1003
|
+
* @param params.deploymentOwners Optional original deployment owners used for address calculation
|
|
1004
|
+
* @param params.signerOwnerIndex Index of the signer within the **current** owners set (defaults to 0)
|
|
1005
|
+
* @param params.walletAddress Optional explicit smart wallet address (skips calculation)
|
|
1006
|
+
* @param params.nonce Optional nonce used during original creation
|
|
1007
|
+
* @returns Promise that resolves to the {@link SmartWallet}
|
|
1008
|
+
* @throws Error if the embedded wallet cannot be found
|
|
1009
|
+
*/
|
|
1010
|
+
async getSmartWalletWithEmbeddedSigner(params) {
|
|
1011
|
+
return this.provider.getSmartWalletWithEmbeddedSigner(params);
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Gets an existing embedded wallet by ID
|
|
1015
|
+
*
|
|
1016
|
+
* @public
|
|
1017
|
+
* @category Retrieval
|
|
1018
|
+
* @param params Retrieval parameters
|
|
1019
|
+
* @param params.walletId Embedded wallet ID to retrieve
|
|
1020
|
+
* @returns Promise that resolves to the {@link EmbeddedWallet} (or `null/undefined` per provider contract)
|
|
1021
|
+
*/
|
|
1022
|
+
async getEmbeddedWallet(params) {
|
|
1023
|
+
return this.provider.getEmbeddedWallet(params);
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Gets a smart wallet using a provided signer
|
|
1027
|
+
*
|
|
1028
|
+
* @public
|
|
1029
|
+
* @category Retrieval
|
|
1030
|
+
* @remarks
|
|
1031
|
+
* Use when you already control a signer. Requires either:
|
|
1032
|
+
* - `walletAddress`, or
|
|
1033
|
+
* - `deploymentOwners` (+ optional `nonce`) to derive the address
|
|
1034
|
+
*
|
|
1035
|
+
* @param params Retrieval parameters
|
|
1036
|
+
* @param params.signer Signer (local account)
|
|
1037
|
+
* @param params.deploymentOwners Original deployment owners (required if `walletAddress` is not provided)
|
|
1038
|
+
* @param params.signerOwnerIndex Index of the signer within the **current** owners set (defaults to 0)
|
|
1039
|
+
* @param params.walletAddress Explicit smart wallet address (skips calculation)
|
|
1040
|
+
* @param params.nonce Optional nonce used during original creation
|
|
1041
|
+
* @returns Promise that resolves to the {@link SmartWallet}
|
|
1042
|
+
* @throws Error if neither `walletAddress` nor `deploymentOwners` is provided
|
|
1043
|
+
*/
|
|
1044
|
+
async getSmartWallet(params) {
|
|
1045
|
+
return this.provider.getSmartWallet(params);
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
// src/index.ts
|
|
1050
|
+
var import_chains7 = require("viem/chains");
|
|
1051
|
+
|
|
1052
|
+
// src/wallet/providers/DefaultSmartWalletProvider.ts
|
|
1053
|
+
var import_viem6 = require("viem");
|
|
1054
|
+
var import_account_abstraction3 = require("viem/account-abstraction");
|
|
1055
|
+
|
|
1056
|
+
// src/wallet/base/providers/SmartWalletProvider.ts
|
|
1057
|
+
var SmartWalletProvider = class {
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
// src/wallet/providers/DefaultSmartWalletProvider.ts
|
|
1061
|
+
var DefaultSmartWalletProvider = class extends SmartWalletProvider {
|
|
1062
|
+
/**
|
|
1063
|
+
* Initializes the smart wallet provider
|
|
1064
|
+
*
|
|
1065
|
+
* @internal
|
|
1066
|
+
* @param chainManager Manager for chains and viem clients
|
|
1067
|
+
* @param protocol Selected protocol descriptor that exposes an initialized instance
|
|
1068
|
+
*/
|
|
1069
|
+
constructor(chainManager, protocol, coinbaseCDP) {
|
|
1070
|
+
super();
|
|
1071
|
+
this.chainManager = chainManager;
|
|
1072
|
+
this.protocolProvider = protocol.instance;
|
|
1073
|
+
this.coinbaseCDP = coinbaseCDP;
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Creates a new smart wallet instance that deploys on first use
|
|
1077
|
+
*
|
|
1078
|
+
* @internal
|
|
1079
|
+
* @category Creation
|
|
1080
|
+
* @remarks
|
|
1081
|
+
* Address is derived deterministically from `owners` and `nonce`
|
|
1082
|
+
*
|
|
1083
|
+
* @param params Parameters for wallet creation
|
|
1084
|
+
* @param params.owners Owners as EVM addresses or WebAuthn owners
|
|
1085
|
+
* @param params.signer Local account used to sign UserOperations and transactions
|
|
1086
|
+
* @param params.nonce Optional salt for deterministic address calculation, default 0
|
|
1087
|
+
* @returns Promise that resolves to a {@link DefaultSmartWallet} instance
|
|
1088
|
+
*/
|
|
1089
|
+
async createWallet(params) {
|
|
1090
|
+
const { owners, signer, nonce } = params;
|
|
1091
|
+
return new DefaultSmartWallet(
|
|
1092
|
+
owners,
|
|
1093
|
+
signer,
|
|
1094
|
+
this.chainManager,
|
|
1095
|
+
this.protocolProvider,
|
|
1096
|
+
this.coinbaseCDP,
|
|
1097
|
+
void 0,
|
|
1098
|
+
void 0,
|
|
1099
|
+
nonce
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Predicts the deterministic smart wallet address for the given owners and nonce
|
|
1104
|
+
*
|
|
1105
|
+
* @internal
|
|
1106
|
+
* @category Addressing
|
|
1107
|
+
* @remarks
|
|
1108
|
+
* Uses the smart wallet factory `getAddress` to compute the CREATE2 address
|
|
1109
|
+
*
|
|
1110
|
+
* @param params Address prediction parameters
|
|
1111
|
+
* @param params.owners Owners as EVM addresses or WebAuthn owners
|
|
1112
|
+
* @param params.nonce Optional salt for deterministic address calculation, default 0
|
|
1113
|
+
* @returns Promise that resolves to the predicted wallet address
|
|
1114
|
+
* @throws Error if no supported chains are configured
|
|
1115
|
+
* @throws Error if an owner has an invalid type
|
|
1116
|
+
*/
|
|
1117
|
+
async getWalletAddress(params) {
|
|
1118
|
+
const { owners, nonce = 0n } = params;
|
|
1119
|
+
const owners_bytes = owners.map((owner) => {
|
|
1120
|
+
if (typeof owner === "string") {
|
|
1121
|
+
return (0, import_viem6.pad)(owner);
|
|
1122
|
+
}
|
|
1123
|
+
if (owner.type === "webAuthn") {
|
|
1124
|
+
return owner.publicKey;
|
|
1125
|
+
}
|
|
1126
|
+
throw new Error("invalid owner type");
|
|
1127
|
+
});
|
|
1128
|
+
const supportedChain = this.chainManager.getSupportedChain();
|
|
1129
|
+
if (!supportedChain) {
|
|
1130
|
+
throw new Error("No supported chains configured");
|
|
1131
|
+
}
|
|
1132
|
+
const publicClient = this.chainManager.getPublicClient(supportedChain);
|
|
1133
|
+
const smartWalletAddress = await publicClient.readContract({
|
|
1134
|
+
abi: smartWalletFactoryAbi,
|
|
1135
|
+
address: smartWalletFactoryAddress,
|
|
1136
|
+
functionName: "getAddress",
|
|
1137
|
+
args: [owners_bytes, nonce]
|
|
1138
|
+
});
|
|
1139
|
+
return smartWalletAddress;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Returns a smart wallet instance for an already deployed address
|
|
1143
|
+
*
|
|
1144
|
+
* @internal
|
|
1145
|
+
* @category Retrieval
|
|
1146
|
+
* @remarks
|
|
1147
|
+
* Use when you already know the deployment address and want an instance bound to a signer
|
|
1148
|
+
*
|
|
1149
|
+
* @param params Retrieval parameters
|
|
1150
|
+
* @param params.walletAddress Deployed smart wallet address
|
|
1151
|
+
* @param params.signer Local account to operate the wallet
|
|
1152
|
+
* @param params.ownerIndex Optional index of `signer` within the current owners set, default 0
|
|
1153
|
+
* @returns A {@link DefaultSmartWallet} instance
|
|
1154
|
+
*/
|
|
1155
|
+
getWallet(params) {
|
|
1156
|
+
const { walletAddress, signer, ownerIndex } = params;
|
|
1157
|
+
return new DefaultSmartWallet(
|
|
1158
|
+
[signer.address],
|
|
1159
|
+
signer,
|
|
1160
|
+
this.chainManager,
|
|
1161
|
+
this.protocolProvider,
|
|
1162
|
+
this.coinbaseCDP,
|
|
1163
|
+
walletAddress,
|
|
1164
|
+
ownerIndex
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Funds a wallet via a faucet if supported by the selected chain
|
|
1169
|
+
*
|
|
1170
|
+
* @internal
|
|
1171
|
+
* @category Funding
|
|
1172
|
+
* @remarks
|
|
1173
|
+
* Placeholder for testnet faucet integration
|
|
1174
|
+
*
|
|
1175
|
+
* @returns Future transaction hash or provider response
|
|
1176
|
+
*/
|
|
1177
|
+
fundViaFaucet() {
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
|
|
1181
|
+
// src/wallet/WalletProvider.ts
|
|
1182
|
+
var WalletProvider = class {
|
|
1183
|
+
/**
|
|
1184
|
+
* Creates a unified wallet provider
|
|
1185
|
+
*
|
|
1186
|
+
* @internal
|
|
1187
|
+
* @param embeddedWalletProvider Provider for embedded wallet operations
|
|
1188
|
+
* @param smartWalletProvider Provider for smart wallet operations
|
|
1189
|
+
*/
|
|
1190
|
+
constructor(embeddedWalletProvider, smartWalletProvider) {
|
|
1191
|
+
this.embeddedWalletProvider = embeddedWalletProvider;
|
|
1192
|
+
this.smartWalletProvider = smartWalletProvider;
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Creates an embedded wallet
|
|
1196
|
+
*
|
|
1197
|
+
* @internal
|
|
1198
|
+
* @remarks
|
|
1199
|
+
* Thin wrapper around the embedded wallet provider’s `createWallet`
|
|
1200
|
+
*
|
|
1201
|
+
* @returns Promise that resolves to the newly created {@link EmbeddedWallet}
|
|
1202
|
+
*/
|
|
1203
|
+
async createEmbeddedWallet() {
|
|
1204
|
+
return this.embeddedWalletProvider.createWallet();
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Creates a smart wallet (you provide signer and owners)
|
|
1208
|
+
*
|
|
1209
|
+
* @internal
|
|
1210
|
+
* @remarks
|
|
1211
|
+
* Use when you already control a signer and want to create a smart
|
|
1212
|
+
* wallet without creating an embedded wallet
|
|
1213
|
+
*
|
|
1214
|
+
* @param params Smart wallet creation parameters
|
|
1215
|
+
* @param params.owners Owners array for the smart wallet (EVM addresses or WebAuthn owners)
|
|
1216
|
+
* @param params.signer Signer (local account) used for transactions
|
|
1217
|
+
* @param params.nonce Optional salt/nonce for deterministic address calculation (defaults to 0)
|
|
1218
|
+
* @returns Promise that resolves to the created {@link SmartWallet}
|
|
1219
|
+
*/
|
|
1220
|
+
async createSmartWallet(params) {
|
|
1221
|
+
const { owners, signer, nonce } = params;
|
|
1222
|
+
return this.smartWalletProvider.createWallet({
|
|
1223
|
+
owners,
|
|
1224
|
+
signer,
|
|
1225
|
+
nonce
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Creates a smart wallet with an embedded wallet as signer
|
|
1230
|
+
*
|
|
1231
|
+
* @internal
|
|
1232
|
+
* @remarks
|
|
1233
|
+
* Creates an embedded wallet first, then inserts its address into the owners array
|
|
1234
|
+
* and uses its account as the signer for the smart wallet. Default SDK option, embedded wallets manager is necessary to be provided
|
|
1235
|
+
*
|
|
1236
|
+
* @param params Optional creation parameters
|
|
1237
|
+
* @param params.owners Optional additional owners. The embedded wallet address is inserted at the specified index
|
|
1238
|
+
* @param params.embeddedWalletIndex Optional index where the embedded wallet address should be inserted (defaults to the end)
|
|
1239
|
+
* @param params.nonce Optional salt/nonce for deterministic address calculation (defaults to 0)
|
|
1240
|
+
* @returns Promise that resolves to the created {@link SmartWallet}
|
|
1241
|
+
*/
|
|
1242
|
+
async createWalletWithEmbeddedSigner(params) {
|
|
1243
|
+
const { owners: ownersParam, embeddedWalletIndex, nonce } = params || {};
|
|
1244
|
+
const embeddedWallet = await this.embeddedWalletProvider.createWallet();
|
|
1245
|
+
const account = await embeddedWallet.account();
|
|
1246
|
+
let owners;
|
|
1247
|
+
if (ownersParam) {
|
|
1248
|
+
owners = [...ownersParam];
|
|
1249
|
+
const insertIndex = embeddedWalletIndex ?? owners.length;
|
|
1250
|
+
owners.splice(insertIndex, 0, embeddedWallet.address);
|
|
1251
|
+
} else {
|
|
1252
|
+
owners = [embeddedWallet.address];
|
|
1253
|
+
}
|
|
1254
|
+
return this.smartWalletProvider.createWallet({
|
|
1255
|
+
owners,
|
|
1256
|
+
signer: account,
|
|
1257
|
+
nonce
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Gets a smart wallet using an embedded wallet as the signer
|
|
1262
|
+
*
|
|
1263
|
+
* @internal
|
|
1264
|
+
* @remarks
|
|
1265
|
+
* Fetches an embedded wallet by `walletId` and uses it as signer.
|
|
1266
|
+
* If neither `walletAddress` nor `deploymentOwners` is provided, defaults to using
|
|
1267
|
+
* the embedded wallet as the single owner for deterministic address calculation
|
|
1268
|
+
*
|
|
1269
|
+
* @param params Retrieval parameters
|
|
1270
|
+
* @param params.walletId Embedded wallet ID used to locate the signer wallet
|
|
1271
|
+
* @param params.deploymentOwners Optional original deployment owners used for address calculation
|
|
1272
|
+
* @param params.signerOwnerIndex Index of the signer in the **current** owners set (defaults to 0)
|
|
1273
|
+
* @param params.walletAddress Optional explicit smart wallet address (skips calculation)
|
|
1274
|
+
* @param params.nonce Optional nonce used during original creation
|
|
1275
|
+
* @returns Promise that resolves to the {@link SmartWallet}
|
|
1276
|
+
* @throws Error if the embedded wallet cannot be found
|
|
1277
|
+
*/
|
|
1278
|
+
async getSmartWalletWithEmbeddedSigner(params) {
|
|
1279
|
+
const { walletId, deploymentOwners, walletAddress } = params;
|
|
1280
|
+
const embeddedWallet = await this.embeddedWalletProvider.getWallet({
|
|
1281
|
+
walletId
|
|
1282
|
+
});
|
|
1283
|
+
if (!embeddedWallet) {
|
|
1284
|
+
throw new Error("Embedded wallet not found");
|
|
1285
|
+
}
|
|
1286
|
+
const account = await embeddedWallet.account();
|
|
1287
|
+
const finalDeploymentOwners = deploymentOwners || (walletAddress ? void 0 : [embeddedWallet.address]);
|
|
1288
|
+
return this.getSmartWallet({
|
|
1289
|
+
signer: account,
|
|
1290
|
+
...params,
|
|
1291
|
+
deploymentOwners: finalDeploymentOwners
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Gets an existing embedded wallet by ID
|
|
1296
|
+
*
|
|
1297
|
+
* @internal
|
|
1298
|
+
* @param params Retrieval parameters
|
|
1299
|
+
* @param params.walletId Embedded wallet ID
|
|
1300
|
+
* @returns Promise that resolves to the {@link EmbeddedWallet}, or `null/undefined` per provider’s contract
|
|
1301
|
+
*/
|
|
1302
|
+
async getEmbeddedWallet(params) {
|
|
1303
|
+
const { walletId } = params;
|
|
1304
|
+
return this.embeddedWalletProvider.getWallet({
|
|
1305
|
+
walletId
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Gets a smart wallet using a provided signer
|
|
1310
|
+
*
|
|
1311
|
+
* @internal
|
|
1312
|
+
* @remarks
|
|
1313
|
+
* Use when you already control a `LocalAccount` signer
|
|
1314
|
+
* Requires either:
|
|
1315
|
+
* - `walletAddress`, or
|
|
1316
|
+
* - `deploymentOwners` (+ optional `nonce`) to deterministically derive the address
|
|
1317
|
+
*
|
|
1318
|
+
* @param params Retrieval parameters
|
|
1319
|
+
* @param params.signer Signer (local account)
|
|
1320
|
+
* @param params.deploymentOwners Original deployment owners (required if `walletAddress` is not provided)
|
|
1321
|
+
* @param params.signerOwnerIndex Index of `signer` within the **current** owners set (defaults to 0)
|
|
1322
|
+
* @param params.walletAddress Explicit smart wallet address (skips calculation)
|
|
1323
|
+
* @param params.nonce Optional nonce used during original creation
|
|
1324
|
+
* @returns Promise that resolves to the {@link SmartWallet}
|
|
1325
|
+
* @throws Error if neither `walletAddress` nor `deploymentOwners` is provided
|
|
1326
|
+
*/
|
|
1327
|
+
async getSmartWallet(params) {
|
|
1328
|
+
const {
|
|
1329
|
+
signer,
|
|
1330
|
+
deploymentOwners,
|
|
1331
|
+
signerOwnerIndex,
|
|
1332
|
+
walletAddress: walletAddressParam,
|
|
1333
|
+
nonce
|
|
1334
|
+
} = params;
|
|
1335
|
+
if (!walletAddressParam && !deploymentOwners) {
|
|
1336
|
+
try {
|
|
1337
|
+
throw new Error(
|
|
1338
|
+
"Either walletAddress or deploymentOwners array must be provided to locate the smart wallet"
|
|
1339
|
+
);
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
logger.error("Error getting smart wallet", error, "WalletProvider");
|
|
1342
|
+
throw new Error(
|
|
1343
|
+
"Either walletAddress or deploymentOwners array must be provided to locate the smart wallet"
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
const ownerIndex = signerOwnerIndex ?? 0;
|
|
1348
|
+
const walletAddress = walletAddressParam || await this.smartWalletProvider.getWalletAddress({
|
|
1349
|
+
// Safe to use ! since we validated above
|
|
1350
|
+
owners: deploymentOwners,
|
|
1351
|
+
nonce
|
|
1352
|
+
});
|
|
1353
|
+
return this.smartWalletProvider.getWallet({
|
|
1354
|
+
walletAddress,
|
|
1355
|
+
signer,
|
|
1356
|
+
ownerIndex
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
|
|
1361
|
+
// src/wallet/providers/PrivyEmbeddedWalletProvider.ts
|
|
1362
|
+
var import_viem9 = require("viem");
|
|
1363
|
+
|
|
1364
|
+
// src/wallet/PrivyWallet.ts
|
|
1365
|
+
var import_viem7 = require("@privy-io/server-auth/viem");
|
|
1366
|
+
var import_viem8 = require("viem");
|
|
1367
|
+
var import_chains5 = require("viem/chains");
|
|
1368
|
+
|
|
1369
|
+
// src/wallet/base/wallets/EmbeddedWallet.ts
|
|
1370
|
+
var EmbeddedWallet = class {
|
|
1371
|
+
/**
|
|
1372
|
+
* Creates an embedded wallet instance
|
|
1373
|
+
*
|
|
1374
|
+
* @internal
|
|
1375
|
+
* @param address Ethereum address of the wallet
|
|
1376
|
+
* @param walletId Optional provider-specific identifier
|
|
1377
|
+
*/
|
|
1378
|
+
constructor(address, walletId) {
|
|
1379
|
+
this.address = address;
|
|
1380
|
+
this.walletId = walletId;
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
|
|
1384
|
+
// src/wallet/PrivyWallet.ts
|
|
1385
|
+
var PrivyWallet = class extends EmbeddedWallet {
|
|
1386
|
+
/**
|
|
1387
|
+
* Creates a Privy-backed embedded wallet
|
|
1388
|
+
*
|
|
1389
|
+
* @internal
|
|
1390
|
+
* @param privyClient Privy client used to access wallet and signing APIs
|
|
1391
|
+
* @param walletId Privy wallet identifier
|
|
1392
|
+
* @param address Wallet EVM address
|
|
1393
|
+
* @param chainManager Chain and client manager
|
|
1394
|
+
*/
|
|
1395
|
+
constructor(privyClient, walletId, address, chainManager) {
|
|
1396
|
+
super(address, walletId);
|
|
1397
|
+
this.privyClient = privyClient;
|
|
1398
|
+
this.walletId = walletId;
|
|
1399
|
+
this.chainManager = chainManager;
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Returns a viem-compatible {@link LocalAccount} for this Privy wallet
|
|
1403
|
+
*
|
|
1404
|
+
* @internal
|
|
1405
|
+
* @category Accounts
|
|
1406
|
+
* @remarks
|
|
1407
|
+
* Uses Privy’s signing infra under the hood while exposing the standard viem interface
|
|
1408
|
+
*
|
|
1409
|
+
* @returns Promise that resolves to a {@link LocalAccount}
|
|
1410
|
+
* @throws Error if wallet retrieval or account construction fails
|
|
1411
|
+
*/
|
|
1412
|
+
async account() {
|
|
1413
|
+
const account = await (0, import_viem7.createViemAccount)({
|
|
1414
|
+
walletId: this.walletId,
|
|
1415
|
+
address: this.address,
|
|
1416
|
+
privy: this.privyClient
|
|
1417
|
+
});
|
|
1418
|
+
return account;
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Creates a viem {@link WalletClient} for a given chain
|
|
1422
|
+
*
|
|
1423
|
+
* @internal
|
|
1424
|
+
* @category Accounts
|
|
1425
|
+
* @param chainId Target chain ID
|
|
1426
|
+
* @returns Promise that resolves to a {@link WalletClient}
|
|
1427
|
+
* @throws Error if the chain is unsupported or client creation fails
|
|
1428
|
+
*/
|
|
1429
|
+
async walletClient(chainId) {
|
|
1430
|
+
const account = await this.account();
|
|
1431
|
+
return (0, import_viem8.createWalletClient)({
|
|
1432
|
+
account,
|
|
1433
|
+
chain: this.chainManager.getChain(chainId),
|
|
1434
|
+
transport: (0, import_viem8.http)(this.chainManager.getRpcUrl(chainId))
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Signs a transaction without broadcasting it
|
|
1439
|
+
*
|
|
1440
|
+
* @internal
|
|
1441
|
+
* @category Signing
|
|
1442
|
+
* @param transactionData Transaction payload to sign
|
|
1443
|
+
* @returns Promise that resolves to a signed transaction hex string
|
|
1444
|
+
*/
|
|
1445
|
+
async sign(transactionData) {
|
|
1446
|
+
return await this.signOnly(transactionData);
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Produces a signed transaction using Privy’s wallet API without sending it
|
|
1450
|
+
*
|
|
1451
|
+
* @internal
|
|
1452
|
+
* @category Signing
|
|
1453
|
+
* @remarks
|
|
1454
|
+
* Estimates gas, fees, and nonce to build a complete EIP-1559 transaction
|
|
1455
|
+
* Per Privy docs, if any gas field is set, all must be set
|
|
1456
|
+
*
|
|
1457
|
+
* @param transactionData Transaction payload to sign
|
|
1458
|
+
* @returns Promise that resolves to a signed transaction string
|
|
1459
|
+
* @throws Error if signing fails
|
|
1460
|
+
*/
|
|
1461
|
+
async signOnly(transactionData) {
|
|
1462
|
+
try {
|
|
1463
|
+
const privyWallet = await this.privyClient.walletApi.getWallet({
|
|
1464
|
+
id: this.walletId
|
|
1465
|
+
});
|
|
1466
|
+
const publicClient = this.chainManager.getPublicClient(import_chains5.unichain.id);
|
|
1467
|
+
const gasLimit = await publicClient.estimateGas({
|
|
1468
|
+
account: privyWallet.address,
|
|
1469
|
+
to: transactionData.to,
|
|
1470
|
+
data: transactionData.data,
|
|
1471
|
+
value: BigInt(transactionData.value || 0)
|
|
1472
|
+
});
|
|
1473
|
+
const feeData = await publicClient.estimateFeesPerGas();
|
|
1474
|
+
const nonce = await publicClient.getTransactionCount({
|
|
1475
|
+
address: privyWallet.address,
|
|
1476
|
+
blockTag: "pending"
|
|
1477
|
+
// Use pending to get the next nonce including any pending txs
|
|
1478
|
+
});
|
|
1479
|
+
const txParams = {
|
|
1480
|
+
to: transactionData.to,
|
|
1481
|
+
data: transactionData.data,
|
|
1482
|
+
value: transactionData.value,
|
|
1483
|
+
chainId: 130,
|
|
1484
|
+
// Unichain
|
|
1485
|
+
type: 2,
|
|
1486
|
+
// EIP-1559
|
|
1487
|
+
gasLimit: `0x${gasLimit.toString(16)}`,
|
|
1488
|
+
maxFeePerGas: `0x${(feeData.maxFeePerGas || BigInt(1e9)).toString(16)}`,
|
|
1489
|
+
// fallback to 1 gwei
|
|
1490
|
+
maxPriorityFeePerGas: `0x${(feeData.maxPriorityFeePerGas || BigInt(1e8)).toString(16)}`,
|
|
1491
|
+
// fallback to 0.1 gwei
|
|
1492
|
+
nonce: `0x${nonce.toString(16)}`
|
|
1493
|
+
// Explicitly provide the correct nonce
|
|
1494
|
+
};
|
|
1495
|
+
logger.info(
|
|
1496
|
+
"Complete tx params: ",
|
|
1497
|
+
{
|
|
1498
|
+
txParamsType: txParams.type,
|
|
1499
|
+
txParamsNonce: nonce,
|
|
1500
|
+
txParamsLimit: gasLimit,
|
|
1501
|
+
txParamsMaxFee: feeData.maxFeePerGas || "fallback",
|
|
1502
|
+
txParamsPriority: feeData.maxPriorityFeePerGas || "fallback"
|
|
1503
|
+
},
|
|
1504
|
+
"PrivyWallet"
|
|
1505
|
+
);
|
|
1506
|
+
const response = await this.privyClient.walletApi.ethereum.signTransaction({
|
|
1507
|
+
walletId: this.walletId,
|
|
1508
|
+
transaction: txParams
|
|
1509
|
+
});
|
|
1510
|
+
return response.signedTransaction;
|
|
1511
|
+
} catch (error) {
|
|
1512
|
+
throw new Error(
|
|
1513
|
+
`Failed to sign transaction for wallet ${this.walletId}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Broadcasts a previously-signed transaction
|
|
1519
|
+
*
|
|
1520
|
+
* @internal
|
|
1521
|
+
* @category Sending
|
|
1522
|
+
* @param signedTransaction Signed transaction hex
|
|
1523
|
+
* @param publicClient Viem public client to send the transaction
|
|
1524
|
+
* @returns Promise that resolves to the transaction {@link Hash}
|
|
1525
|
+
* @throws Error if submission fails
|
|
1526
|
+
*/
|
|
1527
|
+
async send(signedTransaction, publicClient) {
|
|
1528
|
+
try {
|
|
1529
|
+
const hash = await publicClient.sendRawTransaction({
|
|
1530
|
+
serializedTransaction: signedTransaction
|
|
1531
|
+
});
|
|
1532
|
+
return hash;
|
|
1533
|
+
} catch (error) {
|
|
1534
|
+
throw new Error(
|
|
1535
|
+
`Failed to send transaction: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
};
|
|
1540
|
+
|
|
1541
|
+
// src/wallet/base/providers/EmbeddedWalletProvider.ts
|
|
1542
|
+
var EmbeddedWalletProvider = class {
|
|
1543
|
+
};
|
|
1544
|
+
|
|
1545
|
+
// src/wallet/providers/PrivyEmbeddedWalletProvider.ts
|
|
1546
|
+
var PrivyEmbeddedWalletProvider = class extends EmbeddedWalletProvider {
|
|
1547
|
+
/**
|
|
1548
|
+
* Creates a new Privy-backed embedded wallet provider
|
|
1549
|
+
*
|
|
1550
|
+
* @internal
|
|
1551
|
+
* @param privyClient Privy client instance
|
|
1552
|
+
* @param chainManager Chain and client manager
|
|
1553
|
+
*/
|
|
1554
|
+
constructor(privyClient, chainManager) {
|
|
1555
|
+
super();
|
|
1556
|
+
this.privy = privyClient;
|
|
1557
|
+
this.chainManager = chainManager;
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* Creates a new wallet using Privy’s wallet API
|
|
1561
|
+
*
|
|
1562
|
+
* @internal
|
|
1563
|
+
* @category Creation
|
|
1564
|
+
* @returns Promise that resolves to a new {@link PrivyWallet} instance
|
|
1565
|
+
* @throws Error if wallet creation fails
|
|
1566
|
+
*/
|
|
1567
|
+
async createWallet() {
|
|
1568
|
+
try {
|
|
1569
|
+
const wallet = await this.privy.walletApi.createWallet({
|
|
1570
|
+
chainType: "ethereum"
|
|
1571
|
+
});
|
|
1572
|
+
const walletInstance = new PrivyWallet(
|
|
1573
|
+
this.privy,
|
|
1574
|
+
wallet.id,
|
|
1575
|
+
(0, import_viem9.getAddress)(wallet.address),
|
|
1576
|
+
this.chainManager
|
|
1577
|
+
);
|
|
1578
|
+
return walletInstance;
|
|
1579
|
+
} catch (error) {
|
|
1580
|
+
logger.error("Failed to create wallet: ", error, "PrivyEmbeddedWalletProvider");
|
|
1581
|
+
throw new Error(`Failed to create wallet: ${error}`);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Retrieves a wallet by its ID via Privy
|
|
1586
|
+
*
|
|
1587
|
+
* @internal
|
|
1588
|
+
* @category Retrieval
|
|
1589
|
+
* @param params Parameters containing walletId
|
|
1590
|
+
* @returns Promise that resolves to a {@link PrivyWallet} instance
|
|
1591
|
+
* @throws Error if the wallet cannot be retrieved
|
|
1592
|
+
*/
|
|
1593
|
+
async getWallet(params) {
|
|
1594
|
+
try {
|
|
1595
|
+
const wallet = await this.privy.walletApi.getWallet({
|
|
1596
|
+
id: params.walletId
|
|
1597
|
+
});
|
|
1598
|
+
const walletInstance = new PrivyWallet(
|
|
1599
|
+
this.privy,
|
|
1600
|
+
wallet.id,
|
|
1601
|
+
(0, import_viem9.getAddress)(wallet.address),
|
|
1602
|
+
this.chainManager
|
|
1603
|
+
);
|
|
1604
|
+
return walletInstance;
|
|
1605
|
+
} catch {
|
|
1606
|
+
throw new Error(`Failed to get wallet with id: ${params.walletId}`);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Retrieves all wallets from Privy with optional filtering and pagination
|
|
1611
|
+
*
|
|
1612
|
+
* @internal
|
|
1613
|
+
* @category Retrieval
|
|
1614
|
+
* @param options Optional filtering and pagination parameters
|
|
1615
|
+
* @returns Promise that resolves to an array of {@link PrivyWallet} instances
|
|
1616
|
+
* @throws Error if wallets cannot be retrieved
|
|
1617
|
+
*/
|
|
1618
|
+
async getAllWallets(options) {
|
|
1619
|
+
try {
|
|
1620
|
+
const response = await this.privy.walletApi.getWallets({
|
|
1621
|
+
limit: options?.limit,
|
|
1622
|
+
cursor: options?.cursor
|
|
1623
|
+
});
|
|
1624
|
+
return response.data.map((wallet) => {
|
|
1625
|
+
const walletInstance = new PrivyWallet(
|
|
1626
|
+
this.privy,
|
|
1627
|
+
wallet.id,
|
|
1628
|
+
(0, import_viem9.getAddress)(wallet.address),
|
|
1629
|
+
this.chainManager
|
|
1630
|
+
);
|
|
1631
|
+
return walletInstance;
|
|
1632
|
+
});
|
|
1633
|
+
} catch {
|
|
1634
|
+
throw new Error("Failed to retrieve wallets");
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
|
|
1639
|
+
// src/index.ts
|
|
1640
|
+
var import_server_auth = require("@privy-io/server-auth");
|
|
1641
|
+
|
|
1642
|
+
// src/protocols/base/BaseProtocol.ts
|
|
1643
|
+
var import_viem10 = require("viem");
|
|
1644
|
+
var BaseProtocol = class {
|
|
1645
|
+
/**
|
|
1646
|
+
* Ensure the protocol has been initialized
|
|
1647
|
+
* @throws Error if `init()` has not been called
|
|
1648
|
+
*/
|
|
1649
|
+
ensureInitialized() {
|
|
1650
|
+
if (!this.chainManager) {
|
|
1651
|
+
throw new Error("Protocol must be initialized before use. Call init() first.");
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Approve a token for protocol use
|
|
1656
|
+
* @param tokenAddress Token address
|
|
1657
|
+
* @param spenderAddress Spender address
|
|
1658
|
+
* @param amount Allowance amount in wei
|
|
1659
|
+
* @param chainId Target chain ID
|
|
1660
|
+
* @param account Account authorizing the approval
|
|
1661
|
+
* @returns Transaction hash
|
|
1662
|
+
*/
|
|
1663
|
+
async approveToken(tokenAddress, spenderAddress, amount, chainId, account) {
|
|
1664
|
+
this.ensureInitialized();
|
|
1665
|
+
const walletClient = (0, import_viem10.createWalletClient)({
|
|
1666
|
+
account,
|
|
1667
|
+
chain: this.chainManager.getChain(chainId),
|
|
1668
|
+
transport: (0, import_viem10.http)(this.chainManager.getRpcUrl(chainId))
|
|
1669
|
+
});
|
|
1670
|
+
const hash = await walletClient.writeContract({
|
|
1671
|
+
address: tokenAddress,
|
|
1672
|
+
abi: import_viem10.erc20Abi,
|
|
1673
|
+
functionName: "approve",
|
|
1674
|
+
args: [spenderAddress, amount],
|
|
1675
|
+
gas: 100000n,
|
|
1676
|
+
maxFeePerGas: (0, import_viem10.parseGwei)("20"),
|
|
1677
|
+
maxPriorityFeePerGas: (0, import_viem10.parseGwei)("2")
|
|
1678
|
+
});
|
|
1679
|
+
return hash;
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Check token allowance for a spender
|
|
1683
|
+
* @param tokenAddress Token address
|
|
1684
|
+
* @param spenderAddress Spender address
|
|
1685
|
+
* @param walletAddress Wallet address granting allowance
|
|
1686
|
+
* @param chainId Target chain ID
|
|
1687
|
+
* @returns Current allowance amount
|
|
1688
|
+
*/
|
|
1689
|
+
async checkAllowance(tokenAddress, spenderAddress, walletAddress, chainId) {
|
|
1690
|
+
this.ensureInitialized();
|
|
1691
|
+
const publicClient = this.chainManager.getPublicClient(chainId);
|
|
1692
|
+
return await publicClient.readContract({
|
|
1693
|
+
address: tokenAddress,
|
|
1694
|
+
abi: import_viem10.erc20Abi,
|
|
1695
|
+
functionName: "allowance",
|
|
1696
|
+
args: [walletAddress, spenderAddress]
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
};
|
|
1700
|
+
|
|
1701
|
+
// src/protocols/implementations/SparkProtocol.ts
|
|
1702
|
+
var import_viem11 = require("viem");
|
|
1703
|
+
|
|
1704
|
+
// src/abis/protocols/spark.ts
|
|
1705
|
+
var SPARK_VAULT_ABI = [
|
|
1706
|
+
{ type: "function", name: "asset", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] },
|
|
1707
|
+
{ type: "function", name: "totalAssets", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] },
|
|
1708
|
+
{ type: "function", name: "convertToShares", stateMutability: "view", inputs: [{ name: "assets", type: "uint256" }], outputs: [{ type: "uint256" }] },
|
|
1709
|
+
{ type: "function", name: "convertToAssets", stateMutability: "view", inputs: [{ name: "shares", type: "uint256" }], outputs: [{ type: "uint256" }] },
|
|
1710
|
+
{ type: "function", name: "balanceOf", stateMutability: "view", inputs: [{ name: "owner", type: "address" }], outputs: [{ type: "uint256" }] },
|
|
1711
|
+
{ type: "function", name: "deposit", stateMutability: "nonpayable", inputs: [
|
|
1712
|
+
{ name: "assets", type: "uint256" },
|
|
1713
|
+
{ name: "receiver", type: "address" }
|
|
1714
|
+
], outputs: [{ type: "uint256" }] },
|
|
1715
|
+
{ type: "function", name: "withdraw", stateMutability: "nonpayable", inputs: [
|
|
1716
|
+
{ name: "assets", type: "uint256" },
|
|
1717
|
+
{ name: "receiver", type: "address" },
|
|
1718
|
+
{ name: "owner", type: "address" }
|
|
1719
|
+
], outputs: [{ type: "uint256" }] },
|
|
1720
|
+
{ type: "function", name: "redeem", stateMutability: "nonpayable", inputs: [
|
|
1721
|
+
{ name: "shares", type: "uint256" },
|
|
1722
|
+
{ name: "receiver", type: "address" },
|
|
1723
|
+
{ name: "owner", type: "address" }
|
|
1724
|
+
], outputs: [{ type: "uint256" }] }
|
|
1725
|
+
];
|
|
1726
|
+
var SPARK_SSR_ORACLE_ABI = [
|
|
1727
|
+
{ "inputs": [], "stateMutability": "nonpayable", "type": "constructor" },
|
|
1728
|
+
{ "inputs": [], "name": "AccessControlBadConfirmation", "type": "error" },
|
|
1729
|
+
{ "inputs": [{ "internalType": "address", "name": "account", "type": "address" }, { "internalType": "bytes32", "name": "neededRole", "type": "bytes32" }], "name": "AccessControlUnauthorizedAccount", "type": "error" },
|
|
1730
|
+
{ "anonymous": false, "inputs": [{ "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "indexed": true, "internalType": "bytes32", "name": "previousAdminRole", "type": "bytes32" }, { "indexed": true, "internalType": "bytes32", "name": "newAdminRole", "type": "bytes32" }], "name": "RoleAdminChanged", "type": "event" },
|
|
1731
|
+
{ "anonymous": false, "inputs": [{ "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "indexed": true, "internalType": "address", "name": "account", "type": "address" }, { "indexed": true, "internalType": "address", "name": "sender", "type": "address" }], "name": "RoleGranted", "type": "event" },
|
|
1732
|
+
{ "anonymous": false, "inputs": [{ "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "indexed": true, "internalType": "address", "name": "account", "type": "address" }, { "indexed": true, "internalType": "address", "name": "sender", "type": "address" }], "name": "RoleRevoked", "type": "event" },
|
|
1733
|
+
{ "anonymous": false, "inputs": [{ "indexed": false, "internalType": "uint256", "name": "maxSSR", "type": "uint256" }], "name": "SetMaxSSR", "type": "event" },
|
|
1734
|
+
{ "anonymous": false, "inputs": [{ "components": [{ "internalType": "uint96", "name": "ssr", "type": "uint96" }, { "internalType": "uint120", "name": "chi", "type": "uint120" }, { "internalType": "uint40", "name": "rho", "type": "uint40" }], "indexed": false, "internalType": "struct ISSROracle.SUSDSData", "name": "nextData", "type": "tuple" }], "name": "SetSUSDSData", "type": "event" },
|
|
1735
|
+
{ "inputs": [], "name": "DATA_PROVIDER_ROLE", "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], "stateMutability": "view", "type": "function" },
|
|
1736
|
+
{ "inputs": [], "name": "DEFAULT_ADMIN_ROLE", "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], "stateMutability": "view", "type": "function" },
|
|
1737
|
+
{ "inputs": [], "name": "getAPR", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" },
|
|
1738
|
+
{ "inputs": [], "name": "getChi", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" },
|
|
1739
|
+
{ "inputs": [{ "internalType": "uint256", "name": "timestamp", "type": "uint256" }], "name": "getConversionRate", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" },
|
|
1740
|
+
{ "inputs": [], "name": "getConversionRate", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" },
|
|
1741
|
+
{ "inputs": [{ "internalType": "uint256", "name": "timestamp", "type": "uint256" }], "name": "getConversionRateBinomialApprox", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" },
|
|
1742
|
+
{ "inputs": [], "name": "getConversionRateBinomialApprox", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" },
|
|
1743
|
+
{ "inputs": [{ "internalType": "uint256", "name": "timestamp", "type": "uint256" }], "name": "getConversionRateLinearApprox", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" },
|
|
1744
|
+
{ "inputs": [], "name": "getConversionRateLinearApprox", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" },
|
|
1745
|
+
{ "inputs": [], "name": "getRho", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" },
|
|
1746
|
+
{ "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }], "name": "getRoleAdmin", "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], "stateMutability": "view", "type": "function" },
|
|
1747
|
+
{ "inputs": [], "name": "getSSR", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" },
|
|
1748
|
+
{ "inputs": [], "name": "getSUSDSData", "outputs": [{ "components": [{ "internalType": "uint96", "name": "ssr", "type": "uint96" }, { "internalType": "uint120", "name": "chi", "type": "uint120" }, { "internalType": "uint40", "name": "rho", "type": "uint40" }], "internalType": "struct ISSROracle.SUSDSData", "name": "", "type": "tuple" }], "stateMutability": "view", "type": "function" },
|
|
1749
|
+
{ "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" }], "name": "grantRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" },
|
|
1750
|
+
{ "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" }], "name": "hasRole", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" },
|
|
1751
|
+
{ "inputs": [], "name": "maxSSR", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" },
|
|
1752
|
+
{ "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "callerConfirmation", "type": "address" }], "name": "renounceRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" },
|
|
1753
|
+
{ "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" }], "name": "revokeRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" },
|
|
1754
|
+
{ "inputs": [{ "internalType": "uint256", "name": "_maxSSR", "type": "uint256" }], "name": "setMaxSSR", "outputs": [], "stateMutability": "nonpayable", "type": "function" },
|
|
1755
|
+
{ "inputs": [{ "components": [{ "internalType": "uint96", "name": "ssr", "type": "uint96" }, { "internalType": "uint120", "name": "chi", "type": "uint120" }, { "internalType": "uint40", "name": "rho", "type": "uint40" }], "internalType": "struct ISSROracle.SUSDSData", "name": "nextData", "type": "tuple" }], "name": "setSUSDSData", "outputs": [], "stateMutability": "nonpayable", "type": "function" },
|
|
1756
|
+
{ "inputs": [{ "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" }], "name": "supportsInterface", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" }
|
|
1757
|
+
];
|
|
1758
|
+
|
|
1759
|
+
// src/protocols/constants/spark.ts
|
|
1760
|
+
var SPARK_SSR_ORACLE_ADDRESS = "0x65d946e533748A998B1f0E430803e39A6388f7a1";
|
|
1761
|
+
var SPARK_VAULT = [
|
|
1762
|
+
{
|
|
1763
|
+
id: "sUSDC",
|
|
1764
|
+
chain: "base",
|
|
1765
|
+
vaultAddress: "0x3128a0f7f0ea68e7b7c9b00afa7e41045828e858",
|
|
1766
|
+
depositTokenAddress: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
1767
|
+
depositTokenDecimals: 6,
|
|
1768
|
+
depositTokenSymbol: "USDC",
|
|
1769
|
+
earnTokenAddress: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
1770
|
+
earnTokenDecimals: 18,
|
|
1771
|
+
earnTokenSymbol: "sUSDC",
|
|
1772
|
+
metadata: {}
|
|
1773
|
+
}
|
|
1774
|
+
];
|
|
1775
|
+
var SECONDS_PER_YEAR = 60 * 60 * 24 * 365;
|
|
1776
|
+
var RAY = BigInt("1000000000000000000000000000");
|
|
1777
|
+
|
|
1778
|
+
// src/protocols/implementations/SparkProtocol.ts
|
|
1779
|
+
var SparkProtocol = class extends BaseProtocol {
|
|
1780
|
+
constructor() {
|
|
1781
|
+
super(...arguments);
|
|
1782
|
+
this.allVaults = [];
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Initialize the Spark protocol with the provided chain manager
|
|
1786
|
+
* @param chainManager Chain manager instance used for network operations
|
|
1787
|
+
*/
|
|
1788
|
+
async init(chainManager) {
|
|
1789
|
+
this.chainManager = chainManager;
|
|
1790
|
+
this.selectedChainId = chainManager.getSupportedChain();
|
|
1791
|
+
this.publicClient = chainManager.getPublicClient(this.selectedChainId);
|
|
1792
|
+
this.allVaults = this.getVaults();
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Get the SSR (Sky Saving Rate) of the Spark protocol
|
|
1796
|
+
* @remarks
|
|
1797
|
+
* The parameter ius necessary to calculate the APY of a vault
|
|
1798
|
+
* @returns
|
|
1799
|
+
*/
|
|
1800
|
+
async getSSR() {
|
|
1801
|
+
if (!this.publicClient) {
|
|
1802
|
+
throw new Error("Public client not initialized");
|
|
1803
|
+
}
|
|
1804
|
+
const ssrRaw = await this.publicClient.readContract({
|
|
1805
|
+
address: SPARK_SSR_ORACLE_ADDRESS,
|
|
1806
|
+
abi: SPARK_SSR_ORACLE_ABI,
|
|
1807
|
+
functionName: "getSSR"
|
|
1808
|
+
});
|
|
1809
|
+
const ssr = Number(ssrRaw) / Number(RAY);
|
|
1810
|
+
return ssr;
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Get the APY of the Spark protocol
|
|
1814
|
+
* @remarks
|
|
1815
|
+
* Calculation based on the formula from the documentation:
|
|
1816
|
+
* https://docs.spark.fi/dev/integration-guides/susds-lending-market#rates
|
|
1817
|
+
* @returns The APY of the Spark protocol in percentage
|
|
1818
|
+
*/
|
|
1819
|
+
async getAPY() {
|
|
1820
|
+
const ssr = await this.getSSR();
|
|
1821
|
+
const apy = Math.exp(Math.log(ssr) * SECONDS_PER_YEAR) - 1;
|
|
1822
|
+
return Number((apy * 100).toFixed(2));
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
*
|
|
1826
|
+
* Get all vault info from a Spark protocol
|
|
1827
|
+
* @returns The list of vaults
|
|
1828
|
+
*/
|
|
1829
|
+
getVaults() {
|
|
1830
|
+
return SPARK_VAULT;
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1833
|
+
* Get the best available Spark vault
|
|
1834
|
+
* @returns The top-ranked Spark vault
|
|
1835
|
+
* @throws Error if no vaults found
|
|
1836
|
+
*/
|
|
1837
|
+
async getBestVault() {
|
|
1838
|
+
if (this.allVaults.length === 0) {
|
|
1839
|
+
throw new Error("No vaults found");
|
|
1840
|
+
}
|
|
1841
|
+
const selectedVault = this.allVaults[0];
|
|
1842
|
+
selectedVault.metadata.apy = await this.getAPY();
|
|
1843
|
+
return selectedVault;
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Fetch a vault where the user previously deposited funds
|
|
1847
|
+
* @param smartWallet Smart wallet to inspect
|
|
1848
|
+
* @returns The vault with user deposits, or null if none found
|
|
1849
|
+
*/
|
|
1850
|
+
async fetchDepositedVaults(smartWallet) {
|
|
1851
|
+
let depositedVault = void 0;
|
|
1852
|
+
const userAddress = await smartWallet.getAddress();
|
|
1853
|
+
for (const vault of this.allVaults) {
|
|
1854
|
+
const balance = await this.getBalance(vault, userAddress);
|
|
1855
|
+
if (parseInt(balance.depositedAmount) > 0) {
|
|
1856
|
+
depositedVault = vault;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
if (depositedVault) {
|
|
1860
|
+
depositedVault.metadata.apy = await this.getAPY();
|
|
1861
|
+
}
|
|
1862
|
+
logger.info("Deposited vaults:", { depositedVault }, "SparkProtocol");
|
|
1863
|
+
return depositedVault || null;
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Deposit funds into a Spark vault
|
|
1867
|
+
* @param amount Amount to deposit (human-readable)
|
|
1868
|
+
* @param smartWallet Smart wallet instance to use
|
|
1869
|
+
* @returns Transaction result with hash
|
|
1870
|
+
*/
|
|
1871
|
+
async deposit(amount, smartWallet) {
|
|
1872
|
+
const depositedVault = await this.fetchDepositedVaults(smartWallet);
|
|
1873
|
+
let vaultInfoToDeposit;
|
|
1874
|
+
logger.info("Previously deposited vault:", { depositedVault }, "SparkProtocol");
|
|
1875
|
+
if (depositedVault) {
|
|
1876
|
+
vaultInfoToDeposit = depositedVault;
|
|
1877
|
+
} else {
|
|
1878
|
+
vaultInfoToDeposit = await this.getBestVault();
|
|
1879
|
+
logger.info("Best vault that found:", { bestVault: vaultInfoToDeposit }, "SparkProtocol");
|
|
1880
|
+
}
|
|
1881
|
+
const owner = await smartWallet.getAddress();
|
|
1882
|
+
const assets = (0, import_viem11.parseUnits)(amount, vaultInfoToDeposit.depositTokenDecimals);
|
|
1883
|
+
logger.info("Raw deposit amount:", { amount, assets }, "SparkProtocol");
|
|
1884
|
+
const allowance = await this.checkAllowance(
|
|
1885
|
+
vaultInfoToDeposit.depositTokenAddress,
|
|
1886
|
+
vaultInfoToDeposit.vaultAddress,
|
|
1887
|
+
owner,
|
|
1888
|
+
this.selectedChainId
|
|
1889
|
+
);
|
|
1890
|
+
logger.info("Current vault contract allowance:", { allowance }, "SparkProtocol");
|
|
1891
|
+
const ops = [];
|
|
1892
|
+
if (allowance < assets) {
|
|
1893
|
+
ops.push({
|
|
1894
|
+
to: vaultInfoToDeposit.depositTokenAddress,
|
|
1895
|
+
data: (0, import_viem11.encodeFunctionData)({
|
|
1896
|
+
abi: import_viem11.erc20Abi,
|
|
1897
|
+
functionName: "approve",
|
|
1898
|
+
args: [vaultInfoToDeposit.vaultAddress, assets]
|
|
1899
|
+
})
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
ops.push({
|
|
1903
|
+
to: vaultInfoToDeposit.vaultAddress,
|
|
1904
|
+
data: (0, import_viem11.encodeFunctionData)({
|
|
1905
|
+
abi: SPARK_VAULT_ABI,
|
|
1906
|
+
functionName: "deposit",
|
|
1907
|
+
args: [assets, owner]
|
|
1908
|
+
})
|
|
1909
|
+
});
|
|
1910
|
+
const hash = await smartWallet.sendBatch(ops, this.selectedChainId);
|
|
1911
|
+
return { success: true, hash };
|
|
1912
|
+
}
|
|
1913
|
+
/**
|
|
1914
|
+
* Withdraw funds from a Spark vault
|
|
1915
|
+
* @param amountInUnderlying Amount in base token units (or undefined to withdraw all)
|
|
1916
|
+
* @param smartWallet Smart wallet instance to withdraw from
|
|
1917
|
+
* @returns Transaction result with hash
|
|
1918
|
+
* @throws Error if no deposited vault found
|
|
1919
|
+
*/
|
|
1920
|
+
async withdraw(amountInUnderlying, smartWallet) {
|
|
1921
|
+
const depositedVault = await this.fetchDepositedVaults(smartWallet);
|
|
1922
|
+
if (!depositedVault) {
|
|
1923
|
+
throw new Error("No vault found to withdraw from");
|
|
1924
|
+
}
|
|
1925
|
+
const owner = await smartWallet.getAddress();
|
|
1926
|
+
let withdrawData;
|
|
1927
|
+
if (amountInUnderlying) {
|
|
1928
|
+
const assets = (0, import_viem11.parseUnits)(amountInUnderlying, depositedVault.depositTokenDecimals);
|
|
1929
|
+
logger.info("Withdraw amount:", { amountInUnderlying, assets }, "SparkProtocol");
|
|
1930
|
+
withdrawData = {
|
|
1931
|
+
to: depositedVault.vaultAddress,
|
|
1932
|
+
data: (0, import_viem11.encodeFunctionData)({
|
|
1933
|
+
abi: SPARK_VAULT_ABI,
|
|
1934
|
+
functionName: "withdraw",
|
|
1935
|
+
args: [assets, owner, owner]
|
|
1936
|
+
})
|
|
1937
|
+
};
|
|
1938
|
+
} else {
|
|
1939
|
+
const maxShares = await this.getMaxRedeemableShares(depositedVault, owner);
|
|
1940
|
+
logger.info("Withdrawing all funds:", { maxShares }, "SparkProtocol");
|
|
1941
|
+
withdrawData = {
|
|
1942
|
+
to: depositedVault.vaultAddress,
|
|
1943
|
+
data: (0, import_viem11.encodeFunctionData)({
|
|
1944
|
+
abi: SPARK_VAULT_ABI,
|
|
1945
|
+
functionName: "redeem",
|
|
1946
|
+
args: [maxShares, owner, owner]
|
|
1947
|
+
})
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
const hash = await smartWallet.send(withdrawData, this.selectedChainId);
|
|
1951
|
+
logger.info("Withdraw transaction sent:", { hash }, "SparkProtocol");
|
|
1952
|
+
return { success: true, hash };
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Get the maximum redeemable shares for a wallet
|
|
1956
|
+
* @param vaultInfo Vault information
|
|
1957
|
+
* @param walletAddress Wallet address to check
|
|
1958
|
+
* @returns Maximum redeemable shares as bigint
|
|
1959
|
+
*/
|
|
1960
|
+
async getMaxRedeemableShares(vaultInfo, walletAddress) {
|
|
1961
|
+
if (!this.publicClient) {
|
|
1962
|
+
throw new Error("Public client not initialized");
|
|
1963
|
+
}
|
|
1964
|
+
const shares = await this.publicClient.readContract({
|
|
1965
|
+
address: vaultInfo.vaultAddress,
|
|
1966
|
+
abi: SPARK_VAULT_ABI,
|
|
1967
|
+
functionName: "balanceOf",
|
|
1968
|
+
args: [walletAddress]
|
|
1969
|
+
});
|
|
1970
|
+
return shares;
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Get amount that a wallet has deposited in a vault
|
|
1974
|
+
* @param vaultInfo Vault information
|
|
1975
|
+
* @param walletAddress Wallet address to check
|
|
1976
|
+
* @returns Object containing shares and deposited amount
|
|
1977
|
+
*/
|
|
1978
|
+
async getBalance(vaultInfo, walletAddress) {
|
|
1979
|
+
if (!this.publicClient) {
|
|
1980
|
+
throw new Error("Public client not initialized");
|
|
1981
|
+
}
|
|
1982
|
+
const shares = await this.publicClient.readContract({
|
|
1983
|
+
address: vaultInfo.vaultAddress,
|
|
1984
|
+
abi: SPARK_VAULT_ABI,
|
|
1985
|
+
functionName: "balanceOf",
|
|
1986
|
+
args: [walletAddress]
|
|
1987
|
+
});
|
|
1988
|
+
if (shares === 0n) {
|
|
1989
|
+
return { shares: "0", depositedAmount: "0", vaultInfo };
|
|
1990
|
+
}
|
|
1991
|
+
const assets = await this.publicClient.readContract({
|
|
1992
|
+
address: vaultInfo.vaultAddress,
|
|
1993
|
+
abi: SPARK_VAULT_ABI,
|
|
1994
|
+
functionName: "convertToAssets",
|
|
1995
|
+
args: [shares]
|
|
1996
|
+
});
|
|
1997
|
+
return {
|
|
1998
|
+
shares: (0, import_viem11.formatUnits)(shares, vaultInfo.earnTokenDecimals),
|
|
1999
|
+
depositedAmount: (0, import_viem11.formatUnits)(assets, vaultInfo.depositTokenDecimals),
|
|
2000
|
+
vaultInfo
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
|
|
2005
|
+
// src/constants/protocols.ts
|
|
2006
|
+
var availableProtocols = [
|
|
2007
|
+
{
|
|
2008
|
+
info: {
|
|
2009
|
+
id: "spark",
|
|
2010
|
+
name: "Spark",
|
|
2011
|
+
website: "https://spark.fi/",
|
|
2012
|
+
logo: "/logos/spark.png",
|
|
2013
|
+
supportedChains: [1, 8453, 42161],
|
|
2014
|
+
riskLevel: "low",
|
|
2015
|
+
isPremium: false
|
|
2016
|
+
},
|
|
2017
|
+
instance: new SparkProtocol()
|
|
2018
|
+
}
|
|
2019
|
+
];
|
|
2020
|
+
|
|
2021
|
+
// src/tools/ApiKeysValidator.ts
|
|
2022
|
+
var ApiKeysValidator = class {
|
|
2023
|
+
// TODO: Implement the validation logic
|
|
2024
|
+
/**
|
|
2025
|
+
* Validates whether the provided API key is valid
|
|
2026
|
+
*
|
|
2027
|
+
* @internal
|
|
2028
|
+
* @param apiKey API key from {@link ProtocolsRouterConfig}
|
|
2029
|
+
* @returns True if the API key is considered valid
|
|
2030
|
+
*/
|
|
2031
|
+
validate(apiKey) {
|
|
2032
|
+
logger.info("Validating api key...", apiKey, "ApiKeysValidator");
|
|
2033
|
+
return true;
|
|
2034
|
+
}
|
|
2035
|
+
};
|
|
2036
|
+
|
|
2037
|
+
// src/router/base/ProtocolRouterBase.ts
|
|
2038
|
+
var ProtocolRouterBase = class {
|
|
2039
|
+
/**
|
|
2040
|
+
* Initialize a base protocol router
|
|
2041
|
+
* @param riskLevel Risk level required by the integrator
|
|
2042
|
+
* @param chainManager Chain manager instance for network operations
|
|
2043
|
+
* @param minApy Optional minimum APY filter
|
|
2044
|
+
* @param apiKey Optional API key for premium protocol access
|
|
2045
|
+
*/
|
|
2046
|
+
constructor(riskLevel, chainManager, minApy, apiKey) {
|
|
2047
|
+
// TODO: Add an API key validation
|
|
2048
|
+
/** API key validator instance */
|
|
2049
|
+
this.apiKeyValidator = new ApiKeysValidator();
|
|
2050
|
+
this.riskLevel = riskLevel;
|
|
2051
|
+
this.minApy = minApy;
|
|
2052
|
+
this.apiKey = apiKey;
|
|
2053
|
+
this.chainManager = chainManager;
|
|
2054
|
+
}
|
|
2055
|
+
};
|
|
2056
|
+
|
|
2057
|
+
// src/router/ProtocolRouter.ts
|
|
2058
|
+
var ProtocolRouter = class extends ProtocolRouterBase {
|
|
2059
|
+
/**
|
|
2060
|
+
* Initialize the protocol router
|
|
2061
|
+
* @param config Router configuration including risk level, min APY, and optional API key
|
|
2062
|
+
* @param chainManager Chain manager instance for network validation
|
|
2063
|
+
*/
|
|
2064
|
+
constructor(config, chainManager) {
|
|
2065
|
+
super(config.riskLevel, chainManager, config.minApy, config.apiKey);
|
|
2066
|
+
}
|
|
2067
|
+
/**
|
|
2068
|
+
* Get all protocols available for the current configuration
|
|
2069
|
+
*
|
|
2070
|
+
* Includes all non-premium protocols and premium protocols if the API key is valid
|
|
2071
|
+
* @returns Array of available protocol definitions
|
|
2072
|
+
*/
|
|
2073
|
+
getProtocols() {
|
|
2074
|
+
const isKeyValid = this.apiKeyValidator.validate(this.apiKey);
|
|
2075
|
+
const allAvailableProtocols = availableProtocols.filter((protocol) => {
|
|
2076
|
+
if (!protocol.info.isPremium) {
|
|
2077
|
+
return true;
|
|
2078
|
+
}
|
|
2079
|
+
return protocol.info.isPremium && isKeyValid;
|
|
2080
|
+
});
|
|
2081
|
+
return allAvailableProtocols;
|
|
2082
|
+
}
|
|
2083
|
+
/**
|
|
2084
|
+
* Check if any protocol supports a given set of chains
|
|
2085
|
+
* @param chainIds List of chain IDs to validate
|
|
2086
|
+
* @returns True if at least one chain is supported by the router
|
|
2087
|
+
*/
|
|
2088
|
+
isProtocolSupportedChain(chainIds) {
|
|
2089
|
+
return chainIds.some((chainId) => this.chainManager.getSupportedChain() === chainId);
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Recommend the best protocol for the current router configuration
|
|
2093
|
+
*
|
|
2094
|
+
* Filters available protocols by risk level and supported chains. More criteria will be added later on
|
|
2095
|
+
*
|
|
2096
|
+
*
|
|
2097
|
+
* @remarks
|
|
2098
|
+
* Currently returns the first match. Future improvements will add
|
|
2099
|
+
* smarter sorting and pool-based APY checks
|
|
2100
|
+
*
|
|
2101
|
+
* @throws Error if no protocols are available for the current risk level
|
|
2102
|
+
* @returns Protocol instance considered the best match
|
|
2103
|
+
*/
|
|
2104
|
+
recommend() {
|
|
2105
|
+
const protocols = this.getProtocols();
|
|
2106
|
+
const eligibleProtocols = protocols.filter((protocol) => {
|
|
2107
|
+
const riskMatches = protocol.info.riskLevel === this.riskLevel;
|
|
2108
|
+
const isSupportedChain = this.isProtocolSupportedChain(protocol.info.supportedChains);
|
|
2109
|
+
return riskMatches && isSupportedChain;
|
|
2110
|
+
});
|
|
2111
|
+
if (eligibleProtocols.length === 0) {
|
|
2112
|
+
throw new Error(`No protocols available for risk level: ${this.riskLevel}`);
|
|
2113
|
+
}
|
|
2114
|
+
const bestProtocol = eligibleProtocols[0];
|
|
2115
|
+
return bestProtocol;
|
|
2116
|
+
}
|
|
2117
|
+
};
|
|
2118
|
+
|
|
2119
|
+
// src/tools/CoinbaseCDP.ts
|
|
2120
|
+
var import_auth = require("@coinbase/cdp-sdk/auth");
|
|
2121
|
+
var import_axios = __toESM(require("axios"), 1);
|
|
2122
|
+
|
|
2123
|
+
// src/utils/urls.ts
|
|
2124
|
+
var checkValidUrl = (url) => {
|
|
2125
|
+
return /^https?:\/\/.+$/.test(url);
|
|
2126
|
+
};
|
|
2127
|
+
|
|
2128
|
+
// src/tools/CoinbaseCDP.ts
|
|
2129
|
+
var CoinbaseCDP = class {
|
|
2130
|
+
constructor(apiKeyId, apiKeySecret, integratorId, chainManager) {
|
|
2131
|
+
this.coinbaseCdpV2Hostname = "https://api.cdp.coinbase.com";
|
|
2132
|
+
this.coinbaseCdpV1Hostname = "https://api.developer.coinbase.com";
|
|
2133
|
+
this.onRampUrlAuthParams = {
|
|
2134
|
+
requestMethod: "POST",
|
|
2135
|
+
requestHost: "api.cdp.coinbase.com",
|
|
2136
|
+
requestPath: "/platform/v2/onramp/sessions",
|
|
2137
|
+
expiresIn: 120
|
|
2138
|
+
};
|
|
2139
|
+
this.onRampConfigAuthParams = {
|
|
2140
|
+
requestMethod: "GET",
|
|
2141
|
+
requestHost: "api.developer.coinbase.com",
|
|
2142
|
+
requestPath: "/onramp/v1/buy/config",
|
|
2143
|
+
expiresIn: 120
|
|
2144
|
+
};
|
|
2145
|
+
this.offRampUrlAuthParams = {
|
|
2146
|
+
requestMethod: "POST",
|
|
2147
|
+
requestHost: "api.developer.coinbase.com",
|
|
2148
|
+
requestPath: "/onramp/v1/sell/quote",
|
|
2149
|
+
expiresIn: 120
|
|
2150
|
+
};
|
|
2151
|
+
this.offRampConfigAuthParams = {
|
|
2152
|
+
requestMethod: "GET",
|
|
2153
|
+
requestHost: "api.developer.coinbase.com",
|
|
2154
|
+
requestPath: "/onramp/v1/sell/config",
|
|
2155
|
+
expiresIn: 120
|
|
2156
|
+
};
|
|
2157
|
+
this.clientV2 = import_axios.default.create({
|
|
2158
|
+
baseURL: this.coinbaseCdpV2Hostname
|
|
2159
|
+
});
|
|
2160
|
+
this.clientV1 = import_axios.default.create({
|
|
2161
|
+
baseURL: this.coinbaseCdpV1Hostname
|
|
2162
|
+
});
|
|
2163
|
+
this.apiKeyId = apiKeyId;
|
|
2164
|
+
this.apiKeySecret = apiKeySecret;
|
|
2165
|
+
this.chainManager = chainManager;
|
|
2166
|
+
this.integratorId = integratorId;
|
|
2167
|
+
this.verifyParameters();
|
|
2168
|
+
}
|
|
2169
|
+
verifyParameters() {
|
|
2170
|
+
if (!this.apiKeyId || !this.apiKeySecret || !this.integratorId) {
|
|
2171
|
+
throw new Error("API key ID, secret and integrator ID are required");
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
/**
|
|
2175
|
+
* @internal
|
|
2176
|
+
* Generate a JWT token for a provided API endpoint and hostname to make a request after
|
|
2177
|
+
* @category Ramp
|
|
2178
|
+
*
|
|
2179
|
+
* @param authParams Authentication parameters
|
|
2180
|
+
* @returns JWT token
|
|
2181
|
+
* @throws Error if the JWT token generation fails
|
|
2182
|
+
*/
|
|
2183
|
+
async auth(authParams) {
|
|
2184
|
+
const jwtToken = await (0, import_auth.generateJwt)({
|
|
2185
|
+
apiKeyId: this.apiKeyId,
|
|
2186
|
+
apiKeySecret: this.apiKeySecret,
|
|
2187
|
+
...authParams
|
|
2188
|
+
});
|
|
2189
|
+
return jwtToken;
|
|
2190
|
+
}
|
|
2191
|
+
/**
|
|
2192
|
+
* @internal
|
|
2193
|
+
* Return a on-ramp URL for the given parameters via Coinbase CDP V2 API
|
|
2194
|
+
* @category Ramp
|
|
2195
|
+
*
|
|
2196
|
+
* @param receiverAddress
|
|
2197
|
+
* @param redirectUrl
|
|
2198
|
+
* @param amount
|
|
2199
|
+
* @param purchaseCurrency
|
|
2200
|
+
* @param paymentCurrency
|
|
2201
|
+
* @param paymentMethod
|
|
2202
|
+
* @param country
|
|
2203
|
+
* @returns OnRampUrlResponse
|
|
2204
|
+
* @throws Error if redirect URL is not a valid URL
|
|
2205
|
+
* @throws CoinbaseCDPError if the request fails
|
|
2206
|
+
*/
|
|
2207
|
+
async getOnRampLink(receiverAddress, redirectUrl, amount, purchaseCurrency = "USDC", paymentCurrency = "USD", paymentMethod = "CARD", country) {
|
|
2208
|
+
if (!checkValidUrl(redirectUrl)) {
|
|
2209
|
+
throw new Error("Redirect URL is not a valid URL");
|
|
2210
|
+
}
|
|
2211
|
+
const chainId = this.chainManager.getSupportedChain();
|
|
2212
|
+
const chainName = chainById[chainId]?.name.toLowerCase();
|
|
2213
|
+
const authJwtToken = await this.auth(this.onRampUrlAuthParams);
|
|
2214
|
+
const response = await this.clientV2.post(
|
|
2215
|
+
this.onRampUrlAuthParams.requestPath,
|
|
2216
|
+
{
|
|
2217
|
+
destinationAddress: receiverAddress,
|
|
2218
|
+
destinationNetwork: chainName,
|
|
2219
|
+
redirectUrl,
|
|
2220
|
+
paymentAmount: amount,
|
|
2221
|
+
purchaseCurrency,
|
|
2222
|
+
paymentCurrency,
|
|
2223
|
+
paymentMethod,
|
|
2224
|
+
country
|
|
2225
|
+
},
|
|
2226
|
+
{
|
|
2227
|
+
method: this.onRampUrlAuthParams.requestMethod,
|
|
2228
|
+
headers: { Authorization: `Bearer ${authJwtToken}`, "Content-Type": "application/json" }
|
|
2229
|
+
}
|
|
2230
|
+
);
|
|
2231
|
+
if (response.status !== 200 && response.status !== 201) {
|
|
2232
|
+
const error = response.data;
|
|
2233
|
+
throw error;
|
|
2234
|
+
}
|
|
2235
|
+
const onRampResponse = response.data;
|
|
2236
|
+
return onRampResponse;
|
|
2237
|
+
}
|
|
2238
|
+
/**
|
|
2239
|
+
* @internal
|
|
2240
|
+
* Current method return all supported countries and payment methods for on-ramp by Coinbase CDP
|
|
2241
|
+
* @category Ramp
|
|
2242
|
+
*
|
|
2243
|
+
* @returns Config with supported countries and payment methods for on-ramp
|
|
2244
|
+
* @throws If API returned an error
|
|
2245
|
+
*/
|
|
2246
|
+
async getOnRampConfig() {
|
|
2247
|
+
const authJwtToken = await this.auth(this.onRampConfigAuthParams);
|
|
2248
|
+
const response = await this.clientV1.get(
|
|
2249
|
+
this.onRampConfigAuthParams.requestPath,
|
|
2250
|
+
{
|
|
2251
|
+
headers: { Authorization: `Bearer ${authJwtToken}` }
|
|
2252
|
+
}
|
|
2253
|
+
);
|
|
2254
|
+
if (response.status !== 200) {
|
|
2255
|
+
const error = response.data;
|
|
2256
|
+
throw error;
|
|
2257
|
+
}
|
|
2258
|
+
const onRampConfigResponse = response.data;
|
|
2259
|
+
return onRampConfigResponse;
|
|
2260
|
+
}
|
|
2261
|
+
/**
|
|
2262
|
+
* @internal
|
|
2263
|
+
* Return a off-ramp URL for the given parameters via Coinbase CDP V1 API
|
|
2264
|
+
* @remarks
|
|
2265
|
+
* Use an integratorId as a partnerUserId in Coinbase CDP API
|
|
2266
|
+
* @category Ramp
|
|
2267
|
+
*
|
|
2268
|
+
* @param address
|
|
2269
|
+
* @param country
|
|
2270
|
+
* @param paymentMethod
|
|
2271
|
+
* @param redirectUrl
|
|
2272
|
+
* @param sellAmount
|
|
2273
|
+
* @param cashoutCurrency
|
|
2274
|
+
* @param sellCurrency
|
|
2275
|
+
* @returns OffRampUrlResponse
|
|
2276
|
+
* @throws Error if redirect URL is not a valid URL
|
|
2277
|
+
* @throws CoinbaseCDPError if the request fails
|
|
2278
|
+
*/
|
|
2279
|
+
async getOffRampLink(address, country, paymentMethod, redirectUrl, sellAmount, cashoutCurrency = "USD", sellCurrency = "USDC") {
|
|
2280
|
+
if (!checkValidUrl(redirectUrl)) {
|
|
2281
|
+
throw new Error("Redirect URL is not a valid URL");
|
|
2282
|
+
}
|
|
2283
|
+
const chainId = this.chainManager.getSupportedChain();
|
|
2284
|
+
const chainName = chainById[chainId]?.name.toLowerCase();
|
|
2285
|
+
const authJwtToken = await this.auth(this.offRampUrlAuthParams);
|
|
2286
|
+
const response = await this.clientV1.post(
|
|
2287
|
+
this.offRampUrlAuthParams.requestPath,
|
|
2288
|
+
{
|
|
2289
|
+
sourceAddress: address,
|
|
2290
|
+
country,
|
|
2291
|
+
paymentMethod,
|
|
2292
|
+
partnerUserId: this.integratorId,
|
|
2293
|
+
redirectUrl,
|
|
2294
|
+
sellAmount,
|
|
2295
|
+
sellNetwork: chainName,
|
|
2296
|
+
cashoutCurrency,
|
|
2297
|
+
sellCurrency
|
|
2298
|
+
},
|
|
2299
|
+
{
|
|
2300
|
+
method: this.offRampUrlAuthParams.requestMethod,
|
|
2301
|
+
headers: { Authorization: `Bearer ${authJwtToken}`, "Content-Type": "application/json" }
|
|
2302
|
+
}
|
|
2303
|
+
);
|
|
2304
|
+
if (response.status !== 200 && response.status !== 201) {
|
|
2305
|
+
const error = response.data;
|
|
2306
|
+
throw error;
|
|
2307
|
+
}
|
|
2308
|
+
const offRampResponse = response.data;
|
|
2309
|
+
return offRampResponse;
|
|
2310
|
+
}
|
|
2311
|
+
/**
|
|
2312
|
+
* @internal
|
|
2313
|
+
* Current method return all supported countries and payment methods for off-ramp by Coinbase CDP
|
|
2314
|
+
* @category Ramp
|
|
2315
|
+
* @returns Config with supported countries and payment methods for off-ramp
|
|
2316
|
+
* @throws If API returned an error
|
|
2317
|
+
*/
|
|
2318
|
+
async getOffRampConfig() {
|
|
2319
|
+
const authJwtToken = await this.auth(this.offRampConfigAuthParams);
|
|
2320
|
+
const response = await this.clientV1.get(
|
|
2321
|
+
this.offRampConfigAuthParams.requestPath,
|
|
2322
|
+
{
|
|
2323
|
+
headers: { Authorization: `Bearer ${authJwtToken}` }
|
|
2324
|
+
}
|
|
2325
|
+
);
|
|
2326
|
+
if (response.status !== 200) {
|
|
2327
|
+
const error = response.data;
|
|
2328
|
+
throw error;
|
|
2329
|
+
}
|
|
2330
|
+
const offRampConfigResponse = response.data;
|
|
2331
|
+
return offRampConfigResponse;
|
|
2332
|
+
}
|
|
2333
|
+
};
|
|
2334
|
+
|
|
2335
|
+
// src/index.ts
|
|
2336
|
+
var MyceliumSDK = class {
|
|
2337
|
+
/**
|
|
2338
|
+
* Creates a new SDK instance
|
|
2339
|
+
*
|
|
2340
|
+
* @param config SDK configuration (networks, wallets, protocol router settings)
|
|
2341
|
+
* @throws Throws if an unsupported wallet provider is given
|
|
2342
|
+
* @see MyceliumSDKConfig
|
|
2343
|
+
*/
|
|
2344
|
+
constructor(config) {
|
|
2345
|
+
/**
|
|
2346
|
+
* Coinbase CDP instance to Coinbase related and onchain operations using Coinbase CDP API
|
|
2347
|
+
* @remarks
|
|
2348
|
+
* If the configuration is not provided, the Coinbase CDP functionality will be disabled.
|
|
2349
|
+
* Calling the respective method will throw an error.
|
|
2350
|
+
* @internal
|
|
2351
|
+
*/
|
|
2352
|
+
this.coinbaseCDP = null;
|
|
2353
|
+
/**
|
|
2354
|
+
* Coinbase CDP configuration methods for ramp operations
|
|
2355
|
+
* @public
|
|
2356
|
+
* @category Tools
|
|
2357
|
+
*/
|
|
2358
|
+
this.rampConfig = {
|
|
2359
|
+
/**
|
|
2360
|
+
* Return all supported countries and payment methods for on-ramp by Coinbase CDP
|
|
2361
|
+
* @public
|
|
2362
|
+
* @category Ramp
|
|
2363
|
+
*
|
|
2364
|
+
* @returns @see {@link RampConfigResponse} with supported countries and payment methods for top-up
|
|
2365
|
+
* @throws If API returned an error
|
|
2366
|
+
*/
|
|
2367
|
+
getTopUpConfig: async () => {
|
|
2368
|
+
if (!this.coinbaseCDP) {
|
|
2369
|
+
throw new Error(
|
|
2370
|
+
"Coinbase CDP is not initialized. Please, provide the configuration in the SDK initialization"
|
|
2371
|
+
);
|
|
2372
|
+
}
|
|
2373
|
+
return await this.coinbaseCDP.getOnRampConfig();
|
|
2374
|
+
},
|
|
2375
|
+
/**
|
|
2376
|
+
* Return all supported countries and payment methods for off-ramp by Coinbase CDP
|
|
2377
|
+
* @public
|
|
2378
|
+
* @category Ramp
|
|
2379
|
+
*
|
|
2380
|
+
*
|
|
2381
|
+
* @returns @see {@link RampConfigResponse} with supported countries and payment methods for cash out
|
|
2382
|
+
* @throws If API returned an error
|
|
2383
|
+
*/
|
|
2384
|
+
getCashOutConfig: async () => {
|
|
2385
|
+
if (!this.coinbaseCDP) {
|
|
2386
|
+
throw new Error(
|
|
2387
|
+
"Coinbase CDP is not initialized. Please, provide the configuration in the SDK initialization"
|
|
2388
|
+
);
|
|
2389
|
+
}
|
|
2390
|
+
return await this.coinbaseCDP.getOffRampConfig();
|
|
2391
|
+
}
|
|
2392
|
+
};
|
|
2393
|
+
this._chainManager = new ChainManager(
|
|
2394
|
+
config.chain || {
|
|
2395
|
+
chainId: import_chains7.base.id,
|
|
2396
|
+
rpcUrl: import_chains7.base.rpcUrls.default.http[0],
|
|
2397
|
+
bundlerUrl: "https://public.pimlico.io/v2/8453/rpc"
|
|
2398
|
+
}
|
|
2399
|
+
);
|
|
2400
|
+
if (!config.chain) {
|
|
2401
|
+
logger.warn(
|
|
2402
|
+
"No chain config provided, using default public RPC and Bundler URLs",
|
|
2403
|
+
"MyceliumSDK"
|
|
2404
|
+
);
|
|
2405
|
+
}
|
|
2406
|
+
if (config.coinbaseCDPConfig) {
|
|
2407
|
+
this.coinbaseCDP = new CoinbaseCDP(
|
|
2408
|
+
config.coinbaseCDPConfig.apiKeyId,
|
|
2409
|
+
config.coinbaseCDPConfig.apiKeySecret,
|
|
2410
|
+
config.integratorId,
|
|
2411
|
+
this.chainManager
|
|
2412
|
+
);
|
|
2413
|
+
}
|
|
2414
|
+
const protocolsRouterConfig = config.protocolsRouterConfig || {
|
|
2415
|
+
riskLevel: "low"
|
|
2416
|
+
};
|
|
2417
|
+
this.protocol = this.findProtocol(protocolsRouterConfig);
|
|
2418
|
+
this.wallet = this.createWalletNamespace(config.walletsConfig);
|
|
2419
|
+
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Returns the chain manager instance for multi-chain operations
|
|
2422
|
+
* @public
|
|
2423
|
+
* @category Tools
|
|
2424
|
+
*
|
|
2425
|
+
* @returns ChainManager instance of the type {@link ChainManager}
|
|
2426
|
+
*/
|
|
2427
|
+
get chainManager() {
|
|
2428
|
+
return this._chainManager;
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Recommends and initializes a protocol based on router settings
|
|
2432
|
+
*
|
|
2433
|
+
* @internal
|
|
2434
|
+
* @param config Protocol router configuration (e.g. risk level)
|
|
2435
|
+
* @returns Selected protocol object of the type {@link Protocol}
|
|
2436
|
+
*/
|
|
2437
|
+
findProtocol(config) {
|
|
2438
|
+
const protocolRouter = new ProtocolRouter(config, this.chainManager);
|
|
2439
|
+
const protocol = protocolRouter.recommend();
|
|
2440
|
+
protocol.instance.init(this.chainManager);
|
|
2441
|
+
return protocol;
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Creates a wallet provider (embedded + smart) and combines them
|
|
2445
|
+
*
|
|
2446
|
+
* @internal
|
|
2447
|
+
* @param config Wallet configuration
|
|
2448
|
+
* @returns Configured {@link WalletProvider}
|
|
2449
|
+
* @throws If an unsupported wallet provider type is specified
|
|
2450
|
+
*/
|
|
2451
|
+
createWalletProvider(config) {
|
|
2452
|
+
if (config.embeddedWalletConfig.provider.type === "privy") {
|
|
2453
|
+
const privyClient = new import_server_auth.PrivyClient(
|
|
2454
|
+
config.embeddedWalletConfig.provider.providerConfig.appId,
|
|
2455
|
+
config.embeddedWalletConfig.provider.providerConfig.appSecret
|
|
2456
|
+
);
|
|
2457
|
+
this.embeddedWalletProvider = new PrivyEmbeddedWalletProvider(
|
|
2458
|
+
privyClient,
|
|
2459
|
+
this._chainManager
|
|
2460
|
+
);
|
|
2461
|
+
} else {
|
|
2462
|
+
throw new Error(
|
|
2463
|
+
`Unsupported embedded wallet provider: ${config.embeddedWalletConfig.provider.type}`
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
if (!config.smartWalletConfig || config.smartWalletConfig.provider.type === "default") {
|
|
2467
|
+
this.smartWalletProvider = new DefaultSmartWalletProvider(
|
|
2468
|
+
this.chainManager,
|
|
2469
|
+
this.protocol,
|
|
2470
|
+
this.coinbaseCDP
|
|
2471
|
+
);
|
|
2472
|
+
} else {
|
|
2473
|
+
throw new Error(
|
|
2474
|
+
`Unsupported smart wallet provider: ${config.smartWalletConfig.provider.type}`
|
|
2475
|
+
);
|
|
2476
|
+
}
|
|
2477
|
+
const walletProvider = new WalletProvider(
|
|
2478
|
+
this.embeddedWalletProvider,
|
|
2479
|
+
this.smartWalletProvider
|
|
2480
|
+
);
|
|
2481
|
+
return walletProvider;
|
|
2482
|
+
}
|
|
2483
|
+
/**
|
|
2484
|
+
* Creates the public wallet namespace
|
|
2485
|
+
*
|
|
2486
|
+
* @internal
|
|
2487
|
+
* @param config Wallet configuration.
|
|
2488
|
+
* @returns A {@link WalletNamespace} instance
|
|
2489
|
+
*/
|
|
2490
|
+
createWalletNamespace(config) {
|
|
2491
|
+
const walletProvider = this.createWalletProvider(config);
|
|
2492
|
+
return new WalletNamespace(walletProvider);
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2496
|
+
0 && (module.exports = {
|
|
2497
|
+
DefaultSmartWallet,
|
|
2498
|
+
MyceliumSDK
|
|
2499
|
+
});
|
|
2500
|
+
//# sourceMappingURL=index.cjs.map
|