Skip to content

Commit

Permalink
feat(contract): support all names delegation to contract
Browse files Browse the repository at this point in the history
  • Loading branch information
davidyuk committed Dec 14, 2023
1 parent a1531db commit 92dae86
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 7 deletions.
9 changes: 5 additions & 4 deletions docs/guides/aens.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,13 +371,14 @@ const aeSdk = new AeSdk({ ... }) // init the SDK instance with AeSdk class
const contractAddress = 'ct_asd2ks...'
// AENS name
const name = 'example.chain'
// Sign with a specific account
const onAccount = aeSdk.address

// this signature will allow the contract to perform a pre-claim on your behalf
const preClaimSig = await aeSdk.createDelegationSignature(contractAddress, [])
const preClaimSig = await aeSdk.signDelegationToContract(contractAddress)

// this signature will allow the contract to perform
// any name related transaction for a specific name that you own
const aensDelegationSig = await aeSdk.createDelegationSignature(contractAddress, [name], { onAccount })
const nameDelegationSig = await aeSdk.signNameDelegationToContract(contractAddress, name)

// alternatively, you can generate a delegation signature suitable for every name you own
const allNamesDelegationSig = await aeSdk.signAllNamesDelegationToContract(contractAddress)
```
10 changes: 9 additions & 1 deletion examples/browser/aepp/src/DelegationSignature.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<div>
<label>
<input v-model="type" type="radio" value="general">
AENS and oracle
AENS preclaim and oracle
</label>
</div>
<div>
Expand All @@ -18,6 +18,12 @@
</label>
<div><input v-model="name"></div>
</div>
<div>
<label>
<input v-model="type" type="radio" value="all-names">
All AENS names
</label>
</div>
<div>
<label>
<input v-model="type" type="radio" value="oracle-query">
Expand Down Expand Up @@ -56,6 +62,8 @@ export default {
return this.aeSdk.signDelegationToContract(this.contractAddress);
case 'name':
return this.aeSdk.signNameDelegationToContract(this.contractAddress, this.name);
case 'all-names':
return this.aeSdk.signAllNamesDelegationToContract(this.contractAddress);
case 'oracle-query':
return this.aeSdk
.signOracleQueryDelegationToContract(this.contractAddress, this.oracleQueryId);
Expand Down
11 changes: 11 additions & 0 deletions examples/browser/wallet-iframe/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,17 @@ export default {
return super.signNameDelegationToContract(contractAddress, name, options);
}

async signAllNamesDelegationToContract(
contractAddress,
{ aeppRpcClientId: id, aeppOrigin, ...options },
) {
if (id != null) {
const opt = { ...options, contractAddress };
genConfirmCallback('sign delegation of all names to contract')(id, opt, aeppOrigin);
}
return super.signAllNamesDelegationToContract(contractAddress, options);
}

async signOracleQueryDelegationToContract(
contractAddress,
oracleQueryId,
Expand Down
11 changes: 11 additions & 0 deletions examples/browser/wallet-web-extension/src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ class AccountMemoryProtected extends MemoryAccount {
return super.signNameDelegationToContract(contractAddress, name, options);
}

async signAllNamesDelegationToContract(
contractAddress,
{ aeppRpcClientId: id, aeppOrigin, ...options },
) {
if (id != null) {
const opt = { ...options, contractAddress };
genConfirmCallback('sign delegation of all names to contract')(id, opt, aeppOrigin);
}
return super.signAllNamesDelegationToContract(contractAddress, options);
}

async signOracleQueryDelegationToContract(
contractAddress,
oracleQueryId,
Expand Down
11 changes: 11 additions & 0 deletions src/AeSdkBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,17 @@ export default class AeSdkBase extends AeSdkMethods {
.signNameDelegationToContract(contractAddress, name, options);
}

async signAllNamesDelegationToContract(
contractAddress: Encoded.ContractAddress,
{ onAccount, ...options }: { onAccount?: OnAccount }
& Parameters<AccountBase['signAllNamesDelegationToContract']>[1] = {},
): Promise<Encoded.Signature> {
options.networkId ??= this.selectedNodeName !== null
? await this.api.getNetworkId() : undefined;
return this._resolveAccount(onAccount)
.signAllNamesDelegationToContract(contractAddress, options);
}

async signOracleQueryDelegationToContract(
contractAddress: Encoded.ContractAddress,
oracleQueryId: Encoded.OracleQueryId,
Expand Down
4 changes: 3 additions & 1 deletion src/AeSdkWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ export default class AeSdkWallet extends AeSdk {
};
},
[METHODS.signDelegationToContract]: async ({
contractAddress, name, oracleQueryId, onAccount = this.address,
contractAddress, name, oracleQueryId, allNames, onAccount = this.address,
}, origin) => {
if (!this._isRpcClientConnected(id)) throw new RpcNotAuthorizeError();
if (!this.addresses().includes(onAccount)) {
Expand All @@ -315,6 +315,8 @@ export default class AeSdkWallet extends AeSdk {
.signNameDelegationToContract(contractAddress, name, parameters))
?? (oracleQueryId == null ? null : this
.signOracleQueryDelegationToContract(contractAddress, oracleQueryId, parameters))
?? (allNames !== true ? null : this
.signAllNamesDelegationToContract(contractAddress, parameters))
?? this.signDelegationToContract(contractAddress, parameters)
);
return { signature };
Expand Down
21 changes: 21 additions & 0 deletions src/account/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,27 @@ export default abstract class AccountBase {
throw new NotImplementedError('signNameDelegationToContract method');
}

/**
* Sign delegation of all AENS names to a contract (not available in Iris)
* @param contractAddress - Address of a contract to delegate permissions to
* @param options - Options
* @returns Signature
*/
// TODO: make abstract in the next major release
// eslint-disable-next-line class-methods-use-this
async signAllNamesDelegationToContract(
/* eslint-disable @typescript-eslint/no-unused-vars */
contractAddress: Encoded.ContractAddress,
options?: {
networkId?: string;
aeppOrigin?: string;
aeppRpcClientId?: string;
},
/* eslint-enable @typescript-eslint/no-unused-vars */
): Promise<Encoded.Signature> {
throw new NotImplementedError('signAllNamesDelegationToContract method');

Check warning on line 141 in src/account/Base.ts

View check run for this annotation

Codecov / codecov/patch

src/account/Base.ts#L141

Added line #L141 was not covered by tests
}

/**
* Sign delegation of oracle query to a contract
*
Expand Down
5 changes: 5 additions & 0 deletions src/account/Generalized.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export default class AccountGeneralized extends AccountBase {
throw new NotImplementedError('signing delegation to contract using generalized account');
}

// eslint-disable-next-line class-methods-use-this
override async signAllNamesDelegationToContract(): Promise<Encoded.Signature> {
throw new NotImplementedError('signing delegation to contract using generalized account');

Check warning on line 58 in src/account/Generalized.ts

View check run for this annotation

Codecov / codecov/patch

src/account/Generalized.ts#L58

Added line #L58 was not covered by tests
}

// eslint-disable-next-line class-methods-use-this
override async signOracleQueryDelegationToContract(): Promise<Encoded.Signature> {
throw new NotImplementedError('signing delegation to contract using generalized account');
Expand Down
5 changes: 5 additions & 0 deletions src/account/Ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ export default class AccountLedger extends AccountBase {
throw new NotImplementedError('signing delegation to contract using Ledger HW');
}

// eslint-disable-next-line class-methods-use-this
override async signAllNamesDelegationToContract(): Promise<Encoded.Signature> {
throw new NotImplementedError('signing delegation to contract using Ledger HW');

Check warning on line 60 in src/account/Ledger.ts

View check run for this annotation

Codecov / codecov/patch

src/account/Ledger.ts#L60

Added line #L60 was not covered by tests
}

// eslint-disable-next-line class-methods-use-this
override async signOracleQueryDelegationToContract(): Promise<Encoded.Signature> {
throw new NotImplementedError('signing delegation to contract using Ledger HW');
Expand Down
15 changes: 15 additions & 0 deletions src/account/Memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,21 @@ export default class AccountMemory extends AccountBase {
return encode(signature, Encoding.Signature);
}

override async signAllNamesDelegationToContract(
contractAddress: Encoded.ContractAddress,
{ networkId }: { networkId?: string } = {},
): Promise<Encoded.Signature> {
if (networkId == null) throw new ArgumentError('networkId', 'provided', networkId);
const payload = concatBuffers([
Buffer.from(networkId),
decode(this.address),
Buffer.from('AENS'),
decode(contractAddress),
]);
const signature = await this.sign(payload);
return encode(signature, Encoding.Signature);
}

override async signOracleQueryDelegationToContract(
contractAddress: Encoded.ContractAddress,
oracleQueryId: Encoded.OracleQueryId,
Expand Down
11 changes: 11 additions & 0 deletions src/account/Rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ export default class AccountRpc extends AccountBase {
return signature;
}

override async signAllNamesDelegationToContract(
contractAddress: Encoded.ContractAddress,
): Promise<Encoded.Signature> {
const { signature } = await this._rpcClient.request(METHODS.signDelegationToContract, {

Check warning on line 99 in src/account/Rpc.ts

View check run for this annotation

Codecov / codecov/patch

src/account/Rpc.ts#L99

Added line #L99 was not covered by tests
onAccount: this.address,
contractAddress,
allNames: true,
});
return signature;

Check warning on line 104 in src/account/Rpc.ts

View check run for this annotation

Codecov / codecov/patch

src/account/Rpc.ts#L104

Added line #L104 was not covered by tests
}

override async signOracleQueryDelegationToContract(
contractAddress: Encoded.ContractAddress,
oracleQueryId: Encoded.OracleQueryId,
Expand Down
1 change: 1 addition & 0 deletions src/aepp-wallet-communication/rpc/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export interface WalletApi {
contractAddress: Encoded.ContractAddress;
name?: AensName;
oracleQueryId?: Encoded.OracleQueryId;
allNames?: boolean;
onAccount: Encoded.AccountAddress;
},
) => Promise<{ signature: Encoded.Signature }>;
Expand Down
32 changes: 31 additions & 1 deletion test/integration/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,10 @@ describe('Contract', () => {
}>;
let contractAddress: Encoded.ContractAddress;
let aens: string;
let isIris: boolean;

before(async () => {
const isIris = (await aeSdk.api.getNodeInfo())
isIris = (await aeSdk.api.getNodeInfo())
.consensusProtocolVersion === ConsensusProtocolVersion.Iris;
aens = isIris ? 'AENS' : 'AENSv2';
contract = await aeSdk.initializeContract({
Expand Down Expand Up @@ -419,6 +420,35 @@ contract DelegateTest =
result.returnType.should.be.equal('ok');
await expect(aeSdk.aensQuery(name)).to.be.rejectedWith(Error);
});

it('works using wildcard delegation signature', async () => {
if (isIris) return;
const allNamesDelSig = decode(await aeSdk.signAllNamesDelegationToContract(contractAddress));
const n = randomName(15);

const commitmentId = decode(commitmentHash(n, salt));
await contract.signedPreclaim(owner, commitmentId, allNamesDelSig);
await aeSdk.awaitHeight(2 + await aeSdk.getHeight());

await contract.signedClaim(owner, n, salt, 20e18, allNamesDelSig);

const pointee = { 'AENSv2.OraclePt': [newOwner] as const };
await contract.signedUpdate(owner, n, 'oracle', pointee, allNamesDelSig);

const nameEntry = (await contract.getName(n)).decodedResult['AENSv2.Name'];
const ttl = nameEntry[1].FixedTTL[0];
expect(ttl).to.be.a('bigint');
expect(nameEntry).to.be.eql([
owner,
{ FixedTTL: [ttl] },
new Map([['oracle', { 'AENSv2.OraclePt': [newOwner] }]]),
]);

await contract.signedTransfer(owner, newOwner, n, allNamesDelSig);
await aeSdk.aensTransfer(n, owner, { onAccount: newOwner });

await contract.signedRevoke(owner, n, allNamesDelSig);
});
});

describe('Oracle operation delegation', () => {
Expand Down

0 comments on commit 92dae86

Please sign in to comment.