Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
a160dcc
feat(db): add Safe wallet schema tables
joelorzet Apr 21, 2026
8fd465a
feat(safe): add canonical contract address registry
joelorzet Apr 21, 2026
076b132
feat: deploy Safe smart accounts per org per chain
joelorzet Apr 21, 2026
b891719
feat: add Allowance Module install and per-token spending limits
joelorzet Apr 21, 2026
1de48a3
fix: remove unsupported metric labels from Safe error logs
joelorzet Apr 21, 2026
36e87a5
feat: expand Safe support to all KeeperHub chains plus testnets
joelorzet Apr 21, 2026
5cfb3bd
fix: render Optimism label for chain 10 in Safe deploy card
joelorzet Apr 21, 2026
454719b
feat: add Optimism mainnet and Sepolia as supported chains
joelorzet Apr 21, 2026
6d55f8b
fix: drop nonce from Prometheus label context in transaction-manager
joelorzet Apr 21, 2026
054c9a8
feat: fix Safe spending-limit decimals + add custom token support
joelorzet Apr 21, 2026
1a57e12
Merge branch 'staging' into feat/keep-177-safe-wallet-integration
joelorzet Apr 21, 2026
cdc5bb8
feat: route workflow writes through Safe when signing is active
joelorzet Apr 21, 2026
6925948
feat: route transfer-funds, transfer-token, approve-token through Safe
joelorzet Apr 21, 2026
d5701db
feat: Safe signing toggle UI + external links + polished layout
joelorzet Apr 21, 2026
f541231
style: render Safe deployed badge in keeperhub-green
joelorzet Apr 21, 2026
359051e
polish: dedupe Safe spending-limit token picker and anchor dropdowns
joelorzet Apr 21, 2026
714145c
fix: mock Safe helpers in write-contract-core test
joelorzet Apr 21, 2026
af7b5b9
chore: gitignore generated workflow manifest under public/.well-known
joelorzet Apr 24, 2026
64ee5cd
chore: add defi-kit and zodiac-roles deps with release-age exclusions
joelorzet Apr 24, 2026
702ea0e
refactor: consolidate Safe branch migrations into 0052_military_photon
joelorzet Apr 24, 2026
6b7bc94
feat: replace Safe Allowance Module with Zodiac Roles Modifier
joelorzet Apr 24, 2026
f066528
feat: unified per-protocol policy wizard with token picker and enforc…
joelorzet Apr 24, 2026
5ecb4a5
feat: per-(protocol, token) Zodiac role allowances
joelorzet Apr 27, 2026
1a42390
feat(safe): persist tokens across collapse, cap dialog height, drop c…
joelorzet Apr 27, 2026
9d44c4d
chore(safe): drop branch-only Safe migrations to make room for stagin…
joelorzet Apr 29, 2026
d1039bb
Merge remote-tracking branch 'origin/staging' into feat/keep-177-safe…
joelorzet Apr 29, 2026
c2017ba
chore(safe): consolidated Safe schema migration + post-merge fixups
joelorzet Apr 29, 2026
a7ea8bd
feat(safe): direct rules persist on install + Safe card moves to its …
joelorzet Apr 29, 2026
e99f142
feat(safe): atomic install writes, on-chain reconcile, scoped rules
joelorzet May 4, 2026
be33b2a
feat(safe): role reconcile + update routes and diff-aware simulate
joelorzet May 4, 2026
e80d665
refactor(safe): split policy protocol card and add explorer link prim…
joelorzet May 4, 2026
9f53919
feat(safe): edit-policies flow, sync from chain, and bookmarks autoco…
joelorzet May 4, 2026
a22d78c
feat(safe): persist direct rules and carry them through API + simulate
joelorzet May 5, 2026
eae322f
refactor(safe): split role permissions card into focused components
joelorzet May 5, 2026
4f11c48
feat(safe): clearer wizard copy, token logos, explorer-linked addresses
joelorzet May 5, 2026
6799eb6
fix(plugins/web3): read token balance from Safe in role-routed transfers
joelorzet May 5, 2026
74c5666
chore(safe): drop branch-only Safe migrations to make room for one sq…
joelorzet May 6, 2026
51992bc
fix(safe): per-rule allowance buckets, drop conflicting unique index,…
joelorzet May 6, 2026
39fd6d9
Merge branch 'staging' into feat/keep-177-safe-wallet-integration
joelorzet May 6, 2026
c9f107a
fix(safe): trim 0069 to safe-only DDL and drop dead triggerType field
joelorzet May 6, 2026
d0e2948
chore(safe): regenerate 0069 via drizzle-kit with synthetic 0068 base…
joelorzet May 6, 2026
2b73eb4
fix(safe): restore safe-table definitions and re-exports lost during …
joelorzet May 6, 2026
7c9bf24
feat(safe): per-rule edit dialog for direct rules
joelorzet May 6, 2026
4e1f5e5
fix(safe): make wizard chevron useful, drop misleading cursor-help
joelorzet May 6, 2026
1ef51f7
fix(safe): gate Zodiac Roles install to chains where the singleton is…
joelorzet May 6, 2026
6575708
feat(safe): surface structured rejection kind on web3 step results
joelorzet May 6, 2026
041b472
feat(safe): per-period value cap for native-transfer direct rules
joelorzet May 6, 2026
e4750a8
fix(safe): defer defi-kit imports to break Next.js page-data overflow
joelorzet May 7, 2026
ec635a8
refactor(safe): wallet overlay account list, per-policy edit, deploy …
joelorzet May 7, 2026
ea62765
refactor(safe): wallet overlay polish (badge alignment, info tooltips…
joelorzet May 7, 2026
66e9939
ref: Add info tooltip to multi-chain support label
joelorzet May 7, 2026
0dcebbc
refactor(safe): semantic Signer/Sender controls on wallet account rows
joelorzet May 11, 2026
2e206be
refactor(ui): thin-scrollbar utility for overlay + Safe scroll contai…
joelorzet May 11, 2026
cde2041
refactor(safe): align role-permissions cards with their section header
joelorzet May 11, 2026
b878d3c
feat(safe): per-Safe balance reads and Safe-routed withdrawals
joelorzet May 11, 2026
11c07e7
feat(safe): per-Safe custom token tracking
joelorzet May 11, 2026
9301de5
Merge branch 'staging' into feat/keep-177-safe-wallet-integration
joelorzet May 11, 2026
0946cdb
chore(db): regenerate Safe migration as single 0071 on top of staging
joelorzet May 11, 2026
a3a5c83
chore: regenerate lockfile after staging merge
joelorzet May 11, 2026
f50d50c
feat(metrics): KEEP-301 Safe wallet Prometheus instrumentation
joelorzet May 11, 2026
6e163fc
Merge branch 'staging' into feat/keep-177-safe-wallet-integration
joelorzet May 12, 2026
3c2f482
chore(db): renumber Safe migration to 0072 after staging merge
joelorzet May 12, 2026
817756a
feat: KEEP-546 add submitSignedTransactionWithFailover helper
suisuss May 13, 2026
46da380
feat: KEEP-546 reconcile broadcast errors via on-chain lookup
suisuss May 13, 2026
21c5022
Merge pull request #1230 from KeeperHub/feat/KEEP-546-rpc-failover-br…
suisuss May 13, 2026
f55e7c7
fix: KEEP-177 route Safe role-modifier chain probe through RPC failover
suisuss May 13, 2026
b8bb227
fix: KEEP-177 pass RpcProviderManager to gas strategy in Safe writes
suisuss May 13, 2026
d9a9f30
fix: KEEP-177 route Safe tx receipt polling through RPC failover
suisuss May 13, 2026
8959ab9
style: KEEP-177 invert chain-probe URL ternary to satisfy noNegationElse
suisuss May 13, 2026
102c2e0
chore: KEEP-551 widen safe-tx metrics with nonce-conflict outcome
suisuss May 13, 2026
5fc6ad5
refactor: KEEP-551 route EOA write paths through sign-once failover h…
suisuss May 13, 2026
65aace5
refactor: KEEP-551 route Safe write paths through sign-once failover …
suisuss May 13, 2026
6c98527
fix: KEEP-551 construct RpcProviderManager in withdraw route
suisuss May 13, 2026
02a214c
test: KEEP-551 rewrite write-failover tests against helper-based impl
suisuss May 13, 2026
5c12f09
fix: KEEP-551 broaden NonceConflictError matcher to handle wrapped er…
suisuss May 13, 2026
45b9af6
test: KEEP-551 add anvil integration test for sign-once failover + re…
suisuss May 13, 2026
36f11aa
Merge pull request #1231 from KeeperHub/feat/KEEP-551-tx-failover-mig…
suisuss May 13, 2026
473e7c7
test: KEEP-552 add unit tests for orchestrator data pipeline
suisuss May 13, 2026
86c0462
test: KEEP-552 anvil-fork integration for orchestrator reconcile no-m…
suisuss May 13, 2026
3023e73
test: KEEP-552 anvil-fork integration for orchestrator install pipeline
suisuss May 13, 2026
bb3d95a
ci: KEEP-552 bring up test-anvil-fork in e2e-vitest workflow
suisuss May 13, 2026
f422960
Merge pull request #1232 from KeeperHub/feat/KEEP-552-orchestrator-un…
suisuss May 13, 2026
42ab6ac
Merge remote-tracking branch 'origin/staging' into feat/KEEP-177-safe…
suisuss May 13, 2026
ec07954
fix(safe): KEEP-177 PR #923 review fixes
joelorzet May 13, 2026
f94c0e2
refactor(safe): tooltip copy for policy + sender controls
joelorzet May 13, 2026
0b7bb3c
docs(safe): public Safe wallet docs + metrics reference + plugin conv…
joelorzet May 13, 2026
da5c35e
Merge origin/staging into feat/KEEP-177-safe-wallet-integration
suisuss May 14, 2026
d32895f
Merge remote-tracking branch 'origin/staging' into feat/keep-177-safe…
joelorzet May 15, 2026
662cd28
chore(db): KEEP-177 regenerate safe migration as 0075 atop staging
joelorzet May 15, 2026
1ae89c5
fix(safe): KEEP-177 dedupe native direct-rule rows via sentinel + NOT…
joelorzet May 15, 2026
de278f7
fix(safe): KEEP-177 harden role API against destructive defaults
joelorzet May 15, 2026
7c6e321
fix(safe): KEEP-177 send protocolSlug when revoking a role allowance
joelorzet May 15, 2026
365a699
fix(safe): KEEP-177 route Safe write paths through RPC failover
joelorzet May 18, 2026
21bb57b
fix(safe): KEEP-177 surface malformed-input drops on role routes
joelorzet May 18, 2026
2de21a9
Merge remote-tracking branch 'origin/staging' into feat/keep-177-safe…
joelorzet May 18, 2026
acd9e53
chore(db): KEEP-177 scope drizzle push to public, declare workflow.wa…
joelorzet May 18, 2026
fd3faef
refactor(safe): KEEP-177 share failover RPC manager helper
joelorzet May 18, 2026
db7aee7
test(safe): KEEP-177 regression coverage for sentinel <-> null wire f…
joelorzet May 18, 2026
0dbaf7f
fix(safe): KEEP-177 tighten Safe admin gating + owner-only Revoke
joelorzet May 18, 2026
6e222c5
feat(web3): KEEP-177 classify Safe GS-codes + Error/Panic in revert d…
joelorzet May 18, 2026
769ddd4
Merge remote-tracking branch 'origin/staging' into feat/keep-177-safe…
joelorzet May 18, 2026
c344b7a
docs(safe): KEEP-566 explain why Safe role-modifier probe uses static…
joelorzet May 19, 2026
4fdd88a
feat(metrics): KEEP-568 add signer-mode distribution counter
joelorzet May 19, 2026
268939a
fix(safe): KEEP-548 route withdraw through per-user RPC + failover
joelorzet May 19, 2026
fd20252
fix(web3): KEEP-565 broadcast EOA writes through submitSignedTransact…
joelorzet May 19, 2026
610aa69
feat(metrics): KEEP-567 emit counter on signer-resolver probe-swallow
joelorzet May 19, 2026
b9a1eb0
feat(safe): group network picker by mainnet / testnet with category l…
joelorzet May 19, 2026
60551c7
fix(safe): show capitalized protocol labels in policy review + edit d…
joelorzet May 19, 2026
3de7a20
feat(safe): adopt-on-collision + reconcile-all endpoint + kind-error …
joelorzet May 19, 2026
9525f6c
feat(safe): add "Sync from chain" button to wallet overlay
joelorzet May 19, 2026
f0bb7a6
feat: KEEP-177 auto-record EOA and Safe wallets in address book
suisuss May 20, 2026
f8bf8c1
Merge origin/staging into feat/KEEP-177-safe-wallet-integration
suisuss May 20, 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
21 changes: 21 additions & 0 deletions .github/workflows/e2e-tests-ephemeral.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,27 @@ jobs:
chain_rpc_config: ${{ secrets.CHAIN_RPC_CONFIG }}
integration_encryption_key: ${{ secrets.TEST_INTEGRATION_ENCRYPTION_KEY }}

- name: Start anvil Sepolia fork for orchestrator integration tests
run: |
docker compose --profile test up -d --wait test-anvil-fork || \
docker compose --profile test up -d test-anvil-fork
# Health-poll the fork up to ~60s -- the wait flag above is best-effort
# because the foundry image's healthcheck depends on cast resolving
# the public Sepolia RPC over IPv4.
retries=0
until curl -sf -X POST http://localhost:8547 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_chainId","id":1}' | grep -q '"0xaa36a7"'; do
retries=$((retries + 1))
if [ "$retries" -ge 30 ]; then
echo "anvil Sepolia fork did not become ready in 60s"
docker logs keeperhub-test-anvil-fork || true
exit 1
fi
sleep 2
done
echo "anvil Sepolia fork ready on localhost:8547"

- name: Run E2E Vitest tests
run: pnpm test:e2e:vitest
env:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ lib/types/integration.ts
lib/workflow/codegen/registry.ts
lib/step-registry.ts
lib/output-display-configs.ts
public/.well-known/
lib/credential-map.ts
public/monaco/

Expand Down
22 changes: 22 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Reject packages published less than 3 days ago to mitigate supply chain attacks
minimum-release-age=3d
# Zodiac Roles SDK + deployments registry are maintained by Gnosis Guild and
# pinned here; skip the release-age gate for this dep (same reason we accept
# their audited contracts on-chain).
minimum-release-age-exclude[]=zodiac-roles-sdk
minimum-release-age-exclude[]=zodiac-roles-deployments
# defi-kit (karpatkey's audited Zodiac Roles preset library) and its
# Gnosis-Guild + CowProtocol SDK transitive deps. Same reason we accept the
# upstream Zodiac packages: maintained by audited DeFi infrastructure teams.
minimum-release-age-exclude[]=defi-kit
minimum-release-age-exclude[]=@gnosis-guild/eth-sdk
minimum-release-age-exclude[]=@gnosis-guild/eth-sdk-client
minimum-release-age-exclude[]=@gnosis-guild/zodiac
minimum-release-age-exclude[]=@cowprotocol/sdk-config
minimum-release-age-exclude[]=@cowprotocol/sdk-common
minimum-release-age-exclude[]=@cowprotocol/sdk-bridging
minimum-release-age-exclude[]=@cowprotocol/sdk-trading
minimum-release-age-exclude[]=@cowprotocol/sdk-order-book
minimum-release-age-exclude[]=@cowprotocol/sdk-order-signing
minimum-release-age-exclude[]=@cowprotocol/sdk-cow-shed
minimum-release-age-exclude[]=@cowprotocol/sdk-trading-sdk
minimum-release-age-exclude[]=@cowprotocol/cow-sdk
minimum-release-age-exclude[]=@cowprotocol/contracts

# Skip the pre-script lockfile freshness check. Recent pnpm versions
# re-run `pnpm install` at the start of every script (`pnpm dev`,
Expand Down
106 changes: 106 additions & 0 deletions app/api/user/safe/[safeId]/role/allowances/[tokenAddress]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { ethers } from "ethers";
import { NextResponse } from "next/server";
import { apiError } from "@/lib/api-error";
import { ErrorCategory, logSystemError } from "@/lib/logging";
import { getSafeForOrg, validateSafeOwner } from "@/lib/safe/auth";
import { PROTOCOL_CATALOG } from "@/lib/safe/protocol-registry";
import {
DIRECT_RULE_PROTOCOL_SLUG,
revokeRoleTokenAllowance,
} from "@/lib/safe/roles-orchestrator";

// Slugs that may key an allowance bucket: any known protocol plus the
// synthetic "direct" slug used by per-rule (transfer/approve) caps.
const ALLOWED_ALLOWANCE_SLUGS: ReadonlySet<string> = new Set<string>([
...Object.keys(PROTOCOL_CATALOG),
DIRECT_RULE_PROTOCOL_SLUG,
]);

type RouteParams = {
params: Promise<{ safeId: string; tokenAddress: string }>;
};

export async function DELETE(
request: Request,
{ params }: RouteParams
): Promise<NextResponse> {
try {
// Authenticate first: don't leak route-shape (specific 400 messages
// about token-address validity or required protocolSlug) to
// unauthenticated probes. Review #923-r3 LOW.
//
// Owner-only by design: revoking a single on-chain allowance bucket
// is destructive. Admins can view + edit but only the org owner can
// revoke. The UI pairs the 403 with a tooltip on the disabled control.
const owner = await validateSafeOwner(request);
if ("error" in owner) {
return NextResponse.json(
{ error: owner.error },
{ status: owner.status }
);
}

const { safeId, tokenAddress } = await params;
if (!ethers.isAddress(tokenAddress)) {
return NextResponse.json(
{ error: `Invalid token address: ${tokenAddress}` },
{ status: 400 }
);
}

const url = new URL(request.url);
const protocolSlug = url.searchParams.get("protocolSlug");
if (!protocolSlug) {
return NextResponse.json(
{ error: "protocolSlug query parameter is required" },
{ status: 400 }
);
}
if (!ALLOWED_ALLOWANCE_SLUGS.has(protocolSlug)) {
return NextResponse.json(
{ error: `Unknown protocolSlug: ${protocolSlug}` },
{ status: 400 }
);
}

const safe = await getSafeForOrg({
safeId,
organizationId: owner.organizationId,
});
if (!safe) {
return NextResponse.json({ error: "Safe not found" }, { status: 404 });
}

const result = await revokeRoleTokenAllowance({
organizationId: owner.organizationId,
chainId: safe.chainId,
protocolSlug,
tokenAddress,
});

if (!result.success) {
logSystemError(
ErrorCategory.TRANSACTION,
`[Safe] Revoke role allowance failed safe=${safe.id} token=${tokenAddress}`,
new Error(result.error),
{
endpoint: "/api/user/safe/[safeId]/role/allowances/[tokenAddress]",
component: "safe-role-allowances-api",
chain_id: safe.chainId.toString(),
}
);
return NextResponse.json({ error: result.error }, { status: 500 });
}

return NextResponse.json({
success: true,
deleted: {
id: result.deleted.id,
tokenAddress: result.deleted.tokenAddress,
tokenSymbol: result.deleted.tokenSymbol,
},
});
} catch (error) {
return apiError(error, "Failed to revoke Safe role allowance");
}
}
192 changes: 192 additions & 0 deletions app/api/user/safe/[safeId]/role/allowances/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { ethers } from "ethers";
import { NextResponse } from "next/server";
import { apiError } from "@/lib/api-error";
import { ErrorCategory, logSystemError } from "@/lib/logging";
import { getSafeForOrg, validateSafeAdmin } from "@/lib/safe/auth";
import { PROTOCOL_CATALOG } from "@/lib/safe/protocol-registry";
import {
DIRECT_RULE_PROTOCOL_SLUG,
getSafeRole,
listRoleAllowances,
setRoleTokenAllowance,
} from "@/lib/safe/roles-orchestrator";

// Slugs that may key an allowance bucket: any known protocol plus the
// synthetic "direct" slug used by per-rule (transfer/approve) caps.
const ALLOWED_ALLOWANCE_SLUGS: ReadonlySet<string> = new Set<string>([
...Object.keys(PROTOCOL_CATALOG),
DIRECT_RULE_PROTOCOL_SLUG,
]);

type RouteParams = { params: Promise<{ safeId: string }> };

type SetAllowanceBody = {
protocolSlug?: string;
tokenAddress?: string;
maxRefillWei?: string;
refillWei?: string;
periodSeconds?: number;
tokenSymbol?: string;
tokenDecimals?: number;
};

export async function GET(
request: Request,
{ params }: RouteParams
): Promise<NextResponse> {
try {
const admin = await validateSafeAdmin(request);
if ("error" in admin) {
return NextResponse.json(
{ error: admin.error },
{ status: admin.status }
);
}

const { safeId } = await params;
const safe = await getSafeForOrg({
safeId,
organizationId: admin.organizationId,
});
if (!safe) {
return NextResponse.json({ error: "Safe not found" }, { status: 404 });
}

const role = await getSafeRole(safe.id);
if (!role) {
return NextResponse.json({ allowances: [] });
}

const rows = await listRoleAllowances(role.id);
return NextResponse.json({
allowances: rows.map((row) => ({
id: row.id,
allowanceKey: row.allowanceKey,
tokenAddress: row.tokenAddress,
tokenSymbol: row.tokenSymbol,
tokenDecimals: row.tokenDecimals,
maxRefillWei: row.maxRefillWei,
refillWei: row.refillWei,
periodSeconds: row.periodSeconds,
lastChainBalanceWei: row.lastChainBalanceWei,
lastChainTimestamp: row.lastChainTimestamp,
lastReconciledAt: row.lastReconciledAt,
lastUpdatedAt: row.lastUpdatedAt,
})),
});
} catch (error) {
return apiError(error, "Failed to list Safe role allowances");
}
}

export async function POST(
request: Request,
{ params }: RouteParams
): Promise<NextResponse> {
try {
const admin = await validateSafeAdmin(request);
if ("error" in admin) {
return NextResponse.json(
{ error: admin.error },
{ status: admin.status }
);
}

const { safeId } = await params;
const safe = await getSafeForOrg({
safeId,
organizationId: admin.organizationId,
});
if (!safe) {
return NextResponse.json({ error: "Safe not found" }, { status: 404 });
}

const body = (await request.json()) as SetAllowanceBody;
if (!body.protocolSlug || typeof body.protocolSlug !== "string") {
return NextResponse.json(
{ error: "protocolSlug is required" },
{ status: 400 }
);
}
if (!ALLOWED_ALLOWANCE_SLUGS.has(body.protocolSlug)) {
return NextResponse.json(
{ error: `Unknown protocolSlug: ${body.protocolSlug}` },
{ status: 400 }
);
}
if (!(body.tokenAddress && ethers.isAddress(body.tokenAddress))) {
return NextResponse.json(
{ error: "tokenAddress is required and must be a valid address" },
{ status: 400 }
);
}
if (!body.maxRefillWei || typeof body.maxRefillWei !== "string") {
return NextResponse.json(
{ error: "maxRefillWei is required (stringified uint128)" },
{ status: 400 }
);
}
if (!body.refillWei || typeof body.refillWei !== "string") {
return NextResponse.json(
{ error: "refillWei is required (stringified uint128)" },
{ status: 400 }
);
}
if (
typeof body.periodSeconds !== "number" ||
!Number.isInteger(body.periodSeconds) ||
body.periodSeconds < 0
) {
return NextResponse.json(
{
error: "periodSeconds is required and must be a non-negative integer",
},
{ status: 400 }
);
}

const result = await setRoleTokenAllowance({
organizationId: admin.organizationId,
chainId: safe.chainId,
protocolSlug: body.protocolSlug,
tokenAddress: body.tokenAddress,
maxRefillWei: body.maxRefillWei,
refillWei: body.refillWei,
periodSeconds: body.periodSeconds,
tokenSymbol: body.tokenSymbol,
tokenDecimals: body.tokenDecimals,
});

if (!result.success) {
logSystemError(
ErrorCategory.TRANSACTION,
`[Safe] Set role allowance failed safe=${safe.id} token=${body.tokenAddress}`,
new Error(result.error),
{
endpoint: "/api/user/safe/[safeId]/role/allowances",
component: "safe-role-allowances-api",
chain_id: safe.chainId.toString(),
}
);
return NextResponse.json({ error: result.error }, { status: 500 });
}

return NextResponse.json({
success: true,
allowance: {
id: result.allowance.id,
allowanceKey: result.allowance.allowanceKey,
tokenAddress: result.allowance.tokenAddress,
tokenSymbol: result.allowance.tokenSymbol,
tokenDecimals: result.allowance.tokenDecimals,
maxRefillWei: result.allowance.maxRefillWei,
refillWei: result.allowance.refillWei,
periodSeconds: result.allowance.periodSeconds,
lastAppliedTxHash: result.allowance.lastAppliedTxHash,
lastUpdatedAt: result.allowance.lastUpdatedAt,
},
});
} catch (error) {
return apiError(error, "Failed to set Safe role allowance");
}
}
Loading
Loading