Skip to content

Commit 9adea8f

Browse files
amirmradj-mueller
andauthored
Remove blacklisted entry (#73)
* Remove blacklist node builder & endpoint * Remove from blacklist server types & endpoint * Unfreeze tab * Fix schema.json --------- Co-authored-by: Jann Müller <[email protected]>
1 parent 88b2621 commit 9adea8f

File tree

9 files changed

+232
-64
lines changed

9 files changed

+232
-64
lines changed

frontend/src/app/mint-authority/page.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default function Home() {
3434
const [mintRecipientAddress, setMintRecipientAddress] = useState('mint recipient address');
3535
const [sendRecipientAddress, setsendRecipientAddress] = useState('send recipient address');
3636
const [freezeAccountNumber, setFreezeAccountNumber] = useState('account to freeze');
37+
const [unfreezeAccountNumber, setUnfreezeAccountNumber] = useState('account to unfreeze');
3738
const [freezeReason, setFreezeReason] = useState('Enter reason here');
3839
const [seizeAccountNumber, setSeizeAccountNumber] = useState('account to seize');
3940
const [seizeReason, setSeizeReason] = useState('Enter reason here');
@@ -216,6 +217,51 @@ export default function Home() {
216217
}
217218
};
218219

220+
const onUnfreeze = async () => {
221+
console.log('unfreeze an account');
222+
lucid.selectWallet.fromSeed(mintAccount.mnemonic);
223+
changeAlertInfo({severity: 'info', message: 'Unfreeze request processing', open: true,});
224+
const requestData = {
225+
issuer: mintAccount.address,
226+
blacklist_address: unfreezeAccountNumber,
227+
};
228+
try {
229+
const response = await axios.post(
230+
'/api/v1/tx/programmable-token/unblacklist',
231+
requestData,
232+
{
233+
headers: {
234+
'Content-Type': 'application/json;charset=utf-8',
235+
},
236+
}
237+
);
238+
console.log('Unfreeze response:', response.data);
239+
const tx = await lucid.fromTx(response.data.cborHex);
240+
await signAndSentTx(lucid, tx);
241+
changeAlertInfo({severity: 'success', message: 'Account successfully unfrozen', open: true,});
242+
const unfrozenWalletKey = (Object.keys(accounts) as (keyof Accounts)[]).find(
243+
(key) => accounts[key].address === freezeAccountNumber
244+
);
245+
if (unfrozenWalletKey) {
246+
changeWalletAccountDetails(unfrozenWalletKey, {
247+
...accounts[unfrozenWalletKey],
248+
status: 'Active',
249+
});
250+
}
251+
} catch (error: any) {
252+
if (error.response.data.includes('BlacklistNodeNotFound')) {
253+
changeAlertInfo({
254+
severity: 'error',
255+
message: 'This account is not frozen.',
256+
open: true,
257+
});
258+
return;
259+
} else {
260+
console.error('Unfreeze failed:', error);
261+
}
262+
}
263+
};
264+
219265
const onSeize = async () => {
220266
console.log('seize account funds');
221267
lucid.selectWallet.fromSeed(mintAccount.mnemonic);
@@ -292,6 +338,15 @@ export default function Home() {
292338
/>
293339
</Box>
294340

341+
const unFreezeContent = <Box>
342+
<WSTTextField
343+
value={unfreezeAccountNumber}
344+
onChange={(e) => setUnfreezeAccountNumber(e.target.value)}
345+
label="Account Number"
346+
fullWidth={true}
347+
/>
348+
</Box>
349+
295350
const seizeContent = <Box>
296351
<WSTTextField
297352
value={seizeAccountNumber}
@@ -358,6 +413,12 @@ maxRows={3}
358413
buttonLabel: 'Freeze',
359414
onAction: onFreeze
360415
},
416+
{
417+
label: 'Unfreeze',
418+
content: unFreezeContent,
419+
buttonLabel: 'Unfreeze',
420+
onAction: onUnfreeze
421+
},
361422
{
362423
label: 'Seize',
363424
content: seizeContent,

generated/openapi/schema.json

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,6 @@
11
{
22
"components": {
33
"schemas": {
4-
"AddToBlacklistArgs": {
5-
"properties": {
6-
"blacklist_address": {
7-
"$ref": "#/components/schemas/Address"
8-
},
9-
"issuer": {
10-
"$ref": "#/components/schemas/Address"
11-
}
12-
},
13-
"required": [
14-
"issuer",
15-
"blacklist_address"
16-
],
17-
"type": "object"
18-
},
194
"AddVKeyWitnessArgs_ConwayEra": {
205
"properties": {
216
"w_tx": {
@@ -39,6 +24,21 @@
3924
"Asset name": {
4025
"type": "string"
4126
},
27+
"BlacklistNodeArgs": {
28+
"properties": {
29+
"blacklist_address": {
30+
"$ref": "#/components/schemas/Address"
31+
},
32+
"issuer": {
33+
"$ref": "#/components/schemas/Address"
34+
}
35+
},
36+
"required": [
37+
"issuer",
38+
"blacklist_address"
39+
],
40+
"type": "object"
41+
},
4242
"Hash PaymentKey": {
4343
"description": "Hash of a payment key",
4444
"example": "f6ac5676b58d8ce280c1f09af4a2e82dd58c1aa2fb075aa005afa1da",
@@ -417,7 +417,7 @@
417417
"content": {
418418
"application/json;charset=utf-8": {
419419
"schema": {
420-
"$ref": "#/components/schemas/AddToBlacklistArgs"
420+
"$ref": "#/components/schemas/BlacklistNodeArgs"
421421
}
422422
}
423423
}
@@ -526,6 +526,35 @@
526526
}
527527
}
528528
},
529+
"/api/v1/tx/programmable-token/unblacklist": {
530+
"post": {
531+
"description": "Remove a credential from the blacklist",
532+
"requestBody": {
533+
"content": {
534+
"application/json;charset=utf-8": {
535+
"schema": {
536+
"$ref": "#/components/schemas/BlacklistNodeArgs"
537+
}
538+
}
539+
}
540+
},
541+
"responses": {
542+
"200": {
543+
"content": {
544+
"application/json;charset=utf-8": {
545+
"schema": {
546+
"$ref": "#/components/schemas/TextEnvelopeJSON"
547+
}
548+
}
549+
},
550+
"description": ""
551+
},
552+
"400": {
553+
"description": "Invalid `body`"
554+
}
555+
}
556+
}
557+
},
529558
"/api/v1/tx/submit": {
530559
"post": {
531560
"description": "Submit a transaction to the blockchain",

src/lib/Wst/AppError.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ data AppError era =
1616
| BlockfrostErr BlockfrostError
1717
| NoTokensToSeize -- ^ No tokens to seize
1818
| DuplicateBlacklistNode -- ^ Attempting to add a duplicate blacklist node
19+
| BlacklistNodeNotFound -- ^ Attempting to remove a blacklist node that does not exist
1920
| TransferBlacklistedCredential Credential -- ^ Attempting to transfer funds from a blacklisted address
2021
| SubmitError (ValidationError era)
2122
deriving stock (Show)

src/lib/Wst/Client.hs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module Wst.Client (
1212
postIssueProgrammableTokenTx,
1313
postTransferProgrammableTokenTx,
1414
postAddToBlacklistTx,
15+
postRemoveFromBlacklistTx,
1516
postSeizeFundsTx
1617
) where
1718

@@ -22,7 +23,7 @@ import Servant.Client (ClientEnv, client, runClientM)
2223
import Servant.Client.Core (ClientError)
2324
import SmartTokens.Types.ProtocolParams (ProgrammableLogicGlobalParams)
2425
import Wst.Offchain.Query (UTxODat)
25-
import Wst.Server.Types (API, APIInEra, AddToBlacklistArgs,
26+
import Wst.Server.Types (API, APIInEra, BlacklistNodeArgs,
2627
IssueProgrammableTokenArgs (..), SeizeAssetsArgs,
2728
TextEnvelopeJSON, TransferProgrammableTokenArgs (..))
2829

@@ -46,13 +47,17 @@ postTransferProgrammableTokenTx env args = do
4647
let _ :<|> _ :<|> ((_ :<|> transferProgrammableTokenTx :<|> _) :<|> _) = client (Proxy @(API era))
4748
runClientM (transferProgrammableTokenTx args) env
4849

49-
postAddToBlacklistTx :: forall era. C.IsShelleyBasedEra era => ClientEnv -> AddToBlacklistArgs -> IO (Either ClientError (TextEnvelopeJSON (C.Tx era)))
50+
postAddToBlacklistTx :: forall era. C.IsShelleyBasedEra era => ClientEnv -> BlacklistNodeArgs -> IO (Either ClientError (TextEnvelopeJSON (C.Tx era)))
5051
postAddToBlacklistTx env args = do
5152
let _ :<|> _ :<|> ((_ :<|> _ :<|> addToBlacklistTx :<|> _) :<|> _) = client (Proxy @(API era))
5253
runClientM (addToBlacklistTx args) env
5354

55+
postRemoveFromBlacklistTx :: forall era. C.IsShelleyBasedEra era => ClientEnv -> BlacklistNodeArgs -> IO (Either ClientError (TextEnvelopeJSON (C.Tx era)))
56+
postRemoveFromBlacklistTx env args = do
57+
let _ :<|> _ :<|> ((_ :<|> _ :<|> _ :<|> removeFromBlacklistTx :<|> _) :<|> _) = client (Proxy @(API era))
58+
runClientM (removeFromBlacklistTx args) env
59+
5460
postSeizeFundsTx :: forall era. C.IsShelleyBasedEra era => ClientEnv -> SeizeAssetsArgs -> IO (Either ClientError (TextEnvelopeJSON (C.Tx era)))
5561
postSeizeFundsTx env args = do
56-
let _ :<|> _ :<|> ((_ :<|> _ :<|> _ :<|> seizeFunds) :<|> _) = client (Proxy @(API era))
62+
let _ :<|> _ :<|> ((_ :<|> _ :<|> _ :<|> _ :<|> seizeFunds) :<|> _) = client (Proxy @(API era))
5763
runClientM (seizeFunds args) env
58-

src/lib/Wst/Offchain/BuildTx/TransferLogic.hs

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module Wst.Offchain.BuildTx.TransferLogic
99
seizeSmartTokens,
1010
initBlacklist,
1111
insertBlacklistNode,
12-
spendBlacklistOutput,
12+
removeBlacklistNode,
1313
paySmartTokensToDestination,
1414
registerTransferScripts,
1515
)
@@ -36,7 +36,7 @@ import Convex.Utxos (UtxoSet (UtxoSet))
3636
import Convex.Wallet (selectMixedInputsCovering)
3737
import Data.Foldable (maximumBy)
3838
import Data.Function (on)
39-
import Data.List (nub, sort)
39+
import Data.List (find, nub, sort)
4040
import Data.Monoid (Last (..))
4141
import GHC.Exts (IsList (..))
4242
import PlutusLedgerApi.Data.V3 (Credential (..), PubKeyHash (PubKeyHash),
@@ -46,7 +46,7 @@ import SmartTokens.Contracts.ExampleTransferLogic (BlacklistProof (..))
4646
import SmartTokens.Types.ProtocolParams
4747
import SmartTokens.Types.PTokenDirectory (BlacklistNode (..),
4848
DirectorySetNode (..))
49-
import Wst.AppError (AppError (DuplicateBlacklistNode, TransferBlacklistedCredential))
49+
import Wst.AppError (AppError (BlacklistNodeNotFound, DuplicateBlacklistNode, TransferBlacklistedCredential))
5050
import Wst.Offchain.BuildTx.ProgrammableLogic (issueProgrammableToken,
5151
seizeProgrammableToken,
5252
transferProgrammableToken)
@@ -135,13 +135,48 @@ insertBlacklistNode cred blacklistNodes = Utils.inBabbage @era $ do
135135
opPkh <- asks (fst . Env.bteOperator . Env.operatorEnv)
136136
addRequiredSignature opPkh
137137

138-
spendBlacklistOutput :: forall era env m. (MonadReader env m, Env.HasOperatorEnv era env, Env.HasTransferLogicEnv env, C.IsBabbageBasedEra era, C.HasScriptLanguageInEra C.PlutusScriptV3 era, MonadBuildTx era m) => C.TxIn -> m ()
139-
spendBlacklistOutput txin = Utils.inBabbage @era $ do
140-
spendingScript <- asks (Env.tleBlacklistSpendingScript . Env.transferLogicEnv)
141-
spendPlutusInlineDatum txin spendingScript ()
138+
removeBlacklistNode :: forall era env m. (MonadReader env m, Env.HasOperatorEnv era env, Env.HasTransferLogicEnv env, C.IsBabbageBasedEra era, C.HasScriptLanguageInEra C.PlutusScriptV3 era, MonadBuildTx era m, MonadError (AppError era) m) => C.PaymentCredential -> [UTxODat era BlacklistNode]-> m ()
139+
removeBlacklistNode cred blacklistNodes = Utils.inBabbage @era $ do
142140
opPkh <- asks (fst . Env.bteOperator . Env.operatorEnv)
141+
blacklistSpendingScript <- asks (Env.tleBlacklistSpendingScript . Env.transferLogicEnv)
142+
blacklistMintingScript <- asks (Env.tleBlacklistMintingScript . Env.transferLogicEnv)
143+
blacklistPolicyId <- asks (Env.blacklistNodePolicyId . Env.transferLogicEnv)
144+
145+
-- find node to remove
146+
UTxODat{uIn = delNodeRef, uOut = (C.TxOut _delAddr delOutVal _ _), uDatum = delNodeDatum}
147+
<- maybe (throwError BlacklistNodeNotFound) pure $ find ((== unwrapCredential (transCredential cred)) . blnKey . uDatum) blacklistNodes
148+
149+
150+
let expectedAssetName = C.AssetName $ case transCredential cred of
151+
PubKeyCredential (PubKeyHash s) -> PlutusTx.fromBuiltin s
152+
ScriptCredential (ScriptHash s) -> PlutusTx.fromBuiltin s
153+
154+
v = C.selectAsset (C.txOutValueToValue delOutVal) (C.AssetId blacklistPolicyId expectedAssetName)
155+
156+
when (v /= 1) $ error "Unexpected blacklist node token quantity. Head node should not be deleted"
157+
158+
-- find the node to update to point to the node after the node to remove
159+
let UTxODat {uIn = prevNodeRef,uOut = (C.TxOut prevAddr prevVal _ _), uDatum = prevNode} =
160+
maximumBy (compare `on` (blnKey . uDatum)) $
161+
filter ((< unwrapCredential (transCredential cred)) . blnKey . uDatum) blacklistNodes
162+
163+
-- update the previous node to point to the node after the node to remove
164+
updatedPrevNode = prevNode {blnNext=blnNext delNodeDatum}
165+
updatedPrevNodeDatum = C.TxOutDatumInline C.babbageBasedEra $ C.toHashableScriptData updatedPrevNode
166+
updatedPrevNodeOutput = C.TxOut prevAddr prevVal updatedPrevNodeDatum C.ReferenceScriptNone
167+
168+
169+
-- spend the node to remove
170+
spendPlutusInlineDatum delNodeRef blacklistSpendingScript ()
171+
-- set previous node output
172+
spendPlutusInlineDatum prevNodeRef blacklistSpendingScript ()
173+
-- set previous node output
174+
prependTxOut updatedPrevNodeOutput
175+
-- burn the removed node blacklist token
176+
mintPlutus blacklistMintingScript () expectedAssetName (-1)
143177
addRequiredSignature opPkh
144178

179+
145180
{-| Add a smart token output that locks the given value,
146181
addressed to the payment credential
147182
-}

src/lib/Wst/Offchain/Endpoints/Deployment.hs

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module Wst.Offchain.Endpoints.Deployment(
1010
issueSmartTokensTx,
1111
transferSmartTokensTx,
1212
insertBlacklistNodeTx,
13-
blacklistCredentialTx,
13+
removeBlacklistNodeTx,
1414
seizeCredentialAssetsTx,
1515
) where
1616

@@ -132,6 +132,13 @@ insertBlacklistNodeTx cred = do
132132
(tx, _) <- Env.balanceTxEnv_ (BuildTx.insertBlacklistNode cred blacklist)
133133
pure (Convex.CoinSelection.signBalancedTxBody [] tx)
134134

135+
removeBlacklistNodeTx :: forall era env m. (MonadReader env m, Env.HasOperatorEnv era env, Env.HasTransferLogicEnv env, MonadBlockchain era m, MonadError (AppError era) m, C.IsBabbageBasedEra era, C.HasScriptLanguageInEra C.PlutusScriptV3 era, MonadUtxoQuery m) => C.PaymentCredential -> m (C.Tx era)
136+
removeBlacklistNodeTx cred = do
137+
blacklist <- Query.blacklistNodes @era
138+
(tx, _) <- Env.balanceTxEnv_ (BuildTx.removeBlacklistNode cred blacklist)
139+
pure (Convex.CoinSelection.signBalancedTxBody [] tx)
140+
141+
135142
{-| Build a transaction that issues a progammable token
136143
-}
137144
issueSmartTokensTx :: forall era env m.
@@ -182,23 +189,6 @@ transferSmartTokensTx assetId quantity destCred = do
182189
BuildTx.transferSmartTokens paramsTxIn blacklist directory userOutputsAtProgrammable (assetId, quantity) destCred
183190
pure (Convex.CoinSelection.signBalancedTxBody [] tx)
184191

185-
blacklistCredentialTx :: forall era env m.
186-
( MonadReader env m
187-
, Env.HasOperatorEnv era env
188-
, Env.HasTransferLogicEnv env
189-
, MonadBlockchain era m
190-
, MonadError (AppError era) m
191-
, C.IsBabbageBasedEra era
192-
, C.HasScriptLanguageInEra C.PlutusScriptV3 era
193-
, MonadUtxoQuery m
194-
)
195-
=> C.PaymentCredential -- ^ Source/User credential
196-
-> m (C.Tx era)
197-
blacklistCredentialTx sanctionedCred = do
198-
blacklist <- Query.blacklistNodes @era
199-
(tx, _) <- Env.balanceTxEnv_ $ do
200-
BuildTx.insertBlacklistNode sanctionedCred blacklist
201-
pure (Convex.CoinSelection.signBalancedTxBody [] tx)
202192

203193
seizeCredentialAssetsTx :: forall era env m.
204194
( MonadReader env m

0 commit comments

Comments
 (0)