diff --git a/.github/workflows/repo--validate-pr-title.yml b/.github/workflows/repo--validate-pr-title.yml index 0f2f1bb62dd..145e7cf8f95 100644 --- a/.github/workflows/repo--validate-pr-title.yml +++ b/.github/workflows/repo--validate-pr-title.yml @@ -42,6 +42,7 @@ jobs: relayer taikoon alpha-nft + ui-lib taiko-client supplementary-contracts requireScope: true diff --git a/packages/bridge-ui/config/schemas/configuredCustomTokens.schema.json b/packages/bridge-ui/config/schemas/configuredCustomTokens.schema.json index 1a92562b13f..f7a13d72ada 100644 --- a/packages/bridge-ui/config/schemas/configuredCustomTokens.schema.json +++ b/packages/bridge-ui/config/schemas/configuredCustomTokens.schema.json @@ -26,15 +26,30 @@ "type": "string" }, "logoURI": { - "type": "string" - }, - "mintable": { - "type": "boolean" + "type": ["string", "null"] }, - "wrapped": { - "type": "boolean" + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "wrapped": { + "type": "boolean" + }, + "supported": { + "type": "boolean" + }, + "stablecoin": { + "type": "boolean" + }, + "mintable": { + "type": "boolean" + } + }, + "additionalProperties": false + } } }, - "required": ["name", "addresses", "symbol", "decimals", "type", "logoURI"] + "required": ["name", "addresses", "symbol", "decimals", "type"] } } diff --git a/packages/bridge-ui/scripts/utils/validateJson.ts b/packages/bridge-ui/scripts/utils/validateJson.ts index 3e9c5e4577e..31d9d92c693 100644 --- a/packages/bridge-ui/scripts/utils/validateJson.ts +++ b/packages/bridge-ui/scripts/utils/validateJson.ts @@ -1,6 +1,7 @@ /* eslint-disable no-console */ import Ajv, { type Schema } from 'ajv'; +import { type NFT, type Token, TokenAttributeKey } from '../../src/libs/token/types'; import { PluginLogger } from './PluginLogger'; const ajv = new Ajv({ strict: false }); @@ -9,7 +10,7 @@ type SchemaWithId = Schema & { $id?: string }; const logger = new PluginLogger('json-validator'); -export const validateJsonAgainstSchema = (json: JSON, schema: SchemaWithId): boolean => { +const validateJsonAgainstSchema = (json: JSON, schema: SchemaWithId): boolean => { logger.info(`Validating ${schema.$id}`); const validate = ajv.compile(schema); @@ -17,9 +18,29 @@ export const validateJsonAgainstSchema = (json: JSON, schema: SchemaWithId): boo if (!valid) { logger.error('Validation failed.'); - console.error('Error details:', ajv.errors); + console.error('Error details:', validate.errors); return false; } + + // Additional validation for attributes against TokenAttributeKey enum + if (Array.isArray(json)) { + json.forEach((token: Token | NFT) => { + if (token.attributes) { + token.attributes.forEach((attribute: Record) => { + Object.keys(attribute).forEach((key) => { + if (!Object.values(TokenAttributeKey).includes(key as TokenAttributeKey)) { + logger.error(`Invalid attribute key: ${key}`); + console.error(`Invalid attribute key: ${key}`); + throw new Error(`Invalid attribute key: ${key}`); + } + }); + }); + } + }); + } + logger.info(`Validation of ${schema.$id} succeeded.`); return true; }; + +export { validateJsonAgainstSchema }; diff --git a/packages/bridge-ui/src/components/Bridge/FungibleBridge.svelte b/packages/bridge-ui/src/components/Bridge/FungibleBridge.svelte index 8f0152513d0..b05d83398ae 100644 --- a/packages/bridge-ui/src/components/Bridge/FungibleBridge.svelte +++ b/packages/bridge-ui/src/components/Bridge/FungibleBridge.svelte @@ -16,6 +16,8 @@ let hasEnoughEth: boolean = false; let bridgingStatus: BridgingStatus; + let needsManualConfirmation: boolean; + const handleTransactionDetailsClick = () => (activeStep = BridgeSteps.RECIPIENT); const handleBackClick = () => (activeStep = BridgeSteps.IMPORT); @@ -51,6 +53,7 @@ {:else if activeStep === BridgeSteps.RECIPIENT} @@ -60,7 +63,7 @@ {/if} - + diff --git a/packages/bridge-ui/src/components/Bridge/FungibleBridgeComponents/ReviewStep/ReviewStep.svelte b/packages/bridge-ui/src/components/Bridge/FungibleBridgeComponents/ReviewStep/ReviewStep.svelte index 099573e7cff..d5bed5c6537 100644 --- a/packages/bridge-ui/src/components/Bridge/FungibleBridgeComponents/ReviewStep/ReviewStep.svelte +++ b/packages/bridge-ui/src/components/Bridge/FungibleBridgeComponents/ReviewStep/ReviewStep.svelte @@ -9,10 +9,11 @@ import { destNetwork as destChain, enteredAmount, selectedToken } from '$components/Bridge/state'; import { PUBLIC_SLOW_L1_BRIDGING_WARNING } from '$env/static/public'; import { LayerType } from '$libs/chain'; - import { isWrapped, type Token } from '$libs/token'; + import { isStablecoin, isSupported, isWrapped, type Token } from '$libs/token'; import { connectedSourceChain } from '$stores/network'; export let hasEnoughEth: boolean = false; + export let needsManualConfirmation = false; let recipientComponent: Recipient; let processingFeeComponent: ProcessingFee; @@ -24,8 +25,19 @@ $: wrapped = $selectedToken !== null && isWrapped($selectedToken as Token); + $: unsupportedStableCoin = + $selectedToken !== null && !isSupported($selectedToken as Token) && isStablecoin($selectedToken as Token); + $: wrappedAssetWarning = $t('bridge.alerts.wrapped_eth'); + $: stableCoinWarning = $t('bridge.alerts.stable_coin'); + + $: if (wrapped || unsupportedStableCoin) { + needsManualConfirmation = true; + } else { + needsManualConfirmation = false; + } + const dispatch = createEventDispatcher(); const editTransactionDetails = () => { @@ -69,11 +81,6 @@ {$t('bridge.alerts.slow_bridging')} {/if} -{#if wrapped} - - {@html wrappedAssetWarning} -{/if} -
+ {@html wrappedAssetWarning} +{/if} + +{#if unsupportedStableCoin} + + {@html stableCoinWarning} +{/if} diff --git a/packages/bridge-ui/src/components/Bridge/FungibleBridgeComponents/StepNavigation/StepNavigation.svelte b/packages/bridge-ui/src/components/Bridge/FungibleBridgeComponents/StepNavigation/StepNavigation.svelte index ac9bc90cfb7..fecc9a881a6 100644 --- a/packages/bridge-ui/src/components/Bridge/FungibleBridgeComponents/StepNavigation/StepNavigation.svelte +++ b/packages/bridge-ui/src/components/Bridge/FungibleBridgeComponents/StepNavigation/StepNavigation.svelte @@ -4,16 +4,20 @@ import { importDone } from '$components/Bridge/state'; import { BridgeSteps, BridgingStatus } from '$components/Bridge/types'; import { ActionButton } from '$components/Button'; + import { Icon } from '$components/Icon'; import { StepBack } from '$components/Stepper'; import { account } from '$stores/account'; export let activeStep: BridgeSteps = BridgeSteps.IMPORT; export let validatingImport = false; + export let needsManualConfirmation: boolean; export let bridgingStatus: BridgingStatus; let nextStepButtonText: string; + let manuallyConfirmed = false; + const getStepText = () => { if (activeStep === BridgeSteps.REVIEW) { return $t('common.confirm'); @@ -45,55 +49,68 @@ } else if (activeStep === BridgeSteps.RECIPIENT) { activeStep = BridgeSteps.REVIEW; } + reset(); }; - $: disabled = !$account || !$account.isConnected; + const reset = () => { + manuallyConfirmed = false; + }; - $: showStepNavigation = true; + $: disabled = !$account || !$account.isConnected; $: { nextStepButtonText = getStepText(); } + + $: needsConfirmation = needsManualConfirmation && !manuallyConfirmed; -{#if showStepNavigation} -
- {#if activeStep === BridgeSteps.IMPORT} -
- handleNextStep()}> - {nextStepButtonText} +
+ {#if activeStep === BridgeSteps.IMPORT} +
+ handleNextStep()}> + {nextStepButtonText} + + {/if} + {#if activeStep === BridgeSteps.REVIEW} + {#if needsManualConfirmation} + (manuallyConfirmed = true)}> + {#if needsConfirmation} + {$t('bridge.actions.acknowledge')} + {:else} + {$t('common.confirmed')} + {/if} {/if} - {#if activeStep === BridgeSteps.REVIEW} - handleNextStep()}> - {nextStepButtonText} - - handlePreviousStep()}> - {$t('common.back')} - - {/if} + handleNextStep()}> + {nextStepButtonText} + + + handlePreviousStep()}> + {$t('common.back')} + + {/if} - {#if activeStep === BridgeSteps.RECIPIENT} + {#if activeStep === BridgeSteps.RECIPIENT} + handleNextStep()}> + {nextStepButtonText} + + {/if} + + {#if activeStep === BridgeSteps.CONFIRM} + {#if bridgingStatus === BridgingStatus.DONE} handleNextStep()}> {nextStepButtonText} + {:else} + handlePreviousStep()}> + {$t('common.back')} + {/if} - - {#if activeStep === BridgeSteps.CONFIRM} - {#if bridgingStatus === BridgingStatus.DONE} - handleNextStep()}> - {nextStepButtonText} - - {:else} - handlePreviousStep()}> - {$t('common.back')} - - {/if} - {/if} -
-{/if} + {/if} +
diff --git a/packages/bridge-ui/src/components/Dialogs/RetryDialog/RetryDialog.svelte b/packages/bridge-ui/src/components/Dialogs/RetryDialog/RetryDialog.svelte index afe32b1f25f..ab01ed55c35 100644 --- a/packages/bridge-ui/src/components/Dialogs/RetryDialog/RetryDialog.svelte +++ b/packages/bridge-ui/src/components/Dialogs/RetryDialog/RetryDialog.svelte @@ -30,13 +30,13 @@ export let loading = false; + export let activeStep: RetrySteps = INITIAL_STEP; + const log = getLogger('RetryDialog'); const dispatch = createEventDispatcher(); const dialogId = `dialog-${uid()}`; - export let activeStep: RetrySteps = INITIAL_STEP; - let canContinue = false; let retrying: boolean; let retryDone = false; diff --git a/packages/bridge-ui/src/components/Faucet/Faucet.svelte b/packages/bridge-ui/src/components/Faucet/Faucet.svelte index eb19cb8a8b6..6ffd72608ff 100644 --- a/packages/bridge-ui/src/components/Faucet/Faucet.svelte +++ b/packages/bridge-ui/src/components/Faucet/Faucet.svelte @@ -1,7 +1,8 @@ {#if isNFT} @@ -108,7 +150,8 @@ {itemAmountDisplay}
{:else} -
+ +
{#if loading}
@@ -128,7 +171,7 @@
{:else} -
+
{truncateString(getChainName(Number(item.srcChainId)), 8)} @@ -142,7 +185,11 @@
{/if}
- +
{:else} -
+ +
{#if isDesktopOrLarger}
@@ -194,7 +245,11 @@ {/if}
- +