Skip to content

Commit 18cc52f

Browse files
committed
Release v1.13.1
Origin-SHA: 9fd7796bd08a9e488d277f604a0e2c541b9d24f1
1 parent 300c9ea commit 18cc52f

12 files changed

Lines changed: 128 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @opensea/cli
22

3+
## 1.13.1
4+
5+
### Patch Changes
6+
7+
- e325f04: Keep the requested OAuth scopes in the auth store so `whoami` can report broader grants, and fix `auth link-wallet` to preserve wallet-adapter method binding and accept `OPENSEA_PRIVATE_KEY`.
8+
39
## 1.13.0
410

511
### Minor Changes

docs/cli-reference.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ opensea whoami
2020
```
2121

2222
`whoami` reads the current local auth token and shows the wallet address,
23-
scopes, their source, and expiry. Use `opensea whoami --diagnostic` to inspect
24-
decoded JWT claims and scope differences. Those claims are unverified,
25-
provider-specific diagnostics only and never authorization data.
23+
requested and granted scopes, any broader-scope warning, the scope source, and
24+
expiry. Use `opensea whoami --diagnostic` to inspect decoded JWT claims and
25+
scope differences. Those claims are unverified, provider-specific diagnostics
26+
only and never authorization data.
2627

2728
## Collections
2829

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opensea/cli",
3-
"version": "1.13.0",
3+
"version": "1.13.1",
44
"type": "module",
55
"description": "OpenSea CLI - Query the OpenSea API from the command line or programmatically",
66
"main": "dist/index.js",

src/auth/store.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface StoredToken {
1818
refreshToken: string
1919
scopedTokenId?: string
2020
expiresAt: string
21+
requestedScopes: string[]
2122
scopes: string[]
2223
scopeSource?:
2324
| "authorization_server"
@@ -42,6 +43,7 @@ const storedTokenSchema = z
4243
refreshToken: z.string().min(1),
4344
scopedTokenId: z.string().min(1).optional(),
4445
expiresAt: z.iso.datetime(),
46+
requestedScopes: z.array(z.string().min(1)),
4547
scopes: z.array(z.string().min(1)),
4648
scopeSource: z
4749
.enum([

src/commands/auth.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ export function authCommand(
260260
refreshToken: scopedToken.token,
261261
scopedTokenId: scopedToken.id,
262262
expiresAt: expiresAt.toISOString(),
263+
requestedScopes: scopes,
263264
scopes: grantedScopes,
264265
scopeSource,
265266
address,
@@ -340,8 +341,14 @@ export function authCommand(
340341
process.exit(1)
341342
}
342343

343-
const adapter = createWalletFromEnv()
344-
const signMessage = adapter.signMessage
344+
const privateKey = process.env.OPENSEA_PRIVATE_KEY
345+
const adapter = privateKey
346+
? new PrivateKeyAdapter({
347+
privateKey,
348+
rpcUrl: process.env.OPENSEA_RPC_URL ?? "https://eth.merkle.io",
349+
})
350+
: createWalletFromEnv()
351+
const signMessage = adapter.signMessage?.bind(adapter)
345352
if (!signMessage) {
346353
console.error("Wallet adapter does not support message signing")
347354
process.exit(1)
@@ -448,6 +455,7 @@ export function authCommand(
448455
accessToken: refreshed.accessToken,
449456
refreshToken: refreshed.refreshToken,
450457
expiresAt: refreshed.expiresAt.toISOString(),
458+
requestedScopes: token.requestedScopes,
451459
scopes: refreshed.scopes,
452460
scopeSource: refreshed.scopeSource,
453461
address: token.address,

src/commands/login.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export function loginCommand(
110110
accessToken: token.accessToken,
111111
refreshToken: token.refreshToken,
112112
expiresAt: token.expiresAt.toISOString(),
113+
requestedScopes: scopes,
113114
scopes: token.scopes,
114115
...(token.scopeSource ? { scopeSource: token.scopeSource } : {}),
115116
address,

src/commands/whoami.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export function whoamiCommand(getFormat: () => OutputFormat): Command {
7474
}
7575

7676
const jwtScopes = jwt?.opensea_scopes ?? []
77+
const broaderScopes = difference(token.scopes, token.requestedScopes)
7778
const diagnostic = options.diagnostic
7879
? {
7980
unverified: true,
@@ -92,7 +93,17 @@ export function whoamiCommand(getFormat: () => OutputFormat): Command {
9293
address: token.address,
9394
auth_method: token.authMethod,
9495
scopes: token.scopes,
96+
requested_scopes: token.requestedScopes,
97+
granted_scopes: token.scopes,
9598
scope_source: token.scopeSource ?? "unknown",
99+
...(broaderScopes.length > 0
100+
? {
101+
scope_warning: {
102+
type: "broader_than_requested",
103+
scopes: broaderScopes,
104+
},
105+
}
106+
: {}),
96107
...(diagnostic ? { diagnostic } : {}),
97108
expires_at: token.expiresAt,
98109
expired,

test/auth/store.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const baseToken = {
2626
accessToken: "access",
2727
refreshToken: "refresh",
2828
expiresAt: "2030-01-01T00:00:00.000Z",
29+
requestedScopes: ["read:eligibility"],
2930
scopes: ["read:eligibility"],
3031
address: "0xAbC",
3132
authMethod: "oauth" as const,
@@ -120,7 +121,7 @@ describe("auth store", () => {
120121
expect(loadCurrentToken()).toEqual(siweToken)
121122
})
122123

123-
test("rejects prerelease stores missing required token fields", () => {
124+
test("rejects prerelease stores missing requested scopes", () => {
124125
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
125126
mkdirSync(`${testHome}/.opensea`, { recursive: true })
126127
writeFileSync(
@@ -134,6 +135,7 @@ describe("auth store", () => {
134135
expiresAt: "2030-01-01T00:00:00.000Z",
135136
scopes: ["read:eligibility"],
136137
address: "0xabc",
138+
authMethod: "oauth",
137139
},
138140
},
139141
}),

test/commands/auth-login.test.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ const { getAddress, loadCurrentToken, removeToken, saveToken, signMessage } =
66
loadCurrentToken: vi.fn(),
77
removeToken: vi.fn(),
88
saveToken: vi.fn(),
9-
signMessage: vi.fn(async () => "0xsigned"),
9+
signMessage: vi.fn(function (this: { getAddress?: unknown }) {
10+
if (!this?.getAddress) throw new Error("wallet adapter binding lost")
11+
return Promise.resolve("0xsigned")
12+
}),
1013
}))
1114

1215
vi.mock("../../src/auth/store.js", () => ({
@@ -33,6 +36,41 @@ describe("auth login", () => {
3336
afterEach(() => {
3437
vi.clearAllMocks()
3538
vi.restoreAllMocks()
39+
delete process.env.OPENSEA_AUTH_TOKEN
40+
delete process.env.OPENSEA_PRIVATE_KEY
41+
})
42+
43+
it("links a wallet with OPENSEA_PRIVATE_KEY and preserves adapter binding", async () => {
44+
process.env.OPENSEA_AUTH_TOKEN = "wallet-jwt"
45+
process.env.OPENSEA_PRIVATE_KEY = "fixture-private-key"
46+
const fetchSpy = vi
47+
.spyOn(globalThis, "fetch")
48+
.mockResolvedValueOnce(
49+
new Response(JSON.stringify({ nonce: "abcd1234efgh5678" }), {
50+
status: 200,
51+
}),
52+
)
53+
.mockResolvedValueOnce(
54+
new Response(
55+
JSON.stringify({
56+
linkedWalletAddress: "0xfBa662e1a8e91a350702cF3b87D0C2d2Fb4BA57F",
57+
}),
58+
{ status: 200 },
59+
),
60+
)
61+
62+
await authCommand(
63+
() => undefined,
64+
createCommandTestContext().getFormat,
65+
).parseAsync(["link-wallet", "--api-key", "api-key"], {
66+
from: "user",
67+
})
68+
69+
expect(fetchSpy).toHaveBeenCalledTimes(2)
70+
expect(signMessage).toHaveBeenCalledOnce()
71+
expect(console.log).toHaveBeenCalledWith(
72+
expect.stringContaining('"status": "linked"'),
73+
)
3674
})
3775

3876
it("uses the current SIWE and scoped-token endpoints", async () => {
@@ -130,6 +168,7 @@ describe("auth login", () => {
130168
accessToken: "wallet-jwt",
131169
refreshToken: "pat-value",
132170
scopedTokenId: "381768924447939181",
171+
requestedScopes: ["write:wallets"],
133172
scopes: ["write:wallets"],
134173
authMethod: "siwe",
135174
}),
@@ -198,6 +237,7 @@ describe("auth login", () => {
198237
refreshToken: "pat-value",
199238
scopedTokenId: "381768924447939181",
200239
expiresAt: "2030-01-01T00:00:00.000Z",
240+
requestedScopes: ["write:wallets"],
201241
scopes: ["write:wallets"],
202242
address: "0xfba662e1a8e91a350702cf3b87d0c2d2fb4ba57f",
203243
authMethod: "siwe",

test/commands/auth-refresh.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const storedToken = {
2929
accessToken: "old-access",
3030
refreshToken: "old-refresh",
3131
expiresAt: "2030-01-01T00:00:00.000Z",
32+
requestedScopes: ["read:eligibility"],
3233
scopes: ["read:eligibility"],
3334
address: "0xabc",
3435
authMethod: "oauth" as const,
@@ -73,6 +74,7 @@ describe("auth refresh", () => {
7374
accessToken: "new-access",
7475
refreshToken: "new-refresh",
7576
expiresAt: "2031-01-01T00:00:00.000Z",
77+
requestedScopes: ["read:eligibility"],
7678
scopes: ["read:eligibility", "write:orders"],
7779
address: "0xabc",
7880
authMethod: "oauth",

0 commit comments

Comments
 (0)