diff --git a/apps/devtool/src/app/_hooks/useDataStoreApi.tsx b/apps/devtool/src/app/_hooks/useDataStoreApi.tsx index ffaa9e1ed..026c18902 100644 --- a/apps/devtool/src/app/_hooks/useDataStoreApi.tsx +++ b/apps/devtool/src/app/_hooks/useDataStoreApi.tsx @@ -14,13 +14,11 @@ import { Alg, Curves, KeyTypes, Payload, hash } from '@narval/signature' import axios from 'axios' import { useEffect, useState } from 'react' import { v4 as uuid } from 'uuid' -import { useAccount } from 'wagmi' import useAccountSignature from './useAccountSignature' type DataStore = { entity: EntityStore; policy: PolicyStore } const useDataStoreApi = () => { - const account = useAccount() const { jwk, signAccountJwt } = useAccountSignature() const [dataStore, setDataStore] = useState() @@ -34,14 +32,9 @@ const useDataStoreApi = () => { } }, [dataStore]) - useEffect(() => { - if (!account.isConnected) return - - createCredential(account.address?.toLowerCase()) - }, [account.address]) - const getDataStore = async () => { const { data } = await axios.get('/api/data-store') + setDataStore(data) return data @@ -61,14 +54,14 @@ const useDataStoreApi = () => { const user: UserEntity = { id: uuid(), role: UserRole.ADMIN } const publicKey: CredentialEntity = { - id: uuid(), + id: address, userId: user.id, key: { kty: KeyTypes.EC, crv: Curves.SECP256K1, alg: Alg.ES256K, - kid: address.toLowerCase(), - addr: address.toLowerCase() + kid: address, + addr: address } } diff --git a/apps/devtool/src/app/_hooks/useEngineApi.tsx b/apps/devtool/src/app/_hooks/useEngineApi.tsx index 94f5e3720..dc750eb73 100644 --- a/apps/devtool/src/app/_hooks/useEngineApi.tsx +++ b/apps/devtool/src/app/_hooks/useEngineApi.tsx @@ -1,5 +1,9 @@ +import { EvaluationRequest, EvaluationResponse } from '@narval/policy-engine-shared' +import { hash } from '@narval/signature' import axios from 'axios' import { useState } from 'react' +import { v4 as uuid } from 'uuid' +import { extractErrorMessage } from '../_lib/utils' import useAccountSignature from './useAccountSignature' import useStore from './useStore' @@ -18,10 +22,11 @@ const useEngineApi = () => { setEngineClientSigner } = useStore() - const { jwk } = useAccountSignature() + const { jwk, signAccountJwt } = useAccountSignature() const [isOnboarded, setIsOnboarded] = useState(false) const [isSynced, setIsSynced] = useState(false) - const [errors, setErrors] = useState() + + const [errors, setErrors] = useState() const onboardClient = async () => { if (!engineAdminApiKey || !jwk) return @@ -57,23 +62,62 @@ const useEngineApi = () => { setIsOnboarded(true) setTimeout(() => setIsOnboarded(false), 5000) } catch (error) { - setErrors(error) + setErrors(extractErrorMessage(error)) } } const syncEngine = async () => { - await axios.post(`${engineUrl}/clients/sync`, null, { - headers: { - 'x-client-id': engineClientId, - 'x-client-secret': engineClientSecret + if (!engineClientId || !engineClientSecret) return + + setErrors(undefined) + + try { + await axios.post(`${engineUrl}/clients/sync`, null, { + headers: { + 'x-client-id': engineClientId, + 'x-client-secret': engineClientSecret + } + }) + + setIsSynced(true) + setTimeout(() => setIsSynced(false), 5000) + } catch (error) { + setErrors(extractErrorMessage(error)) + } + } + + const evaluateRequest = async (evaluationRequest: EvaluationRequest | undefined) => { + if (!engineClientId || !engineClientSecret || !evaluationRequest) return + + setErrors(undefined) + + try { + const payload = { + iss: uuid(), + sub: evaluationRequest.request.resourceId, + requestHash: hash(evaluationRequest.request) } - }) - setIsSynced(true) - setTimeout(() => setIsSynced(false), 5000) + const authentication = await signAccountJwt(payload) + + const { data: evaluation } = await axios.post( + `${engineUrl}/evaluations`, + { ...evaluationRequest, authentication }, + { + headers: { + 'x-client-id': engineClientId, + 'x-client-secret': engineClientSecret + } + } + ) + + return { evaluation, authentication } + } catch (error) { + setErrors(extractErrorMessage(error)) + } } - return { isOnboarded, isSynced, errors, onboardClient, syncEngine } + return { isOnboarded, isSynced, errors, onboardClient, syncEngine, evaluateRequest } } export default useEngineApi diff --git a/apps/devtool/src/app/_hooks/useVaultApi.tsx b/apps/devtool/src/app/_hooks/useVaultApi.tsx index 09cfd7df2..a358f912a 100644 --- a/apps/devtool/src/app/_hooks/useVaultApi.tsx +++ b/apps/devtool/src/app/_hooks/useVaultApi.tsx @@ -1,19 +1,26 @@ +import { Request, WalletEntity } from '@narval/policy-engine-shared' import axios from 'axios' import { useState } from 'react' +import { extractErrorMessage } from '../_lib/utils' +import useAccountSignature from './useAccountSignature' import useStore from './useStore' const useVaultApi = () => { const { vaultUrl, vaultAdminApiKey, + vaultClientId, + vaultClientSecret, engineClientSigner, setVaultClientId, setVaultClientSecret, setEngineClientSigner } = useStore() + const { signAccountJwsd } = useAccountSignature() + const [isOnboarded, setIsOnboarded] = useState(false) - const [errors, setErrors] = useState() + const [errors, setErrors] = useState() const onboardClient = async () => { if (!vaultAdminApiKey) return @@ -38,11 +45,56 @@ const useVaultApi = () => { setIsOnboarded(true) setTimeout(() => setIsOnboarded(false), 5000) } catch (error) { - setErrors(error) + setErrors(extractErrorMessage(error)) + } + } + + const importPrivateKey = async (privateKey: string) => { + if (!vaultClientId || !vaultClientSecret || !privateKey) return + + setErrors(undefined) + + try { + const { data } = await axios.post( + `${vaultUrl}/import/private-key`, + { privateKey }, + { + headers: { + 'x-client-id': vaultClientId, + 'x-client-secret': vaultClientSecret + } + } + ) + + return data + } catch (error) { + setErrors(extractErrorMessage(error)) + } + } + + const signTransaction = async (payload: { request: Request }, accessToken: string) => { + if (!vaultClientId || !vaultClientSecret || !accessToken) return + + setErrors(undefined) + + try { + const detachedJws = await signAccountJwsd(payload, accessToken) + + const { data } = await axios.post(`${vaultUrl}/sign`, payload, { + headers: { + 'x-client-id': vaultClientId, + 'detached-jws': detachedJws, + authorization: `GNAP ${accessToken}` + } + }) + + return data.signature + } catch (error) { + setErrors(extractErrorMessage(error)) } } - return { isOnboarded, errors, onboardClient } + return { isOnboarded, errors, onboardClient, importPrivateKey, signTransaction } } export default useVaultApi diff --git a/apps/devtool/src/app/_lib/utils.ts b/apps/devtool/src/app/_lib/utils.ts index 69795ab2c..559d4a107 100644 --- a/apps/devtool/src/app/_lib/utils.ts +++ b/apps/devtool/src/app/_lib/utils.ts @@ -1,3 +1,4 @@ +import { AxiosError } from 'axios' import { extendTailwindMerge } from 'tailwind-merge' export const classNames = (...classes: Array) => { @@ -6,3 +7,9 @@ export const classNames = (...classes: Array) => { } export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.toLowerCase().slice(1) + +export const extractErrorMessage = (err: unknown) => { + const error = err as AxiosError + const data = error.response?.data as any + return data?.message || error.message +} diff --git a/apps/devtool/src/app/api/data-store/example.json b/apps/devtool/src/app/api/data-store/example.json index 9fdb31407..38cf2473a 100644 --- a/apps/devtool/src/app/api/data-store/example.json +++ b/apps/devtool/src/app/api/data-store/example.json @@ -1,12 +1,34 @@ { "entity": { - "signature": "", + "signature": "eyJhbGciOiJFSVAxOTEiLCJraWQiOiIweDAwMGMwZDE5MTMwOEEzMzYzNTZCRWUzODEzQ0MxN0Y2ODY4OTcyQzQiLCJ0eXAiOiJKV1QifQ.eyJkYXRhIjoiMHgzNTE5MTkzZDVkNjQxYjIzYzdmYTIwYjRhMDE2NGI5YmZkZmI4MmQ2Njk5NjZiZWU1YTFhNmYwNmExMzI4ZTdlIiwiaWF0IjoxNzEyODUxNzg0LCJpc3MiOiJodHRwczovL2RldnRvb2wubmFydmFsLnh5eiIsInN1YiI6IjB4MDAwYzBkMTkxMzA4QTMzNjM1NkJFZTM4MTNDQzE3RjY4Njg5NzJDNCJ9.ReYKAos1x-UjAxfMsZVsE6tA2oURthN5iaLW9i9pr6gO37LVLCrKqdJ0ekouNHdJNQ-eI7VwlKct9esi9A1V_Bs", "data": { "addressBook": [], "credentials": [ + { + "id": "0x9D432a09CBf55F22Aa6a2E290acB12D57d29B2Fc", + "userId": "7a09904c-070e-4d00-8fa8-5f1dfafff2a5", + "key": { + "kty": "EC", + "crv": "secp256k1", + "alg": "ES256K", + "kid": "0x9D432a09CBf55F22Aa6a2E290acB12D57d29B2Fc", + "addr": "0x9D432a09CBf55F22Aa6a2E290acB12D57d29B2Fc" + } + }, + { + "id": "0x0C151023EDedCC419caB0f49ABaCd2e87a4FF013", + "userId": "db5bf088-05f7-492c-a2a9-0b62696ac9c7", + "key": { + "kty": "EC", + "crv": "secp256k1", + "alg": "ES256K", + "kid": "0x0C151023EDedCC419caB0f49ABaCd2e87a4FF013", + "addr": "0x0C151023EDedCC419caB0f49ABaCd2e87a4FF013" + } + }, { "id": "0x000c0d191308A336356BEe3813CC17F6868972C4", - "userId": "fe723044-35df-4e99-9739-122a48d4ab96", + "userId": "61e775a9-5f68-41ab-a775-5806845e6e72", "key": { "kty": "EC", "crv": "secp256k1", @@ -21,13 +43,21 @@ "userGroups": [], "userWallets": [ { - "userId": "fe723044-35df-4e99-9739-122a48d4ab96", - "walletId": "eip155:eoa:0x56085a7d2e308ec8ce6fd707cdc5282431d025db" + "userId": "61e775a9-5f68-41ab-a775-5806845e6e72", + "walletId": "eip155:eoa:0x494042504a8148a6d00ab10ed26043f5579ce00f" } ], "users": [ { - "id": "fe723044-35df-4e99-9739-122a48d4ab96", + "id": "7a09904c-070e-4d00-8fa8-5f1dfafff2a5", + "role": "admin" + }, + { + "id": "db5bf088-05f7-492c-a2a9-0b62696ac9c7", + "role": "admin" + }, + { + "id": "61e775a9-5f68-41ab-a775-5806845e6e72", "role": "admin" } ], @@ -35,15 +65,15 @@ "walletGroups": [], "wallets": [ { - "id": "eip155:eoa:0x56085a7d2e308ec8ce6fd707cdc5282431d025db", - "address": "0x56085a7d2e308ec8ce6fd707cdc5282431d025db", + "id": "eip155:eoa:0x494042504a8148a6d00ab10ed26043f5579ce00f", + "address": "0x494042504a8148a6d00ab10ed26043f5579ce00f", "accountType": "eoa" } ] } }, "policy": { - "signature": "", + "signature": "eyJhbGciOiJFSVAxOTEiLCJraWQiOiIweDAwMGMwZDE5MTMwOEEzMzYzNTZCRWUzODEzQ0MxN0Y2ODY4OTcyQzQiLCJ0eXAiOiJKV1QifQ.eyJkYXRhIjoiMHhhNDRhNmM5YjQzNmQyMDk5Y2Y4MzFjNTdkYmIyZGRmOTUzZTNhNTZhZWE5Y2E2NWI2Yzg3OTA1MDZkYjNjZjExIiwiaWF0IjoxNzEyODU5NDUyLCJpc3MiOiJodHRwczovL2RldnRvb2wubmFydmFsLnh5eiIsInN1YiI6IjB4MDAwYzBkMTkxMzA4QTMzNjM1NkJFZTM4MTNDQzE3RjY4Njg5NzJDNCJ9.9BCnmnwq3rN9YEquH3H1fTvCh7fo1eOlsSszi7guVnAZE54h3yPhGLwRtNHABVgajMKK_DaQg0hOVgKwVvoI9Rs", "data": [ { "id": "a68e8d20-0419-475c-8fcc-b17d4de8c955", @@ -60,6 +90,43 @@ { "criterion": "checkIntentType", "args": ["transferErc721", "transferErc1155"] + }, + { + "criterion": "checkApprovals", + "args": [ + { + "approvalCount": 2, + "countPrincipal": false, + "approvalEntityType": "Narval::User", + "entityIds": ["7a09904c-070e-4d00-8fa8-5f1dfafff2a5", "db5bf088-05f7-492c-a2a9-0b62696ac9c7"] + } + ] + } + ], + "then": "permit" + }, + { + "id": "a68e8d20-0519-475c-9fcc-b17d4de8c955", + "description": "Authorized transfers <= 1 MATIC on a 24h sliding window", + "when": [ + { + "criterion": "checkAction", + "args": ["signTransaction"] + }, + { + "criterion": "checkIntentType", + "args": ["transferNative"] + }, + { + "criterion": "checkSpendingLimit", + "args": { + "limit": "1000000000000000000", + "operator": "lte", + "timeWindow": { + "type": "rolling", + "value": 43200 + } + } } ], "then": "permit" diff --git a/apps/devtool/src/app/data-store/_components/DataStoreConfig.tsx b/apps/devtool/src/app/data-store/_components/DataStoreConfig.tsx index 295e4ff82..78a35da39 100644 --- a/apps/devtool/src/app/data-store/_components/DataStoreConfig.tsx +++ b/apps/devtool/src/app/data-store/_components/DataStoreConfig.tsx @@ -33,7 +33,7 @@ const DataStoreConfig = () => { useDataStoreApi() const [codeEditor, setCodeEditor] = useState() - const [displayCodeEditor, setDisplayCodeEditor] = useState(false) + const [displayCodeEditor, setDisplayCodeEditor] = useState(true) const [isDialogOpen, setIsDialogOpen] = useState(false) useEffect(() => { diff --git a/apps/devtool/src/app/data-store/_components/sections/Wallets.tsx b/apps/devtool/src/app/data-store/_components/sections/Wallets.tsx index 164fdfe78..d8e1ed6fd 100644 --- a/apps/devtool/src/app/data-store/_components/sections/Wallets.tsx +++ b/apps/devtool/src/app/data-store/_components/sections/Wallets.tsx @@ -3,12 +3,11 @@ import { faUpload, faUserPlus } from '@fortawesome/pro-regular-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Address, Namespace, UserWalletEntity, WalletEntity, toAccountId } from '@narval/policy-engine-shared' -import axios from 'axios' import { groupBy } from 'lodash' import { FC, useMemo, useState } from 'react' import NarButton from '../../../_design-system/NarButton' import NarDialog from '../../../_design-system/NarDialog' -import useStore from '../../../_hooks/useStore' +import useVaultApi from '../../../_hooks/useVaultApi' import ImportWalletForm from '../forms/ImportWalletForm' import WalletForm from '../forms/WalletForm' import DataCard from '../layouts/DataCard' @@ -28,7 +27,7 @@ interface WalletsProps { } const Wallets: FC = ({ wallets, userWallets, onChange }) => { - const { vaultUrl, vaultClientId, vaultClientSecret } = useStore() + const { importPrivateKey } = useVaultApi() const [isDialogOpen, setIsDialogOpen] = useState(false) const [isImportForm, setIsImportForm] = useState(false) const [isWalletForm, setIsWalletForm] = useState(false) @@ -105,26 +104,11 @@ const Wallets: FC = ({ wallets, userWallets, onChange }) => { } const handleImport = async () => { - if (!vaultClientId || !vaultClientSecret || !privateKey) return - - try { - const { data: wallet } = await axios.post( - `${vaultUrl}/import/private-key`, - { privateKey }, - { - headers: { - 'x-client-id': vaultClientId, - 'x-client-secret': vaultClientSecret - } - } - ) - - const newWallets = wallets ? [...wallets] : [] - newWallets.push({ ...wallet, address: wallet.address.toLowerCase() as Address, accountType: 'eoa' }) - onChange(newWallets) - } catch (error) { - console.log(error) - } + const wallet = await importPrivateKey(privateKey) + if (!wallet) return + const newWallets = wallets ? [...wallets] : [] + newWallets.push({ ...wallet, address: wallet.address.toLowerCase() as Address, accountType: 'eoa' }) + onChange(newWallets) } const onSaveDialog = async () => { diff --git a/apps/devtool/src/app/request-playground/_components/PlaygroundEditor.tsx b/apps/devtool/src/app/request-playground/_components/PlaygroundEditor.tsx index b96967c79..773c841c5 100644 --- a/apps/devtool/src/app/request-playground/_components/PlaygroundEditor.tsx +++ b/apps/devtool/src/app/request-playground/_components/PlaygroundEditor.tsx @@ -1,99 +1,104 @@ 'use client' -import { faArrowsRotate, faCheckCircle, faFileSignature, faXmarkCircle } from '@fortawesome/pro-regular-svg-icons' +import { + faArrowsRotate, + faCheck, + faCheckCircle, + faFileSignature, + faTriangleExclamation, + faXmarkCircle +} from '@fortawesome/pro-regular-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Editor } from '@monaco-editor/react' -import { Decision, EvaluationResponse } from '@narval/policy-engine-shared' +import { Decision, EvaluationRequest, EvaluationResponse } from '@narval/policy-engine-shared' import { hash } from '@narval/signature' -import axios from 'axios' import { useMemo, useRef, useState } from 'react' import NarButton from '../../_design-system/NarButton' import useAccountSignature from '../../_hooks/useAccountSignature' -import useStore from '../../_hooks/useStore' +import useEngineApi from '../../_hooks/useEngineApi' +import useVaultApi from '../../_hooks/useVaultApi' import example from './example.json' const PlaygroundEditor = () => { - const { engineUrl, engineClientId, engineClientSecret, vaultUrl, vaultClientId } = useStore() - const { jwk, signAccountJwsd, signAccountJwt } = useAccountSignature() - const [data, setData] = useState(JSON.stringify(example, null, 2)) + const { signAccountJwt } = useAccountSignature() + const { errors: evaluationErrors, evaluateRequest } = useEngineApi() + const { errors: signatureErrors, signTransaction } = useVaultApi() + const [codeEditor, setCodeEditor] = useState(JSON.stringify(example, null, 2)) const [isProcessing, setIsProcessing] = useState(false) const [evaluationResponse, setEvaluationResponse] = useState() const [signature, setSignature] = useState() + const error = evaluationErrors || signatureErrors const editorRef = useRef(null) const monacoRef = useRef(null) const canBeSigned = useMemo(() => { + if (!codeEditor || !evaluationResponse) return false + try { - const transactionRequest = JSON.parse(data || '{}') - return transactionRequest?.authentication && evaluationResponse?.decision === Decision.PERMIT + const transactionRequest = JSON.parse(codeEditor) + return transactionRequest.authentication && evaluationResponse.decision === Decision.PERMIT } catch (error) { return false } - }, [data, evaluationResponse]) + }, [codeEditor, evaluationResponse]) - const sendEvaluation = async () => { - if (!data || !jwk) return + const getApprovalSignature = async () => { + if (!codeEditor) return - setIsProcessing(true) + const transactionRequest = JSON.parse(codeEditor) - try { - const transactionRequest = JSON.parse(data) + const payload = { + iss: 'fe723044-35df-4e99-9739-122a48d4ab96', + sub: transactionRequest.request.resourceId, + requestHash: hash(transactionRequest.request) + } + + const authentication = await signAccountJwt(payload) - const payload = { - iss: 'fe723044-35df-4e99-9739-122a48d4ab96', - sub: transactionRequest.request.resourceId, - requestHash: hash(transactionRequest.request) - } + console.log(authentication) + } + + const sendEvaluation = async () => { + if (!codeEditor) return + + setIsProcessing(true) + setEvaluationResponse(undefined) + setSignature(undefined) - const authentication = await signAccountJwt(payload) + const request: EvaluationRequest = JSON.parse(codeEditor) + const { evaluation, authentication } = (await evaluateRequest(request)) || {} - const evaluation = await axios.post( - `${engineUrl}/evaluations`, - { ...transactionRequest, authentication }, + setCodeEditor( + JSON.stringify( { - headers: { - 'x-client-id': engineClientId, - 'x-client-secret': engineClientSecret - } - } + ...request, + ...(evaluation?.decision === Decision.PERMIT && { authentication }) + }, + null, + 2 ) + ) - setData(JSON.stringify({ ...transactionRequest, authentication }, null, 2)) - setEvaluationResponse(evaluation.data) - } catch (error) { - console.log(error) - } - + setEvaluationResponse(evaluation) setIsProcessing(false) } const signRequest = async () => { - if (!evaluationResponse || !jwk) return + if (!evaluationResponse) return const { accessToken, request } = evaluationResponse if (!accessToken?.value || !request) return - try { - const bodyPayload = { request } - - const detachedJws = await signAccountJwsd(bodyPayload, accessToken.value) + setIsProcessing(true) + setEvaluationResponse(undefined) + setSignature(undefined) - const { data } = await axios.post(`${vaultUrl}/sign`, bodyPayload, { - headers: { - 'x-client-id': vaultClientId, - 'detached-jws': detachedJws, - authorization: `GNAP ${accessToken.value}` - } - }) - - setSignature(data.signature) - setEvaluationResponse(undefined) - } catch (error) { - console.log(error) - } + const signature = await signTransaction({ request }, accessToken.value) + setSignature(signature) + setEvaluationResponse(undefined) setIsProcessing(false) } @@ -103,8 +108,8 @@ const PlaygroundEditor = () => { setData(value)} + value={codeEditor} + onChange={(value) => setCodeEditor(value)} onMount={(editor, monaco) => { editorRef.current = editor monacoRef.current = monaco @@ -125,22 +130,31 @@ const PlaygroundEditor = () => { onClick={signRequest} disabled={isProcessing || !canBeSigned} /> - {!isProcessing && evaluationResponse && ( + {false && ( + } onClick={getApprovalSignature} /> + )} + {!isProcessing && !error && evaluationResponse && (
- + {evaluationResponse.decision === Decision.PERMIT && ( + + )} + {evaluationResponse.decision === Decision.FORBID && ( + + )} + {evaluationResponse.decision === Decision.CONFIRM && ( + + )}
{evaluationResponse.decision}
)} - {!isProcessing && evaluationResponse && ( + {!isProcessing && error &&
{error}
} + {!isProcessing && !error && evaluationResponse && (
{JSON.stringify(evaluationResponse, null, 3)}
)} - {!isProcessing && signature &&
{signature}
} + {!isProcessing && !error && signature &&
{signature}
} ) diff --git a/apps/devtool/src/app/request-playground/_components/approvals.json b/apps/devtool/src/app/request-playground/_components/approvals.json new file mode 100644 index 000000000..9644e9611 --- /dev/null +++ b/apps/devtool/src/app/request-playground/_components/approvals.json @@ -0,0 +1,18 @@ +{ + "request": { + "resourceId": "eip155:eoa:0x494042504a8148a6d00ab10ed26043f5579ce00f", + "action": "signTransaction", + "nonce": "6bb2b3e9-fa8a-4081-a837-575184ceb3a5", + "transactionRequest": { + "from": "0x24f0914062f66d487dc082802aac53cd10328d96", + "to": "0x43a595aa07275b4f72061106d80b21e02ff8a960", + "data": "0x42842e0e00000000000000000000000024f0914062f66d487dc082802aac53cd10328d960000000000000000000000008cc7929789e14ce2ac6d4e8f2294ee4c501283e30000000000000000000000000000000000000000000000000000000000000085", + "chainId": 137, + "type": "2" + } + }, + "approvals": [ + "eyJhbGciOiJFSVAxOTEiLCJraWQiOiIweDlENDMyYTA5Q0JmNTVGMjJBYTZhMkUyOTBhY0IxMkQ1N2QyOUIyRmMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJmZTcyMzA0NC0zNWRmLTRlOTktOTczOS0xMjJhNDhkNGFiOTYiLCJyZXF1ZXN0SGFzaCI6IjB4ZjdmY2MyMzRhMTMzNWNmNGE0NDE0MzAwNWJhOWJhNWQyY2VlMTJhMmZmZDFlNDMyYTU4Yjc5YjllNzcyYzk5NiIsInN1YiI6ImVpcDE1NTplb2E6MHg0OTQwNDI1MDRhODE0OGE2ZDAwYWIxMGVkMjYwNDNmNTU3OWNlMDBmIn0.BxgEr9TILTIocaNpQs-59vKhBSePpS-q0D4VWfVpqIs0bRSBp8vBMHkKa7AxdtfMwRCBd86vldj-1Ebb5UtmMhs", + "eyJhbGciOiJFSVAxOTEiLCJraWQiOiIweDBDMTUxMDIzRURlZENDNDE5Y2FCMGY0OUFCYUNkMmU4N2E0RkYwMTMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJmZTcyMzA0NC0zNWRmLTRlOTktOTczOS0xMjJhNDhkNGFiOTYiLCJyZXF1ZXN0SGFzaCI6IjB4ZjdmY2MyMzRhMTMzNWNmNGE0NDE0MzAwNWJhOWJhNWQyY2VlMTJhMmZmZDFlNDMyYTU4Yjc5YjllNzcyYzk5NiIsInN1YiI6ImVpcDE1NTplb2E6MHg0OTQwNDI1MDRhODE0OGE2ZDAwYWIxMGVkMjYwNDNmNTU3OWNlMDBmIn0.Lnb8_P_4ilWhefkQXYVC563YYQBygpS2u3WZoFtfyFQzl9h35EzZgrgeXYPtFO05OuyRweyeH1960y6iEExV4xw" + ] +} diff --git a/apps/devtool/src/app/request-playground/_components/example.json b/apps/devtool/src/app/request-playground/_components/example.json index 35eb9a775..9644e9611 100644 --- a/apps/devtool/src/app/request-playground/_components/example.json +++ b/apps/devtool/src/app/request-playground/_components/example.json @@ -1,6 +1,6 @@ { "request": { - "resourceId": "eip155:eoa:0x8101c44b3bf060f0a92e47516192faa8df719761", + "resourceId": "eip155:eoa:0x494042504a8148a6d00ab10ed26043f5579ce00f", "action": "signTransaction", "nonce": "6bb2b3e9-fa8a-4081-a837-575184ceb3a5", "transactionRequest": { @@ -11,5 +11,8 @@ "type": "2" } }, - "approvals": [] + "approvals": [ + "eyJhbGciOiJFSVAxOTEiLCJraWQiOiIweDlENDMyYTA5Q0JmNTVGMjJBYTZhMkUyOTBhY0IxMkQ1N2QyOUIyRmMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJmZTcyMzA0NC0zNWRmLTRlOTktOTczOS0xMjJhNDhkNGFiOTYiLCJyZXF1ZXN0SGFzaCI6IjB4ZjdmY2MyMzRhMTMzNWNmNGE0NDE0MzAwNWJhOWJhNWQyY2VlMTJhMmZmZDFlNDMyYTU4Yjc5YjllNzcyYzk5NiIsInN1YiI6ImVpcDE1NTplb2E6MHg0OTQwNDI1MDRhODE0OGE2ZDAwYWIxMGVkMjYwNDNmNTU3OWNlMDBmIn0.BxgEr9TILTIocaNpQs-59vKhBSePpS-q0D4VWfVpqIs0bRSBp8vBMHkKa7AxdtfMwRCBd86vldj-1Ebb5UtmMhs", + "eyJhbGciOiJFSVAxOTEiLCJraWQiOiIweDBDMTUxMDIzRURlZENDNDE5Y2FCMGY0OUFCYUNkMmU4N2E0RkYwMTMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJmZTcyMzA0NC0zNWRmLTRlOTktOTczOS0xMjJhNDhkNGFiOTYiLCJyZXF1ZXN0SGFzaCI6IjB4ZjdmY2MyMzRhMTMzNWNmNGE0NDE0MzAwNWJhOWJhNWQyY2VlMTJhMmZmZDFlNDMyYTU4Yjc5YjllNzcyYzk5NiIsInN1YiI6ImVpcDE1NTplb2E6MHg0OTQwNDI1MDRhODE0OGE2ZDAwYWIxMGVkMjYwNDNmNTU3OWNlMDBmIn0.Lnb8_P_4ilWhefkQXYVC563YYQBygpS2u3WZoFtfyFQzl9h35EzZgrgeXYPtFO05OuyRweyeH1960y6iEExV4xw" + ] } diff --git a/apps/devtool/src/app/request-playground/_components/spending-limits.json b/apps/devtool/src/app/request-playground/_components/spending-limits.json new file mode 100644 index 000000000..cc4d64260 --- /dev/null +++ b/apps/devtool/src/app/request-playground/_components/spending-limits.json @@ -0,0 +1,40 @@ +{ + "request": { + "resourceId": "eip155:eoa:0x494042504a8148a6d00ab10ed26043f5579ce00f", + "action": "signTransaction", + "nonce": "6bb2b3e9-fa8a-4081-a837-575184ceb3a5", + "transactionRequest": { + "from": "0xed123cf8e3ba51c6c15da1eac74b2b5deea31448", + "to": "0x031d8c0ca142921c459bcb28104c0ff37928f9ed", + "value": "0xde0b6b3a7640000", + "chainId": 137, + "type": "2" + } + }, + "feeds": [ + { + "source": "armory/price-feed", + "sig": "eyJhbGciOiJFSVAxOTEiLCJraWQiOiIweDlENDMyYTA5Q0JmNTVGMjJBYTZhMkUyOTBhY0IxMkQ1N2QyOUIyRmMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJmZTcyMzA0NC0zNWRmLTRlOTktOTczOS0xMjJhNDhkNGFiOTYiLCJyZXF1ZXN0SGFzaCI6IjB4ZjdmY2MyMzRhMTMzNWNmNGE0NDE0MzAwNWJhOWJhNWQyY2VlMTJhMmZmZDFlNDMyYTU4Yjc5YjllNzcyYzk5NiIsInN1YiI6ImVpcDE1NTplb2E6MHg0OTQwNDI1MDRhODE0OGE2ZDAwYWIxMGVkMjYwNDNmNTU3OWNlMDBmIn0.BxgEr9TILTIocaNpQs-59vKhBSePpS-q0D4VWfVpqIs0bRSBp8vBMHkKa7AxdtfMwRCBd86vldj-1Ebb5UtmMhs", + "data": { + "eip155:137/slip44:966": { + "fiat:usd": "0.99", + "fiat:eur": "1.10" + } + } + }, + { + "source": "armory/historical-transfer-feed", + "sig": "eyJhbGciOiJFSVAxOTEiLCJraWQiOiIweDlENDMyYTA5Q0JmNTVGMjJBYTZhMkUyOTBhY0IxMkQ1N2QyOUIyRmMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJmZTcyMzA0NC0zNWRmLTRlOTktOTczOS0xMjJhNDhkNGFiOTYiLCJyZXF1ZXN0SGFzaCI6IjB4ZjdmY2MyMzRhMTMzNWNmNGE0NDE0MzAwNWJhOWJhNWQyY2VlMTJhMmZmZDFlNDMyYTU4Yjc5YjllNzcyYzk5NiIsInN1YiI6ImVpcDE1NTplb2E6MHg0OTQwNDI1MDRhODE0OGE2ZDAwYWIxMGVkMjYwNDNmNTU3OWNlMDBmIn0.BxgEr9TILTIocaNpQs-59vKhBSePpS-q0D4VWfVpqIs0bRSBp8vBMHkKa7AxdtfMwRCBd86vldj-1Ebb5UtmMhs", + "data": [ + { + "amount": "500000000000000000", + "from": "eip155:eoa:0x494042504a8148a6d00ab10ed26043f5579ce00f", + "token": "eip155:137/slip44:966", + "rates": { "fiat:usd": "0.90", "fiat:eur": "1.10" }, + "chainId": 137, + "initiatedBy": "61e775a9-5f68-41ab-a775-5806845e6e72" + } + ] + } + ] +} diff --git a/apps/policy-engine/src/open-policy-agent/core/open-policy-agent.engine.ts b/apps/policy-engine/src/open-policy-agent/core/open-policy-agent.engine.ts index 98bb8566d..fe5c256a0 100644 --- a/apps/policy-engine/src/open-policy-agent/core/open-policy-agent.engine.ts +++ b/apps/policy-engine/src/open-policy-agent/core/open-policy-agent.engine.ts @@ -166,7 +166,7 @@ export class OpenPolicyAgentEngine implements Engine { } private getCredential(id: string): CredentialEntity | null { - return this.getEntities().credentials.find((cred) => cred.id === id) || null + return this.getEntities().credentials.find((cred) => cred.id.toLowerCase() === id.toLowerCase()) || null } private async opaEvaluate( diff --git a/apps/policy-engine/src/open-policy-agent/core/type/open-policy-agent.type.ts b/apps/policy-engine/src/open-policy-agent/core/type/open-policy-agent.type.ts index 980f52663..87ab793f3 100644 --- a/apps/policy-engine/src/open-policy-agent/core/type/open-policy-agent.type.ts +++ b/apps/policy-engine/src/open-policy-agent/core/type/open-policy-agent.type.ts @@ -4,6 +4,7 @@ import { Action, Address, CredentialEntity, + Feed, HistoricalTransfer, TransactionRequest, UserRole @@ -24,7 +25,7 @@ export type Input = { principal: CredentialEntity resource?: { uid: string } approvals?: CredentialEntity[] - transfers?: HistoricalTransfer[] + feeds?: Feed[] } // TODO: (@wcalderipe, 18/03/24) Check with @samteb how can we replace these diff --git a/apps/policy-engine/src/open-policy-agent/core/util/evaluation.util.ts b/apps/policy-engine/src/open-policy-agent/core/util/evaluation.util.ts index 1d31025f8..5426e167f 100644 --- a/apps/policy-engine/src/open-policy-agent/core/util/evaluation.util.ts +++ b/apps/policy-engine/src/open-policy-agent/core/util/evaluation.util.ts @@ -3,6 +3,7 @@ import { CredentialEntity, Entities, EvaluationRequest, + Feed, Request, SignMessageAction, SignRawAction, @@ -15,9 +16,14 @@ import { indexBy } from 'lodash/fp' import { OpenPolicyAgentException } from '../exception/open-policy-agent.exception' import { Data, Input, UserGroup, WalletGroup } from '../type/open-policy-agent.type' -type Mapping = (request: R, principal: CredentialEntity, approvals?: CredentialEntity[]) => Input +type Mapping = ( + request: R, + principal: CredentialEntity, + approvals?: CredentialEntity[], + feeds?: Feed[] +) => Input -const toSignTransaction: Mapping = (request, principal, approvals): Input => { +const toSignTransaction: Mapping = (request, principal, approvals, feeds): Input => { const result = safeDecode({ input: { type: InputType.TRANSACTION_REQUEST, @@ -32,7 +38,8 @@ const toSignTransaction: Mapping = (request, principal, a approvals, intent: result.intent, transactionRequest: request.transactionRequest, - resource: { uid: request.resourceId } + resource: { uid: request.resourceId }, + feeds } } @@ -106,7 +113,7 @@ export const toInput = (params: { const mapper = mappers.get(action) if (mapper) { - return mapper(evaluation.request, params.principal, params.approvals) + return mapper(evaluation.request, params.principal, params.approvals, evaluation.feeds) } throw new OpenPolicyAgentException({