Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d3b036a
feat: Add Safe tx-service data layer
thekidnamedkd Aug 20, 2026
ff82ea0
fix: Normalize Safe tx-service responses
thekidnamedkd Aug 20, 2026
c031892
chore: Ignore agent-generated scratch files
thekidnamedkd Aug 20, 2026
5683276
feat: Add read-only Safe account view
thekidnamedkd Aug 20, 2026
098b5cd
fix: Resolve Safe account view address checksum client-side
thekidnamedkd Aug 20, 2026
535d884
feat: Add Safe body slot resolver and correlation utils (WIP)
thekidnamedkd Aug 20, 2026
16ecb57
feat: implement Safe proposal body card
thekidnamedkd Aug 20, 2026
8e8afb5
style: Sort Safe account page props per Biome autofix
thekidnamedkd Aug 20, 2026
a40821a
fix: Move externalPluginId out of client dialog into server-safe cons…
thekidnamedkd Aug 20, 2026
b429897
fix: Remove nested card chrome from Safe body breakdown
thekidnamedkd Aug 20, 2026
d3e571f
feat: enable Safe owner proposal reports
thekidnamedkd Aug 20, 2026
1f2f996
fix: execute confirmed Safe reports without resigning
thekidnamedkd Aug 20, 2026
e9a3b9f
fix: Send Safe propose payload in tx-service wire shape
thekidnamedkd Aug 20, 2026
a610ce3
style: Add spacing above Safe body report row
thekidnamedkd Aug 20, 2026
dc9ed8c
feat: Link Safe body card to its Safe account view
thekidnamedkd Aug 20, 2026
17afa12
Merge branch 'main' of github.com:aragon/app into safe-account-impl
thekidnamedkd Aug 25, 2026
515621b
fix: Drop needless async from proposeSafeTransaction
thekidnamedkd Aug 25, 2026
a677ee7
style: Apply Biome import-order and format autofixes
thekidnamedkd Aug 25, 2026
512feed
test: Assert flattened tx-service wire body in propose test
thekidnamedkd Aug 25, 2026
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
5 changes: 5 additions & 0 deletions .changeset/safe-account-read-view.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@aragon/app": minor
---

Add a read-only Safe account view at `/safe/{network}/{address}` showing owners, threshold, version, nonce, the live pending queue and balances
5 changes: 5 additions & 0 deletions .changeset/safe-account-writes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@aragon/app": minor
---

Let Safe owner wallets submit gasless approval or veto signatures from SPP proposal body cards and execute the report once the Safe threshold is reached
5 changes: 5 additions & 0 deletions .changeset/safe-body-card.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@aragon/app": minor
---

Show live Safe owners, signature progress, nonces and superseded result reports inside SPP proposal body cards
5 changes: 5 additions & 0 deletions .changeset/safe-tx-service-data-layer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@aragon/app": none
---

Add the Safe transaction service data layer: a keyed `/api/safe` proxy route and the `safeService` queries for Safe info, live queue and balances
4 changes: 4 additions & 0 deletions apps/app/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ NEXT_SECRET_DRPC_RPC_KEY=YOUR_VALUE_HERE
# Api key for connecting to the Aragon backend service
NEXT_SECRET_ARAGON_BACKEND_API_KEY=YOUR_VALUE_HERE

# Api key for the Safe transaction service, sent as an Authorization Bearer header by the
# /api/safe proxy route (see https://docs.safe.global/core-api/how-to-use-api-keys)
NEXT_SECRET_SAFE_API_KEY=YOUR_VALUE_HERE

# Envio API key and endpoint
NEXT_SECRET_ENVIO_GRAPHQL_ENDPOINT=YOUR_VALUE_HERE
NEXT_SECRET_ENVIO_API_TOKEN=YOUR_VALUE_HERE
2 changes: 2 additions & 0 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
"@reown/appkit": "^1.8.23",
"@reown/appkit-adapter-wagmi": "^1.8.23",
"@reown/walletkit": "^1.5.6",
"@safe-global/protocol-kit": "^8.0.6",

"@sentry/core": "^10.70.0",
"@sentry/nextjs": "^10.70.0",
"@tanstack/react-query": "catalog:",
Expand Down
4 changes: 4 additions & 0 deletions apps/app/src/app/api/safe/[chainId]/[...path]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { proxySafeUtils } from '@/modules/application/utils/proxySafeUtils';

export const GET = proxySafeUtils.request;
export const POST = proxySafeUtils.request;
27 changes: 27 additions & 0 deletions apps/app/src/app/safe/[network]/[address]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { notFound } from 'next/navigation-original';
import { SafeAccountPage } from '@/modules/safe/pages/safeAccountPage';
import type { ISafeAccountPageParams } from '@/modules/safe/types';
import { networkUtils } from '@/shared/utils/networkUtils';

interface ISafePageProps {
/**
* Safe account route parameters.
*/
params: Promise<ISafeAccountPageParams>;
}

// `next/navigation` is aliased to the app's client-side wrapper, so the server helpers are
// imported from `next/navigation-original`.
const SafePage = async (props: ISafePageProps) => {
const { network, address } = await props.params;

// A malformed network is a missing page, never a server error. The address is normalised to
// its checksum in the client page, matching how the DAO page resolves its parameters.
if (!networkUtils.isValidNetwork(network)) {
notFound();
}

return <SafeAccountPage address={address} network={network} />;
};

export default SafePage;
146 changes: 145 additions & 1 deletion apps/app/src/assets/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3134,6 +3134,72 @@
"entity": "Votes"
}
},
"safeMultisig": {
"safeMultisigGovernanceSettings": {
"approvalRule": "Approval rule",
"owners": "Owners",
"threshold": "{{min}} of {{max}} owners",
"unknownVersion": "Unknown",
"version": "Safe version",
"vetoRule": "Veto rule"
},
"safeMultisigProposalVotingBreakdown": {
"details": {
"safeNonce": "Safe nonce",
"transactionNonce": "Transaction nonce",
"unavailable": "Not available",
"unknown": "Unknown",
"version": "Safe version"
},
"error": "Safe state could not be loaded. Try again later.",
"loading": "Loading Safe state…",
"partialError": "The pending transaction state could not be refreshed.",
"report": {
"approval": {
"executed": "Approved and executed",
"pending": "Approval pending — {{count}} of {{required}}",
"superseded": "Approval superseded — {{count}} of {{required}}"
},
"none": {
"executed": "No result executed",
"pending": "No-result report pending — {{count}} of {{required}}",
"superseded": "No-result report superseded — {{count}} of {{required}}"
},
"nonePending": {
"approval": "No approval submitted yet",
"veto": "No veto submitted yet"
},
"veto": {
"executed": "Vetoed and executed",
"pending": "Veto pending — {{count}} of {{required}}",
"superseded": "Veto superseded — {{count}} of {{required}}"
}
},
"signers": {
"empty": "No owner has signed this report yet.",
"title": "Signers ({{count}})"
},
"tag": {
"competition": "Competing transaction",
"superseded": "Superseded",
"youSigned": "You have signed"
},
"viewSafeAccount": "View Safe account"
},
"safeMultisigSubmitVote": {
"approve": "Approve proposal",
"approved": "Approved",
"error": "The Safe action could not be completed. Try again.",
"executeApproval": "Execute approval",
"executeVeto": "Execute veto",
"ownerRequired": "Connect an owner wallet for this Safe to continue.",
"unknownVersion": "unknown",
"versionUnsupported": "Contract-owner signatures require Safe v1.4.1 or newer. This Safe is {{version}}.",
"veto": "Veto proposal",
"vetoed": "Vetoed",
"waitingForOwners": "Your signature is submitted. Waiting for the remaining Safe owners."
}
},
"spp": {
"advanceStageDialog": {
"button": {
Expand Down Expand Up @@ -3208,7 +3274,6 @@
"approve": "Approve proposal",
"approved": "Approved",
"helpText": "If you are an owner of this address you can connect with WalletConnect to vote on this proposal.",
"helpTextSafe": "To approve this proposal from the Safe{Wallet} UI, add Aragon as a custom Safe App or connect directly with a WalletConnect QR code.",
"veto": "Veto proposal",
"vetoed": "Vetoed"
},
Expand Down Expand Up @@ -3596,6 +3661,85 @@
}
}
},
"safe": {
"safeAccountPage": {
"aside": {
"details": {
"address": "Address",
"chain": "Chain",
"nonce": "Nonce",
"threshold": "Threshold",
"thresholdValue": "{{threshold}} of {{owners}}",
"title": "Details",
"unknown": "Unknown",
"version": "Version"
}
},
"header": {
"description": "A read-only view of this Safe: its owners, signing threshold, contract version, nonce, queued transactions and balances."
},
"main": {
"assets": {
"title": "Assets"
},
"owners": {
"title": "Owners"
},
"pending": {
"description": "Transactions waiting to be executed. Transactions below the current nonce can never execute and are not shown.",
"title": "Pending transactions"
}
},
"stats": {
"nonce": "Nonce",
"owners": "Owners",
"threshold": "Threshold",
"thresholdSuffix": "of {{owners}}"
},
"unsupportedNetwork": {
"description": "Safe does not run a transaction service on {{network}}, so this Safe cannot be read here.",
"heading": "Not supported on this network"
}
},
"safeBalanceList": {
"empty": {
"description": "This Safe does not hold any assets yet.",
"heading": "No assets"
},
"entity": "Assets",
"error": {
"description": "The balances of this Safe could not be loaded. Please try again later.",
"heading": "Unable to load assets"
}
},
"safeOwnerList": {
"empty": {
"description": "No owners were reported for this Safe.",
"heading": "No owners"
},
"entity": "Owners",
"error": {
"description": "The owners of this Safe could not be loaded. Please try again later.",
"heading": "Unable to load owners"
}
},
"safePendingTransactionList": {
"empty": {
"description": "This Safe has no transactions waiting to be executed.",
"heading": "No pending transactions"
},
"entity": "Transactions",
"error": {
"description": "The pending transactions of this Safe could not be loaded. Please try again later.",
"heading": "Unable to load pending transactions"
},
"item": {
"confirmations": "{{count}} of {{required}} confirmations",
"nonce": "Nonce {{nonce}}",
"submittedOn": "Submitted {{date}}"
}
}
},
"settings": {
"daoPluginInfo": {
"bodyName": "Body name",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ProxySafeUtils } from './proxySafeUtils';

// The singleton asserts the server-side Safe key at boot, so this barrel is server-only. Client
// code that needs the chain coverage table imports `./safeTxServiceNetworks` directly.
export const proxySafeUtils = new ProxySafeUtils();
export type { ISafeRequestParams } from './proxySafeUtils';
Loading
Loading