Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ const walletGetUserInfo: RpcRequestInput = {
params: [],
}

const walletGetContext: RpcRequestInput = {
method: 'wallet_getContext',
params: [],
}

const walletSendCalls: RpcRequestInput = {
method: 'wallet_sendCalls',
params: [
Expand Down Expand Up @@ -56,4 +61,5 @@ export const walletTxMethods = [
walletShowCallsStatus,
walletSendCalls,
walletGetUserInfo,
walletGetContext,
]
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "app-sdk",
"version": "1.3.1",
"version": "1.4.1",
"repository": "https://github.com/base/account-sdk",
"author": "Base",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/app-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@startale/app-sdk",
"version": "1.3.1",
"version": "1.4.1",
"description": "Superapp SDK",
"keywords": [
"startale",
Expand Down
5 changes: 5 additions & 0 deletions packages/app-sdk/src/core/rpc/wallet_connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,9 @@ export type WalletConnectResponse = {
authType: string
userId: string
}
context?: {
chain: string
user: { username: string }
startale: { starPoints: number; eoaWallets: string[] }
}
Comment thread
bobo-k2 marked this conversation as resolved.
}
256 changes: 256 additions & 0 deletions packages/app-sdk/src/sign/app-sdk/Signer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ describe('Signer', () => {
},
subAccountConfig: undefined,
userInfo: {},
context: {},
}))
})

Expand Down Expand Up @@ -1645,6 +1646,7 @@ describe('Signer', () => {
version: '1.0.0',
},
userInfo: {},
context: {},
}))

signer['accounts'] = [globalAccountAddress]
Expand Down Expand Up @@ -1858,6 +1860,7 @@ describe('Signer', () => {
name: 'Test User',
authType: 'oauth',
},
context: {},
}))

signer['accounts'] = [globalAccountAddress]
Expand Down Expand Up @@ -2160,6 +2163,258 @@ describe('Signer', () => {
name: 'Test User',
authType: 'oauth',
},
context: {},
}))
})
})

describe('wallet_getContext', () => {
let stateSpy: MockInstance

const mockContext = {
chain: 'soneium',
user: { username: 'tester' },
startale: {
starPoints: 100,
eoaWallets: ['0xabc'],
},
}

beforeEach(() => {
stateSpy = vi.spyOn(store, 'getState').mockImplementation(() => ({
account: {
accounts: [globalAccountAddress],
},
chains: [],
keys: {},
spendPermissions: [],
config: {
metadata: mockMetadata,
preference: { walletUrl: CB_KEYS_URL, options: 'all' },
version: '1.0.0',
},
userInfo: {},
context: mockContext,
}))

signer['accounts'] = [globalAccountAddress]
})

afterEach(() => {
stateSpy.mockRestore()
})

it('should return context when available', async () => {
const request = {
method: 'wallet_getContext',
params: [],
}

const result = await signer.request(request)

expect(result).toEqual(mockContext)
})

it('should return partial context when some fields are missing', async () => {
stateSpy.mockImplementation(() => ({
account: {
accounts: [globalAccountAddress],
},
chains: [],
keys: {},
spendPermissions: [],
config: {
metadata: mockMetadata,
preference: { walletUrl: CB_KEYS_URL, options: 'all' },
version: '1.0.0',
},
userInfo: {},
context: {
chain: 'soneium',
// user and startale are missing
},
}))

const request = {
method: 'wallet_getContext',
params: [],
}

const result = await signer.request(request)

expect(result).toEqual({ chain: 'soneium' })
})

it('should throw unauthorized error when context is undefined', async () => {
stateSpy.mockImplementation(() => ({
account: {
accounts: [globalAccountAddress],
},
chains: [],
keys: {},
spendPermissions: [],
config: {
metadata: mockMetadata,
preference: { walletUrl: CB_KEYS_URL, options: 'all' },
version: '1.0.0',
},
userInfo: {},
context: undefined,
}))

const request = {
method: 'wallet_getContext',
params: [],
}

await expect(signer.request(request)).rejects.toThrow(
standardErrors.provider.unauthorized('No context found'),
)
})

it('should throw unauthorized error when context is null', async () => {
stateSpy.mockImplementation(() => ({
account: {
accounts: [globalAccountAddress],
},
chains: [],
keys: {},
spendPermissions: [],
config: {
metadata: mockMetadata,
preference: { walletUrl: CB_KEYS_URL, options: 'all' },
version: '1.0.0',
},
userInfo: {},
context: null,
}))

const request = {
method: 'wallet_getContext',
params: [],
}

await expect(signer.request(request)).rejects.toThrow(
standardErrors.provider.unauthorized('No context found'),
)
})

it('should return empty object when context is empty object', async () => {
stateSpy.mockImplementation(() => ({
account: {
accounts: [globalAccountAddress],
},
chains: [],
keys: {},
spendPermissions: [],
config: {
metadata: mockMetadata,
preference: { walletUrl: CB_KEYS_URL, options: 'all' },
version: '1.0.0',
},
userInfo: {},
context: {},
}))

const request = {
method: 'wallet_getContext',
params: [],
}

// An empty object {} is truthy in JavaScript, so it will be returned
const result = await signer.request(request)
expect(result).toEqual({})
})

it('should not make any network requests', async () => {
const request = {
method: 'wallet_getContext',
params: [],
}

await signer.request(request)

expect(
mockCommunicator.postRequestAndWaitForResponse,
).not.toHaveBeenCalled()
expect(fetchRPCRequest).not.toHaveBeenCalled()
})

it('should handle context set from wallet_connect response', async () => {
// Remove the stateSpy to allow real store updates
stateSpy.mockRestore()

// First, clean up and simulate wallet_connect setting context
await signer.cleanup()

;(decryptContent as Mock).mockResolvedValueOnce({
result: {
value: null,
},
})

await signer.handshake({ method: 'handshake' })

// Mock wallet_connect response with context
;(decryptContent as Mock).mockResolvedValueOnce({
result: {
value: {
accounts: [
{
address: globalAccountAddress,
capabilities: {},
},
],
context: {
chain: 'soneium',
user: { username: 'connected-user' },
startale: {
starPoints: 500,
eoaWallets: ['0xdef'],
},
},
},
},
})

// Simulate wallet_connect
await signer.request({
method: 'wallet_connect',
params: [],
})

// Now test wallet_getContext
const contextRequest = {
method: 'wallet_getContext',
params: [],
}

const result = await signer.request(contextRequest)

expect(result).toEqual({
chain: 'soneium',
user: { username: 'connected-user' },
startale: {
starPoints: 500,
eoaWallets: ['0xdef'],
},
})

// Restore the mock for other tests
stateSpy = vi.spyOn(store, 'getState').mockImplementation(() => ({
account: {
accounts: [globalAccountAddress],
},
chains: [],
keys: {},
spendPermissions: [],
config: {
metadata: mockMetadata,
preference: { walletUrl: CB_KEYS_URL, options: 'all' },
version: '1.0.0',
},
userInfo: {},
context: mockContext,
}))
})
})
Expand Down Expand Up @@ -2191,6 +2446,7 @@ describe('Signer', () => {
version: '1.0.0',
},
userInfo: {},
context: {},
}))

;(fetchRPCRequest as Mock).mockResolvedValue({
Expand Down
14 changes: 14 additions & 0 deletions packages/app-sdk/src/sign/app-sdk/Signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ export class Signer {
return this.handleGetCapabilitiesRequest(request)
case 'wallet_getUserInfo':
return this.handleGetUserInfoRequest(request)
case 'wallet_getContext':
return this.handleGetContextRequest(request)
Comment thread
bobo-k2 marked this conversation as resolved.
case 'wallet_switchEthereumChain':
return this.handleSwitchChainRequest(request)
case 'eth_ecRecover':
Expand Down Expand Up @@ -407,6 +409,9 @@ export class Signer {
const userInfo = response.userInfo
store.userInfo.set(userInfo)

const context = response.context
store.context.set(context)

const account = response.accounts.at(0)
const capabilities = account?.capabilities

Expand Down Expand Up @@ -554,6 +559,15 @@ export class Signer {
return userInfo
}

private async handleGetContextRequest(_request: RequestArguments) {
const context = store.getState().context
if (!context) {
throw standardErrors.provider.unauthorized('No context found')
}

return context
}

private async sendEncryptedRequest(
request: RequestArguments,
): Promise<RPCResponseMessage> {
Expand Down
Loading