@orb-labs/orby-core 0.0.32 → 0.0.34

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.
@@ -1,8 +1,10 @@
1
1
  import { extractActivity, extractOperationSet, extractOperationStatuses, } from "../utils/action_helpers.js";
2
2
  import { CurrencyAmount } from "../entities/financial/currency_amount.js";
3
- import { AccountType, ActivityStatus, OperationDataFormat, OperationOrigin, OperationStatusType, OperationType, } from "../enums.js";
3
+ import { AccountType, ActivityStatus, LIBRARY_TYPE, OperationDataFormat, OperationOrigin, OperationStatusType, OperationType, } from "../enums.js";
4
4
  import { LibraryRequest } from "../entities/library_request.js";
5
5
  import { Account } from "../entities/account.js";
6
+ import { createClient, http } from "viem";
7
+ import { ethers } from "ethers";
6
8
  // from here: https://stackoverflow.com/questions/65152373/typescript-serialize-bigint-in-json
7
9
  BigInt.prototype.toJSON = function () {
8
10
  return this.toString();
@@ -46,6 +48,20 @@ export class OperationActions extends LibraryRequest {
46
48
  ]);
47
49
  return extractOperationSet(operationSet);
48
50
  }
51
+ async getOperationsForStablecoinAutoDepositSettings(accountClusterId, settings, gasToken) {
52
+ const formattedSettings = await Promise.all(settings.map(async (setting) => ({
53
+ ...setting,
54
+ chainId: await this.getOrbyChainId(setting.chainId),
55
+ })));
56
+ const operationSet = await this.sendRequest("orby_getOperationsForStablecoinAutoDepositSettings", [
57
+ {
58
+ accountClusterId,
59
+ settings: formattedSettings,
60
+ gasToken: await this.formatGasToken(gasToken),
61
+ },
62
+ ]);
63
+ return extractOperationSet(operationSet);
64
+ }
49
65
  async getOperationsToCancelTransaction(accountClusterId, operationSetId) {
50
66
  const operationSet = await this.sendRequest("orby_getOperationsToCancelTransaction", [
51
67
  {
@@ -55,13 +71,17 @@ export class OperationActions extends LibraryRequest {
55
71
  ]);
56
72
  return extractOperationSet(operationSet);
57
73
  }
58
- async isTransactionPreconditionSatisfied(accountClusterId, data, to, value) {
74
+ async isTransactionPreconditionSatisfied(accountClusterId, data, to, value, gasLimit, maxFeePerGas, maxPriorityFeePerGas, gasPrice) {
59
75
  const { satisfied } = await this.sendRequest("orby_isTransactionPreconditionSatisfied", [
60
76
  {
61
77
  accountClusterId,
62
78
  to,
63
79
  data,
64
80
  value,
81
+ gasLimit,
82
+ maxFeePerGas,
83
+ maxPriorityFeePerGas,
84
+ gasPrice,
65
85
  },
66
86
  ]);
67
87
  return satisfied;
@@ -83,7 +103,10 @@ export class OperationActions extends LibraryRequest {
83
103
  const { operationSetId, operationResponses, code, message } = await this.sendRequest("orby_sendSignedOperations", [
84
104
  {
85
105
  accountClusterId,
86
- signedOperations,
106
+ signedOperations: await Promise.all(signedOperations.map(async (signedOperation) => ({
107
+ ...signedOperation,
108
+ chainId: await this.getOrbyChainId(signedOperation.chainId),
109
+ }))),
87
110
  },
88
111
  ]);
89
112
  if (code && message) {
@@ -397,7 +420,7 @@ export class OperationActions extends LibraryRequest {
397
420
  signature: signature,
398
421
  data: operation.data,
399
422
  from: operation.from,
400
- chainId: await this.getOrbyChainId(operation.chainId),
423
+ chainId: operation.chainId,
401
424
  });
402
425
  }
403
426
  // batch sign all the smart contract transactions
@@ -427,7 +450,7 @@ export class OperationActions extends LibraryRequest {
427
450
  signature: userOperation.signature,
428
451
  data: JSON.stringify(userOperation),
429
452
  from: transactions[0].from,
430
- chainId: await this.getOrbyChainId(transactions[0].chainId),
453
+ chainId: transactions[0].chainId,
431
454
  });
432
455
  }
433
456
  const { operationSetId, operationResponses } = await this.sendSignedOperations(accountCluster.accountClusterId, signedOperations);
@@ -441,4 +464,162 @@ export class OperationActions extends LibraryRequest {
441
464
  },
442
465
  };
443
466
  }
467
+ async sendOperationSetWithLocalOrchestration(accountCluster, operationSet, sendTransaction, sendUserOperation, signTypedData, onCompletionCallback, onOperationSubmissionCallback, timeout) {
468
+ // 1. filter out all the operations that are not orby operations
469
+ const orbyOperations = operationSet.intents
470
+ ?.map((intent) => intent.intentOperations)
471
+ .flat()
472
+ ?.concat(operationSet.primaryOperation)
473
+ .filter((value) => (value != undefined && value != null) ||
474
+ value?.origin == OperationOrigin.EXTERNAL);
475
+ if (orbyOperations?.length == 0) {
476
+ onCompletionCallback?.([], OperationStatusType.SUCCESSFUL);
477
+ return;
478
+ }
479
+ else if (!orbyOperations) {
480
+ onCompletionCallback?.([], OperationStatusType.FAILED);
481
+ return;
482
+ }
483
+ // 2. group operations that can be sent in a single transaction together
484
+ const accountsMap = accountCluster.accounts.reduce((acc, account) => {
485
+ acc.set(account.key, account);
486
+ return acc;
487
+ }, new Map());
488
+ const smartAccountTransactions = orbyOperations.reduce((acc, operation) => {
489
+ const accountKey = Account.key(operation.from, operation.chainId);
490
+ const account = accountsMap.get(accountKey);
491
+ if (account?.type == AccountType.SCA &&
492
+ operation.format == OperationDataFormat.TRANSACTION) {
493
+ if (!acc.has(accountKey)) {
494
+ acc.set(accountKey, []);
495
+ }
496
+ acc.get(accountKey).push(operation);
497
+ }
498
+ return acc;
499
+ }, new Map());
500
+ const groupedOperations = [];
501
+ for (let i = 0; i < orbyOperations.length; i++) {
502
+ const operation = orbyOperations[i];
503
+ if (operation.format == OperationDataFormat.TRANSACTION) {
504
+ const accountKey = Account.key(operation.from, operation.chainId);
505
+ const account = accountsMap.get(accountKey);
506
+ if (account?.type == AccountType.SCA) {
507
+ continue;
508
+ }
509
+ else {
510
+ groupedOperations.push([operation]);
511
+ }
512
+ }
513
+ else if (operation.format == OperationDataFormat.TYPED_DATA) {
514
+ groupedOperations.push([operation]);
515
+ }
516
+ }
517
+ // 3. group the operations that can be sent in a single transaction together
518
+ for (const accountKey of smartAccountTransactions.keys()) {
519
+ const transactions = smartAccountTransactions.get(accountKey);
520
+ transactions.sort((operationA, _) => {
521
+ if (operationA.type == OperationType.APPROVE_ERC20_TOKEN) {
522
+ return -1;
523
+ }
524
+ return 0;
525
+ });
526
+ groupedOperations.push(transactions);
527
+ }
528
+ const timeoutOrDefault = timeout ?? 300_000;
529
+ const waitTime = 2000;
530
+ let intervalId = null;
531
+ let totalWaitTime = 0;
532
+ let isProcessing = false;
533
+ const hashedOperations = new Array(groupedOperations.length).fill(undefined);
534
+ // 3. send the operations in batches
535
+ intervalId = setInterval(async () => {
536
+ if (isProcessing) {
537
+ return;
538
+ }
539
+ try {
540
+ isProcessing = true;
541
+ const promises = groupedOperations.map(async (operations, index) => {
542
+ if (hashedOperations[index]) {
543
+ return;
544
+ }
545
+ const operation = operations[0];
546
+ // create a new operation actions client for each operation
547
+ let opsActions;
548
+ if (this.library == LIBRARY_TYPE.VIEM) {
549
+ opsActions = new OperationActions(LIBRARY_TYPE.VIEM, createClient({
550
+ transport: http(operation.txRpcUrl),
551
+ }), undefined);
552
+ }
553
+ else if (this.library == LIBRARY_TYPE.ETHERS) {
554
+ opsActions = new OperationActions(LIBRARY_TYPE.ETHERS, undefined, new ethers.providers.JsonRpcProvider(operation.txRpcUrl));
555
+ }
556
+ if (operation.format === OperationDataFormat.TRANSACTION) {
557
+ const isPreconditionSatisfied = await opsActions.isTransactionPreconditionSatisfied(accountCluster.accountClusterId, operation.data, operation.to, operation.value, operation.gasLimit, operation.maxFeePerGas, operation.maxPriorityFeePerGas, operation.gasPrice);
558
+ if (isPreconditionSatisfied) {
559
+ const accountKey = Account.key(operation.from, operation.chainId);
560
+ const account = accountsMap.get(accountKey);
561
+ if (account?.type == AccountType.SCA) {
562
+ const hash = await sendUserOperation?.(operations, operation.from, operation.chainId, operation.txRpcUrl);
563
+ onOperationSubmissionCallback?.(hash, OperationStatusType.PENDING, operations);
564
+ hashedOperations[index] = {
565
+ operationId: hash,
566
+ status: OperationStatusType.SUCCESSFUL,
567
+ operations,
568
+ };
569
+ }
570
+ else {
571
+ const hash = await sendTransaction?.(operation);
572
+ onOperationSubmissionCallback?.(hash, OperationStatusType.PENDING, operations);
573
+ hashedOperations[index] = {
574
+ operationId: hash,
575
+ status: OperationStatusType.SUCCESSFUL,
576
+ operations,
577
+ };
578
+ }
579
+ }
580
+ }
581
+ else if (operation.format === OperationDataFormat.TYPED_DATA) {
582
+ const isPreconditionSatisfied = await opsActions.isTypedDataPreconditionSatisfied(accountCluster.accountClusterId, operation.data);
583
+ if (isPreconditionSatisfied) {
584
+ const signature = await signTypedData?.(operation);
585
+ const sendOperation = await opsActions.sendSignedOperations(accountCluster.accountClusterId, [
586
+ {
587
+ type: operation.type,
588
+ signature,
589
+ data: operation.data,
590
+ from: operation.from,
591
+ chainId: operation.chainId,
592
+ },
593
+ ]);
594
+ onOperationSubmissionCallback?.(sendOperation.operationResponses[0].hash, OperationStatusType.SUCCESSFUL, operations);
595
+ hashedOperations[index] = {
596
+ operationId: sendOperation.operationResponses[0].hash,
597
+ status: OperationStatusType.SUCCESSFUL,
598
+ operations,
599
+ };
600
+ }
601
+ }
602
+ });
603
+ await Promise.all(promises);
604
+ if (hashedOperations.every((result) => result)) {
605
+ onCompletionCallback?.(hashedOperations, OperationStatusType.SUCCESSFUL);
606
+ clearInterval(intervalId);
607
+ intervalId = null;
608
+ }
609
+ }
610
+ catch (error) {
611
+ console.error("[sendOperationSetWithLocalOrchestration]", error);
612
+ }
613
+ finally {
614
+ isProcessing = false; // Reset processing flag
615
+ totalWaitTime += waitTime;
616
+ if (intervalId && totalWaitTime >= timeoutOrDefault) {
617
+ onCompletionCallback?.(hashedOperations, OperationStatusType.WAITING_PRECONDITION);
618
+ // we cant wait for more than 2 ETH blocks
619
+ clearInterval(intervalId);
620
+ intervalId = null;
621
+ }
622
+ }
623
+ }, waitTime);
624
+ }
444
625
  }
@@ -12,4 +12,5 @@ export declare class TokenActions extends LibraryRequest {
12
12
  chainId: bigint;
13
13
  }[]): Promise<StandardizedToken[]>;
14
14
  getFungibleTokenData(standardizedTokenIds: string[]): Promise<StandardizedToken[]>;
15
+ listAbstractableStablecoins(chainId?: bigint): Promise<StandardizedToken[]>;
15
16
  }
@@ -44,4 +44,15 @@ export class TokenActions extends LibraryRequest {
44
44
  }
45
45
  return extractStandardizedTokens(data);
46
46
  }
47
+ async listAbstractableStablecoins(chainId) {
48
+ const orbyChainId = chainId
49
+ ? await this.getOrbyChainId(chainId)
50
+ : undefined;
51
+ const { stablecoins, message, code } = await this.sendRequest("orby_listAbstractableStablecoins", [{ chainId: orbyChainId }]);
52
+ if (code && message) {
53
+ console.error("[listAbstractableStablecoins]", code, message);
54
+ return undefined;
55
+ }
56
+ return extractStandardizedTokens(stablecoins);
57
+ }
47
58
  }
@@ -1,5 +1,5 @@
1
1
  import { Account } from "../entities/account.js";
2
- import { AccountCluster, Activity, ChainEndpoint, FungibleTokenOverview, StandardizedBalance, VirtualNodeRpcUrlForSupportedChain } from "../types.js";
2
+ import { AccountCluster, Activity, ChainEndpoint, FungibleTokenOverview, StablecoinAutoDepositSettingsForAccount, StandardizedBalance, VirtualNodeRpcUrlForSupportedChain } from "../types.js";
3
3
  import { ChainSupportStatus, Order } from "../enums.js";
4
4
  export interface IAccountClusterActions {
5
5
  createAccountCluster(accounts: Account[]): Promise<AccountCluster>;
@@ -27,4 +27,5 @@ export interface IAccountClusterActions {
27
27
  getPortfolioOverview(accountClusterId: string): Promise<FungibleTokenOverview>;
28
28
  getFungibleTokenPortfolio(accountClusterId: string): Promise<StandardizedBalance[]>;
29
29
  getFungibleTokenBalances(accountClusterId: string, offset?: number, limit?: number, chainId?: bigint, tokensToOmit?: string[], standardizedTokenIds?: string[]): Promise<StandardizedBalance[]>;
30
+ getStablecoinAutoDepositSettingsForAccount(accountClusterId: string, account?: Account): Promise<StablecoinAutoDepositSettingsForAccount[]>;
30
31
  }
@@ -1,4 +1,4 @@
1
- import { AccountCluster, Activity, OnchainOperation, OperationSet, OperationStatus, SignedOperation, UserOperation } from "../types.js";
1
+ import { AccountCluster, Activity, MiniProgram, OnchainOperation, OperationSet, OperationStatus, SignedOperation, UserOperation } from "../types.js";
2
2
  import { CurrencyAmount } from "../entities/financial/currency_amount.js";
3
3
  import { OperationStatusType, QuoteType } from "../enums.js";
4
4
  export interface IOperationActions {
@@ -23,8 +23,23 @@ export interface IOperationActions {
23
23
  address?: string;
24
24
  }[];
25
25
  }, options?: Map<string, any>): Promise<OperationSet>;
26
+ getOperationsForStablecoinAutoDepositSettings(accountClusterId: string, settings: {
27
+ userStablecoins: {
28
+ stablecoinAddress: string;
29
+ targetPercentage: number;
30
+ }[];
31
+ chainId: bigint;
32
+ miniProgramHash: string;
33
+ miniProgram: MiniProgram;
34
+ }[], gasToken?: {
35
+ standardizedTokenId: string;
36
+ tokenSources?: {
37
+ chainId: bigint;
38
+ address?: string;
39
+ }[];
40
+ }): Promise<OperationSet>;
26
41
  getOperationsToCancelTransaction(accountClusterId: string, operationSetId: string): Promise<OperationSet>;
27
- isTransactionPreconditionSatisfied(accountClusterId: string, data: string, to?: string, value?: bigint): Promise<boolean>;
42
+ isTransactionPreconditionSatisfied(accountClusterId: string, data: string, to?: string, value?: bigint, gasLimit?: bigint, maxFeePerGas?: bigint, maxPriorityFeePerGas?: bigint, gasPrice?: bigint): Promise<boolean>;
28
43
  isTypedDataPreconditionSatisfied(accountClusterId: string, data: string): Promise<boolean>;
29
44
  sendSignedOperations(accountClusterId: string, signedOperations: SignedOperation[]): Promise<{
30
45
  operationSetId: string;
@@ -105,4 +120,9 @@ export interface IOperationActions {
105
120
  primaryOperationStatus?: OperationStatus;
106
121
  operationResponses?: OperationStatus[];
107
122
  }>;
123
+ sendOperationSetWithLocalOrchestration(accountCluster: AccountCluster, operationSet: OperationSet, sendTransaction?: (operation: OnchainOperation) => Promise<string | undefined>, sendUserOperation?: (operations: OnchainOperation[], accountAddress: string, chainId: bigint, txRpcUrl: string) => Promise<string | undefined>, signTypedData?: (operation: OnchainOperation) => Promise<string | undefined>, onCompletionCallback?: (operationStatuses?: {
124
+ operationId: string;
125
+ status: OperationStatusType;
126
+ operations: OnchainOperation[];
127
+ }[], statusSummary?: OperationStatusType) => void, onOperationSubmissionCallback?: (operationId: string, status: OperationStatusType, operations: OnchainOperation[]) => void, timeout?: number): Promise<void>;
108
128
  }
@@ -9,4 +9,5 @@ export interface ITokenActions {
9
9
  chainId: bigint;
10
10
  }[]): Promise<StandardizedToken[]>;
11
11
  getFungibleTokenData(standardizedTokenIds: string[]): Promise<StandardizedToken[]>;
12
+ listAbstractableStablecoins(chainId?: bigint): Promise<StandardizedToken[]>;
12
13
  }
@@ -79,7 +79,7 @@ export type SignedOperation = {
79
79
  category?: Category;
80
80
  data?: string;
81
81
  from?: string;
82
- chainId?: string;
82
+ chainId?: bigint;
83
83
  };
84
84
  export type OperationStatus = {
85
85
  blockHash?: string;
@@ -173,3 +173,21 @@ export type VirtualNodeRpcUrlForSupportedChain = {
173
173
  entrypointAccountAddress: string;
174
174
  virtualNodeRpcUrl: string;
175
175
  };
176
+ export type MiniProgram = {
177
+ verificationContract: string;
178
+ miniProgram: string;
179
+ };
180
+ export type StablecoinAutoDepositProportion = {
181
+ token: FungibleToken;
182
+ targetPercentage: number;
183
+ };
184
+ export type StablecoinAutoDepositPerChainUserStablecoinsSettings = {
185
+ userStablecoins: StablecoinAutoDepositProportion[];
186
+ chainId: bigint;
187
+ miniProgram: MiniProgram;
188
+ miniProgramHash: string;
189
+ };
190
+ export type StablecoinAutoDepositSettingsForAccount = {
191
+ account: Account;
192
+ settings: StablecoinAutoDepositPerChainUserStablecoinsSettings[];
193
+ };
@@ -1,4 +1,4 @@
1
- import { AccountCluster, Activity, AllowlistWithOpenFallback, BlockchainInformation, FungibleTokenOverview, GasSpendForInstance, GasSponsorshipData, GasSponsorshipPolicy, Intent, MaxAmountPerInterval, OnchainOperation, OperationSet, OperationStatus, StandardizedBalance, StandardizedToken } from "../types.js";
1
+ import { AccountCluster, Activity, AllowlistWithOpenFallback, BlockchainInformation, FungibleTokenOverview, GasSpendForInstance, GasSponsorshipData, GasSponsorshipPolicy, Intent, MaxAmountPerInterval, OnchainOperation, OperationSet, OperationStatus, StablecoinAutoDepositPerChainUserStablecoinsSettings, StablecoinAutoDepositProportion, StablecoinAutoDepositSettingsForAccount, StandardizedBalance, StandardizedToken } from "../types.js";
2
2
  export declare const extractAccountCluster: (accountCluster?: any) => AccountCluster;
3
3
  export declare const extractOperationSet: (operationSet?: any) => OperationSet;
4
4
  export declare const extractOnchainOperation: (onchainOperation?: any) => OnchainOperation;
@@ -20,3 +20,9 @@ export declare const extractGasSpendForInstances: (gasSpendForInstances?: any[])
20
20
  export declare const extractGasSpendForInstance: (gasSpendForInstance?: any) => GasSpendForInstance;
21
21
  export declare const extractGasSponsorshipInformation: (gasSponsorshipData?: any[]) => GasSponsorshipData[];
22
22
  export declare const extractGasSponsorshipData: (gasSponsorshipData?: any) => GasSponsorshipData;
23
+ export declare const extractStablecoinAutoDepositSettingsForAccount: (settings?: any[]) => StablecoinAutoDepositSettingsForAccount[];
24
+ export declare const extractStablecoinAutoDepositSettingsForAccountItem: (setting?: any) => StablecoinAutoDepositSettingsForAccount;
25
+ export declare const extractStablecoinAutoDepositPerChainUserStablecoinsSettings: (settings?: any[]) => StablecoinAutoDepositPerChainUserStablecoinsSettings[];
26
+ export declare const extractStablecoinAutoDepositPerChainUserStablecoinsSettingsItem: (setting?: any) => StablecoinAutoDepositPerChainUserStablecoinsSettings;
27
+ export declare const extractStablecoinAutoDepositProportions: (proportions?: any[]) => StablecoinAutoDepositProportion[];
28
+ export declare const extractStablecoinAutoDepositProportion: (proportion?: any) => StablecoinAutoDepositProportion;
@@ -247,3 +247,56 @@ export const extractGasSponsorshipData = (gasSponsorshipData) => {
247
247
  totalTransactionCount: gasSponsorshipData.totalTransactionCount,
248
248
  };
249
249
  };
250
+ export const extractStablecoinAutoDepositSettingsForAccount = (settings) => {
251
+ if (!settings) {
252
+ return undefined;
253
+ }
254
+ return settings
255
+ .map((setting) => extractStablecoinAutoDepositSettingsForAccountItem(setting))
256
+ .filter(notEmpty);
257
+ };
258
+ export const extractStablecoinAutoDepositSettingsForAccountItem = (setting) => {
259
+ if (!setting) {
260
+ return undefined;
261
+ }
262
+ return {
263
+ account: Account.toAccount(setting.account),
264
+ settings: extractStablecoinAutoDepositPerChainUserStablecoinsSettings(setting.settings),
265
+ };
266
+ };
267
+ export const extractStablecoinAutoDepositPerChainUserStablecoinsSettings = (settings) => {
268
+ if (!settings) {
269
+ return undefined;
270
+ }
271
+ return settings
272
+ .map((setting) => extractStablecoinAutoDepositPerChainUserStablecoinsSettingsItem(setting))
273
+ .filter(notEmpty);
274
+ };
275
+ export const extractStablecoinAutoDepositPerChainUserStablecoinsSettingsItem = (setting) => {
276
+ if (!setting) {
277
+ return undefined;
278
+ }
279
+ return {
280
+ userStablecoins: extractStablecoinAutoDepositProportions(setting.userStablecoins),
281
+ chainId: getChainIdFromOrbyChainId(setting.chainId),
282
+ miniProgram: setting.miniProgram,
283
+ miniProgramHash: setting.miniProgramHash,
284
+ };
285
+ };
286
+ export const extractStablecoinAutoDepositProportions = (proportions) => {
287
+ if (!proportions) {
288
+ return undefined;
289
+ }
290
+ return proportions
291
+ .map((proportion) => extractStablecoinAutoDepositProportion(proportion))
292
+ .filter(notEmpty);
293
+ };
294
+ export const extractStablecoinAutoDepositProportion = (proportion) => {
295
+ if (!proportion) {
296
+ return undefined;
297
+ }
298
+ return {
299
+ token: FungibleToken.toFungibleToken(proportion.token),
300
+ targetPercentage: proportion.targetPercentage,
301
+ };
302
+ };