Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
1f3069c
add: first attempt of implemneting helios in Ambire
Oxbobby Jun 17, 2026
0df82b3
Merge branch 'v2' of github.com:AmbireTech/ambire-common into feature…
Oxbobby Jun 22, 2026
d5cbb8b
move the helios code to its own file
Oxbobby Jun 22, 2026
09c3cda
add: configure a helios RPC that doesn't fire 100 requests per second
Oxbobby Jun 22, 2026
309b16f
add: a verification controller that initiliazes helios and call helio…
Oxbobby Jun 22, 2026
efb9c63
add: configure a retry mechanism if the rpc is out of sync
Oxbobby Jun 22, 2026
d9e1991
remove the helios implementation, use colibri only and use it for bal…
Oxbobby Jun 22, 2026
eabf07f
Merge branch 'v2' of github.com:AmbireTech/ambire-common into feature…
Oxbobby Jun 23, 2026
948e502
add a domain as verified upon successfull verification
Oxbobby Jun 23, 2026
298ad1b
resolve conflicts from v2
Oxbobby Jun 24, 2026
3baf432
add: the option to enable colibri on gnosis; do not perform verificat…
Oxbobby Jun 24, 2026
ef0e8e1
add: colibri tests for verifying ENS & portfolio
Oxbobby Jun 24, 2026
dffa7f8
make sure the rpc and colibri are fetching and comparing the same blo…
Oxbobby Jun 24, 2026
2437e07
add: ENS expiry fetch for selected account and transfer validation
PetromirDev Jun 24, 2026
a71fb6f
Merge branch 'ens-reverse-lookup-privacy' into feature/ens-expiry-ux
PetromirDev Jun 25, 2026
610474f
add better error handling
sonytooo Jul 2, 2026
f0c1d15
WIP: Extract Portfolio verification into its own controller to reduc…
jordan-enev Jul 2, 2026
c4bbd95
fixes
sonytooo Jul 2, 2026
eeb88a3
refactor constants, helper functions and imports to reduce UI bundle …
PetromirDev Jul 3, 2026
1df1810
refactor: move code around so the UI doesn't load huge modules as a s…
PetromirDev Jul 3, 2026
cec6038
Merge branch 'v2' into feature/ledger-on-mobile
sonytooo Jul 6, 2026
9e4c34b
Merge remote-tracking branch 'origin/v2' into refactor/reduce-scripti…
PetromirDev Jul 7, 2026
c0b9191
resolve conflicts from v2
Oxbobby Jul 8, 2026
a702d6f
delete the verified status upon a new load
Oxbobby Jul 8, 2026
f1b7869
Merge pull request #2517 from AmbireTech/feature/colibri-separate-por…
Oxbobby Jul 8, 2026
8ff026c
pull latest
Oxbobby Jul 8, 2026
c3dbd23
fix: typehint errors
Oxbobby Jul 8, 2026
999a620
add: a catch up logic for colibri to go to the latest block
Oxbobby Jul 8, 2026
ab4e57e
Merge remote-tracking branch 'origin/v2' into refactor/reduce-scripti…
PetromirDev Jul 8, 2026
49b6d37
fix: signMessage lib test
PetromirDev Jul 8, 2026
81c4b40
Merge remote-tracking branch 'origin/v2' into feature/ens-expiry-ux
PetromirDev Jul 8, 2026
3aff320
fix: ens expiry for subnames and expired ens filtering
PetromirDev Jul 8, 2026
66dbd69
add: fee collector as referrer to ens renew link
PetromirDev Jul 8, 2026
2577a3e
update: ens subname expiry handling and expiry warn window to 60 days
PetromirDev Jul 8, 2026
4d6e974
add: manual dispatch trigger to tests
PetromirDev Jul 8, 2026
baea7b7
fix: tests failing from ens expiry refactoring
PetromirDev Jul 8, 2026
39922bd
Merge pull request #2507 from AmbireTech/feature/ens-expiry-ux
PetromirDev Jul 8, 2026
7f1ece0
resolve conflicts from v2
Oxbobby Jul 9, 2026
b22acbb
add: a script that deploys the balance getter and a const for the bal…
Oxbobby Jul 9, 2026
4d91094
add: a deploy script for the nftgetter and add the contract to deploy…
Oxbobby Jul 9, 2026
e85039a
add: introduce a Verifier mode in deployless - it is used during coli…
Oxbobby Jul 9, 2026
c414383
set prover_mode to remove as our RPC cannot generate proofs atm
Oxbobby Jul 9, 2026
6ec6d1d
use all available colibri provers for ethereum & gnosis for availability
Oxbobby Jul 9, 2026
e8e88c1
Merge branch 'v2' into feature/ledger-on-mobile
sonytooo Jul 9, 2026
0e1f939
fix: returns the exact registrar expiry + 90-day grace for .eth name
PetromirDev Jul 9, 2026
479dc13
Merge pull request #2531 from AmbireTech/fix/ens-domains-expiry-test
PetromirDev Jul 9, 2026
1340596
Merge branch 'v2' of github.com:AmbireTech/ambire-common into feature…
Oxbobby Jul 9, 2026
3557b78
rename DeploylessMode.Verifier to DeploylessMode.Predeployed
Oxbobby Jul 10, 2026
37d92a3
fix: deployless tests because of mismatching string
Oxbobby Jul 10, 2026
5688635
Merge pull request #2506 from AmbireTech/feature/colibri
Oxbobby Jul 10, 2026
84516f3
Merge branch 'v2' into feature/ledger-on-mobile
sonytooo Jul 10, 2026
a071c40
Merge pull request #2516 from AmbireTech/feature/ledger-on-mobile
sonytooo Jul 10, 2026
dce4f91
Merge branch 'release/v2.103' of github.com:AmbireTech/ambire-common …
superKalo Jul 10, 2026
6b34c34
fix: ens expiry test
PetromirDev Jul 13, 2026
c1b3234
Merge pull request #2537 from AmbireTech/fix/ens-domains-expiry-test
PetromirDev Jul 13, 2026
5421c7c
Merge remote-tracking branch 'origin/v2' into refactor/reduce-scripti…
PetromirDev Jul 13, 2026
f109dcf
fix: ts error in domains
PetromirDev Jul 13, 2026
f9ba1a9
fix: ts error
sonytooo Jul 13, 2026
405f69f
fix: domains ctrl test
PetromirDev Jul 13, 2026
b9ba592
Merge pull request #2538 from AmbireTech/fix/domains-test
PetromirDev Jul 13, 2026
e6b9433
Merge pull request #2520 from AmbireTech/refactor/reduce-scripting-time
PetromirDev Jul 13, 2026
348591f
Merge branch 'release/v2.103' of github.com:AmbireTech/ambire-common …
superKalo Jul 15, 2026
855302e
Merge branch 'main' of github.com:AmbireTech/ambire-common into v2
superKalo Jul 15, 2026
164e764
Changed: bump up the v
superKalo Jul 15, 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
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Execute Unit Tests

on:
workflow_dispatch:
push:
branches:
- v2
Expand Down
219 changes: 135 additions & 84 deletions contracts/compiled/EnsGetter.json

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions contracts/deployless/EnsGetter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ interface IUniversalResolver {
) external view returns (string memory resolvedName, address resolver, address reverseResolver);
}

interface IBaseRegistrar {
function nameExpires(uint256 id) external view returns (uint256);

function GRACE_PERIOD() external view returns (uint256);
}

interface INameWrapper {
function getData(uint256 id) external view returns (address owner, uint32 fuses, uint64 expiry);
}

contract EnsGetter {
struct ReverseLookupResult {
string resolvedName;
Expand All @@ -19,6 +29,15 @@ contract EnsGetter {
bool needsOffchainLookup;
}

struct ExpiryResult {
// Registration expiry, in seconds
uint256 expiry;
// Grace period, in seconds. 0 on the NameWrapper path (the wrapper expiry has no separate grace)
uint256 gracePeriod;
// block.timestamp of the eth_call, so the caller's updatedAt is consistent with the expiry snapshot
uint256 blockTimestamp;
}

// EIP-3668
bytes4 constant OFFCHAIN_LOOKUP_SELECTOR = 0x556f1830;

Expand Down Expand Up @@ -67,4 +86,25 @@ contract EnsGetter {
gateways
);
}

// Batches ENS expiry calls. Routing is decided by the caller:
// - useRegistrar == true: `.eth` 2LD, read from the BaseRegistrar (expiry + separate GRACE_PERIOD).
// - useRegistrar == false: subnames / non-`.eth` names, read from the NameWrapper (no grace period).
// `id` is the registrar token id (labelhash of the first label) or the wrapper node (namehash),
function getExpiry(
bool useRegistrar,
address baseRegistrar,
address nameWrapper,
uint256 id
) external view returns (ExpiryResult memory result) {
result.blockTimestamp = block.timestamp;

if (useRegistrar) {
result.expiry = IBaseRegistrar(baseRegistrar).nameExpires(id);
result.gracePeriod = IBaseRegistrar(baseRegistrar).GRACE_PERIOD();
} else {
(, , uint64 wrapperExpiry) = INameWrapper(nameWrapper).getData(id);
result.expiry = wrapperExpiry;
}
}
}
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "2.103.3",
"version": "2.104.0",
"name": "ambire-common",
"description": "Common ground for the Ambire apps",
"scripts": {
Expand All @@ -16,6 +16,7 @@
},
"dependencies": {
"@ambire/signature-validator": "^1.5.0",
"@corpus-core/colibri-stateless": "^1.1.30",
"@lifi/types": "^17.7.1",
"@metamask/eth-sig-util": "^8.2.0",
"@safe-global/api-kit": "^4.2.0",
Expand Down
46 changes: 46 additions & 0 deletions scripts/deployBalanceGetter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require('dotenv').config()

const { ethers } = require('hardhat')
const { Interface } = require('ethers')
const balanceGetter = require('../contracts/compiled/BalanceGetter.json')

async function main() {
const [deployer] = await ethers.getSigners()

const bytecode = balanceGetter.bin
const salt = '0x0000000000000000000000000000000000000000000000000000000000000000'
const singletonABI = [
{
inputs: [
{ internalType: 'bytes', name: '_initCode', type: 'bytes' },
{ internalType: 'bytes32', name: '_salt', type: 'bytes32' }
],
name: 'deploy',
outputs: [{ internalType: 'address payable', name: 'createdContract', type: 'address' }],
stateMutability: 'nonpayable',
type: 'function'
}
]
const singletonInterface = new Interface(singletonABI)

const tx = {
to: '0xce0042B868300000d44A59004Da54A005ffdcf9f', // the singleton
value: 0n,
data: singletonInterface.encodeFunctionData('deploy', [bytecode, salt]),
gasLimit: 3500000n
}

// Send the transaction
const txResponse = await deployer.sendTransaction(tx)

// Wait for the transaction to be mined
const txReceipt = await txResponse.wait()

console.log('Transaction sent!')
console.log(txReceipt)
}

main().catch((error) => {
console.error(error)
process.exit(1)
})
46 changes: 46 additions & 0 deletions scripts/deployNftGetter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require('dotenv').config()

const { ethers } = require('hardhat')
const { Interface } = require('ethers')
const nftGetter = require('../contracts/compiled/NftGetter.json')

async function main() {
const [deployer] = await ethers.getSigners()

const bytecode = nftGetter.bin
const salt = '0x0000000000000000000000000000000000000000000000000000000000000000'
const singletonABI = [
{
inputs: [
{ internalType: 'bytes', name: '_initCode', type: 'bytes' },
{ internalType: 'bytes32', name: '_salt', type: 'bytes32' }
],
name: 'deploy',
outputs: [{ internalType: 'address payable', name: 'createdContract', type: 'address' }],
stateMutability: 'nonpayable',
type: 'function'
}
]
const singletonInterface = new Interface(singletonABI)

const tx = {
to: '0xce0042B868300000d44A59004Da54A005ffdcf9f', // the singleton
value: 0n,
data: singletonInterface.encodeFunctionData('deploy', [bytecode, salt]),
gasLimit: 3000000n
}

// Send the transaction
const txResponse = await deployer.sendTransaction(tx)

// Wait for the transaction to be mined
const txReceipt = await txResponse.wait()

console.log('Transaction sent!')
console.log(txReceipt)
}

main().catch((error) => {
console.error(error)
process.exit(1)
})
20 changes: 20 additions & 0 deletions src/consts/autoLogin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const DEFAULT_AUTO_LOGIN_DURATION_OPTION = {
label: '30 days',
value: 30 * 24 * 60 * 60 * 1000
}

// Implemented here to ensure consistency between the controller and the UI
// Also, in the future when the duration setting becomes exposed to the UI we
// will need to validate the input from the UI, so these will be useful
export const AUTO_LOGIN_DURATION_OPTIONS = [
{ label: '24 hours', value: 24 * 60 * 60 * 1000 },
{
label: '7 days',
value: 7 * 24 * 60 * 60 * 1000
},
{
label: '14 days',
value: 14 * 24 * 60 * 60 * 1000
},
DEFAULT_AUTO_LOGIN_DURATION_OPTION
]
4 changes: 4 additions & 0 deletions src/consts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ export const EIP_7702_METAMASK = '0x63c0c19a282a1B52b07dD5a65b58948A07DAE32B'
export const EIP_7702_GRID_PLUS = '0x8D6220c9961E8DD1844108C854F514E120226E20'
export const EIP_7702_KATANA = '0x8226995E02C70293595E0634C5F89547EDb08126'
export const SAFE_SENDER = '0x3979FF5F4DF3128dd05b3d74CF9b7F462e0d9BF1'

// used for colibri verification
export const BALANCE_GETTER = '0x3f58D86408988FBD8aeEA5AD063173F249f5B214'
export const NFT_GETTER = '0xE8340AEFEd909d64117929c1Ce5F6e217faf4362'
78 changes: 69 additions & 9 deletions src/controllers/activity/activity.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getAddress } from 'ethers'
import fetch from 'node-fetch'

import { generateUuid } from '@/utils/uuid'
Expand Down Expand Up @@ -167,14 +168,14 @@ describe('Activity Controller ', () => {
const { controller } = await prepareTest()

const trustedRecipient = '0xF0cD725D2195b1D3f4BD038c3786005B793237DB'
const poisoningRecipient4 = '0xF0cDaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa37DB'
const poisoningRecipient5 = '0xF0cD7bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb237DB'
const poisoningRecipient6 = '0xF0cD72ccccccccccccccccccccccccccccc237DB'
const poisoningRecipient4to8 = '0xF0cDdddddddddddddddddddddddddddd793237DB'
const poisoningRecipient3to8 = '0xF0ceeeeeeeeeeeeeeeeeeeeeeeeeeeee793237DB'
const poisoningRecipient0to8 = '0xAb12ffffffffffffffffffffffffffff793237DB'
const poisoningRecipient3to4 = '0xF0caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa37DB'
const poisoningRecipient0to0 = '0xAb12eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeCDef'
const poisoningRecipient4 = '0xF0cdAaAaaAAAaAAAaaaAaAaAAaAaaaAaAaaA37Db'
const poisoningRecipient5 = '0xf0cd7BbbBbbbBbBBbbbBBBBbbbbBBbbbbbb237DB'
const poisoningRecipient6 = '0xf0cd72ccCCccCcCccCCCCcCCcCcCCCCccCC237DB'
const poisoningRecipient4to8 = '0xF0CdDDDddddDdDdDdDDDDDDDDdddDDDD793237db'
const poisoningRecipient3to8 = '0xF0cEEeeeEEEEEeEEEEeeEEEEeeEeeeEe793237db'
const poisoningRecipient0to8 = '0xaB12ffFFfFFFFfFfFFFffffFffFFFfFF793237DB'
const poisoningRecipient3to4 = '0xF0cAAaAaAaaaAAaAAaaaaAAaaaAaAAAaAAaa37dB'
const poisoningRecipient0to0 = '0xAB12eeeeeeeEeeeEeeeEEeeeEEEEEeeEeEeeCdef'

await controller.addAccountOp({
...SUBMITTED_ACCOUNT_OP,
Expand Down Expand Up @@ -304,7 +305,7 @@ describe('Activity Controller ', () => {
test('should not detect poisoning without transaction history', async () => {
const { controller } = await prepareTest()

const poisoningRecipient4to4 = '0xF0cDaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa37DB'
const poisoningRecipient4to4 = '0xF0cdAaAaaAAAaAAAaaaAaAaAAaAaaaAaAaaA37Db'
const normalizedPoisoningRecipient4to4 = poisoningRecipient4to4.toLowerCase()

const firstTimeSendResult = await controller.hasAccountOpsSentTo(
Expand Down Expand Up @@ -1176,4 +1177,63 @@ describe('Activity Controller ', () => {
expect(controller.accountsOps[sessionId]!.result!.items.length).toEqual(0)
expect(controller.signedMessages[sessionId]!.result!.items.length).toEqual(0)
})

describe('Sent-to history', () => {
const DOMAIN_ADDR_A = '0xF0cD725D2195b1D3f4BD038c3786005B793237DB'
const DOMAIN_ADDR_B = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'

const SENT_AT = new Date('2024-03-01T10:00:00Z').getTime()
const SENT_AT_LATER = new Date('2024-06-15T18:30:00Z').getTime()

it('records and reads the address a domain was sent to (global, case-insensitive)', async () => {
const { controller } = await prepareTest()

// await controller.recordSentToDomain('alice.eth', DOMAIN_ADDR_A, SENT_AT)
await controller.addAccountOp({
...SUBMITTED_ACCOUNT_OP,
calls: [{ to: DOMAIN_ADDR_A, recipientDomain: 'alice.eth', value: 0n, data: '0x' }]
})

// Checksummed, and the domain lookup is case-insensitive.
expect(controller.getSentToDomainAddress('alice.eth')).toBe(getAddress(DOMAIN_ADDR_A))
expect(controller.getSentToDomainAddress('ALICE.eth')).toBe(getAddress(DOMAIN_ADDR_A))
})

it('overwrites with the most recent address', async () => {
const { controller } = await prepareTest()

await controller.addAccountOp({
...SUBMITTED_ACCOUNT_OP,
timestamp: SENT_AT,
calls: [{ to: DOMAIN_ADDR_B, recipientDomain: 'alice.eth', value: 0n, data: '0x' }]
})
await controller.addAccountOp({
...SUBMITTED_ACCOUNT_OP,
timestamp: SENT_AT_LATER,
calls: [{ to: DOMAIN_ADDR_A, recipientDomain: 'alice.eth', value: 0n, data: '0x' }]
})

expect(controller.getSentToDomainAddress('alice.eth')).toBe(getAddress(DOMAIN_ADDR_A))
})

it('stores recipients checksummed', async () => {
const { controller } = await prepareTest()

const recipientLower = '0xf0cd725d2195b1d3f4bd038c3786005b793237db'
await controller.addAccountOp({
...SUBMITTED_ACCOUNT_OP,
nonce: 302n,
txnId: '0x4c8a1d6f93b072e5af18c34d9e6072b1f5a83c0d7e29b46f1a0c5d8e3b97f246',
timestamp: SENT_AT_LATER,
calls: [{ to: recipientLower, value: 0n, data: '0x' }]
})

const stored = await storage.get('sentToHistory', { domains: {}, recipients: {} })
const recipientsForAccount = stored.recipients[SUBMITTED_ACCOUNT_OP.accountAddr]
expect(recipientsForAccount).toBeDefined()

expect(recipientsForAccount![getAddress(recipientLower)]).toBe(SENT_AT_LATER)
expect(recipientsForAccount![recipientLower]).toBeUndefined()
})
})
})
Loading
Loading