Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import {
type Auditor,
} from '@tangle-network/tangle-shared-ui/blueprintApps/trustScore';
import { getContractsByChainId } from '@tangle-network/dapp-config/contracts';
import { readBlueprintCore } from '@tangle-network/tangle-shared-ui/data/blueprints/readBlueprintCore';
import type { Address } from 'viem';
import TangleABI from '@tangle-network/tangle-shared-ui/abi/tangle';

import TrustScoreGauge from './TrustScoreGauge';
import AttestationBadge from './AttestationBadge';
Expand Down Expand Up @@ -94,12 +94,14 @@ const useBlueprintOwner = (blueprintId: bigint | undefined) => {
try {
const contracts = getContractsByChainId(chainId);
const tangle = contracts.tangle as Address;
const result = (await publicClient.readContract({
address: tangle,
abi: TangleABI,
functionName: 'getBlueprint',
args: [blueprintId],
})) as { owner: Address };
// Revision-aware: the 0.18 Blueprint struct dropped operatorCount,
// so a fixed-ABI getBlueprint decode throws on the other revision.
const result = await readBlueprintCore(
publicClient,
tangle,
blueprintId,
chainId,
);
return result.owner;
} catch {
return null;
Expand Down
18 changes: 15 additions & 3 deletions apps/tangle-dapp/src/pages/staking/withdraw/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ import filterBy from '@tangle-network/tangle-shared-ui/utils/filterBy';
import { chainsConfig } from '@tangle-network/dapp-config/chains';
import AssetListItem from '../../../components/Lists/AssetListItem';
import MULTI_ASSET_DELEGATION_ABI from '@tangle-network/tangle-shared-ui/abi/multiAssetDelegation';
import { getContractsByChainId } from '@tangle-network/dapp-config/contracts';
import {
getContractsByChainId,
getTntCoreRevisionByChainId,
} from '@tangle-network/dapp-config/contracts';
import { useResilientReadContract } from '@tangle-network/tangle-shared-ui/hooks/useResilientReadContract';
import { useResilientReadContracts } from '@tangle-network/tangle-shared-ui/hooks/useResilientReadContracts';

Expand Down Expand Up @@ -296,15 +299,24 @@ const StakingWithdrawForm: FC = () => {
expiryBlock: bigint;
}>;

// tnt-core 0.18 redefined the LockInfo slot as a unix TIMESTAMP while
// keeping the same position/type, so the decode silently succeeds on
// both revisions — the comparison basis is what must branch. Comparing
// a timestamp against a block number reads "locked for decades".
const nowSeconds = BigInt(Math.floor(Date.now() / 1000));
const locked = locks.reduce((sum, lock) => {
return lock.expiryBlock > currentBlockNumber ? sum + lock.amount : sum;
const stillLocked =
getTntCoreRevisionByChainId(chainId) === 'v018'
? lock.expiryBlock > nowSeconds
: lock.expiryBlock > currentBlockNumber;
return stillLocked ? sum + lock.amount : sum;
}, BigInt(0));

map.set(token.toLowerCase(), locked);
});

return map;
}, [currentBlockNumber, lockResults, tokenAddresses]);
}, [chainId, currentBlockNumber, lockResults, tokenAddresses]);

const selectedAssetId = watch('assetId');
const amount = watch('amount');
Expand Down
29 changes: 29 additions & 0 deletions libs/dapp-config/src/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,35 @@ export const ETHEREUM_HOLESKY_CONTRACTS: ContractAddresses = {
blueprintAuditors: '0x0000000000000000000000000000000000000000',
};

/**
* Which tnt-core contract revision a chain runs.
*
* - `legacy`: pre-0.18 deployments — `getBlueprint` returns `operatorCount`
* inside the struct, `getOperatorPreferences` persists `rpcAddress`, and
* MultiAssetDelegation `LockInfo.expiryBlock` is a BLOCK NUMBER.
* - `v018`: tnt-core 0.18 greenfield redeploys — `operatorCount` moved to the
* `blueprintOperatorCount(uint64)` view (struct is 6 fields; decoding the
* legacy 7-field tuple throws), display prose is event-sourced from
* `BlueprintDefinitionRecorded`, and the `LockInfo` slot named
* `expiryBlock` carries a unix TIMESTAMP.
*
* Flip a chain to `v018` in the same change that updates its addresses to
* the redeployed contracts — never separately.
*/
export type TntCoreRevision = 'legacy' | 'v018';

export const getTntCoreRevisionByChainId = (
chainId: number,
): TntCoreRevision => {
switch (chainId) {
// Every currently-wired chain runs pre-0.18 contracts (Base Sepolia is
// pre-#193; local anvil is post-#193 but pre-#194, which for every
// surface the dapp branches on behaves like `legacy`).
default:
return 'legacy';
}
};

// Get contracts by chain ID
export const getContractsByChainId = (chainId: number): ContractAddresses => {
switch (chainId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Address, PublicClient } from 'viem';
import { getContractsByChainId } from '@tangle-network/dapp-config/contracts';
import TANGLE_ABI from '../../abi/tangle';
import { readBlueprintCore } from './readBlueprintCore';
import type { Blueprint } from '../graphql/useBlueprints';

/**
Expand Down Expand Up @@ -61,18 +62,7 @@ export const fetchBlueprintsOnChain = async (
ids.map(async (id) => {
try {
const [blueprint, mutableMeta] = await Promise.all([
publicClient.readContract({
address: tangleAddress,
abi: TANGLE_ABI,
functionName: 'getBlueprint',
args: [id],
}) as Promise<{
owner: Address;
manager: Address;
createdAt: bigint;
operatorCount: number;
active: boolean;
}>,
readBlueprintCore(publicClient, tangleAddress, id, chainId),
publicClient.readContract({
address: tangleAddress,
abi: TANGLE_ABI,
Expand Down Expand Up @@ -104,14 +94,14 @@ export const fetchBlueprintsOnChain = async (
? definition.metadataHash
: null,
active: blueprint.active,
createdAt: BigInt(blueprint.createdAt),
createdAt: blueprint.createdAt,
// `updatedAt` is tracked off-chain by the indexer (event log of
// updateBlueprint calls). Without that, the best we can do is
// surface the original createdAt so downstream sorts behave
// predictably; the value is honest about "not seen any
// updates since creation".
updatedAt: BigInt(blueprint.createdAt),
operatorCount: BigInt(blueprint.operatorCount),
updatedAt: blueprint.createdAt,
operatorCount: blueprint.operatorCount,
};
return result;
} catch {
Expand Down Expand Up @@ -150,18 +140,7 @@ export const fetchBlueprintByIdOnChain = async (

try {
const [blueprint, mutableMeta] = await Promise.all([
publicClient.readContract({
address: tangleAddress,
abi: TANGLE_ABI,
functionName: 'getBlueprint',
args: [blueprintId],
}) as Promise<{
owner: Address;
manager: Address;
createdAt: bigint;
operatorCount: number;
active: boolean;
}>,
readBlueprintCore(publicClient, tangleAddress, blueprintId, chainId),
publicClient.readContract({
address: tangleAddress,
abi: TANGLE_ABI,
Expand Down Expand Up @@ -193,9 +172,9 @@ export const fetchBlueprintByIdOnChain = async (
? definition.metadataHash
: null,
active: blueprint.active,
createdAt: BigInt(blueprint.createdAt),
updatedAt: BigInt(blueprint.createdAt),
operatorCount: BigInt(blueprint.operatorCount),
createdAt: blueprint.createdAt,
updatedAt: blueprint.createdAt,
operatorCount: blueprint.operatorCount,
};
} catch {
return null;
Expand Down
103 changes: 103 additions & 0 deletions libs/tangle-shared-ui/src/data/blueprints/readBlueprintCore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type { Address, PublicClient } from 'viem';
import {
getTntCoreRevisionByChainId,
type TntCoreRevision,
} from '@tangle-network/dapp-config/contracts';
import TANGLE_ABI from '../../abi/tangle';

/**
* tnt-core 0.18 `Types.Blueprint` — `operatorCount` was removed from the
* struct (derived from the operator set via `blueprintOperatorCount`), so the
* legacy 7-field tuple in the synced ABI throws on 0.18 returndata and this
* 6-field variant throws on legacy returndata. Selection is keyed by the
* per-chain `TntCoreRevision`; do not merge the two entries into one ABI.
*/
const GET_BLUEPRINT_V018_ABI = [
{
type: 'function',
name: 'getBlueprint',
stateMutability: 'view',
inputs: [{ name: 'blueprintId', type: 'uint64' }],
outputs: [
{
name: '',
type: 'tuple',
components: [
{ name: 'owner', type: 'address' },
{ name: 'manager', type: 'address' },
{ name: 'createdAt', type: 'uint64' },
{ name: 'membership', type: 'uint8' },
{ name: 'pricing', type: 'uint8' },
{ name: 'active', type: 'bool' },
],
},
],
},
] as const;

export type BlueprintCore = {
owner: Address;
manager: Address;
createdAt: bigint;
active: boolean;
operatorCount: bigint;
};

/**
* Revision-aware read of a blueprint's core fields.
*
* `legacy`: one `getBlueprint` call; `operatorCount` comes from the struct.
* `v018`: `getBlueprint` (6-field tuple) + the `blueprintOperatorCount` view.
*/
export const readBlueprintCore = async (
publicClient: PublicClient,
tangleAddress: Address,
blueprintId: bigint,
chainId: number,
): Promise<BlueprintCore> => {
const revision: TntCoreRevision = getTntCoreRevisionByChainId(chainId);

if (revision === 'legacy') {
const blueprint = (await publicClient.readContract({
address: tangleAddress,
abi: TANGLE_ABI,
functionName: 'getBlueprint',
args: [blueprintId],
})) as {
owner: Address;
manager: Address;
createdAt: bigint;
operatorCount: number;
active: boolean;
};
return {
owner: blueprint.owner,
manager: blueprint.manager,
createdAt: BigInt(blueprint.createdAt),
active: blueprint.active,
operatorCount: BigInt(blueprint.operatorCount),
};
}

const [blueprint, operatorCount] = await Promise.all([
publicClient.readContract({
address: tangleAddress,
abi: GET_BLUEPRINT_V018_ABI,
functionName: 'getBlueprint',
args: [blueprintId],
}),
publicClient.readContract({
address: tangleAddress,
abi: TANGLE_ABI,
functionName: 'blueprintOperatorCount',
args: [blueprintId],
}) as Promise<bigint>,
]);
return {
owner: blueprint.owner,
manager: blueprint.manager,
createdAt: BigInt(blueprint.createdAt),
active: blueprint.active,
operatorCount,
};
};
Loading