diff --git a/packages/data/src/circlesData.ts b/packages/data/src/circlesData.ts index d85f116..d58f18b 100644 --- a/packages/data/src/circlesData.ts +++ b/packages/data/src/circlesData.ts @@ -95,35 +95,58 @@ function calculateBalances(row: TransactionHistoryRow) { let circles: number; let staticAttoCircles: bigint; let staticCircles: number; + let attoCrc: bigint; + let crc: number; - if (tokenInfo?.isInflationary) { - staticAttoCircles = BigInt(rawBalance); - staticCircles = attoCirclesToCircles(staticAttoCircles); + if (row.version === 1) { + attoCrc = BigInt(rawBalance); + crc = attoCirclesToCircles(attoCrc); - circles = crcToTc(new Date(), staticAttoCircles); + circles = crcToTc(new Date(), attoCrc); attoCircles = circlesToAttoCircles(circles); + + staticCircles = crc * 3.0; + staticAttoCircles = attoCircles * 3n; } else { - attoCircles = BigInt(rawBalance); - circles = attoCirclesToCircles(attoCircles); + if (tokenInfo?.isInflationary) { + staticAttoCircles = BigInt(rawBalance); + staticCircles = attoCirclesToCircles(staticAttoCircles); + + circles = crcToTc(new Date(), staticAttoCircles / 3n); + attoCircles = circlesToAttoCircles(circles); + + attoCrc = tcToCrc(new Date(), circles); + crc = attoCirclesToCircles(attoCrc); + } else { + attoCircles = BigInt(rawBalance); + circles = attoCirclesToCircles(attoCircles); + + attoCrc = tcToCrc(new Date(), circles); + crc = attoCirclesToCircles(attoCrc); - staticAttoCircles = tcToCrc(new Date(), circles); - staticCircles = attoCirclesToCircles(staticAttoCircles); + staticAttoCircles = tcToCrc(new Date(), circles) * 3n; + staticCircles = attoCirclesToCircles(staticAttoCircles); + } } return { attoCircles, circles, staticAttoCircles, - staticCircles + staticCircles, + attoCrc, + crc }; } catch (e) { - console.error(e); - console.log(row); + // console.error(e); + // console.log(row); return { attoCircles: 0n, circles: 0, staticAttoCircles: 0n, - inflationaryCircles: 0 + inflationaryCircles: 0, + attoCrc: 0n, + crc: 0 } } } @@ -224,6 +247,12 @@ export class CirclesData implements CirclesDataInterface { }, { name: "staticAttoCircles", generator: async (row: TransactionHistoryRow) => calculateBalances(row).staticAttoCircles + }, { + name: "crc", + generator: async (row: TransactionHistoryRow) => calculateBalances(row).crc + }, { + name: "attoCrc", + generator: async (row: TransactionHistoryRow) => calculateBalances(row).attoCrc }]); } @@ -400,7 +429,8 @@ export class CirclesData implements CirclesDataInterface { 'type', 'avatar', 'tokenId', - 'cidV0Digest' + 'cidV0Digest', + 'name' ], filter: [ { @@ -603,8 +633,10 @@ export class CirclesData implements CirclesDataInterface { 'name', 'symbol', 'cidV0Digest', + 'memberCount', + 'trustedCount' ], - sortOrder: 'DESC', + sortOrder: "DESC", limit: pageSize }; diff --git a/packages/data/src/circlesDataInterface.ts b/packages/data/src/circlesDataInterface.ts index b847d08..7291861 100644 --- a/packages/data/src/circlesDataInterface.ts +++ b/packages/data/src/circlesDataInterface.ts @@ -15,7 +15,7 @@ export interface GroupQueryParams { nameStartsWith?: string; symbolStartsWith?: string; groupAddressIn?: string[]; - sortBy?: 'age_asc' | 'age_desc' | 'name_asc' | 'name_desc' | 'symbol_asc' | 'symbol_desc'; + sortBy?: 'age_asc' | 'age_desc' | 'name_asc' | 'name_desc' | 'symbol_asc' | 'symbol_desc' | 'memberCount_desc' | 'memberCount_asc' | 'trustedCount_desc' | 'trustedCount_asc'; } export interface CirclesDataInterface { diff --git a/packages/data/src/events/events.ts b/packages/data/src/events/events.ts index 3a3b0a0..38c4cb0 100644 --- a/packages/data/src/events/events.ts +++ b/packages/data/src/events/events.ts @@ -1,252 +1,298 @@ // Base event type export type CirclesBaseEvent = { - $event: CirclesEventType, - blockNumber: number; - timestamp?: number; - transactionIndex: number; - logIndex: number; - transactionHash?: string; + $event: CirclesEventType, + blockNumber: number; + timestamp?: number; + transactionIndex: number; + logIndex: number; + transactionHash?: string; }; // Event types export type CrcV1_HubTransfer = CirclesBaseEvent & { - $event: "CrcV1_HubTransfer", - from?: string; - to?: string; - amount?: bigint; + $event: "CrcV1_HubTransfer", + from?: string; + to?: string; + amount?: bigint; }; export type CrcV1_Signup = CirclesBaseEvent & { - $event: "CrcV1_Signup", - user?: string; - token?: string; + $event: "CrcV1_Signup", + user?: string; + token?: string; }; export type CrcV1_OrganizationSignup = CirclesBaseEvent & { - $event: "CrcV1_OrganizationSignup", - organization?: string; + $event: "CrcV1_OrganizationSignup", + organization?: string; }; export type CrcV1_Trust = CirclesBaseEvent & { - $event: "CrcV1_Trust", - canSendTo?: string; - user?: string; - limit?: bigint; + $event: "CrcV1_Trust", + canSendTo?: string; + user?: string; + limit?: bigint; }; export type CrcV1_Transfer = CirclesBaseEvent & { - $event: "CrcV1_Transfer", - tokenAddress?: string; - from?: string; - to?: string; - amount?: bigint; + $event: "CrcV1_Transfer", + tokenAddress?: string; + from?: string; + to?: string; + amount?: bigint; }; export type CrcV2_InviteHuman = CirclesBaseEvent & { - $event: "CrcV2_InviteHuman", - inviter?: string; - invited?: string; + $event: "CrcV2_InviteHuman", + inviter?: string; + invited?: string; }; export type CrcV2_PersonalMint = CirclesBaseEvent & { - $event: "CrcV2_PersonalMint", - human?: string; - amount?: bigint; - startPeriod?: bigint; - endPeriod?: bigint; + $event: "CrcV2_PersonalMint", + human?: string; + amount?: bigint; + startPeriod?: bigint; + endPeriod?: bigint; }; export type CrcV2_RegisterGroup = CirclesBaseEvent & { - $event: "CrcV2_RegisterGroup", - group?: string; - mint?: string; - treasury?: string; - name?: string; - symbol?: string; + $event: "CrcV2_RegisterGroup", + group?: string; + mint?: string; + treasury?: string; + name?: string; + symbol?: string; }; export type CrcV2_RegisterHuman = CirclesBaseEvent & { - $event: "CrcV2_RegisterHuman", - avatar?: string; + $event: "CrcV2_RegisterHuman", + avatar?: string; }; export type CrcV2_RegisterOrganization = CirclesBaseEvent & { - $event: "CrcV2_RegisterOrganization", - organization?: string; - name?: string; + $event: "CrcV2_RegisterOrganization", + organization?: string; + name?: string; }; export type CrcV2_Stopped = CirclesBaseEvent & { - $event: "CrcV2_Stopped", - avatar?: string; + $event: "CrcV2_Stopped", + avatar?: string; }; export type CrcV2_Trust = CirclesBaseEvent & { - $event: "CrcV2_Trust", - truster?: string; - trustee?: string; - expiryTime?: bigint; + $event: "CrcV2_Trust", + truster?: string; + trustee?: string; + expiryTime?: bigint; }; export type CrcV2_TransferSingle = CirclesBaseEvent & { - $event: "CrcV2_TransferSingle", - operator?: string; - from?: string; - to?: string; - id?: bigint; - value?: bigint; + $event: "CrcV2_TransferSingle", + operator?: string; + from?: string; + to?: string; + id?: bigint; + value?: bigint; }; export type CrcV2_URI = CirclesBaseEvent & { - $event: "CrcV2_URI", - value?: string; - id?: bigint; + $event: "CrcV2_URI", + value?: string; + id?: bigint; }; export type CrcV2_ApprovalForAll = CirclesBaseEvent & { - $event: "CrcV2_ApprovalForAll", - account?: string; - operator?: string; - approved?: boolean; + $event: "CrcV2_ApprovalForAll", + account?: string; + operator?: string; + approved?: boolean; }; export type CrcV2_TransferBatch = CirclesBaseEvent & { - $event: "CrcV2_TransferBatch", - batchIndex: number; - operator?: string; - from?: string; - to?: string; - id?: bigint; - value?: bigint; + $event: "CrcV2_TransferBatch", + batchIndex: number; + operator?: string; + from?: string; + to?: string; + id?: bigint; + value?: bigint; }; export type CrcV2_RegisterShortName = CirclesBaseEvent & { - $event: "CrcV2_RegisterShortName", - avatar?: string; - shortName?: bigint; - nonce?: bigint; + $event: "CrcV2_RegisterShortName", + avatar?: string; + shortName?: bigint; + nonce?: bigint; }; export type CrcV2_UpdateMetadataDigest = CirclesBaseEvent & { - $event: "CrcV2_UpdateMetadataDigest", - avatar?: string; - metadataDigest?: Uint8Array; + $event: "CrcV2_UpdateMetadataDigest", + avatar?: string; + metadataDigest?: Uint8Array; }; export type CrcV2_CidV0 = CirclesBaseEvent & { - $event: "CrcV2_CidV0", - avatar?: string; - cidV0Digest?: Uint8Array; + $event: "CrcV2_CidV0", + avatar?: string; + cidV0Digest?: Uint8Array; }; export type CrcV2_StreamCompleted = CirclesBaseEvent & { - $event: "CrcV2_StreamCompleted", - operator?: string; - from?: string; - to?: string; - id?: bigint; - amount?: bigint; + $event: "CrcV2_StreamCompleted", + operator?: string; + from?: string; + to?: string; + id?: bigint; + amount?: bigint; }; export type CrcV2_CreateVault = CirclesBaseEvent & { - $event: "CrcV2_CreateVault", - group?: string; - vault?: string; + $event: "CrcV2_CreateVault", + group?: string; + vault?: string; }; export type CrcV2_GroupMintSingle = CirclesBaseEvent & { - $event: "CrcV2_GroupMintSingle", - group?: string; - id?: bigint; - value?: bigint; - userData?: Uint8Array; + $event: "CrcV2_GroupMintSingle", + group?: string; + id?: bigint; + value?: bigint; + userData?: Uint8Array; }; export type CrcV2_GroupMintBatch = CirclesBaseEvent & { - $event: "CrcV2_GroupMintBatch", - batchIndex: number; - group?: string; - id?: bigint; - value?: bigint; - userData?: Uint8Array; + $event: "CrcV2_GroupMintBatch", + batchIndex: number; + group?: string; + id?: bigint; + value?: bigint; + userData?: Uint8Array; }; export type CrcV2_GroupRedeem = CirclesBaseEvent & { - $event: "CrcV2_GroupRedeem", - group?: string; - id?: bigint; - value?: bigint; - data?: Uint8Array; + $event: "CrcV2_GroupRedeem", + group?: string; + id?: bigint; + value?: bigint; + data?: Uint8Array; }; export type CrcV2_GroupRedeemCollateralReturn = CirclesBaseEvent & { - $event: "CrcV2_GroupRedeemCollateralReturn", - batchIndex: number; - group?: string; - to?: string; - id?: bigint; - value?: bigint; + $event: "CrcV2_GroupRedeemCollateralReturn", + batchIndex: number; + group?: string; + to?: string; + id?: bigint; + value?: bigint; }; export type CrcV2_GroupRedeemCollateralBurn = CirclesBaseEvent & { - $event: "CrcV2_GroupRedeemCollateralBurn", - batchIndex: number; - group?: string; - id?: bigint; - value?: bigint; + $event: "CrcV2_GroupRedeemCollateralBurn", + batchIndex: number; + group?: string; + id?: bigint; + value?: bigint; +}; + +export type CrcV2_DepositDemurraged = CirclesBaseEvent & { + $event: "CrcV2_DepositDemurraged", + account?: string; + amount?: bigint; + inflationaryAmount?: bigint; +}; + +export type CrcV2_DepositInflationary = CirclesBaseEvent & { + $event: "CrcV2_DepositInflationary", + account?: string; + amount?: bigint; + demurragedAmount?: bigint; +}; + +export type CrcV2_WithdrawDemurraged = CirclesBaseEvent & { + $event: "CrcV2_WithdrawDemurraged", + account?: string; + amount?: bigint; + inflationaryAmount?: bigint; +}; + +export type CrcV2_WithdrawInflationary = CirclesBaseEvent & { + $event: "CrcV2_WithdrawInflationary", + account?: string; + amount?: bigint; + demurragedAmount?: bigint; +}; + +export type CrcV2_Erc20WrapperTransfer = CirclesBaseEvent & { + $event: "CrcV2_Erc20WrapperTransfer", + tokenAddress?: string; + from?: string; + to?: string; + amount?: bigint; }; export type CirclesEvent = - | CrcV1_HubTransfer - | CrcV1_Signup - | CrcV1_OrganizationSignup - | CrcV1_Trust - | CrcV1_Transfer - | CrcV2_InviteHuman - | CrcV2_PersonalMint - | CrcV2_RegisterGroup - | CrcV2_RegisterHuman - | CrcV2_RegisterOrganization - | CrcV2_Stopped - | CrcV2_Trust - | CrcV2_TransferSingle - | CrcV2_URI - | CrcV2_ApprovalForAll - | CrcV2_TransferBatch - | CrcV2_RegisterShortName - | CrcV2_UpdateMetadataDigest - | CrcV2_CidV0 - | CrcV2_StreamCompleted - | CrcV2_CreateVault - | CrcV2_GroupMintSingle - | CrcV2_GroupMintBatch - | CrcV2_GroupRedeem - | CrcV2_GroupRedeemCollateralReturn - | CrcV2_GroupRedeemCollateralBurn; + | CrcV1_HubTransfer + | CrcV1_Signup + | CrcV1_OrganizationSignup + | CrcV1_Trust + | CrcV1_Transfer + | CrcV2_InviteHuman + | CrcV2_PersonalMint + | CrcV2_RegisterGroup + | CrcV2_RegisterHuman + | CrcV2_RegisterOrganization + | CrcV2_Stopped + | CrcV2_Trust + | CrcV2_TransferSingle + | CrcV2_Erc20WrapperTransfer + | CrcV2_URI + | CrcV2_ApprovalForAll + | CrcV2_TransferBatch + | CrcV2_RegisterShortName + | CrcV2_UpdateMetadataDigest + | CrcV2_CidV0 + | CrcV2_StreamCompleted + | CrcV2_CreateVault + | CrcV2_GroupMintSingle + | CrcV2_GroupMintBatch + | CrcV2_GroupRedeem + | CrcV2_GroupRedeemCollateralReturn + | CrcV2_GroupRedeemCollateralBurn + | CrcV2_DepositDemurraged + | CrcV2_DepositInflationary + | CrcV2_WithdrawDemurraged + | CrcV2_WithdrawInflationary; export type CirclesEventType = - | 'CrcV1_HubTransfer' - | 'CrcV1_Signup' - | 'CrcV1_OrganizationSignup' - | 'CrcV1_Trust' - | 'CrcV1_Transfer' - | 'CrcV2_InviteHuman' - | 'CrcV2_PersonalMint' - | 'CrcV2_RegisterGroup' - | 'CrcV2_RegisterHuman' - | 'CrcV2_RegisterOrganization' - | 'CrcV2_Stopped' - | 'CrcV2_Trust' - | 'CrcV2_TransferSingle' - | 'CrcV2_URI' - | 'CrcV2_ApprovalForAll' - | 'CrcV2_TransferBatch' - | 'CrcV2_RegisterShortName' - | 'CrcV2_UpdateMetadataDigest' - | 'CrcV2_CidV0' - | 'CrcV2_StreamCompleted' - | 'CrcV2_CreateVault' - | 'CrcV2_GroupMintSingle' - | 'CrcV2_GroupMintBatch' - | 'CrcV2_GroupRedeem' - | 'CrcV2_GroupRedeemCollateralReturn' - | 'CrcV2_GroupRedeemCollateralBurn'; + | 'CrcV1_HubTransfer' + | 'CrcV1_Signup' + | 'CrcV1_OrganizationSignup' + | 'CrcV1_Trust' + | 'CrcV1_Transfer' + | 'CrcV2_InviteHuman' + | 'CrcV2_PersonalMint' + | 'CrcV2_RegisterGroup' + | 'CrcV2_RegisterHuman' + | 'CrcV2_RegisterOrganization' + | 'CrcV2_Stopped' + | 'CrcV2_Trust' + | 'CrcV2_TransferSingle' + | 'CrcV2_Erc20WrapperTransfer' + | 'CrcV2_URI' + | 'CrcV2_ApprovalForAll' + | 'CrcV2_TransferBatch' + | 'CrcV2_RegisterShortName' + | 'CrcV2_UpdateMetadataDigest' + | 'CrcV2_CidV0' + | 'CrcV2_StreamCompleted' + | 'CrcV2_CreateVault' + | 'CrcV2_GroupMintSingle' + | 'CrcV2_GroupMintBatch' + | 'CrcV2_GroupRedeem' + | 'CrcV2_GroupRedeemCollateralReturn' + | 'CrcV2_GroupRedeemCollateralBurn' + | 'CrcV2_DepositDemurraged' + | 'CrcV2_DepositInflationary' + | 'CrcV2_WithdrawDemurraged' + | 'CrcV2_WithdrawInflationary'; diff --git a/packages/data/src/events/parser.ts b/packages/data/src/events/parser.ts index 3628a44..24502fd 100644 --- a/packages/data/src/events/parser.ts +++ b/packages/data/src/events/parser.ts @@ -245,6 +245,47 @@ const parseEventValues = (event: CirclesEventType, values: EventValues): Circles id: values.id ? hexToBigInt(values.id) : undefined, value: values.value ? hexToBigInt(values.value) : undefined }; + case "CrcV2_DepositDemurraged": + return { + ...baseEvent, + $event: "CrcV2_DepositDemurraged", + account: values.account, + amount: values.amount ? hexToBigInt(values.amount) : undefined, + inflationaryAmount: values.inflationaryAmount ? hexToBigInt(values.inflationaryAmount) : undefined + }; + case "CrcV2_DepositInflationary": + return { + ...baseEvent, + $event: "CrcV2_DepositInflationary", + account: values.account, + amount: values.amount ? hexToBigInt(values.amount) : undefined, + demurragedAmount: values.demurragedAmount ? hexToBigInt(values.demurragedAmount) : undefined + }; + case "CrcV2_WithdrawDemurraged": + return { + ...baseEvent, + $event: "CrcV2_WithdrawDemurraged", + account: values.account, + amount: values.amount ? hexToBigInt(values.amount) : undefined, + inflationaryAmount: values.inflationaryAmount ? hexToBigInt(values.inflationaryAmount) : undefined + }; + case "CrcV2_WithdrawInflationary": + return { + ...baseEvent, + $event: "CrcV2_WithdrawInflationary", + account: values.account, + amount: values.amount ? hexToBigInt(values.amount) : undefined, + demurragedAmount: values.demurragedAmount ? hexToBigInt(values.demurragedAmount) : undefined + } + case "CrcV2_Erc20WrapperTransfer": + return { + ...baseEvent, + $event: "CrcV2_Erc20WrapperTransfer", + tokenAddress: values.tokenAddress, + from: values.from, + to: values.to, + amount: values.value ? hexToBigInt(values.value) : undefined + } default: throw new Error(`Unknown event type: ${event}`); } diff --git a/packages/data/src/index.ts b/packages/data/src/index.ts index 74fd98e..5ed584f 100644 --- a/packages/data/src/index.ts +++ b/packages/data/src/index.ts @@ -48,4 +48,6 @@ export { CrcV2_PersonalMint, CrcV2_UpdateMetadataDigest } from './events/events'; -export {TrustEvent} from "./circlesData"; \ No newline at end of file +export {TrustEvent} from "./circlesData"; +export {TokenType} from './rows/tokenInfoRow'; +export {TrustRelation} from './rows/trustRelationRow'; \ No newline at end of file diff --git a/packages/data/src/rows/avatarRow.ts b/packages/data/src/rows/avatarRow.ts index d3c2e3d..b260494 100644 --- a/packages/data/src/rows/avatarRow.ts +++ b/packages/data/src/rows/avatarRow.ts @@ -62,4 +62,12 @@ export interface AvatarRow extends EventRow { * Indicates whether the entity is a human. */ isHuman: boolean; + /** + * Groups have a names + */ + name?: string; + /** + * Groups have a symbol + */ + symbol?: string; } \ No newline at end of file diff --git a/packages/data/src/rows/groupRow.ts b/packages/data/src/rows/groupRow.ts index fb5bcd7..99a9f6c 100644 --- a/packages/data/src/rows/groupRow.ts +++ b/packages/data/src/rows/groupRow.ts @@ -8,4 +8,6 @@ export interface GroupRow extends EventRow { symbol: string; isMember?: boolean; cidV0Digest?: string; + memberCount?: number; + trustedCount?: number; } \ No newline at end of file diff --git a/packages/data/src/rows/tokenBalanceRow.ts b/packages/data/src/rows/tokenBalanceRow.ts index df2c5b4..f9529e2 100644 --- a/packages/data/src/rows/tokenBalanceRow.ts +++ b/packages/data/src/rows/tokenBalanceRow.ts @@ -8,6 +8,8 @@ export interface TokenBalanceRow { circles: number; staticAttoCircles: string; staticCircles: number; + attoCrc: string; + crc: number; isErc20: boolean, isErc1155: boolean, isWrapped: boolean, diff --git a/packages/data/src/rows/tokenInfoRow.ts b/packages/data/src/rows/tokenInfoRow.ts index 227c847..fa4561e 100644 --- a/packages/data/src/rows/tokenInfoRow.ts +++ b/packages/data/src/rows/tokenInfoRow.ts @@ -1,6 +1,6 @@ import { EventRow } from '../pagedQuery/eventRow'; -export type TokenType = "CrcV1_Signup!" | "CrcV2_RegisterHuman" | "CrcV2_RegisterGroup" | "CrcV2_ERC20WrapperDeployed_Inflationary" | "CrcV2_ERC20WrapperDeployed_Demurraged"; +export type TokenType = "CrcV1_Signup" | "CrcV2_RegisterHuman" | "CrcV2_RegisterGroup" | "CrcV2_ERC20WrapperDeployed_Inflationary" | "CrcV2_ERC20WrapperDeployed_Demurraged"; export interface TokenInfoRow extends EventRow { /* diff --git a/packages/data/src/rows/transactionHistoryRow.ts b/packages/data/src/rows/transactionHistoryRow.ts index 1acd4d7..15a170b 100644 --- a/packages/data/src/rows/transactionHistoryRow.ts +++ b/packages/data/src/rows/transactionHistoryRow.ts @@ -9,8 +9,15 @@ export interface TransactionHistoryRow extends EventRow { to: string; id: string; value: string; - timeCircles: number; tokenAddress?: string; type: string; tokenType: string; + circles: number; + attoCircles: bigint; + staticCircles: number; + staticAttoCircles: bigint; + crc: number; + attoCrc: bigint; + v1Circles: number; + v1AttoCircles: bigint; } \ No newline at end of file diff --git a/packages/sdk/src/AvatarInterface.ts b/packages/sdk/src/AvatarInterface.ts index f276e49..6ebf11f 100644 --- a/packages/sdk/src/AvatarInterface.ts +++ b/packages/sdk/src/AvatarInterface.ts @@ -2,7 +2,7 @@ import { AvatarRow, CirclesQuery, TokenBalanceRow, TransactionHistoryRow, TrustRelationRow } from '@circles-sdk/data'; -import {ContractTransactionReceipt} from 'ethers'; +import {ContractTransactionReceipt, TransactionReceipt} from 'ethers'; import {Profile} from "@circles-sdk/profiles"; /** @@ -41,7 +41,7 @@ export interface AvatarInterface { * @param amount The amount to transfer. * @param token The token to transfer (address). Leave empty to allow transitive transfers. */ - transfer(to: string, amount: bigint, token?: string): Promise; + transfer(to: string, amount: bigint, token?: string): Promise; /** * Trusts another avatar. Trusting an avatar means you're willing to accept Circles that have been issued by this avatar. diff --git a/packages/sdk/src/avatar.ts b/packages/sdk/src/avatar.ts index a43204c..790d8b1 100644 --- a/packages/sdk/src/avatar.ts +++ b/packages/sdk/src/avatar.ts @@ -1,5 +1,5 @@ import {V1Avatar} from './v1/v1Avatar'; -import {ContractTransactionReceipt, parseEther} from 'ethers'; +import {ContractTransactionReceipt, parseEther, TransactionReceipt} from 'ethers'; import {Sdk} from './sdk'; import {AvatarInterface, AvatarInterfaceV2} from './AvatarInterface'; import { @@ -61,13 +61,24 @@ export class Avatar implements AvatarInterfaceV2 { private _events: Observable | undefined; - /** - * Initializes the avatar. - */ - initialize = async () => { + unsubscribeFromEvents = () => { if (this._tokenEventSubscription) { this._tokenEventSubscription(); } + } + + subscribeToEvents = async () => { + if (!this._avatarInfo) { + throw new Error('Avatar is not initialized'); + } + this._events = await this._sdk.data.subscribeToEvents(this._avatarInfo.avatar); + } + + /** + * Initializes the avatar. + */ + initialize = async (subscribe: boolean = true) => { + this.unsubscribeFromEvents(); this._avatarInfo = await this._sdk.data.getAvatarInfo(this.address); if (!this._avatarInfo) { @@ -101,7 +112,9 @@ export class Avatar implements AvatarInterfaceV2 { throw new Error('Unsupported avatar'); } - this._events = await this._sdk.data.subscribeToEvents(this._avatarInfo.avatar); + if (subscribe) { + await this.subscribeToEvents(); + } }; private onlyIfInitialized(func: () => T) { @@ -152,9 +165,9 @@ export class Avatar implements AvatarInterfaceV2 { * @param amount The amount to transfer. * @param token The token to transfer. Leave empty to allow transitive transfers. */ - transfer(to: string, amount: number, token?: string): Promise; - transfer(to: string, amount: bigint, token?: string): Promise; - transfer(to: string, amount: number | bigint, token?: string): Promise { + transfer(to: string, amount: number, token?: string): Promise; + transfer(to: string, amount: bigint, token?: string): Promise; + transfer(to: string, amount: number | bigint, token?: string): Promise { if (typeof amount === 'number') { const sendValue = this?.avatarInfo?.version === 1 ? tcToCrc(new Date(), amount) @@ -207,7 +220,7 @@ export class Avatar implements AvatarInterfaceV2 { * Gets the avatar's total Circles balance. * * Note: This queries either the v1 or the v2 balance of an avatar. Check the `avatarInfo` property to see which version your avatar uses. - * Token holdings in v1 can be migrated to v2. Check out `Sdk.migrateAvatar` or `Sdk.migrateAllV1Tokens` for more information. + * Token holdings in v1 can be migrated to v2. Check out `Sdk.migrateAvatar` or `Sdk.migrateV1Tokens` for more information. */ getTotalBalance = (): Promise => this.onlyIfInitialized(() => this._avatar!.getTotalBalance()); diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 468e9b2..ed1bcbc 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -4,6 +4,6 @@ export { Sdk } from './sdk'; export { V1Avatar } from './v1/v1Avatar'; export { CirclesConfig } from './circlesConfig'; export { AvatarRow, TrustListRow, TrustRelationRow } from '@circles-sdk/data'; -export { AvatarInterface } from './AvatarInterface'; +export { AvatarInterface, AvatarInterfaceV2 } from './AvatarInterface'; export { parseError } from './errors'; export { Pathfinder, TransferPath, TransferStep } from './v1/pathfinder'; \ No newline at end of file diff --git a/packages/sdk/src/sdk.ts b/packages/sdk/src/sdk.ts index a9207cf..003a3c8 100644 --- a/packages/sdk/src/sdk.ts +++ b/packages/sdk/src/sdk.ts @@ -191,12 +191,13 @@ export class Sdk implements SdkInterface { /** * Gets an avatar by its address. * @param avatarAddress The avatar's address. + * @param subscribe Whether to subscribe to avatar events. * @returns The avatar instance. * @throws If the given avatar address is not signed up at Circles. */ - getAvatar = async (avatarAddress: string): Promise => { + getAvatar = async (avatarAddress: string, subscribe: boolean = true): Promise => { const avatar = new Avatar(this, avatarAddress); - await avatar.initialize(); + await avatar.initialize(subscribe); return avatar; }; @@ -396,33 +397,32 @@ export class Sdk implements SdkInterface { await calculateIssuanceTx.wait(); // 4. Migrate V1 tokens - await this.migrateAllV1Tokens(avatar); + await this.migrateV1Tokens(avatar); } else { throw new Error('Avatar is not a V1 avatar'); } }; - /** - * Migrates all V1 tokens of an avatar to V2. - * @param avatar The avatar's address. - */ /** * Migrates all V1 token holdings of an avatar to V2. * @param avatar The avatar whose tokens to migrate. + * @param tokens An optional list of token addresses to migrate. If not provided, all tokens will be migrated. */ - migrateAllV1Tokens = async (avatar: string): Promise => { + migrateV1Tokens = async (avatar: string, tokens?: string[]): Promise => { if (!this.circlesConfig.migrationAddress) { throw new Error('Migration address not set'); } + const balances = await this.data.getTokenBalances(avatar); - const v1Balances = balances.filter(o => o.version === 1); - const tokensToMigrate = balances - .filter(o => BigInt(o.staticAttoCircles) > 0); + const v1Balances = balances.filter(o => o.version === 1 && (tokens ? tokens.map(o => o.toLowerCase()).includes(o.tokenAddress?.toLowerCase()) : true)); + const tokensToMigrate = v1Balances.filter(o => BigInt(o.attoCrc) > 0); + console.log(`Migrating the following v1 token:`, tokensToMigrate); // TODO: Send in one transaction if sent to Safe - await Promise.all(tokensToMigrate.map(async (t, i) => { - const balance = BigInt(t.staticAttoCircles); - const token = Token__factory.connect(t.tokenAddress, this.contractRunner); + await Promise.all(tokensToMigrate.map(async (tokenToMigrate) => { + const balance = BigInt(tokenToMigrate.attoCrc); + console.log(`tokenToMigrate`, tokenToMigrate); + const token = Token__factory.connect(tokenToMigrate.tokenAddress, this.contractRunner); const allowance = await token.allowance(avatar, this.circlesConfig.migrationAddress!); if (allowance < balance) { const increase = balance - allowance; @@ -434,7 +434,7 @@ export class Sdk implements SdkInterface { const migrationContract = Migration__factory.connect(this.circlesConfig.migrationAddress, this.contractRunner); const migrateTx = await migrationContract.migrate( tokensToMigrate.map(o => o.tokenOwner) - , tokensToMigrate.map(o => BigInt(o.staticAttoCircles))); + , tokensToMigrate.map(o => BigInt(o.attoCrc))); await migrateTx.wait(); }; diff --git a/packages/sdk/src/v1/v1Avatar.ts b/packages/sdk/src/v1/v1Avatar.ts index 2838e1a..be9ce5e 100644 --- a/packages/sdk/src/v1/v1Avatar.ts +++ b/packages/sdk/src/v1/v1Avatar.ts @@ -1,9 +1,9 @@ import { - ContractTransactionReceipt + ContractTransactionReceipt, ethers, TransactionReceipt } from 'ethers'; -import { Sdk } from '../sdk'; -import { AvatarInterface } from '../AvatarInterface'; -import { Token, Token__factory } from '@circles-sdk/abi-v1'; +import {Sdk} from '../sdk'; +import {AvatarInterface} from '../AvatarInterface'; +import {Token, Token__factory} from '@circles-sdk/abi-v1'; import { AvatarRow, CirclesQuery, @@ -48,8 +48,7 @@ export class V1Avatar implements AvatarInterface { } async getBalances(): Promise { - const allBalances = await this.sdk.data.getTokenBalances(this.address); - return allBalances.filter(o => o.version === 1); + return await this.sdk.data.getTokenBalances(this.address); } /** @@ -91,27 +90,47 @@ export class V1Avatar implements AvatarInterface { * Utilizes the pathfinder to transitively send `amount` Circles to `to`. * @param to The recipient * @param amount The amount to send + * @param token The token to transfer (address). Leave empty to allow transitive transfers. */ - async transfer(to: string, amount: bigint): Promise { + async transfer(to: string, amount: bigint, token?: string): Promise { this.throwIfNotInitialized(); - this.throwIfPathfinderIsNotAvailable(); + let receipt: TransactionReceipt | null = null; + if (!token) { + this.throwIfPathfinderIsNotAvailable(); + // transitive transfer + const transferPath = await this.sdk.v1Pathfinder!.getTransferPath( + this.address, + to, + amount); + + if (!transferPath.isValid || transferPath.transferSteps.length === 0) { + throw new Error(`Couldn't find a valid path from ${this.address} to ${to} for ${amount}.`); + } - const transferPath = await this.sdk.v1Pathfinder!.getTransferPath( - this.address, - to, - amount); + const tokenOwners = transferPath.transferSteps.map(o => o.token_owner); + const srcs = transferPath.transferSteps.map(o => o.from); + const dests = transferPath.transferSteps.map(o => o.to); + const wads = transferPath.transferSteps.map(o => BigInt(o.value)); + const tx = await this.sdk.v1Hub.transferThrough(tokenOwners, srcs, dests, wads); - if (!transferPath.isValid || transferPath.transferSteps.length === 0) { - throw new Error(`Couldn't find a valid path from ${this.address} to ${to} for ${amount}.`); - } + receipt = await tx.wait(); + } else { + // erc20 transfer via ethers.Interface + const iface = new ethers.Interface(['function transfer(address to, uint256 value)']); + const data = iface.encodeFunctionData('transfer', [to, amount]); - const tokenOwners = transferPath.transferSteps.map(o => o.token_owner); - const srcs = transferPath.transferSteps.map(o => o.from); - const dests = transferPath.transferSteps.map(o => o.to); - const wads = transferPath.transferSteps.map(o => BigInt(o.value)); + if (!this.sdk?.contractRunner?.sendTransaction) { + throw new Error('ContractRunner not available'); + } + + const tx = await this.sdk.contractRunner.sendTransaction({ + to: token, + data: data + }); + + receipt = await tx.wait(); + } - const tx = await this.sdk.v1Hub.transferThrough(tokenOwners, srcs, dests, wads); - const receipt = await tx.wait(); if (!receipt) { throw new Error(`The transferThrough call for '${this.address} -> ${to}: ${amount}' didn't yield a receipt.`); } diff --git a/packages/sdk/src/v2/v2Avatar.ts b/packages/sdk/src/v2/v2Avatar.ts index de19fb1..cdec193 100644 --- a/packages/sdk/src/v2/v2Avatar.ts +++ b/packages/sdk/src/v2/v2Avatar.ts @@ -1,6 +1,6 @@ import {AvatarInterfaceV2} from '../AvatarInterface'; import { - ContractTransactionReceipt, formatEther + ContractTransactionReceipt, ethers, formatEther, TransactionReceipt } from 'ethers'; import {Sdk} from '../sdk'; import { @@ -13,6 +13,7 @@ import { import {addressToUInt256, cidV0ToUint8Array} from '@circles-sdk/utils'; import {Pathfinder} from './pathfinderV2'; import {Profile} from "@circles-sdk/profiles"; +import {TokenType} from "@circles-sdk/data/dist/rows/tokenInfoRow"; export type FlowEdge = { streamSinkId: bigint; @@ -49,6 +50,7 @@ export class V2Avatar implements AvatarInterfaceV2 { trusts(otherAvatar: string): Promise { return this.sdk.v2Hub!.isTrusted(this.address, otherAvatar); } + isTrustedBy(otherAvatar: string): Promise { return this.sdk.v2Hub!.isTrusted(otherAvatar, this.address); } @@ -121,8 +123,7 @@ export class V2Avatar implements AvatarInterfaceV2 { } async getBalances(): Promise { - const allBalances = await this.sdk.data.getTokenBalances(this.address); - return allBalances.filter(o => o.version === 2); + return await this.sdk.data.getTokenBalances(this.address); } async personalMint(): Promise { @@ -160,14 +161,47 @@ export class V2Avatar implements AvatarInterfaceV2 { return receipt; } - private async directTransfer(to: string, amount: bigint, tokenAddress: string): Promise { + private async directTransfer(to: string, amount: bigint, tokenAddress: string): Promise { const tokenInf = await this.sdk.data.getTokenInfo(tokenAddress); console.log(`Direct transfer - of: ${amount} - tokenId: ${tokenInf?.token} - to: ${to}`); if (!tokenInf) { throw new Error('Token not found'); } - const numericTokenId = addressToUInt256(tokenInf.token); + const erc1155Types = new Set(['CrcV2_RegisterHuman', 'CrcV2_RegisterGroup']); + const erc20Types = new Set(['CrcV2_ERC20WrapperDeployed_Demurraged', 'CrcV2_ERC20WrapperDeployed_Inflationary', 'CrcV1_Signup']); + + if (erc1155Types.has(tokenInf.type)) { + return await this.transferErc1155(tokenAddress, to, amount); + } else if (erc20Types.has(tokenInf.type)) { + return await this.transferErc20(to, amount, tokenAddress); + } + throw new Error(`Token type ${tokenInf.type} not supported`); + } + + private async transferErc20(to: string, amount: bigint, tokenAddress: string) { + const iface = new ethers.Interface(['function transfer(address to, uint256 value)']); + const data = iface.encodeFunctionData('transfer', [to, amount]); + + if (!this.sdk?.contractRunner?.sendTransaction) { + throw new Error('ContractRunner not available'); + } + + const tx = await this.sdk.contractRunner.sendTransaction({ + to: tokenAddress, + data: data + }); + + const receipt = await tx.wait(); + if (!receipt) { + throw new Error('Transfer failed'); + } + + return receipt; + } + + private async transferErc1155(tokenAddress: string, to: string, amount: bigint) { + const numericTokenId = addressToUInt256(tokenAddress); console.log(`numericTokenId: ${numericTokenId}`); const tx = await this.sdk.v2Hub?.safeTransferFrom( this.address, @@ -184,7 +218,7 @@ export class V2Avatar implements AvatarInterfaceV2 { return receipt; } - async transfer(to: string, amount: bigint, tokenAddress?: string): Promise { + async transfer(to: string, amount: bigint, tokenAddress?: string): Promise { if (!tokenAddress) { const approvalStatus = await this.sdk.v2Hub!.isApprovedForAll(this.address, this.address); if (!approvalStatus) { @@ -302,7 +336,7 @@ export class V2Avatar implements AvatarInterfaceV2 { const demurragedWrapper = await this.sdk.getDemurragedWrapper(wrapperTokenAddress); const tx = await demurragedWrapper.unwrap(amount); const receipt = await tx.wait(); - if (!receipt){ + if (!receipt) { throw new Error('Unwrap failed'); } return receipt; @@ -312,7 +346,7 @@ export class V2Avatar implements AvatarInterfaceV2 { const inflationWrapper = await this.sdk.getInflationaryWrapper(wrapperTokenAddress); const tx = await inflationWrapper.unwrap(amount); const receipt = await tx.wait(); - if (!receipt){ + if (!receipt) { throw new Error('Unwrap failed'); } return receipt; @@ -326,7 +360,7 @@ export class V2Avatar implements AvatarInterfaceV2 { async decodeErc20WrapperDeployed(receipt: ContractTransactionReceipt): Promise { // Decode: event ERC20WrapperDeployed(address indexed avatar, address indexed erc20Wrapper, CirclesType circlesType); const decoded = this.sdk.v2Hub?.interface.parseLog(receipt.logs[0]); - console.log(`decoded: ${JSON.stringify(decoded)}`); + console.log(`decoded: ${JSON.stringify(decoded, ((key: any, value: any) => typeof value === 'bigint' ? value.toString() : value), 2)}`); throw new Error('Not implemented'); }