ts: Reorganize program namespaces into well typed elements (#322)
This commit is contained in:
parent
e1229362bc
commit
2f780e0d27
|
@ -19,9 +19,10 @@ incremented for features.
|
|||
|
||||
## Breaking Changes
|
||||
|
||||
* ts: Retrieving deserialized accounts from the `<program>.account.<my-account>` and `<program>.state` namespaces now require explicitly invoking the `fetch` API. For example, `program.account.myAccount(<adddress>)` and `program.state()` is now `program.account.myAccount.fetch(<address>)` and `program.state.fetch()` ([#322](https://github.com/project-serum/anchor/pull/322)).
|
||||
* lang: `#[account(associated)]` now requires `init` to be provided to create an associated account. If not provided, then the address will be assumed to exist, and a constraint will be added to ensure its correctness ([#318](https://github.com/project-serum/anchor/pull/318)).
|
||||
* lang, ts: Change account discriminator pre-image of the `#[state]` account discriminator to be namespaced by "state:". This change should only be noticed by library maintainers ([#320](https://github.com/project-serum/anchor/pull/320)).
|
||||
* lang, ts: Change domain delimiters for the pre-image of the instruciton sighash to be a single colon `:` to be consistent with accounts. This change should only be noticed by library maintainers.
|
||||
* lang, ts: Change account discriminator pre-image of the `#[state]` account discriminator to be namespaced by "state:" ([#320](https://github.com/project-serum/anchor/pull/320)).
|
||||
* lang, ts: Change domain delimiters for the pre-image of the instruciton sighash to be a single colon `:` to be consistent with accounts ([#321](https://github.com/project-serum/anchor/pull/321)).
|
||||
|
||||
## [0.6.0] - 2021-05-23
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ describe("cashiers-check", () => {
|
|||
],
|
||||
});
|
||||
|
||||
const checkAccount = await program.account.check(check.publicKey);
|
||||
const checkAccount = await program.account.check.fetch(check.publicKey);
|
||||
assert.ok(checkAccount.from.equals(god));
|
||||
assert.ok(checkAccount.to.equals(receiver));
|
||||
assert.ok(checkAccount.amount.eq(new anchor.BN(100)));
|
||||
|
@ -91,7 +91,7 @@ describe("cashiers-check", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const checkAccount = await program.account.check(check.publicKey);
|
||||
const checkAccount = await program.account.check.fetch(check.publicKey);
|
||||
assert.ok(checkAccount.burned === true);
|
||||
|
||||
let vaultAccount = await serumCmn.getTokenAccount(
|
||||
|
|
|
@ -25,7 +25,7 @@ describe("chat", () => {
|
|||
signers: [chatRoom],
|
||||
});
|
||||
|
||||
const chat = await program.account.chatRoom(chatRoom.publicKey);
|
||||
const chat = await program.account.chatRoom.fetch(chatRoom.publicKey);
|
||||
const name = new TextDecoder("utf-8").decode(new Uint8Array(chat.name));
|
||||
assert.ok(name.startsWith("Test Chat")); // [u8; 280] => trailing zeros.
|
||||
assert.ok(chat.messages.length === 33607);
|
||||
|
@ -76,7 +76,7 @@ describe("chat", () => {
|
|||
}
|
||||
|
||||
// Check the chat room state is as expected.
|
||||
const chat = await program.account.chatRoom(chatRoom.publicKey);
|
||||
const chat = await program.account.chatRoom.fetch(chatRoom.publicKey);
|
||||
const name = new TextDecoder("utf-8").decode(new Uint8Array(chat.name));
|
||||
assert.ok(name.startsWith("Test Chat")); // [u8; 280] => trailing zeros.
|
||||
assert.ok(chat.messages.length === 33607);
|
||||
|
|
|
@ -41,8 +41,8 @@ describe("composite", () => {
|
|||
}
|
||||
);
|
||||
|
||||
const dummyAAccount = await program.account.dummyA(dummyA.publicKey);
|
||||
const dummyBAccount = await program.account.dummyB(dummyB.publicKey);
|
||||
const dummyAAccount = await program.account.dummyA.fetch(dummyA.publicKey);
|
||||
const dummyBAccount = await program.account.dummyB.fetch(dummyB.publicKey);
|
||||
|
||||
assert.ok(dummyAAccount.data.eq(new anchor.BN(1234)));
|
||||
assert.ok(dummyBAccount.data.eq(new anchor.BN(4321)));
|
||||
|
|
|
@ -10,7 +10,7 @@ describe("interface", () => {
|
|||
it("Is initialized!", async () => {
|
||||
await counter.state.rpc.new(counterAuth.programId);
|
||||
|
||||
const stateAccount = await counter.state();
|
||||
const stateAccount = await counter.state.fetch();
|
||||
assert.ok(stateAccount.count.eq(new anchor.BN(0)));
|
||||
assert.ok(stateAccount.authProgram.equals(counterAuth.programId));
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ describe("interface", () => {
|
|||
authProgram: counterAuth.programId,
|
||||
},
|
||||
});
|
||||
const stateAccount = await counter.state();
|
||||
const stateAccount = await counter.state.fetch();
|
||||
assert.ok(stateAccount.count.eq(new anchor.BN(3)));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -37,7 +37,7 @@ describe("Lockup and Registry", () => {
|
|||
});
|
||||
|
||||
lockupAddress = await lockup.state.address();
|
||||
const lockupAccount = await lockup.state();
|
||||
const lockupAccount = await lockup.state.fetch();
|
||||
|
||||
assert.ok(lockupAccount.authority.equals(provider.wallet.publicKey));
|
||||
assert.ok(lockupAccount.whitelist.length === WHITELIST_SIZE);
|
||||
|
@ -63,7 +63,7 @@ describe("Lockup and Registry", () => {
|
|||
},
|
||||
});
|
||||
|
||||
let lockupAccount = await lockup.state();
|
||||
let lockupAccount = await lockup.state.fetch();
|
||||
assert.ok(lockupAccount.authority.equals(newAuthority.publicKey));
|
||||
|
||||
await lockup.state.rpc.setAuthority(provider.wallet.publicKey, {
|
||||
|
@ -73,7 +73,7 @@ describe("Lockup and Registry", () => {
|
|||
signers: [newAuthority],
|
||||
});
|
||||
|
||||
lockupAccount = await lockup.state();
|
||||
lockupAccount = await lockup.state.fetch();
|
||||
assert.ok(lockupAccount.authority.equals(provider.wallet.publicKey));
|
||||
});
|
||||
|
||||
|
@ -97,7 +97,7 @@ describe("Lockup and Registry", () => {
|
|||
|
||||
await lockup.state.rpc.whitelistAdd(entries[0], { accounts });
|
||||
|
||||
let lockupAccount = await lockup.state();
|
||||
let lockupAccount = await lockup.state.fetch();
|
||||
|
||||
assert.ok(lockupAccount.whitelist.length === 1);
|
||||
assert.deepEqual(lockupAccount.whitelist, [entries[0]]);
|
||||
|
@ -106,7 +106,7 @@ describe("Lockup and Registry", () => {
|
|||
await lockup.state.rpc.whitelistAdd(entries[k], { accounts });
|
||||
}
|
||||
|
||||
lockupAccount = await lockup.state();
|
||||
lockupAccount = await lockup.state.fetch();
|
||||
|
||||
assert.deepEqual(lockupAccount.whitelist, entries);
|
||||
|
||||
|
@ -129,7 +129,7 @@ describe("Lockup and Registry", () => {
|
|||
authority: provider.wallet.publicKey,
|
||||
},
|
||||
});
|
||||
let lockupAccount = await lockup.state();
|
||||
let lockupAccount = await lockup.state.fetch();
|
||||
assert.deepEqual(lockupAccount.whitelist, entries.slice(1));
|
||||
});
|
||||
|
||||
|
@ -185,7 +185,7 @@ describe("Lockup and Registry", () => {
|
|||
}
|
||||
);
|
||||
|
||||
vestingAccount = await lockup.account.vesting(vesting.publicKey);
|
||||
vestingAccount = await lockup.account.vesting.fetch(vesting.publicKey);
|
||||
|
||||
assert.ok(vestingAccount.beneficiary.equals(provider.wallet.publicKey));
|
||||
assert.ok(vestingAccount.mint.equals(mint));
|
||||
|
@ -246,7 +246,7 @@ describe("Lockup and Registry", () => {
|
|||
},
|
||||
});
|
||||
|
||||
vestingAccount = await lockup.account.vesting(vesting.publicKey);
|
||||
vestingAccount = await lockup.account.vesting.fetch(vesting.publicKey);
|
||||
assert.ok(vestingAccount.outstanding.eq(new anchor.BN(0)));
|
||||
|
||||
const vaultAccount = await serumCmn.getTokenAccount(
|
||||
|
@ -287,7 +287,7 @@ describe("Lockup and Registry", () => {
|
|||
accounts: { lockupProgram: lockup.programId },
|
||||
});
|
||||
|
||||
const state = await registry.state();
|
||||
const state = await registry.state.fetch();
|
||||
assert.ok(state.lockupProgram.equals(lockup.programId));
|
||||
|
||||
// Should not allow a second initializatoin.
|
||||
|
@ -324,7 +324,7 @@ describe("Lockup and Registry", () => {
|
|||
}
|
||||
);
|
||||
|
||||
registrarAccount = await registry.account.registrar(registrar.publicKey);
|
||||
registrarAccount = await registry.account.registrar.fetch(registrar.publicKey);
|
||||
|
||||
assert.ok(registrarAccount.authority.equals(provider.wallet.publicKey));
|
||||
assert.equal(registrarAccount.nonce, nonce);
|
||||
|
@ -385,7 +385,7 @@ describe("Lockup and Registry", () => {
|
|||
|
||||
let txSigs = await provider.sendAll(allTxs);
|
||||
|
||||
memberAccount = await registry.account.member(member.publicKey);
|
||||
memberAccount = await registry.account.member.fetch(member.publicKey);
|
||||
|
||||
assert.ok(memberAccount.registrar.equals(registrar.publicKey));
|
||||
assert.ok(memberAccount.beneficiary.equals(provider.wallet.publicKey));
|
||||
|
@ -516,7 +516,7 @@ describe("Lockup and Registry", () => {
|
|||
}
|
||||
);
|
||||
|
||||
const vendorAccount = await registry.account.rewardVendor(
|
||||
const vendorAccount = await registry.account.rewardVendor.fetch(
|
||||
unlockedVendor.publicKey
|
||||
);
|
||||
|
||||
|
@ -531,7 +531,7 @@ describe("Lockup and Registry", () => {
|
|||
assert.ok(vendorAccount.rewardEventQCursor === 0);
|
||||
assert.deepEqual(vendorAccount.kind, rewardKind);
|
||||
|
||||
const rewardQAccount = await registry.account.rewardQueue(
|
||||
const rewardQAccount = await registry.account.rewardQueue.fetch(
|
||||
rewardQ.publicKey
|
||||
);
|
||||
assert.ok(rewardQAccount.head === 1);
|
||||
|
@ -571,7 +571,7 @@ describe("Lockup and Registry", () => {
|
|||
let tokenAccount = await serumCmn.getTokenAccount(provider, token);
|
||||
assert.ok(tokenAccount.amount.eq(new anchor.BN(200)));
|
||||
|
||||
const memberAccount = await registry.account.member(member.publicKey);
|
||||
const memberAccount = await registry.account.member.fetch(member.publicKey);
|
||||
assert.ok(memberAccount.rewardsCursor == 1);
|
||||
});
|
||||
|
||||
|
@ -635,7 +635,7 @@ describe("Lockup and Registry", () => {
|
|||
}
|
||||
);
|
||||
|
||||
const vendorAccount = await registry.account.rewardVendor(
|
||||
const vendorAccount = await registry.account.rewardVendor.fetch(
|
||||
lockedVendor.publicKey
|
||||
);
|
||||
|
||||
|
@ -653,7 +653,7 @@ describe("Lockup and Registry", () => {
|
|||
JSON.stringify(lockedRewardKind)
|
||||
);
|
||||
|
||||
const rewardQAccount = await registry.account.rewardQueue(
|
||||
const rewardQAccount = await registry.account.rewardQueue.fetch(
|
||||
rewardQ.publicKey
|
||||
);
|
||||
assert.ok(rewardQAccount.head === 2);
|
||||
|
@ -727,7 +727,7 @@ describe("Lockup and Registry", () => {
|
|||
],
|
||||
});
|
||||
|
||||
const lockupAccount = await lockup.account.vesting(
|
||||
const lockupAccount = await lockup.account.vesting.fetch(
|
||||
vendoredVesting.publicKey
|
||||
);
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ describe("misc", () => {
|
|||
it("Can allocate extra space for a state constructor", async () => {
|
||||
const tx = await program.state.rpc.new();
|
||||
const addr = await program.state.address();
|
||||
const state = await program.state();
|
||||
const state = await program.state.fetch();
|
||||
const accountInfo = await program.provider.connection.getAccountInfo(addr);
|
||||
assert.ok(state.v.equals(Buffer.from([])));
|
||||
assert.ok(accountInfo.data.length === 99);
|
||||
|
@ -32,7 +32,7 @@ describe("misc", () => {
|
|||
instructions: [await program.account.data.createInstruction(data)],
|
||||
}
|
||||
);
|
||||
const dataAccount = await program.account.data(data.publicKey);
|
||||
const dataAccount = await program.account.data.fetch(data.publicKey);
|
||||
assert.ok(dataAccount.udata.eq(new anchor.BN(1234)));
|
||||
assert.ok(dataAccount.idata.eq(new anchor.BN(22)));
|
||||
});
|
||||
|
@ -47,7 +47,7 @@ describe("misc", () => {
|
|||
signers: [data],
|
||||
instructions: [await program.account.dataU16.createInstruction(data)],
|
||||
});
|
||||
const dataAccount = await program.account.dataU16(data.publicKey);
|
||||
const dataAccount = await program.account.dataU16.fetch(data.publicKey);
|
||||
assert.ok(dataAccount.data === 99);
|
||||
});
|
||||
|
||||
|
@ -110,7 +110,7 @@ describe("misc", () => {
|
|||
authority: program.provider.wallet.publicKey,
|
||||
},
|
||||
});
|
||||
let stateAccount = await misc2Program.state();
|
||||
let stateAccount = await misc2Program.state.fetch();
|
||||
assert.ok(stateAccount.data.eq(oldData));
|
||||
assert.ok(stateAccount.auth.equals(program.provider.wallet.publicKey));
|
||||
const newData = new anchor.BN(2134);
|
||||
|
@ -121,7 +121,7 @@ describe("misc", () => {
|
|||
misc2Program: misc2Program.programId,
|
||||
},
|
||||
});
|
||||
stateAccount = await misc2Program.state();
|
||||
stateAccount = await misc2Program.state.fetch();
|
||||
assert.ok(stateAccount.data.eq(newData));
|
||||
assert.ok(stateAccount.auth.equals(program.provider.wallet.publicKey));
|
||||
});
|
||||
|
@ -145,7 +145,7 @@ describe("misc", () => {
|
|||
);
|
||||
await assert.rejects(
|
||||
async () => {
|
||||
await program.account.testData(associatedAccount);
|
||||
await program.account.testData.fetch(associatedAccount);
|
||||
},
|
||||
(err) => {
|
||||
assert.ok(
|
||||
|
@ -234,7 +234,7 @@ describe("misc", () => {
|
|||
instructions: [await program.account.dataI8.createInstruction(data)],
|
||||
signers: [data],
|
||||
});
|
||||
const dataAccount = await program.account.dataI8(data.publicKey);
|
||||
const dataAccount = await program.account.dataI8.fetch(data.publicKey);
|
||||
assert.ok(dataAccount.data === -3);
|
||||
});
|
||||
|
||||
|
@ -250,14 +250,14 @@ describe("misc", () => {
|
|||
instructions: [await program.account.dataI16.createInstruction(data)],
|
||||
signers: [data],
|
||||
});
|
||||
const dataAccount = await program.account.dataI16(data.publicKey);
|
||||
const dataAccount = await program.account.dataI16.fetch(data.publicKey);
|
||||
assert.ok(dataAccount.data === -2048);
|
||||
|
||||
dataPubkey = data.publicKey;
|
||||
});
|
||||
|
||||
it("Can use base58 strings to fetch an account", async () => {
|
||||
const dataAccount = await program.account.dataI16(dataPubkey.toString());
|
||||
const dataAccount = await program.account.dataI16.fetch(dataPubkey.toString());
|
||||
assert.ok(dataAccount.data === -2048);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -39,7 +39,7 @@ describe("multisig", () => {
|
|||
signers: [multisig],
|
||||
});
|
||||
|
||||
let multisigAccount = await program.account.multisig(multisig.publicKey);
|
||||
let multisigAccount = await program.account.multisig.fetch(multisig.publicKey);
|
||||
|
||||
assert.equal(multisigAccount.nonce, nonce);
|
||||
assert.ok(multisigAccount.threshold.eq(new anchor.BN(2)));
|
||||
|
@ -81,7 +81,7 @@ describe("multisig", () => {
|
|||
signers: [transaction, ownerA],
|
||||
});
|
||||
|
||||
const txAccount = await program.account.transaction(transaction.publicKey);
|
||||
const txAccount = await program.account.transaction.fetch(transaction.publicKey);
|
||||
|
||||
assert.ok(txAccount.programId.equals(pid));
|
||||
assert.deepEqual(txAccount.accounts, accounts);
|
||||
|
@ -124,7 +124,7 @@ describe("multisig", () => {
|
|||
}),
|
||||
});
|
||||
|
||||
multisigAccount = await program.account.multisig(multisig.publicKey);
|
||||
multisigAccount = await program.account.multisig.fetch(multisig.publicKey);
|
||||
|
||||
assert.equal(multisigAccount.nonce, nonce);
|
||||
assert.ok(multisigAccount.threshold.eq(new anchor.BN(2)));
|
||||
|
|
|
@ -43,7 +43,7 @@ describe("basic-1", () => {
|
|||
// #endregion code-separated
|
||||
|
||||
// Fetch the newly created account from the cluster.
|
||||
const account = await program.account.myAccount(myAccount.publicKey);
|
||||
const account = await program.account.myAccount.fetch(myAccount.publicKey);
|
||||
|
||||
// Check it's state was initialized.
|
||||
assert.ok(account.data.eq(new anchor.BN(1234)));
|
||||
|
@ -81,7 +81,7 @@ describe("basic-1", () => {
|
|||
});
|
||||
|
||||
// Fetch the newly created account from the cluster.
|
||||
const account = await program.account.myAccount(myAccount.publicKey);
|
||||
const account = await program.account.myAccount.fetch(myAccount.publicKey);
|
||||
|
||||
// Check it's state was initialized.
|
||||
assert.ok(account.data.eq(new anchor.BN(1234)));
|
||||
|
@ -108,7 +108,7 @@ describe("basic-1", () => {
|
|||
// #endregion code-simplified
|
||||
|
||||
// Fetch the newly created account from the cluster.
|
||||
const account = await program.account.myAccount(myAccount.publicKey);
|
||||
const account = await program.account.myAccount.fetch(myAccount.publicKey);
|
||||
|
||||
// Check it's state was initialized.
|
||||
assert.ok(account.data.eq(new anchor.BN(1234)));
|
||||
|
@ -133,7 +133,7 @@ describe("basic-1", () => {
|
|||
});
|
||||
|
||||
// Fetch the newly updated account.
|
||||
const account = await program.account.myAccount(myAccount.publicKey);
|
||||
const account = await program.account.myAccount.fetch(myAccount.publicKey);
|
||||
|
||||
// Check it's state was mutated.
|
||||
assert.ok(account.data.eq(new anchor.BN(4321)));
|
||||
|
|
|
@ -23,7 +23,7 @@ describe('basic-2', () => {
|
|||
instructions: [await program.account.counter.createInstruction(counter)],
|
||||
})
|
||||
|
||||
let counterAccount = await program.account.counter(counter.publicKey)
|
||||
let counterAccount = await program.account.counter.fetch(counter.publicKey)
|
||||
|
||||
assert.ok(counterAccount.authority.equals(provider.wallet.publicKey))
|
||||
assert.ok(counterAccount.count.toNumber() === 0)
|
||||
|
@ -37,7 +37,7 @@ describe('basic-2', () => {
|
|||
},
|
||||
})
|
||||
|
||||
const counterAccount = await program.account.counter(counter.publicKey)
|
||||
const counterAccount = await program.account.counter.fetch(counter.publicKey)
|
||||
|
||||
assert.ok(counterAccount.authority.equals(provider.wallet.publicKey))
|
||||
assert.ok(counterAccount.count.toNumber() == 1)
|
||||
|
|
|
@ -41,7 +41,7 @@ describe("basic-3", () => {
|
|||
});
|
||||
|
||||
// Check the state updated.
|
||||
puppetAccount = await puppet.account.puppet(newPuppetAccount.publicKey);
|
||||
puppetAccount = await puppet.account.puppet.fetch(newPuppetAccount.publicKey);
|
||||
assert.ok(puppetAccount.data.eq(new anchor.BN(111)));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@ describe("basic-4", () => {
|
|||
|
||||
// Fetch the state struct from the network.
|
||||
// #region accessor
|
||||
const state = await program.state();
|
||||
const state = await program.state.fetch();
|
||||
// #endregion accessor
|
||||
|
||||
assert.ok(state.count.eq(new anchor.BN(0)));
|
||||
|
@ -35,7 +35,7 @@ describe("basic-4", () => {
|
|||
},
|
||||
});
|
||||
// #endregion instruction
|
||||
const state = await program.state();
|
||||
const state = await program.state.fetch();
|
||||
assert.ok(state.count.eq(new anchor.BN(1)));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ describe("zero-copy", () => {
|
|||
authority: program.provider.wallet.publicKey,
|
||||
},
|
||||
});
|
||||
const state = await program.state();
|
||||
const state = await program.state.fetch();
|
||||
assert.ok(state.authority.equals(program.provider.wallet.publicKey));
|
||||
assert.ok(state.events.length === 250);
|
||||
state.events.forEach((event, idx) => {
|
||||
|
@ -36,7 +36,7 @@ describe("zero-copy", () => {
|
|||
authority: program.provider.wallet.publicKey,
|
||||
},
|
||||
});
|
||||
const state = await program.state();
|
||||
const state = await program.state.fetch();
|
||||
assert.ok(state.authority.equals(program.provider.wallet.publicKey));
|
||||
assert.ok(state.events.length === 250);
|
||||
state.events.forEach((event, idx) => {
|
||||
|
@ -60,7 +60,7 @@ describe("zero-copy", () => {
|
|||
instructions: [await program.account.foo.createInstruction(foo)],
|
||||
signers: [foo],
|
||||
});
|
||||
const account = await program.account.foo(foo.publicKey);
|
||||
const account = await program.account.foo.fetch(foo.publicKey);
|
||||
assert.ok(
|
||||
JSON.stringify(account.authority.toBuffer()) ===
|
||||
JSON.stringify(program.provider.wallet.publicKey.toBuffer())
|
||||
|
@ -81,7 +81,7 @@ describe("zero-copy", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const account = await program.account.foo(foo.publicKey);
|
||||
const account = await program.account.foo.fetch(foo.publicKey);
|
||||
|
||||
assert.ok(
|
||||
JSON.stringify(account.authority.toBuffer()) ===
|
||||
|
@ -103,7 +103,7 @@ describe("zero-copy", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const account = await program.account.foo(foo.publicKey);
|
||||
const account = await program.account.foo.fetch(foo.publicKey);
|
||||
|
||||
assert.ok(
|
||||
JSON.stringify(account.authority.toBuffer()) ===
|
||||
|
@ -172,7 +172,7 @@ describe("zero-copy", () => {
|
|||
],
|
||||
signers: [eventQ],
|
||||
});
|
||||
const account = await program.account.eventQ(eventQ.publicKey);
|
||||
const account = await program.account.eventQ.fetch(eventQ.publicKey);
|
||||
assert.ok(account.events.length === 25000);
|
||||
account.events.forEach((event) => {
|
||||
assert.ok(event.from.equals(new PublicKey()));
|
||||
|
@ -189,7 +189,7 @@ describe("zero-copy", () => {
|
|||
},
|
||||
});
|
||||
// Verify update.
|
||||
let account = await program.account.eventQ(eventQ.publicKey);
|
||||
let account = await program.account.eventQ.fetch(eventQ.publicKey);
|
||||
assert.ok(account.events.length === 25000);
|
||||
account.events.forEach((event, idx) => {
|
||||
if (idx === 0) {
|
||||
|
@ -209,7 +209,7 @@ describe("zero-copy", () => {
|
|||
},
|
||||
});
|
||||
// Verify update.
|
||||
account = await program.account.eventQ(eventQ.publicKey);
|
||||
account = await program.account.eventQ.fetch(eventQ.publicKey);
|
||||
assert.ok(account.events.length === 25000);
|
||||
account.events.forEach((event, idx) => {
|
||||
if (idx === 0) {
|
||||
|
@ -232,7 +232,7 @@ describe("zero-copy", () => {
|
|||
},
|
||||
});
|
||||
// Verify update.
|
||||
account = await program.account.eventQ(eventQ.publicKey);
|
||||
account = await program.account.eventQ.fetch(eventQ.publicKey);
|
||||
assert.ok(account.events.length === 25000);
|
||||
account.events.forEach((event, idx) => {
|
||||
if (idx === 0) {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"lint:fix": "prettier src/** -w",
|
||||
"watch": "tsc -p tsconfig.cjs.json --watch",
|
||||
"prepublishOnly": "yarn build",
|
||||
"docs": "typedoc --excludePrivate --includeVersion --out ../docs/src/.vuepress/dist/ts/ src/index.ts"
|
||||
"docs": "typedoc --excludePrivate --includeVersion --out ../docs/src/.vuepress/dist/ts/ --readme none src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@project-serum/borsh": "^0.2.2",
|
||||
|
|
571
ts/src/coder.ts
571
ts/src/coder.ts
|
@ -1,571 +0,0 @@
|
|||
import camelCase from "camelcase";
|
||||
import * as base64 from "base64-js";
|
||||
import { snakeCase } from "snake-case";
|
||||
import { Layout } from "buffer-layout";
|
||||
import * as sha256 from "js-sha256";
|
||||
import * as borsh from "@project-serum/borsh";
|
||||
import {
|
||||
Idl,
|
||||
IdlField,
|
||||
IdlTypeDef,
|
||||
IdlEnumVariant,
|
||||
IdlType,
|
||||
IdlStateMethod,
|
||||
} from "./idl";
|
||||
import { IdlError } from "./error";
|
||||
import { Event } from "./program/event";
|
||||
|
||||
/**
|
||||
* Number of bytes of the account discriminator.
|
||||
*/
|
||||
export const ACCOUNT_DISCRIMINATOR_SIZE = 8;
|
||||
/**
|
||||
* Namespace for state method function signatures.
|
||||
*/
|
||||
export const SIGHASH_STATE_NAMESPACE = "state";
|
||||
/**
|
||||
* Namespace for global instruction function signatures (i.e. functions
|
||||
* that aren't namespaced by the state or any of its trait implementations).
|
||||
*/
|
||||
export const SIGHASH_GLOBAL_NAMESPACE = "global";
|
||||
|
||||
/**
|
||||
* Coder provides a facade for encoding and decoding all IDL related objects.
|
||||
*/
|
||||
export default class Coder {
|
||||
/**
|
||||
* Instruction coder.
|
||||
*/
|
||||
readonly instruction: InstructionCoder;
|
||||
|
||||
/**
|
||||
* Account coder.
|
||||
*/
|
||||
readonly accounts: AccountsCoder;
|
||||
|
||||
/**
|
||||
* Types coder.
|
||||
*/
|
||||
readonly types: TypesCoder;
|
||||
|
||||
/**
|
||||
* Coder for state structs.
|
||||
*/
|
||||
readonly state: StateCoder;
|
||||
|
||||
/**
|
||||
* Coder for events.
|
||||
*/
|
||||
readonly events: EventCoder;
|
||||
|
||||
constructor(idl: Idl) {
|
||||
this.instruction = new InstructionCoder(idl);
|
||||
this.accounts = new AccountsCoder(idl);
|
||||
this.types = new TypesCoder(idl);
|
||||
this.events = new EventCoder(idl);
|
||||
if (idl.state) {
|
||||
this.state = new StateCoder(idl);
|
||||
}
|
||||
}
|
||||
|
||||
public sighash(nameSpace: string, ixName: string): Buffer {
|
||||
return sighash(nameSpace, ixName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes and decodes program instructions.
|
||||
*/
|
||||
class InstructionCoder {
|
||||
/**
|
||||
* Instruction args layout. Maps namespaced method
|
||||
*/
|
||||
private ixLayout: Map<string, Layout>;
|
||||
|
||||
public constructor(idl: Idl) {
|
||||
this.ixLayout = InstructionCoder.parseIxLayout(idl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a program instruction.
|
||||
*/
|
||||
public encode(ixName: string, ix: any) {
|
||||
return this._encode(SIGHASH_GLOBAL_NAMESPACE, ixName, ix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a program state instruction.
|
||||
*/
|
||||
public encodeState(ixName: string, ix: any) {
|
||||
return this._encode(SIGHASH_STATE_NAMESPACE, ixName, ix);
|
||||
}
|
||||
|
||||
private _encode(nameSpace: string, ixName: string, ix: any): Buffer {
|
||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||
const methodName = camelCase(ixName);
|
||||
const len = this.ixLayout.get(methodName).encode(ix, buffer);
|
||||
const data = buffer.slice(0, len);
|
||||
return Buffer.concat([sighash(nameSpace, ixName), data]);
|
||||
}
|
||||
|
||||
private static parseIxLayout(idl: Idl): Map<string, Layout> {
|
||||
const stateMethods = idl.state ? idl.state.methods : [];
|
||||
|
||||
const ixLayouts = stateMethods
|
||||
.map((m: IdlStateMethod) => {
|
||||
let fieldLayouts = m.args.map((arg: IdlField) => {
|
||||
return IdlCoder.fieldLayout(arg, idl.types);
|
||||
});
|
||||
const name = camelCase(m.name);
|
||||
return [name, borsh.struct(fieldLayouts, name)];
|
||||
})
|
||||
.concat(
|
||||
idl.instructions.map((ix) => {
|
||||
let fieldLayouts = ix.args.map((arg: IdlField) =>
|
||||
IdlCoder.fieldLayout(arg, idl.types)
|
||||
);
|
||||
const name = camelCase(ix.name);
|
||||
return [name, borsh.struct(fieldLayouts, name)];
|
||||
})
|
||||
);
|
||||
// @ts-ignore
|
||||
return new Map(ixLayouts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes and decodes account objects.
|
||||
*/
|
||||
class AccountsCoder {
|
||||
/**
|
||||
* Maps account type identifier to a layout.
|
||||
*/
|
||||
private accountLayouts: Map<string, Layout>;
|
||||
|
||||
public constructor(idl: Idl) {
|
||||
if (idl.accounts === undefined) {
|
||||
this.accountLayouts = new Map();
|
||||
return;
|
||||
}
|
||||
const layouts: [string, Layout][] = idl.accounts.map((acc) => {
|
||||
return [acc.name, IdlCoder.typeDefLayout(acc, idl.types)];
|
||||
});
|
||||
|
||||
this.accountLayouts = new Map(layouts);
|
||||
}
|
||||
|
||||
public async encode<T = any>(
|
||||
accountName: string,
|
||||
account: T
|
||||
): Promise<Buffer> {
|
||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||
const layout = this.accountLayouts.get(accountName);
|
||||
const len = layout.encode(account, buffer);
|
||||
let accountData = buffer.slice(0, len);
|
||||
let discriminator = await accountDiscriminator(accountName);
|
||||
return Buffer.concat([discriminator, accountData]);
|
||||
}
|
||||
|
||||
public decode<T = any>(accountName: string, ix: Buffer): T {
|
||||
// Chop off the discriminator before decoding.
|
||||
const data = ix.slice(8);
|
||||
const layout = this.accountLayouts.get(accountName);
|
||||
return layout.decode(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes and decodes user defined types.
|
||||
*/
|
||||
class TypesCoder {
|
||||
/**
|
||||
* Maps account type identifier to a layout.
|
||||
*/
|
||||
private layouts: Map<string, Layout>;
|
||||
|
||||
public constructor(idl: Idl) {
|
||||
if (idl.types === undefined) {
|
||||
this.layouts = new Map();
|
||||
return;
|
||||
}
|
||||
const layouts = idl.types.map((acc) => {
|
||||
return [acc.name, IdlCoder.typeDefLayout(acc, idl.types)];
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
this.layouts = new Map(layouts);
|
||||
}
|
||||
|
||||
public encode<T = any>(accountName: string, account: T): Buffer {
|
||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||
const layout = this.layouts.get(accountName);
|
||||
const len = layout.encode(account, buffer);
|
||||
return buffer.slice(0, len);
|
||||
}
|
||||
|
||||
public decode<T = any>(accountName: string, ix: Buffer): T {
|
||||
const layout = this.layouts.get(accountName);
|
||||
return layout.decode(ix);
|
||||
}
|
||||
}
|
||||
|
||||
class EventCoder {
|
||||
/**
|
||||
* Maps account type identifier to a layout.
|
||||
*/
|
||||
private layouts: Map<string, Layout>;
|
||||
|
||||
/**
|
||||
* Maps base64 encoded event discriminator to event name.
|
||||
*/
|
||||
private discriminators: Map<string, string>;
|
||||
|
||||
public constructor(idl: Idl) {
|
||||
if (idl.events === undefined) {
|
||||
this.layouts = new Map();
|
||||
return;
|
||||
}
|
||||
const layouts = idl.events.map((event) => {
|
||||
let eventTypeDef: IdlTypeDef = {
|
||||
name: event.name,
|
||||
type: {
|
||||
kind: "struct",
|
||||
fields: event.fields.map((f) => {
|
||||
return { name: f.name, type: f.type };
|
||||
}),
|
||||
},
|
||||
};
|
||||
return [event.name, IdlCoder.typeDefLayout(eventTypeDef, idl.types)];
|
||||
});
|
||||
// @ts-ignore
|
||||
this.layouts = new Map(layouts);
|
||||
|
||||
this.discriminators = new Map<string, string>(
|
||||
idl.events === undefined
|
||||
? []
|
||||
: idl.events.map((e) => [
|
||||
base64.fromByteArray(eventDiscriminator(e.name)),
|
||||
e.name,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
public decode(log: string): Event | null {
|
||||
const logArr = Buffer.from(base64.toByteArray(log));
|
||||
const disc = base64.fromByteArray(logArr.slice(0, 8));
|
||||
|
||||
// Only deserialize if the discriminator implies a proper event.
|
||||
const eventName = this.discriminators.get(disc);
|
||||
if (eventName === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const layout = this.layouts.get(eventName);
|
||||
const data = layout.decode(logArr.slice(8));
|
||||
return { data, name: eventName };
|
||||
}
|
||||
}
|
||||
|
||||
class StateCoder {
|
||||
private layout: Layout;
|
||||
|
||||
public constructor(idl: Idl) {
|
||||
if (idl.state === undefined) {
|
||||
throw new Error("Idl state not defined.");
|
||||
}
|
||||
this.layout = IdlCoder.typeDefLayout(idl.state.struct, idl.types);
|
||||
}
|
||||
|
||||
public async encode<T = any>(name: string, account: T): Promise<Buffer> {
|
||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||
const len = this.layout.encode(account, buffer);
|
||||
|
||||
const disc = await stateDiscriminator(name);
|
||||
const accData = buffer.slice(0, len);
|
||||
|
||||
return Buffer.concat([disc, accData]);
|
||||
}
|
||||
|
||||
public decode<T = any>(ix: Buffer): T {
|
||||
// Chop off discriminator.
|
||||
const data = ix.slice(8);
|
||||
return this.layout.decode(data);
|
||||
}
|
||||
}
|
||||
|
||||
class IdlCoder {
|
||||
public static fieldLayout(field: IdlField, types?: IdlTypeDef[]): Layout {
|
||||
const fieldName =
|
||||
field.name !== undefined ? camelCase(field.name) : undefined;
|
||||
switch (field.type) {
|
||||
case "bool": {
|
||||
return borsh.bool(fieldName);
|
||||
}
|
||||
case "u8": {
|
||||
return borsh.u8(fieldName);
|
||||
}
|
||||
case "i8": {
|
||||
return borsh.i8(fieldName);
|
||||
}
|
||||
case "u16": {
|
||||
return borsh.u16(fieldName);
|
||||
}
|
||||
case "i16": {
|
||||
return borsh.i16(fieldName);
|
||||
}
|
||||
case "u32": {
|
||||
return borsh.u32(fieldName);
|
||||
}
|
||||
case "i32": {
|
||||
return borsh.i32(fieldName);
|
||||
}
|
||||
case "u64": {
|
||||
return borsh.u64(fieldName);
|
||||
}
|
||||
case "i64": {
|
||||
return borsh.i64(fieldName);
|
||||
}
|
||||
case "u128": {
|
||||
return borsh.u128(fieldName);
|
||||
}
|
||||
case "i128": {
|
||||
return borsh.i128(fieldName);
|
||||
}
|
||||
case "bytes": {
|
||||
return borsh.vecU8(fieldName);
|
||||
}
|
||||
case "string": {
|
||||
return borsh.str(fieldName);
|
||||
}
|
||||
case "publicKey": {
|
||||
return borsh.publicKey(fieldName);
|
||||
}
|
||||
default: {
|
||||
// @ts-ignore
|
||||
if (field.type.vec) {
|
||||
return borsh.vec(
|
||||
IdlCoder.fieldLayout(
|
||||
{
|
||||
name: undefined,
|
||||
// @ts-ignore
|
||||
type: field.type.vec,
|
||||
},
|
||||
types
|
||||
),
|
||||
fieldName
|
||||
);
|
||||
// @ts-ignore
|
||||
} else if (field.type.option) {
|
||||
return borsh.option(
|
||||
IdlCoder.fieldLayout(
|
||||
{
|
||||
name: undefined,
|
||||
// @ts-ignore
|
||||
type: field.type.option,
|
||||
},
|
||||
types
|
||||
),
|
||||
fieldName
|
||||
);
|
||||
// @ts-ignore
|
||||
} else if (field.type.defined) {
|
||||
// User defined type.
|
||||
if (types === undefined) {
|
||||
throw new IdlError("User defined types not provided");
|
||||
}
|
||||
// @ts-ignore
|
||||
const filtered = types.filter((t) => t.name === field.type.defined);
|
||||
if (filtered.length !== 1) {
|
||||
throw new IdlError(`Type not found: ${JSON.stringify(field)}`);
|
||||
}
|
||||
return IdlCoder.typeDefLayout(filtered[0], types, fieldName);
|
||||
// @ts-ignore
|
||||
} else if (field.type.array) {
|
||||
// @ts-ignore
|
||||
let arrayTy = field.type.array[0];
|
||||
// @ts-ignore
|
||||
let arrayLen = field.type.array[1];
|
||||
let innerLayout = IdlCoder.fieldLayout(
|
||||
{
|
||||
name: undefined,
|
||||
type: arrayTy,
|
||||
},
|
||||
types
|
||||
);
|
||||
return borsh.array(innerLayout, arrayLen, fieldName);
|
||||
} else {
|
||||
throw new Error(`Not yet implemented: ${field}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static typeDefLayout(
|
||||
typeDef: IdlTypeDef,
|
||||
types: IdlTypeDef[],
|
||||
name?: string
|
||||
): Layout {
|
||||
if (typeDef.type.kind === "struct") {
|
||||
const fieldLayouts = typeDef.type.fields.map((field) => {
|
||||
const x = IdlCoder.fieldLayout(field, types);
|
||||
return x;
|
||||
});
|
||||
return borsh.struct(fieldLayouts, name);
|
||||
} else if (typeDef.type.kind === "enum") {
|
||||
let variants = typeDef.type.variants.map((variant: IdlEnumVariant) => {
|
||||
const name = camelCase(variant.name);
|
||||
if (variant.fields === undefined) {
|
||||
return borsh.struct([], name);
|
||||
}
|
||||
// @ts-ignore
|
||||
const fieldLayouts = variant.fields.map((f: IdlField | IdlType) => {
|
||||
// @ts-ignore
|
||||
if (f.name === undefined) {
|
||||
throw new Error("Tuple enum variants not yet implemented.");
|
||||
}
|
||||
// @ts-ignore
|
||||
return IdlCoder.fieldLayout(f, types);
|
||||
});
|
||||
return borsh.struct(fieldLayouts, name);
|
||||
});
|
||||
|
||||
if (name !== undefined) {
|
||||
// Buffer-layout lib requires the name to be null (on construction)
|
||||
// when used as a field.
|
||||
return borsh.rustEnum(variants).replicate(name);
|
||||
}
|
||||
|
||||
return borsh.rustEnum(variants, name);
|
||||
} else {
|
||||
throw new Error(`Unknown type kint: ${typeDef}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculates unique 8 byte discriminator prepended to all anchor accounts.
|
||||
export async function accountDiscriminator(name: string): Promise<Buffer> {
|
||||
// @ts-ignore
|
||||
return Buffer.from(sha256.digest(`account:${name}`)).slice(0, 8);
|
||||
}
|
||||
|
||||
// Calculates unique 8 byte discriminator prepended to all anchor state accounts.
|
||||
export async function stateDiscriminator(name: string): Promise<Buffer> {
|
||||
// @ts-ignore
|
||||
return Buffer.from(sha256.digest(`state:${name}`)).slice(0, 8);
|
||||
}
|
||||
|
||||
export function eventDiscriminator(name: string): Buffer {
|
||||
// @ts-ignore
|
||||
return Buffer.from(sha256.digest(`event:${name}`)).slice(0, 8);
|
||||
}
|
||||
|
||||
// Returns the size of the type in bytes. For variable length types, just return
|
||||
// 1. Users should override this value in such cases.
|
||||
function typeSize(idl: Idl, ty: IdlType): number {
|
||||
switch (ty) {
|
||||
case "bool":
|
||||
return 1;
|
||||
case "u8":
|
||||
return 1;
|
||||
case "i8":
|
||||
return 1;
|
||||
case "i16":
|
||||
return 2;
|
||||
case "u16":
|
||||
return 2;
|
||||
case "u32":
|
||||
return 4;
|
||||
case "i32":
|
||||
return 4;
|
||||
case "u64":
|
||||
return 8;
|
||||
case "i64":
|
||||
return 8;
|
||||
case "u128":
|
||||
return 16;
|
||||
case "i128":
|
||||
return 16;
|
||||
case "bytes":
|
||||
return 1;
|
||||
case "string":
|
||||
return 1;
|
||||
case "publicKey":
|
||||
return 32;
|
||||
default:
|
||||
// @ts-ignore
|
||||
if (ty.vec !== undefined) {
|
||||
return 1;
|
||||
}
|
||||
// @ts-ignore
|
||||
if (ty.option !== undefined) {
|
||||
// @ts-ignore
|
||||
return 1 + typeSize(idl, ty.option);
|
||||
}
|
||||
// @ts-ignore
|
||||
if (ty.defined !== undefined) {
|
||||
// @ts-ignore
|
||||
const filtered = idl.types.filter((t) => t.name === ty.defined);
|
||||
if (filtered.length !== 1) {
|
||||
throw new IdlError(`Type not found: ${JSON.stringify(ty)}`);
|
||||
}
|
||||
let typeDef = filtered[0];
|
||||
|
||||
return accountSize(idl, typeDef);
|
||||
}
|
||||
// @ts-ignore
|
||||
if (ty.array !== undefined) {
|
||||
// @ts-ignore
|
||||
let arrayTy = ty.array[0];
|
||||
// @ts-ignore
|
||||
let arraySize = ty.array[1];
|
||||
// @ts-ignore
|
||||
return typeSize(idl, arrayTy) * arraySize;
|
||||
}
|
||||
throw new Error(`Invalid type ${JSON.stringify(ty)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function accountSize(
|
||||
idl: Idl,
|
||||
idlAccount: IdlTypeDef
|
||||
): number | undefined {
|
||||
if (idlAccount.type.kind === "enum") {
|
||||
let variantSizes = idlAccount.type.variants.map(
|
||||
(variant: IdlEnumVariant) => {
|
||||
if (variant.fields === undefined) {
|
||||
return 0;
|
||||
}
|
||||
// @ts-ignore
|
||||
return (
|
||||
variant.fields
|
||||
// @ts-ignore
|
||||
.map((f: IdlField | IdlType) => {
|
||||
// @ts-ignore
|
||||
if (f.name === undefined) {
|
||||
throw new Error("Tuple enum variants not yet implemented.");
|
||||
}
|
||||
// @ts-ignore
|
||||
return typeSize(idl, f.type);
|
||||
})
|
||||
.reduce((a: number, b: number) => a + b)
|
||||
);
|
||||
}
|
||||
);
|
||||
return Math.max(...variantSizes) + 1;
|
||||
}
|
||||
if (idlAccount.type.fields === undefined) {
|
||||
return 0;
|
||||
}
|
||||
return idlAccount.type.fields
|
||||
.map((f) => typeSize(idl, f.type))
|
||||
.reduce((a, b) => a + b);
|
||||
}
|
||||
|
||||
// Not technically sighash, since we don't include the arguments, as Rust
|
||||
// doesn't allow function overloading.
|
||||
function sighash(nameSpace: string, ixName: string): Buffer {
|
||||
let name = snakeCase(ixName);
|
||||
let preimage = `${nameSpace}:${name}`;
|
||||
// @ts-ignore
|
||||
return Buffer.from(sha256.digest(preimage)).slice(0, 8);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import { Layout } from "buffer-layout";
|
||||
import { Idl } from "../idl";
|
||||
import { IdlCoder } from "./idl";
|
||||
import { sha256 } from "js-sha256";
|
||||
|
||||
/**
|
||||
* Number of bytes of the account discriminator.
|
||||
*/
|
||||
export const ACCOUNT_DISCRIMINATOR_SIZE = 8;
|
||||
|
||||
/**
|
||||
* Encodes and decodes account objects.
|
||||
*/
|
||||
export class AccountsCoder {
|
||||
/**
|
||||
* Maps account type identifier to a layout.
|
||||
*/
|
||||
private accountLayouts: Map<string, Layout>;
|
||||
|
||||
public constructor(idl: Idl) {
|
||||
if (idl.accounts === undefined) {
|
||||
this.accountLayouts = new Map();
|
||||
return;
|
||||
}
|
||||
const layouts: [string, Layout][] = idl.accounts.map((acc) => {
|
||||
return [acc.name, IdlCoder.typeDefLayout(acc, idl.types)];
|
||||
});
|
||||
|
||||
this.accountLayouts = new Map(layouts);
|
||||
}
|
||||
|
||||
public async encode<T = any>(
|
||||
accountName: string,
|
||||
account: T
|
||||
): Promise<Buffer> {
|
||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||
const layout = this.accountLayouts.get(accountName);
|
||||
const len = layout.encode(account, buffer);
|
||||
let accountData = buffer.slice(0, len);
|
||||
let discriminator = await accountDiscriminator(accountName);
|
||||
return Buffer.concat([discriminator, accountData]);
|
||||
}
|
||||
|
||||
public decode<T = any>(accountName: string, ix: Buffer): T {
|
||||
// Chop off the discriminator before decoding.
|
||||
const data = ix.slice(8);
|
||||
const layout = this.accountLayouts.get(accountName);
|
||||
return layout.decode(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculates unique 8 byte discriminator prepended to all anchor accounts.
|
||||
export async function accountDiscriminator(name: string): Promise<Buffer> {
|
||||
return Buffer.from(sha256.digest(`account:${name}`)).slice(0, 8);
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
import { snakeCase } from "snake-case";
|
||||
import { sha256 } from "js-sha256";
|
||||
import { Idl, IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl";
|
||||
import { IdlError } from "../error";
|
||||
|
||||
export function accountSize(
|
||||
idl: Idl,
|
||||
idlAccount: IdlTypeDef
|
||||
): number | undefined {
|
||||
if (idlAccount.type.kind === "enum") {
|
||||
let variantSizes = idlAccount.type.variants.map(
|
||||
(variant: IdlEnumVariant) => {
|
||||
if (variant.fields === undefined) {
|
||||
return 0;
|
||||
}
|
||||
return (
|
||||
variant.fields
|
||||
// @ts-ignore
|
||||
.map((f: IdlField | IdlType) => {
|
||||
// @ts-ignore
|
||||
if (f.name === undefined) {
|
||||
throw new Error("Tuple enum variants not yet implemented.");
|
||||
}
|
||||
// @ts-ignore
|
||||
return typeSize(idl, f.type);
|
||||
})
|
||||
.reduce((a: number, b: number) => a + b)
|
||||
);
|
||||
}
|
||||
);
|
||||
return Math.max(...variantSizes) + 1;
|
||||
}
|
||||
if (idlAccount.type.fields === undefined) {
|
||||
return 0;
|
||||
}
|
||||
return idlAccount.type.fields
|
||||
.map((f) => typeSize(idl, f.type))
|
||||
.reduce((a, b) => a + b);
|
||||
}
|
||||
|
||||
// Returns the size of the type in bytes. For variable length types, just return
|
||||
// 1. Users should override this value in such cases.
|
||||
function typeSize(idl: Idl, ty: IdlType): number {
|
||||
switch (ty) {
|
||||
case "bool":
|
||||
return 1;
|
||||
case "u8":
|
||||
return 1;
|
||||
case "i8":
|
||||
return 1;
|
||||
case "i16":
|
||||
return 2;
|
||||
case "u16":
|
||||
return 2;
|
||||
case "u32":
|
||||
return 4;
|
||||
case "i32":
|
||||
return 4;
|
||||
case "u64":
|
||||
return 8;
|
||||
case "i64":
|
||||
return 8;
|
||||
case "u128":
|
||||
return 16;
|
||||
case "i128":
|
||||
return 16;
|
||||
case "bytes":
|
||||
return 1;
|
||||
case "string":
|
||||
return 1;
|
||||
case "publicKey":
|
||||
return 32;
|
||||
default:
|
||||
// @ts-ignore
|
||||
if (ty.vec !== undefined) {
|
||||
return 1;
|
||||
}
|
||||
// @ts-ignore
|
||||
if (ty.option !== undefined) {
|
||||
// @ts-ignore
|
||||
return 1 + typeSize(idl, ty.option);
|
||||
}
|
||||
// @ts-ignore
|
||||
if (ty.defined !== undefined) {
|
||||
// @ts-ignore
|
||||
const filtered = idl.types.filter((t) => t.name === ty.defined);
|
||||
if (filtered.length !== 1) {
|
||||
throw new IdlError(`Type not found: ${JSON.stringify(ty)}`);
|
||||
}
|
||||
let typeDef = filtered[0];
|
||||
|
||||
return accountSize(idl, typeDef);
|
||||
}
|
||||
// @ts-ignore
|
||||
if (ty.array !== undefined) {
|
||||
// @ts-ignore
|
||||
let arrayTy = ty.array[0];
|
||||
// @ts-ignore
|
||||
let arraySize = ty.array[1];
|
||||
// @ts-ignore
|
||||
return typeSize(idl, arrayTy) * arraySize;
|
||||
}
|
||||
throw new Error(`Invalid type ${JSON.stringify(ty)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Not technically sighash, since we don't include the arguments, as Rust
|
||||
// doesn't allow function overloading.
|
||||
export function sighash(nameSpace: string, ixName: string): Buffer {
|
||||
let name = snakeCase(ixName);
|
||||
let preimage = `${nameSpace}:${name}`;
|
||||
return Buffer.from(sha256.digest(preimage)).slice(0, 8);
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import * as base64 from "base64-js";
|
||||
import { Layout } from "buffer-layout";
|
||||
import { sha256 } from "js-sha256";
|
||||
import { Idl, IdlTypeDef } from "../idl";
|
||||
import { Event } from "../program/event";
|
||||
import { IdlCoder } from "./idl";
|
||||
|
||||
export class EventCoder {
|
||||
/**
|
||||
* Maps account type identifier to a layout.
|
||||
*/
|
||||
private layouts: Map<string, Layout>;
|
||||
|
||||
/**
|
||||
* Maps base64 encoded event discriminator to event name.
|
||||
*/
|
||||
private discriminators: Map<string, string>;
|
||||
|
||||
public constructor(idl: Idl) {
|
||||
if (idl.events === undefined) {
|
||||
this.layouts = new Map();
|
||||
return;
|
||||
}
|
||||
const layouts = idl.events.map((event) => {
|
||||
let eventTypeDef: IdlTypeDef = {
|
||||
name: event.name,
|
||||
type: {
|
||||
kind: "struct",
|
||||
fields: event.fields.map((f) => {
|
||||
return { name: f.name, type: f.type };
|
||||
}),
|
||||
},
|
||||
};
|
||||
return [event.name, IdlCoder.typeDefLayout(eventTypeDef, idl.types)];
|
||||
});
|
||||
// @ts-ignore
|
||||
this.layouts = new Map(layouts);
|
||||
|
||||
this.discriminators = new Map<string, string>(
|
||||
idl.events === undefined
|
||||
? []
|
||||
: idl.events.map((e) => [
|
||||
base64.fromByteArray(eventDiscriminator(e.name)),
|
||||
e.name,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
public decode(log: string): Event | null {
|
||||
const logArr = Buffer.from(base64.toByteArray(log));
|
||||
const disc = base64.fromByteArray(logArr.slice(0, 8));
|
||||
|
||||
// Only deserialize if the discriminator implies a proper event.
|
||||
const eventName = this.discriminators.get(disc);
|
||||
if (eventName === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const layout = this.layouts.get(eventName);
|
||||
const data = layout.decode(logArr.slice(8));
|
||||
return { data, name: eventName };
|
||||
}
|
||||
}
|
||||
|
||||
export function eventDiscriminator(name: string): Buffer {
|
||||
return Buffer.from(sha256.digest(`event:${name}`)).slice(0, 8);
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
import camelCase from "camelcase";
|
||||
import { Layout } from "buffer-layout";
|
||||
import * as borsh from "@project-serum/borsh";
|
||||
import { IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl";
|
||||
import { IdlError } from "../error";
|
||||
|
||||
export class IdlCoder {
|
||||
public static fieldLayout(field: IdlField, types?: IdlTypeDef[]): Layout {
|
||||
const fieldName =
|
||||
field.name !== undefined ? camelCase(field.name) : undefined;
|
||||
switch (field.type) {
|
||||
case "bool": {
|
||||
return borsh.bool(fieldName);
|
||||
}
|
||||
case "u8": {
|
||||
return borsh.u8(fieldName);
|
||||
}
|
||||
case "i8": {
|
||||
return borsh.i8(fieldName);
|
||||
}
|
||||
case "u16": {
|
||||
return borsh.u16(fieldName);
|
||||
}
|
||||
case "i16": {
|
||||
return borsh.i16(fieldName);
|
||||
}
|
||||
case "u32": {
|
||||
return borsh.u32(fieldName);
|
||||
}
|
||||
case "i32": {
|
||||
return borsh.i32(fieldName);
|
||||
}
|
||||
case "u64": {
|
||||
return borsh.u64(fieldName);
|
||||
}
|
||||
case "i64": {
|
||||
return borsh.i64(fieldName);
|
||||
}
|
||||
case "u128": {
|
||||
return borsh.u128(fieldName);
|
||||
}
|
||||
case "i128": {
|
||||
return borsh.i128(fieldName);
|
||||
}
|
||||
case "bytes": {
|
||||
return borsh.vecU8(fieldName);
|
||||
}
|
||||
case "string": {
|
||||
return borsh.str(fieldName);
|
||||
}
|
||||
case "publicKey": {
|
||||
return borsh.publicKey(fieldName);
|
||||
}
|
||||
default: {
|
||||
// @ts-ignore
|
||||
if (field.type.vec) {
|
||||
return borsh.vec(
|
||||
IdlCoder.fieldLayout(
|
||||
{
|
||||
name: undefined,
|
||||
// @ts-ignore
|
||||
type: field.type.vec,
|
||||
},
|
||||
types
|
||||
),
|
||||
fieldName
|
||||
);
|
||||
// @ts-ignore
|
||||
} else if (field.type.option) {
|
||||
return borsh.option(
|
||||
IdlCoder.fieldLayout(
|
||||
{
|
||||
name: undefined,
|
||||
// @ts-ignore
|
||||
type: field.type.option,
|
||||
},
|
||||
types
|
||||
),
|
||||
fieldName
|
||||
);
|
||||
// @ts-ignore
|
||||
} else if (field.type.defined) {
|
||||
// User defined type.
|
||||
if (types === undefined) {
|
||||
throw new IdlError("User defined types not provided");
|
||||
}
|
||||
// @ts-ignore
|
||||
const filtered = types.filter((t) => t.name === field.type.defined);
|
||||
if (filtered.length !== 1) {
|
||||
throw new IdlError(`Type not found: ${JSON.stringify(field)}`);
|
||||
}
|
||||
return IdlCoder.typeDefLayout(filtered[0], types, fieldName);
|
||||
// @ts-ignore
|
||||
} else if (field.type.array) {
|
||||
// @ts-ignore
|
||||
let arrayTy = field.type.array[0];
|
||||
// @ts-ignore
|
||||
let arrayLen = field.type.array[1];
|
||||
let innerLayout = IdlCoder.fieldLayout(
|
||||
{
|
||||
name: undefined,
|
||||
type: arrayTy,
|
||||
},
|
||||
types
|
||||
);
|
||||
return borsh.array(innerLayout, arrayLen, fieldName);
|
||||
} else {
|
||||
throw new Error(`Not yet implemented: ${field}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static typeDefLayout(
|
||||
typeDef: IdlTypeDef,
|
||||
types: IdlTypeDef[],
|
||||
name?: string
|
||||
): Layout {
|
||||
if (typeDef.type.kind === "struct") {
|
||||
const fieldLayouts = typeDef.type.fields.map((field) => {
|
||||
const x = IdlCoder.fieldLayout(field, types);
|
||||
return x;
|
||||
});
|
||||
return borsh.struct(fieldLayouts, name);
|
||||
} else if (typeDef.type.kind === "enum") {
|
||||
let variants = typeDef.type.variants.map((variant: IdlEnumVariant) => {
|
||||
const name = camelCase(variant.name);
|
||||
if (variant.fields === undefined) {
|
||||
return borsh.struct([], name);
|
||||
}
|
||||
// @ts-ignore
|
||||
const fieldLayouts = variant.fields.map((f: IdlField | IdlType) => {
|
||||
// @ts-ignore
|
||||
if (f.name === undefined) {
|
||||
throw new Error("Tuple enum variants not yet implemented.");
|
||||
}
|
||||
// @ts-ignore
|
||||
return IdlCoder.fieldLayout(f, types);
|
||||
});
|
||||
return borsh.struct(fieldLayouts, name);
|
||||
});
|
||||
|
||||
if (name !== undefined) {
|
||||
// Buffer-layout lib requires the name to be null (on construction)
|
||||
// when used as a field.
|
||||
return borsh.rustEnum(variants).replicate(name);
|
||||
}
|
||||
|
||||
return borsh.rustEnum(variants, name);
|
||||
} else {
|
||||
throw new Error(`Unknown type kint: ${typeDef}`);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import { Idl } from "../idl";
|
||||
import { InstructionCoder } from "./instruction";
|
||||
import { AccountsCoder } from "./accounts";
|
||||
import { TypesCoder } from "./types";
|
||||
import { EventCoder } from "./event";
|
||||
import { StateCoder } from "./state";
|
||||
import { sighash } from "./common";
|
||||
|
||||
export { accountSize } from "./common";
|
||||
export { TypesCoder } from "./types";
|
||||
export { InstructionCoder } from "./instruction";
|
||||
export {
|
||||
AccountsCoder,
|
||||
accountDiscriminator,
|
||||
ACCOUNT_DISCRIMINATOR_SIZE,
|
||||
} from "./accounts";
|
||||
export { EventCoder, eventDiscriminator } from "./event";
|
||||
export { StateCoder, stateDiscriminator } from "./state";
|
||||
|
||||
/**
|
||||
* Coder provides a facade for encoding and decoding all IDL related objects.
|
||||
*/
|
||||
export default class Coder {
|
||||
/**
|
||||
* Instruction coder.
|
||||
*/
|
||||
readonly instruction: InstructionCoder;
|
||||
|
||||
/**
|
||||
* Account coder.
|
||||
*/
|
||||
readonly accounts: AccountsCoder;
|
||||
|
||||
/**
|
||||
* Types coder.
|
||||
*/
|
||||
readonly types: TypesCoder;
|
||||
|
||||
/**
|
||||
* Coder for state structs.
|
||||
*/
|
||||
readonly state: StateCoder;
|
||||
|
||||
/**
|
||||
* Coder for events.
|
||||
*/
|
||||
readonly events: EventCoder;
|
||||
|
||||
constructor(idl: Idl) {
|
||||
this.instruction = new InstructionCoder(idl);
|
||||
this.accounts = new AccountsCoder(idl);
|
||||
this.types = new TypesCoder(idl);
|
||||
this.events = new EventCoder(idl);
|
||||
if (idl.state) {
|
||||
this.state = new StateCoder(idl);
|
||||
}
|
||||
}
|
||||
|
||||
public sighash(nameSpace: string, ixName: string): Buffer {
|
||||
return sighash(nameSpace, ixName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
import camelCase from "camelcase";
|
||||
import { Layout } from "buffer-layout";
|
||||
import * as borsh from "@project-serum/borsh";
|
||||
import { Idl, IdlField, IdlStateMethod } from "../idl";
|
||||
import { IdlCoder } from "./idl";
|
||||
import { sighash } from "./common";
|
||||
|
||||
/**
|
||||
* Namespace for state method function signatures.
|
||||
*/
|
||||
export const SIGHASH_STATE_NAMESPACE = "state";
|
||||
/**
|
||||
* Namespace for global instruction function signatures (i.e. functions
|
||||
* that aren't namespaced by the state or any of its trait implementations).
|
||||
*/
|
||||
export const SIGHASH_GLOBAL_NAMESPACE = "global";
|
||||
|
||||
/**
|
||||
* Encodes and decodes program instructions.
|
||||
*/
|
||||
export class InstructionCoder {
|
||||
/**
|
||||
* Instruction args layout. Maps namespaced method
|
||||
*/
|
||||
private ixLayout: Map<string, Layout>;
|
||||
|
||||
public constructor(idl: Idl) {
|
||||
this.ixLayout = InstructionCoder.parseIxLayout(idl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a program instruction.
|
||||
*/
|
||||
public encode(ixName: string, ix: any) {
|
||||
return this._encode(SIGHASH_GLOBAL_NAMESPACE, ixName, ix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a program state instruction.
|
||||
*/
|
||||
public encodeState(ixName: string, ix: any) {
|
||||
return this._encode(SIGHASH_STATE_NAMESPACE, ixName, ix);
|
||||
}
|
||||
|
||||
private _encode(nameSpace: string, ixName: string, ix: any): Buffer {
|
||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||
const methodName = camelCase(ixName);
|
||||
const len = this.ixLayout.get(methodName).encode(ix, buffer);
|
||||
const data = buffer.slice(0, len);
|
||||
return Buffer.concat([sighash(nameSpace, ixName), data]);
|
||||
}
|
||||
|
||||
private static parseIxLayout(idl: Idl): Map<string, Layout> {
|
||||
const stateMethods = idl.state ? idl.state.methods : [];
|
||||
|
||||
const ixLayouts = stateMethods
|
||||
.map((m: IdlStateMethod) => {
|
||||
let fieldLayouts = m.args.map((arg: IdlField) => {
|
||||
return IdlCoder.fieldLayout(arg, idl.types);
|
||||
});
|
||||
const name = camelCase(m.name);
|
||||
return [name, borsh.struct(fieldLayouts, name)];
|
||||
})
|
||||
.concat(
|
||||
idl.instructions.map((ix) => {
|
||||
let fieldLayouts = ix.args.map((arg: IdlField) =>
|
||||
IdlCoder.fieldLayout(arg, idl.types)
|
||||
);
|
||||
const name = camelCase(ix.name);
|
||||
return [name, borsh.struct(fieldLayouts, name)];
|
||||
})
|
||||
);
|
||||
// @ts-ignore
|
||||
return new Map(ixLayouts);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import { Layout } from "buffer-layout";
|
||||
import { sha256 } from "js-sha256";
|
||||
import { Idl } from "../idl";
|
||||
import { IdlCoder } from "./idl";
|
||||
|
||||
export class StateCoder {
|
||||
private layout: Layout;
|
||||
|
||||
public constructor(idl: Idl) {
|
||||
if (idl.state === undefined) {
|
||||
throw new Error("Idl state not defined.");
|
||||
}
|
||||
this.layout = IdlCoder.typeDefLayout(idl.state.struct, idl.types);
|
||||
}
|
||||
|
||||
public async encode<T = any>(name: string, account: T): Promise<Buffer> {
|
||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||
const len = this.layout.encode(account, buffer);
|
||||
|
||||
const disc = await stateDiscriminator(name);
|
||||
const accData = buffer.slice(0, len);
|
||||
|
||||
return Buffer.concat([disc, accData]);
|
||||
}
|
||||
|
||||
public decode<T = any>(ix: Buffer): T {
|
||||
// Chop off discriminator.
|
||||
const data = ix.slice(8);
|
||||
return this.layout.decode(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculates unique 8 byte discriminator prepended to all anchor state accounts.
|
||||
export async function stateDiscriminator(name: string): Promise<Buffer> {
|
||||
return Buffer.from(sha256.digest(`state:${name}`)).slice(0, 8);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { Layout } from "buffer-layout";
|
||||
import { Idl } from "../idl";
|
||||
import { IdlCoder } from "./idl";
|
||||
|
||||
/**
|
||||
* Encodes and decodes user defined types.
|
||||
*/
|
||||
export class TypesCoder {
|
||||
/**
|
||||
* Maps account type identifier to a layout.
|
||||
*/
|
||||
private layouts: Map<string, Layout>;
|
||||
|
||||
public constructor(idl: Idl) {
|
||||
if (idl.types === undefined) {
|
||||
this.layouts = new Map();
|
||||
return;
|
||||
}
|
||||
const layouts = idl.types.map((acc) => {
|
||||
return [acc.name, IdlCoder.typeDefLayout(acc, idl.types)];
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
this.layouts = new Map(layouts);
|
||||
}
|
||||
|
||||
public encode<T = any>(accountName: string, account: T): Buffer {
|
||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||
const layout = this.layouts.get(accountName);
|
||||
const len = layout.encode(account, buffer);
|
||||
return buffer.slice(0, len);
|
||||
}
|
||||
|
||||
public decode<T = any>(accountName: string, ix: Buffer): T {
|
||||
const layout = this.layouts.get(accountName);
|
||||
return layout.decode(ix);
|
||||
}
|
||||
}
|
|
@ -29,8 +29,6 @@ export type IdlInstruction = {
|
|||
args: IdlField[];
|
||||
};
|
||||
|
||||
// IdlStateMethods are similar to instructions, except they only allow
|
||||
// for a single account, the state account.
|
||||
export type IdlState = {
|
||||
struct: IdlTypeDef;
|
||||
methods: IdlStateMethod[];
|
||||
|
@ -80,6 +78,8 @@ export type IdlType =
|
|||
| "i32"
|
||||
| "u64"
|
||||
| "i64"
|
||||
| "u128"
|
||||
| "i128"
|
||||
| "bytes"
|
||||
| "string"
|
||||
| "publicKey"
|
||||
|
|
|
@ -1,21 +1,46 @@
|
|||
import BN from "bn.js";
|
||||
import * as web3 from "@solana/web3.js";
|
||||
import Provider, { NodeWallet as Wallet } from "./provider";
|
||||
import Coder from "./coder";
|
||||
import Coder, {
|
||||
InstructionCoder,
|
||||
EventCoder,
|
||||
StateCoder,
|
||||
TypesCoder,
|
||||
} from "./coder";
|
||||
import { Idl } from "./idl";
|
||||
import workspace from "./workspace";
|
||||
import utils from "./utils";
|
||||
import { Program } from "./program";
|
||||
import { Address } from "./program/common";
|
||||
import { ProgramAccount } from "./program/namespace";
|
||||
import { Event } from "./program/event";
|
||||
import {
|
||||
ProgramAccount,
|
||||
AccountNamespace,
|
||||
AccountClient,
|
||||
StateClient,
|
||||
RpcNamespace,
|
||||
RpcFn,
|
||||
SimulateNamespace,
|
||||
SimulateFn,
|
||||
TransactionNamespace,
|
||||
TransactionFn,
|
||||
InstructionNamespace,
|
||||
InstructionFn,
|
||||
} from "./program/namespace";
|
||||
import { Context, Accounts } from "./program/context";
|
||||
|
||||
let _provider: Provider | null = null;
|
||||
|
||||
/**
|
||||
* Sets the default provider on the client.
|
||||
*/
|
||||
function setProvider(provider: Provider) {
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default provider being used by the client.
|
||||
*/
|
||||
function getProvider(): Provider {
|
||||
if (_provider === null) {
|
||||
return Provider.local();
|
||||
|
@ -26,10 +51,26 @@ function getProvider(): Provider {
|
|||
export {
|
||||
workspace,
|
||||
Program,
|
||||
AccountNamespace,
|
||||
AccountClient,
|
||||
StateClient,
|
||||
RpcNamespace,
|
||||
RpcFn,
|
||||
SimulateNamespace,
|
||||
SimulateFn,
|
||||
TransactionNamespace,
|
||||
TransactionFn,
|
||||
InstructionNamespace,
|
||||
InstructionFn,
|
||||
ProgramAccount,
|
||||
Context,
|
||||
Accounts,
|
||||
Coder,
|
||||
InstructionCoder,
|
||||
EventCoder,
|
||||
StateCoder,
|
||||
TypesCoder,
|
||||
Event,
|
||||
setProvider,
|
||||
getProvider,
|
||||
Provider,
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import EventEmitter from "eventemitter3";
|
||||
import * as bs58 from "bs58";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Idl, IdlInstruction, IdlAccountItem, IdlStateMethod } from "../idl";
|
||||
import { ProgramError } from "../error";
|
||||
import { Accounts } from "./context";
|
||||
import Provider from "../provider";
|
||||
|
||||
export type Subscription = {
|
||||
listener: number;
|
||||
|
|
|
@ -8,7 +8,7 @@ import NamespaceFactory, {
|
|||
InstructionNamespace,
|
||||
TransactionNamespace,
|
||||
AccountNamespace,
|
||||
StateNamespace,
|
||||
StateClient,
|
||||
SimulateNamespace,
|
||||
} from "./namespace";
|
||||
import { getProvider } from "../";
|
||||
|
@ -28,27 +28,29 @@ import { Address, translateAddress } from "./common";
|
|||
* changes, and listen to events.
|
||||
*
|
||||
* In addition to field accessors and methods, the object provides a set of
|
||||
* dynamically generated properties (internally referred to as namespaces) that
|
||||
* map one-to-one to program instructions and accounts. These namespaces
|
||||
* generally can be used as follows:
|
||||
* dynamically generated properties, also known as namespaces, that
|
||||
* map one-to-one to program methods and accounts. These namespaces generally
|
||||
* can be used as follows:
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```javascript
|
||||
* program.<namespace>.<program-specific-field>
|
||||
* program.<namespace>.<program-specific-method>
|
||||
* ```
|
||||
*
|
||||
* API specifics are namespace dependent. The examples used in the documentation
|
||||
* below will refer to the two counter examples found
|
||||
* [here](https://project-serum.github.io/anchor/ts/#examples).
|
||||
* [here](https://github.com/project-serum/anchor#examples).
|
||||
*/
|
||||
export class Program {
|
||||
/**
|
||||
* Async methods to send signed transactions invoking *non*-state methods
|
||||
* on an Anchor program.
|
||||
* Async methods to send signed transactions to *non*-state methods on the
|
||||
* program, returning a [[TransactionSignature]].
|
||||
*
|
||||
* ## rpc
|
||||
* ## Usage
|
||||
*
|
||||
* ```javascript
|
||||
* program.rpc.<method>(...args, ctx);
|
||||
* rpc.<method>(...args, ctx);
|
||||
* ```
|
||||
*
|
||||
* ## Parameters
|
||||
|
@ -74,32 +76,32 @@ export class Program {
|
|||
readonly rpc: RpcNamespace;
|
||||
|
||||
/**
|
||||
* Async functions to fetch deserialized program accounts from a cluster.
|
||||
* The namespace provides handles to an [[AccountClient]] object for each
|
||||
* account in the program.
|
||||
*
|
||||
* ## account
|
||||
* ## Usage
|
||||
*
|
||||
* ```javascript
|
||||
* program.account.<account>(address);
|
||||
* program.account.<account-client>
|
||||
* ```
|
||||
*
|
||||
* ## Parameters
|
||||
*
|
||||
* 1. `address` - The [[Address]] of the account.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* To fetch a `Counter` object from the above example,
|
||||
* To fetch a `Counter` account from the above example,
|
||||
*
|
||||
* ```javascript
|
||||
* const counter = await program.account.counter(address);
|
||||
* const counter = await program.account.counter.fetch(address);
|
||||
* ```
|
||||
*
|
||||
* For the full API, see the [[AccountClient]] reference.
|
||||
*/
|
||||
readonly account: AccountNamespace;
|
||||
|
||||
/**
|
||||
* Functions to build [[TransactionInstruction]] objects for program methods.
|
||||
* The namespace provides functions to build [[TransactionInstruction]]
|
||||
* objects for each method of a program.
|
||||
*
|
||||
* ## instruction
|
||||
* ## Usage
|
||||
*
|
||||
* ```javascript
|
||||
* program.instruction.<method>(...args, ctx);
|
||||
|
@ -127,9 +129,10 @@ export class Program {
|
|||
readonly instruction: InstructionNamespace;
|
||||
|
||||
/**
|
||||
* Functions to build [[Transaction]] objects.
|
||||
* The namespace provides functions to build [[Transaction]] objects for each
|
||||
* method of a program.
|
||||
*
|
||||
* ## transaction
|
||||
* ## Usage
|
||||
*
|
||||
* ```javascript
|
||||
* program.transaction.<method>(...args, ctx);
|
||||
|
@ -157,8 +160,9 @@ export class Program {
|
|||
readonly transaction: TransactionNamespace;
|
||||
|
||||
/**
|
||||
* Async functions to simulate instructions against an Anchor program,
|
||||
* returning a list of deserialized events *and* raw program logs.
|
||||
* The namespace provides functions to simulate transactions for each method
|
||||
* of a program, returning a list of deserialized events *and* raw program
|
||||
* logs.
|
||||
*
|
||||
* One can use this to read data calculated from a program on chain, by
|
||||
* emitting an event in the program and reading the emitted event client side
|
||||
|
@ -182,7 +186,7 @@ export class Program {
|
|||
* To simulate the `increment` method above,
|
||||
*
|
||||
* ```javascript
|
||||
* const tx = await program.simulate.increment({
|
||||
* const events = await program.simulate.increment({
|
||||
* accounts: {
|
||||
* counter,
|
||||
* },
|
||||
|
@ -192,9 +196,11 @@ export class Program {
|
|||
readonly simulate: SimulateNamespace;
|
||||
|
||||
/**
|
||||
* Object with state account accessors and rpcs.
|
||||
* A client for the program state. Similar to the base [[Program]] client,
|
||||
* one can use this to send transactions and read accounts for the state
|
||||
* abstraction.
|
||||
*/
|
||||
readonly state: StateNamespace;
|
||||
readonly state: StateClient;
|
||||
|
||||
/**
|
||||
* Address of the program.
|
||||
|
@ -249,15 +255,15 @@ export class Program {
|
|||
instruction,
|
||||
transaction,
|
||||
account,
|
||||
state,
|
||||
simulate,
|
||||
state,
|
||||
] = NamespaceFactory.build(idl, this._coder, programId, this._provider);
|
||||
this.rpc = rpc;
|
||||
this.instruction = instruction;
|
||||
this.transaction = transaction;
|
||||
this.account = account;
|
||||
this.state = state;
|
||||
this.simulate = simulate;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,39 +9,260 @@ import {
|
|||
Commitment,
|
||||
} from "@solana/web3.js";
|
||||
import Provider from "../../provider";
|
||||
import { Idl } from "../../idl";
|
||||
import { Idl, IdlTypeDef } from "../../idl";
|
||||
import Coder, {
|
||||
ACCOUNT_DISCRIMINATOR_SIZE,
|
||||
accountDiscriminator,
|
||||
accountSize,
|
||||
} from "../../coder";
|
||||
import { Subscription, Address, translateAddress } from "../common";
|
||||
import { getProvider } from "../../";
|
||||
|
||||
/**
|
||||
* Accounts is a dynamically generated object to fetch any given account
|
||||
* of a program.
|
||||
*/
|
||||
export interface AccountNamespace {
|
||||
[key: string]: AccountFn;
|
||||
export default class AccountFactory {
|
||||
public static build(
|
||||
idl: Idl,
|
||||
coder: Coder,
|
||||
programId: PublicKey,
|
||||
provider: Provider
|
||||
): AccountNamespace {
|
||||
const accountFns: AccountNamespace = {};
|
||||
|
||||
idl.accounts.forEach((idlAccount) => {
|
||||
const name = camelCase(idlAccount.name);
|
||||
accountFns[name] = new AccountClient(
|
||||
idl,
|
||||
idlAccount,
|
||||
programId,
|
||||
provider,
|
||||
coder
|
||||
);
|
||||
});
|
||||
|
||||
return accountFns;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Account is a function returning a deserialized account, given an address.
|
||||
* The namespace provides handles to an [[AccountClient]] object for each
|
||||
* account in a program.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```javascript
|
||||
* account.<account-client>
|
||||
* ```
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* To fetch a `Counter` account from the above example,
|
||||
*
|
||||
* ```javascript
|
||||
* const counter = await program.account.counter.fetch(address);
|
||||
* ```
|
||||
*
|
||||
* For the full API, see the [[AccountClient]] reference.
|
||||
*/
|
||||
export type AccountFn<T = any> = AccountProps & ((address: PublicKey) => T);
|
||||
export interface AccountNamespace {
|
||||
[key: string]: AccountClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Non function properties on the acccount namespace.
|
||||
*/
|
||||
type AccountProps = {
|
||||
size: number;
|
||||
all: (filter?: Buffer) => Promise<ProgramAccount<any>[]>;
|
||||
subscribe: (address: Address, commitment?: Commitment) => EventEmitter;
|
||||
unsubscribe: (address: Address) => void;
|
||||
createInstruction: (signer: Signer) => Promise<TransactionInstruction>;
|
||||
associated: (...args: PublicKey[]) => Promise<any>;
|
||||
associatedAddress: (...args: PublicKey[]) => Promise<PublicKey>;
|
||||
};
|
||||
export class AccountClient {
|
||||
/**
|
||||
* Returns the number of bytes in this account.
|
||||
*/
|
||||
get size(): number {
|
||||
return this._size;
|
||||
}
|
||||
private _size: number;
|
||||
|
||||
/**
|
||||
* Returns the program ID owning all accounts.
|
||||
*/
|
||||
get programId(): PublicKey {
|
||||
return this._programId;
|
||||
}
|
||||
private _programId: PublicKey;
|
||||
|
||||
/**
|
||||
* Returns the cleint's wallet and network provider.
|
||||
*/
|
||||
get provider(): Provider {
|
||||
return this._provider;
|
||||
}
|
||||
private _provider: Provider;
|
||||
|
||||
/**
|
||||
* Returns the coder.
|
||||
*/
|
||||
get coder(): Coder {
|
||||
return this._coder;
|
||||
}
|
||||
private _coder: Coder;
|
||||
|
||||
private _idlAccount: IdlTypeDef;
|
||||
|
||||
constructor(
|
||||
idl: Idl,
|
||||
idlAccount: IdlTypeDef,
|
||||
programId: PublicKey,
|
||||
provider?: Provider,
|
||||
coder?: Coder
|
||||
) {
|
||||
this._idlAccount = idlAccount;
|
||||
this._programId = programId;
|
||||
this._provider = provider ?? getProvider();
|
||||
this._coder = coder ?? new Coder(idl);
|
||||
this._size = ACCOUNT_DISCRIMINATOR_SIZE + accountSize(idl, idlAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a deserialized account.
|
||||
*
|
||||
* @param address The address of the account to fetch.
|
||||
*/
|
||||
async fetch(address: Address): Promise<Object> {
|
||||
const accountInfo = await this._provider.connection.getAccountInfo(
|
||||
translateAddress(address)
|
||||
);
|
||||
if (accountInfo === null) {
|
||||
throw new Error(`Account does not exist ${address.toString()}`);
|
||||
}
|
||||
|
||||
// Assert the account discriminator is correct.
|
||||
const discriminator = await accountDiscriminator(this._idlAccount.name);
|
||||
if (discriminator.compare(accountInfo.data.slice(0, 8))) {
|
||||
throw new Error("Invalid account discriminator");
|
||||
}
|
||||
|
||||
return this._coder.accounts.decode(this._idlAccount.name, accountInfo.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all instances of this account type for the program.
|
||||
*/
|
||||
async all(filter?: Buffer): Promise<ProgramAccount<any>[]> {
|
||||
let bytes = await accountDiscriminator(this._idlAccount.name);
|
||||
if (filter !== undefined) {
|
||||
bytes = Buffer.concat([bytes, filter]);
|
||||
}
|
||||
|
||||
let resp = await this._provider.connection.getProgramAccounts(
|
||||
this._programId,
|
||||
{
|
||||
commitment: this._provider.connection.commitment,
|
||||
filters: [
|
||||
{
|
||||
memcmp: {
|
||||
offset: 0,
|
||||
bytes: bs58.encode(bytes),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
return resp.map(({ pubkey, account }) => {
|
||||
return {
|
||||
publicKey: pubkey,
|
||||
account: this._coder.accounts.decode(
|
||||
this._idlAccount.name,
|
||||
account.data
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an `EventEmitter` emitting a "change" event whenever the account
|
||||
* changes.
|
||||
*/
|
||||
subscribe(address: Address, commitment?: Commitment): EventEmitter {
|
||||
if (subscriptions.get(address.toString())) {
|
||||
return subscriptions.get(address.toString()).ee;
|
||||
}
|
||||
|
||||
const ee = new EventEmitter();
|
||||
address = translateAddress(address);
|
||||
const listener = this._provider.connection.onAccountChange(
|
||||
address,
|
||||
(acc) => {
|
||||
const account = this._coder.accounts.decode(
|
||||
this._idlAccount.name,
|
||||
acc.data
|
||||
);
|
||||
ee.emit("change", account);
|
||||
},
|
||||
commitment
|
||||
);
|
||||
|
||||
subscriptions.set(address.toString(), {
|
||||
ee,
|
||||
listener,
|
||||
});
|
||||
|
||||
return ee;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes from the account at the given address.
|
||||
*/
|
||||
unsubscribe(address: Address) {
|
||||
let sub = subscriptions.get(address.toString());
|
||||
if (!sub) {
|
||||
console.warn("Address is not subscribed");
|
||||
return;
|
||||
}
|
||||
if (subscriptions) {
|
||||
this._provider.connection
|
||||
.removeAccountChangeListener(sub.listener)
|
||||
.then(() => {
|
||||
subscriptions.delete(address.toString());
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instruction for creating this account.
|
||||
*/
|
||||
async createInstruction(
|
||||
signer: Signer,
|
||||
sizeOverride?: number
|
||||
): Promise<TransactionInstruction> {
|
||||
const size = this.size;
|
||||
|
||||
return SystemProgram.createAccount({
|
||||
fromPubkey: this._provider.wallet.publicKey,
|
||||
newAccountPubkey: signer.publicKey,
|
||||
space: sizeOverride ?? size,
|
||||
lamports: await this._provider.connection.getMinimumBalanceForRentExemption(
|
||||
sizeOverride ?? size
|
||||
),
|
||||
programId: this._programId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Function returning the associated account. Args are keys to associate.
|
||||
* Order matters.
|
||||
*/
|
||||
async associated(...args: PublicKey[]): Promise<any> {
|
||||
const addr = await this.associatedAddress(...args);
|
||||
return await this.fetch(addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function returning the associated address. Args are keys to associate.
|
||||
* Order matters.
|
||||
*/
|
||||
async associatedAddress(...args: PublicKey[]): Promise<PublicKey> {
|
||||
let seeds = [Buffer.from([97, 110, 99, 104, 111, 114])]; // b"anchor".
|
||||
args.forEach((arg) => {
|
||||
seeds.push(translateAddress(arg).toBuffer());
|
||||
});
|
||||
const [assoc] = await PublicKey.findProgramAddress(seeds, this._programId);
|
||||
return assoc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
|
@ -55,176 +276,3 @@ export type ProgramAccount<T = any> = {
|
|||
|
||||
// Tracks all subscriptions.
|
||||
const subscriptions: Map<string, Subscription> = new Map();
|
||||
|
||||
export default class AccountFactory {
|
||||
// Returns the generated accounts namespace.
|
||||
public static build(
|
||||
idl: Idl,
|
||||
coder: Coder,
|
||||
programId: PublicKey,
|
||||
provider: Provider
|
||||
): AccountNamespace {
|
||||
const accountFns: AccountNamespace = {};
|
||||
|
||||
idl.accounts.forEach((idlAccount) => {
|
||||
const name = camelCase(idlAccount.name);
|
||||
|
||||
// Fetches the decoded account from the network.
|
||||
const accountsNamespace = async (address: Address): Promise<any> => {
|
||||
const accountInfo = await provider.connection.getAccountInfo(
|
||||
translateAddress(address)
|
||||
);
|
||||
if (accountInfo === null) {
|
||||
throw new Error(`Account does not exist ${address.toString()}`);
|
||||
}
|
||||
|
||||
// Assert the account discriminator is correct.
|
||||
const discriminator = await accountDiscriminator(idlAccount.name);
|
||||
if (discriminator.compare(accountInfo.data.slice(0, 8))) {
|
||||
throw new Error("Invalid account discriminator");
|
||||
}
|
||||
|
||||
return coder.accounts.decode(idlAccount.name, accountInfo.data);
|
||||
};
|
||||
|
||||
// Returns the size of the account.
|
||||
// @ts-ignore
|
||||
accountsNamespace["size"] =
|
||||
ACCOUNT_DISCRIMINATOR_SIZE + accountSize(idl, idlAccount);
|
||||
|
||||
// Returns an instruction for creating this account.
|
||||
// @ts-ignore
|
||||
accountsNamespace["createInstruction"] = async (
|
||||
signer: Signer,
|
||||
sizeOverride?: number
|
||||
): Promise<TransactionInstruction> => {
|
||||
// @ts-ignore
|
||||
const size = accountsNamespace["size"];
|
||||
|
||||
return SystemProgram.createAccount({
|
||||
fromPubkey: provider.wallet.publicKey,
|
||||
newAccountPubkey: signer.publicKey,
|
||||
space: sizeOverride ?? size,
|
||||
lamports: await provider.connection.getMinimumBalanceForRentExemption(
|
||||
sizeOverride ?? size
|
||||
),
|
||||
programId,
|
||||
});
|
||||
};
|
||||
|
||||
// Subscribes to all changes to this account.
|
||||
// @ts-ignore
|
||||
accountsNamespace["subscribe"] = (
|
||||
address: Address,
|
||||
commitment?: Commitment
|
||||
): EventEmitter => {
|
||||
if (subscriptions.get(address.toString())) {
|
||||
return subscriptions.get(address.toString()).ee;
|
||||
}
|
||||
|
||||
const ee = new EventEmitter();
|
||||
address = translateAddress(address);
|
||||
const listener = provider.connection.onAccountChange(
|
||||
address,
|
||||
(acc) => {
|
||||
const account = coder.accounts.decode(idlAccount.name, acc.data);
|
||||
ee.emit("change", account);
|
||||
},
|
||||
commitment
|
||||
);
|
||||
|
||||
subscriptions.set(address.toString(), {
|
||||
ee,
|
||||
listener,
|
||||
});
|
||||
|
||||
return ee;
|
||||
};
|
||||
|
||||
// Unsubscribes to account changes.
|
||||
// @ts-ignore
|
||||
accountsNamespace["unsubscribe"] = (address: Address) => {
|
||||
let sub = subscriptions.get(address.toString());
|
||||
if (!sub) {
|
||||
console.warn("Address is not subscribed");
|
||||
return;
|
||||
}
|
||||
if (subscriptions) {
|
||||
provider.connection
|
||||
.removeAccountChangeListener(sub.listener)
|
||||
.then(() => {
|
||||
subscriptions.delete(address.toString());
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
};
|
||||
|
||||
// Returns all instances of this account type for the program.
|
||||
// @ts-ignore
|
||||
accountsNamespace["all"] = async (
|
||||
filter?: Buffer
|
||||
): Promise<ProgramAccount<any>[]> => {
|
||||
let bytes = await accountDiscriminator(idlAccount.name);
|
||||
if (filter !== undefined) {
|
||||
bytes = Buffer.concat([bytes, filter]);
|
||||
}
|
||||
// @ts-ignore
|
||||
let resp = await provider.connection._rpcRequest("getProgramAccounts", [
|
||||
programId.toBase58(),
|
||||
{
|
||||
commitment: provider.connection.commitment,
|
||||
filters: [
|
||||
{
|
||||
memcmp: {
|
||||
offset: 0,
|
||||
bytes: bs58.encode(bytes),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
if (resp.error) {
|
||||
console.error(resp);
|
||||
throw new Error("Failed to get accounts");
|
||||
}
|
||||
return (
|
||||
resp.result
|
||||
// @ts-ignore
|
||||
.map(({ pubkey, account: { data } }) => {
|
||||
data = bs58.decode(data);
|
||||
return {
|
||||
publicKey: new PublicKey(pubkey),
|
||||
account: coder.accounts.decode(idlAccount.name, data),
|
||||
};
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
// Function returning the associated address. Args are keys to associate.
|
||||
// Order matters.
|
||||
accountsNamespace["associatedAddress"] = async (
|
||||
...args: Address[]
|
||||
): Promise<PublicKey> => {
|
||||
let seeds = [Buffer.from([97, 110, 99, 104, 111, 114])]; // b"anchor".
|
||||
args.forEach((arg) => {
|
||||
seeds.push(translateAddress(arg).toBuffer());
|
||||
});
|
||||
const [assoc] = await PublicKey.findProgramAddress(seeds, programId);
|
||||
return assoc;
|
||||
};
|
||||
|
||||
// Function returning the associated account. Args are keys to associate.
|
||||
// Order matters.
|
||||
accountsNamespace["associated"] = async (
|
||||
...args: Address[]
|
||||
): Promise<any> => {
|
||||
const addr = await accountsNamespace["associatedAddress"](...args);
|
||||
return await accountsNamespace(addr);
|
||||
};
|
||||
|
||||
accountFns[name] = accountsNamespace;
|
||||
});
|
||||
|
||||
return accountFns;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,21 +3,21 @@ import { PublicKey } from "@solana/web3.js";
|
|||
import Coder from "../../coder";
|
||||
import Provider from "../../provider";
|
||||
import { Idl } from "../../idl";
|
||||
import { parseIdlErrors } from "../common";
|
||||
import StateFactory, { StateNamespace } from "./state";
|
||||
import StateFactory, { StateClient } from "./state";
|
||||
import InstructionFactory, { InstructionNamespace } from "./instruction";
|
||||
import TransactionFactory, { TransactionNamespace } from "./transaction";
|
||||
import RpcFactory, { RpcNamespace } from "./rpc";
|
||||
import AccountFactory, { AccountNamespace } from "./account";
|
||||
import SimulateFactory, { SimulateNamespace } from "./simulate";
|
||||
import { parseIdlErrors } from "../common";
|
||||
|
||||
// Re-exports.
|
||||
export { StateNamespace } from "./state";
|
||||
export { InstructionNamespace } from "./instruction";
|
||||
export { TransactionNamespace, TxFn } from "./transaction";
|
||||
export { StateClient } from "./state";
|
||||
export { InstructionNamespace, InstructionFn } from "./instruction";
|
||||
export { TransactionNamespace, TransactionFn } from "./transaction";
|
||||
export { RpcNamespace, RpcFn } from "./rpc";
|
||||
export { AccountNamespace, AccountFn, ProgramAccount } from "./account";
|
||||
export { SimulateNamespace } from "./simulate";
|
||||
export { AccountNamespace, AccountClient, ProgramAccount } from "./account";
|
||||
export { SimulateNamespace, SimulateFn } from "./simulate";
|
||||
|
||||
export default class NamespaceFactory {
|
||||
/**
|
||||
|
@ -33,26 +33,24 @@ export default class NamespaceFactory {
|
|||
InstructionNamespace,
|
||||
TransactionNamespace,
|
||||
AccountNamespace,
|
||||
StateNamespace,
|
||||
SimulateNamespace
|
||||
SimulateNamespace,
|
||||
StateClient
|
||||
] {
|
||||
const idlErrors = parseIdlErrors(idl);
|
||||
|
||||
const rpc: RpcNamespace = {};
|
||||
const instruction: InstructionNamespace = {};
|
||||
const transaction: TransactionNamespace = {};
|
||||
const simulate: SimulateNamespace = {};
|
||||
|
||||
const state = StateFactory.build(
|
||||
idl,
|
||||
coder,
|
||||
programId,
|
||||
idlErrors,
|
||||
provider
|
||||
);
|
||||
const idlErrors = parseIdlErrors(idl);
|
||||
|
||||
const state = StateFactory.build(idl, coder, programId, provider);
|
||||
|
||||
idl.instructions.forEach((idlIx) => {
|
||||
const ixItem = InstructionFactory.build(idlIx, coder, programId);
|
||||
const ixItem = InstructionFactory.build(
|
||||
idlIx,
|
||||
(ixName: string, ix: any) => coder.instruction.encode(ixName, ix),
|
||||
programId
|
||||
);
|
||||
const txItem = TransactionFactory.build(idlIx, ixItem);
|
||||
const rpcItem = RpcFactory.build(idlIx, txItem, idlErrors, provider);
|
||||
const simulateItem = SimulateFactory.build(
|
||||
|
@ -77,6 +75,6 @@ export default class NamespaceFactory {
|
|||
? AccountFactory.build(idl, coder, programId, provider)
|
||||
: {};
|
||||
|
||||
return [rpc, instruction, transaction, account, state, simulate];
|
||||
return [rpc, instruction, transaction, account, simulate, state];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
|
||||
import { IdlAccount, IdlInstruction, IdlAccountItem } from "../../idl";
|
||||
import { IdlError } from "../../error";
|
||||
import Coder from "../../coder";
|
||||
import {
|
||||
toInstruction,
|
||||
validateAccounts,
|
||||
|
@ -10,28 +9,12 @@ import {
|
|||
} from "../common";
|
||||
import { Accounts, splitArgsAndCtx } from "../context";
|
||||
|
||||
/**
|
||||
* Dynamically generated instruction namespace.
|
||||
*/
|
||||
export interface InstructionNamespace {
|
||||
[key: string]: IxFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ix is a function to create a `TransactionInstruction` generated from an IDL.
|
||||
*/
|
||||
export type IxFn = IxProps & ((...args: any[]) => any);
|
||||
type IxProps = {
|
||||
accounts: (ctx: Accounts) => any;
|
||||
};
|
||||
|
||||
export default class InstructionNamespaceFactory {
|
||||
// Builds the instuction namespace.
|
||||
public static build(
|
||||
idlIx: IdlInstruction,
|
||||
coder: Coder,
|
||||
encodeFn: InstructionEncodeFn,
|
||||
programId: PublicKey
|
||||
): IxFn {
|
||||
): InstructionFn {
|
||||
if (idlIx.name === "_inner") {
|
||||
throw new IdlError("the _inner name is reserved");
|
||||
}
|
||||
|
@ -41,10 +24,7 @@ export default class InstructionNamespaceFactory {
|
|||
validateAccounts(idlIx.accounts, ctx.accounts);
|
||||
validateInstruction(idlIx, ...args);
|
||||
|
||||
const keys = InstructionNamespaceFactory.accountsArray(
|
||||
ctx.accounts,
|
||||
idlIx.accounts
|
||||
);
|
||||
const keys = ix.accounts(ctx.accounts);
|
||||
|
||||
if (ctx.remainingAccounts !== undefined) {
|
||||
keys.push(...ctx.remainingAccounts);
|
||||
|
@ -56,10 +36,7 @@ export default class InstructionNamespaceFactory {
|
|||
return new TransactionInstruction({
|
||||
keys,
|
||||
programId,
|
||||
data: coder.instruction.encode(
|
||||
idlIx.name,
|
||||
toInstruction(idlIx, ...ixArgs)
|
||||
),
|
||||
data: encodeFn(idlIx.name, toInstruction(idlIx, ...ixArgs)),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -96,6 +73,51 @@ export default class InstructionNamespaceFactory {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The namespace provides functions to build [[TransactionInstruction]]
|
||||
* objects for each method of a program.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```javascript
|
||||
* instruction.<method>(...args, ctx);
|
||||
* ```
|
||||
*
|
||||
* ## Parameters
|
||||
*
|
||||
* 1. `args` - The positional arguments for the program. The type and number
|
||||
* of these arguments depend on the program being used.
|
||||
* 2. `ctx` - [[Context]] non-argument parameters to pass to the method.
|
||||
* Always the last parameter in the method call.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* To create an instruction for the `increment` method above,
|
||||
*
|
||||
* ```javascript
|
||||
* const tx = await program.instruction.increment({
|
||||
* accounts: {
|
||||
* counter,
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export interface InstructionNamespace {
|
||||
[key: string]: InstructionFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to create a `TransactionInstruction` generated from an IDL.
|
||||
* Additionally it provides an `accounts` utility method, returning a list
|
||||
* of ordered accounts for the instruction.
|
||||
*/
|
||||
export type InstructionFn = IxProps & ((...args: any[]) => any);
|
||||
type IxProps = {
|
||||
accounts: (ctx: Accounts) => any;
|
||||
};
|
||||
|
||||
export type InstructionEncodeFn = (ixName: string, ix: any) => Buffer;
|
||||
|
||||
// Throws error if any argument required for the `ix` is not given.
|
||||
function validateInstruction(ix: IdlInstruction, ...args: any[]) {
|
||||
// todo
|
||||
|
|
|
@ -3,25 +3,12 @@ import Provider from "../../provider";
|
|||
import { IdlInstruction } from "../../idl";
|
||||
import { translateError } from "../common";
|
||||
import { splitArgsAndCtx } from "../context";
|
||||
import { TxFn } from "./transaction";
|
||||
|
||||
/**
|
||||
* Dynamically generated rpc namespace.
|
||||
*/
|
||||
export interface RpcNamespace {
|
||||
[key: string]: RpcFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* RpcFn is a single rpc method generated from an IDL.
|
||||
*/
|
||||
export type RpcFn = (...args: any[]) => Promise<TransactionSignature>;
|
||||
import { TransactionFn } from "./transaction";
|
||||
|
||||
export default class RpcFactory {
|
||||
// Builds the rpc namespace.
|
||||
public static build(
|
||||
idlIx: IdlInstruction,
|
||||
txFn: TxFn,
|
||||
txFn: TransactionFn,
|
||||
idlErrors: Map<number, string>,
|
||||
provider: Provider
|
||||
): RpcFn {
|
||||
|
@ -44,3 +31,47 @@ export default class RpcFactory {
|
|||
return rpc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The namespace provides async methods to send signed transactions for each
|
||||
* *non*-state method on Anchor program.
|
||||
*
|
||||
* Keys are method names, values are RPC functions returning a
|
||||
* [[TransactionInstruction]].
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```javascript
|
||||
* rpc.<method>(...args, ctx);
|
||||
* ```
|
||||
*
|
||||
* ## Parameters
|
||||
*
|
||||
* 1. `args` - The positional arguments for the program. The type and number
|
||||
* of these arguments depend on the program being used.
|
||||
* 2. `ctx` - [[Context]] non-argument parameters to pass to the method.
|
||||
* Always the last parameter in the method call.
|
||||
* ```
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* To send a transaction invoking the `increment` method above,
|
||||
*
|
||||
* ```javascript
|
||||
* const txSignature = await program.rpc.increment({
|
||||
* accounts: {
|
||||
* counter,
|
||||
* authority,
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export interface RpcNamespace {
|
||||
[key: string]: RpcFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* RpcFn is a single RPC method generated from an IDL, sending a transaction
|
||||
* paid for and signed by the configured provider.
|
||||
*/
|
||||
export type RpcFn = (...args: any[]) => Promise<TransactionSignature>;
|
||||
|
|
|
@ -3,33 +3,15 @@ import Provider from "../../provider";
|
|||
import { IdlInstruction } from "../../idl";
|
||||
import { translateError } from "../common";
|
||||
import { splitArgsAndCtx } from "../context";
|
||||
import { TxFn } from "./transaction";
|
||||
import { TransactionFn } from "./transaction";
|
||||
import { EventParser } from "../event";
|
||||
import Coder from "../../coder";
|
||||
import { Idl } from "../../idl";
|
||||
|
||||
/**
|
||||
* Dynamically generated simualte namespace.
|
||||
*/
|
||||
export interface SimulateNamespace {
|
||||
[key: string]: SimulateFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* RpcFn is a single rpc method generated from an IDL.
|
||||
*/
|
||||
export type SimulateFn = (...args: any[]) => Promise<SimulateResponse>;
|
||||
|
||||
type SimulateResponse = {
|
||||
events: Event[];
|
||||
raw: string[];
|
||||
};
|
||||
|
||||
export default class SimulateFactory {
|
||||
// Builds the rpc namespace.
|
||||
public static build(
|
||||
idlIx: IdlInstruction,
|
||||
txFn: TxFn,
|
||||
txFn: TransactionFn,
|
||||
idlErrors: Map<number, string>,
|
||||
provider: Provider,
|
||||
coder: Coder,
|
||||
|
@ -74,3 +56,54 @@ export default class SimulateFactory {
|
|||
return simulate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The namespace provides functions to simulate transactions for each method
|
||||
* of a program, returning a list of deserialized events *and* raw program
|
||||
* logs.
|
||||
*
|
||||
* One can use this to read data calculated from a program on chain, by
|
||||
* emitting an event in the program and reading the emitted event client side
|
||||
* via the `simulate` namespace.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```javascript
|
||||
* program.simulate.<method>(...args, ctx);
|
||||
* ```
|
||||
*
|
||||
* ## Parameters
|
||||
*
|
||||
* 1. `args` - The positional arguments for the program. The type and number
|
||||
* of these arguments depend on the program being used.
|
||||
* 2. `ctx` - [[Context]] non-argument parameters to pass to the method.
|
||||
* Always the last parameter in the method call.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* To simulate the `increment` method above,
|
||||
*
|
||||
* ```javascript
|
||||
* const events = await program.simulate.increment({
|
||||
* accounts: {
|
||||
* counter,
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export interface SimulateNamespace {
|
||||
[key: string]: SimulateFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* RpcFn is a single method generated from an IDL. It simulates a method
|
||||
* against a cluster configured by the provider, returning a list of all the
|
||||
* events and raw logs that were emitted during the execution of the
|
||||
* method.
|
||||
*/
|
||||
export type SimulateFn = (...args: any[]) => Promise<SimulateResponse>;
|
||||
|
||||
type SimulateResponse = {
|
||||
events: Event[];
|
||||
raw: string[];
|
||||
};
|
||||
|
|
|
@ -1,176 +1,224 @@
|
|||
import EventEmitter from "eventemitter3";
|
||||
import camelCase from "camelcase";
|
||||
import {
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
Transaction,
|
||||
TransactionSignature,
|
||||
TransactionInstruction,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
Commitment,
|
||||
} from "@solana/web3.js";
|
||||
import Provider from "../../provider";
|
||||
import { Idl, IdlStateMethod } from "../../idl";
|
||||
import Coder, { stateDiscriminator } from "../../coder";
|
||||
import { RpcNamespace, InstructionNamespace } from "./";
|
||||
import {
|
||||
Subscription,
|
||||
translateError,
|
||||
toInstruction,
|
||||
validateAccounts,
|
||||
} from "../common";
|
||||
import { Accounts, splitArgsAndCtx } from "../context";
|
||||
import { RpcNamespace, InstructionNamespace, TransactionNamespace } from "./";
|
||||
import { getProvider } from "../../";
|
||||
import { Subscription, validateAccounts, parseIdlErrors } from "../common";
|
||||
import { findProgramAddressSync, createWithSeedSync } from "../../utils/pubkey";
|
||||
import { Accounts } from "../context";
|
||||
import InstructionNamespaceFactory from "./instruction";
|
||||
|
||||
export type StateNamespace = () =>
|
||||
| Promise<any>
|
||||
| {
|
||||
address: () => Promise<PublicKey>;
|
||||
rpc: RpcNamespace;
|
||||
instruction: InstructionNamespace;
|
||||
subscribe: (commitment?: Commitment) => EventEmitter;
|
||||
unsubscribe: () => void;
|
||||
};
|
||||
import RpcNamespaceFactory from "./rpc";
|
||||
import TransactionNamespaceFactory from "./transaction";
|
||||
|
||||
export default class StateFactory {
|
||||
// Builds the state namespace.
|
||||
public static build(
|
||||
idl: Idl,
|
||||
coder: Coder,
|
||||
programId: PublicKey,
|
||||
idlErrors: Map<number, string>,
|
||||
provider: Provider
|
||||
): StateNamespace | undefined {
|
||||
): StateClient | undefined {
|
||||
if (idl.state === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return new StateClient(idl, programId, provider, coder);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetches the state object from the blockchain.
|
||||
const state = async (): Promise<any> => {
|
||||
const addr = await programStateAddress(programId);
|
||||
const accountInfo = await provider.connection.getAccountInfo(addr);
|
||||
if (accountInfo === null) {
|
||||
throw new Error(`Account does not exist ${addr.toString()}`);
|
||||
}
|
||||
// Assert the account discriminator is correct.
|
||||
const expectedDiscriminator = await stateDiscriminator(
|
||||
idl.state.struct.name
|
||||
);
|
||||
if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) {
|
||||
throw new Error("Invalid account discriminator");
|
||||
}
|
||||
return coder.state.decode(accountInfo.data);
|
||||
};
|
||||
/**
|
||||
* A client for the program state. Similar to the base [[Program]] client,
|
||||
* one can use this to send transactions and read accounts for the state
|
||||
* abstraction.
|
||||
*/
|
||||
export class StateClient {
|
||||
/**
|
||||
* [[RpcNamespace]] for all state methods.
|
||||
*/
|
||||
readonly rpc: RpcNamespace;
|
||||
|
||||
// Namespace with all rpc functions.
|
||||
const rpc: RpcNamespace = {};
|
||||
const ix: InstructionNamespace = {};
|
||||
/**
|
||||
* [[InstructionNamespace]] for all state methods.
|
||||
*/
|
||||
readonly instruction: InstructionNamespace;
|
||||
|
||||
idl.state.methods.forEach((m: IdlStateMethod) => {
|
||||
const accounts = async (accounts: Accounts): Promise<any> => {
|
||||
const keys = await stateInstructionKeys(
|
||||
programId,
|
||||
provider,
|
||||
/**
|
||||
* [[TransactionNamespace]] for all state methods.
|
||||
*/
|
||||
readonly transaction: TransactionNamespace;
|
||||
|
||||
/**
|
||||
* Returns the program ID owning the state.
|
||||
*/
|
||||
get programId(): PublicKey {
|
||||
return this._programId;
|
||||
}
|
||||
private _programId: PublicKey;
|
||||
|
||||
/**
|
||||
* Returns the client's wallet and network provider.
|
||||
*/
|
||||
get provider(): Provider {
|
||||
return this._provider;
|
||||
}
|
||||
private _provider: Provider;
|
||||
|
||||
/**
|
||||
* Returns the coder.
|
||||
*/
|
||||
get coder(): Coder {
|
||||
return this._coder;
|
||||
}
|
||||
|
||||
private _address: PublicKey;
|
||||
private _coder: Coder;
|
||||
private _idl: Idl;
|
||||
private _sub: Subscription | null;
|
||||
|
||||
constructor(
|
||||
idl: Idl,
|
||||
programId: PublicKey,
|
||||
provider?: Provider,
|
||||
coder?: Coder
|
||||
) {
|
||||
this._idl = idl;
|
||||
this._programId = programId;
|
||||
this._address = programStateAddress(programId);
|
||||
this._provider = provider ?? getProvider();
|
||||
this._coder = coder ?? new Coder(idl);
|
||||
this._sub = null;
|
||||
|
||||
// Build namespaces.
|
||||
const [instruction, transaction, rpc] = ((): [
|
||||
InstructionNamespace,
|
||||
TransactionNamespace,
|
||||
RpcNamespace
|
||||
] => {
|
||||
let instruction: InstructionNamespace = {};
|
||||
let transaction: TransactionNamespace = {};
|
||||
let rpc: RpcNamespace = {};
|
||||
|
||||
idl.state.methods.forEach((m: IdlStateMethod) => {
|
||||
// Build instruction method.
|
||||
const ixItem = InstructionNamespaceFactory.build(
|
||||
m,
|
||||
accounts
|
||||
(ixName: string, ix: any) =>
|
||||
coder.instruction.encodeState(ixName, ix),
|
||||
programId
|
||||
);
|
||||
return keys.concat(
|
||||
InstructionNamespaceFactory.accountsArray(accounts, m.accounts)
|
||||
);
|
||||
};
|
||||
const ixFn = async (...args: any[]): Promise<TransactionInstruction> => {
|
||||
const [ixArgs, ctx] = splitArgsAndCtx(m, [...args]);
|
||||
return new TransactionInstruction({
|
||||
keys: await accounts(ctx.accounts),
|
||||
programId,
|
||||
data: coder.instruction.encodeState(
|
||||
m.name,
|
||||
toInstruction(m, ...ixArgs)
|
||||
),
|
||||
});
|
||||
};
|
||||
ixFn["accounts"] = accounts;
|
||||
ix[m.name] = ixFn;
|
||||
|
||||
rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
|
||||
const [, ctx] = splitArgsAndCtx(m, [...args]);
|
||||
const tx = new Transaction();
|
||||
if (ctx.instructions !== undefined) {
|
||||
tx.add(...ctx.instructions);
|
||||
}
|
||||
tx.add(await ix[m.name](...args));
|
||||
try {
|
||||
const txSig = await provider.send(tx, ctx.signers, ctx.options);
|
||||
return txSig;
|
||||
} catch (err) {
|
||||
let translatedErr = translateError(idlErrors, err);
|
||||
if (translatedErr === null) {
|
||||
throw err;
|
||||
}
|
||||
throw translatedErr;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
state["rpc"] = rpc;
|
||||
state["instruction"] = ix;
|
||||
// Calculates the address of the program's global state object account.
|
||||
state["address"] = async (): Promise<PublicKey> =>
|
||||
programStateAddress(programId);
|
||||
|
||||
// Subscription singleton.
|
||||
let sub: null | Subscription = null;
|
||||
|
||||
// Subscribe to account changes.
|
||||
state["subscribe"] = (commitment?: Commitment): EventEmitter => {
|
||||
if (sub !== null) {
|
||||
return sub.ee;
|
||||
}
|
||||
const ee = new EventEmitter();
|
||||
|
||||
state["address"]().then((address) => {
|
||||
const listener = provider.connection.onAccountChange(
|
||||
address,
|
||||
(acc) => {
|
||||
const account = coder.state.decode(acc.data);
|
||||
ee.emit("change", account);
|
||||
},
|
||||
commitment
|
||||
);
|
||||
|
||||
sub = {
|
||||
ee,
|
||||
listener,
|
||||
ixItem["accounts"] = (accounts: Accounts) => {
|
||||
const keys = stateInstructionKeys(programId, provider, m, accounts);
|
||||
return keys.concat(
|
||||
InstructionNamespaceFactory.accountsArray(accounts, m.accounts)
|
||||
);
|
||||
};
|
||||
// Build transaction method.
|
||||
const txItem = TransactionNamespaceFactory.build(m, ixItem);
|
||||
// Build RPC method.
|
||||
const rpcItem = RpcNamespaceFactory.build(
|
||||
m,
|
||||
txItem,
|
||||
parseIdlErrors(idl),
|
||||
provider
|
||||
);
|
||||
|
||||
// Attach them all to their respective namespaces.
|
||||
const name = camelCase(m.name);
|
||||
instruction[name] = ixItem;
|
||||
transaction[name] = txItem;
|
||||
rpc[name] = rpcItem;
|
||||
});
|
||||
|
||||
return ee;
|
||||
return [instruction, transaction, rpc];
|
||||
})();
|
||||
this.instruction = instruction;
|
||||
this.transaction = transaction;
|
||||
this.rpc = rpc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the deserialized state account.
|
||||
*/
|
||||
async fetch(): Promise<Object> {
|
||||
const addr = this.address();
|
||||
const accountInfo = await this.provider.connection.getAccountInfo(addr);
|
||||
if (accountInfo === null) {
|
||||
throw new Error(`Account does not exist ${addr.toString()}`);
|
||||
}
|
||||
// Assert the account discriminator is correct.
|
||||
const expectedDiscriminator = await stateDiscriminator(
|
||||
this._idl.state.struct.name
|
||||
);
|
||||
if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) {
|
||||
throw new Error("Invalid account discriminator");
|
||||
}
|
||||
return this.coder.state.decode(accountInfo.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state address.
|
||||
*/
|
||||
address(): PublicKey {
|
||||
return this._address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an `EventEmitter` with a `"change"` event that's fired whenever
|
||||
* the state account cahnges.
|
||||
*/
|
||||
subscribe(commitment?: Commitment): EventEmitter {
|
||||
if (this._sub !== null) {
|
||||
return this._sub.ee;
|
||||
}
|
||||
const ee = new EventEmitter();
|
||||
|
||||
const listener = this.provider.connection.onAccountChange(
|
||||
this.address(),
|
||||
(acc) => {
|
||||
const account = this.coder.state.decode(acc.data);
|
||||
ee.emit("change", account);
|
||||
},
|
||||
commitment
|
||||
);
|
||||
|
||||
this._sub = {
|
||||
ee,
|
||||
listener,
|
||||
};
|
||||
|
||||
// Unsubscribe from account changes.
|
||||
state["unsubscribe"] = () => {
|
||||
if (sub !== null) {
|
||||
provider.connection
|
||||
.removeAccountChangeListener(sub.listener)
|
||||
.then(async () => {
|
||||
sub = null;
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
};
|
||||
return ee;
|
||||
}
|
||||
|
||||
return state;
|
||||
/**
|
||||
* Unsubscribes to state changes.
|
||||
*/
|
||||
unsubscribe() {
|
||||
if (this._sub !== null) {
|
||||
this.provider.connection
|
||||
.removeAccountChangeListener(this._sub.listener)
|
||||
.then(async () => {
|
||||
this._sub = null;
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculates the deterministic address of the program's "state" account.
|
||||
async function programStateAddress(programId: PublicKey): Promise<PublicKey> {
|
||||
let [registrySigner] = await PublicKey.findProgramAddress([], programId);
|
||||
return PublicKey.createWithSeed(registrySigner, "unversioned", programId);
|
||||
function programStateAddress(programId: PublicKey): PublicKey {
|
||||
let [registrySigner] = findProgramAddressSync([], programId);
|
||||
return createWithSeedSync(registrySigner, "unversioned", programId);
|
||||
}
|
||||
|
||||
// Returns the common keys that are prepended to all instructions targeting
|
||||
// the "state" of a program.
|
||||
async function stateInstructionKeys(
|
||||
function stateInstructionKeys(
|
||||
programId: PublicKey,
|
||||
provider: Provider,
|
||||
m: IdlStateMethod,
|
||||
|
@ -178,7 +226,7 @@ async function stateInstructionKeys(
|
|||
) {
|
||||
if (m.name === "new") {
|
||||
// Ctor `new` method.
|
||||
const [programSigner] = await PublicKey.findProgramAddress([], programId);
|
||||
const [programSigner] = findProgramAddressSync([], programId);
|
||||
return [
|
||||
{
|
||||
pubkey: provider.wallet.publicKey,
|
||||
|
@ -186,7 +234,7 @@ async function stateInstructionKeys(
|
|||
isSigner: true,
|
||||
},
|
||||
{
|
||||
pubkey: await programStateAddress(programId),
|
||||
pubkey: programStateAddress(programId),
|
||||
isWritable: true,
|
||||
isSigner: false,
|
||||
},
|
||||
|
@ -208,7 +256,7 @@ async function stateInstructionKeys(
|
|||
validateAccounts(m.accounts, accounts);
|
||||
return [
|
||||
{
|
||||
pubkey: await programStateAddress(programId),
|
||||
pubkey: programStateAddress(programId),
|
||||
isWritable: true,
|
||||
isSigner: false,
|
||||
},
|
||||
|
|
|
@ -1,23 +1,13 @@
|
|||
import { Transaction } from "@solana/web3.js";
|
||||
import { IdlInstruction } from "../../idl";
|
||||
import { splitArgsAndCtx } from "../context";
|
||||
import { IxFn } from "./instruction";
|
||||
|
||||
/**
|
||||
* Dynamically generated transaction namespace.
|
||||
*/
|
||||
export interface TransactionNamespace {
|
||||
[key: string]: TxFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tx is a function to create a `Transaction` generate from an IDL.
|
||||
*/
|
||||
export type TxFn = (...args: any[]) => Transaction;
|
||||
import { InstructionFn } from "./instruction";
|
||||
|
||||
export default class TransactionFactory {
|
||||
// Builds the transaction namespace.
|
||||
public static build(idlIx: IdlInstruction, ixFn: IxFn): TxFn {
|
||||
public static build(
|
||||
idlIx: IdlInstruction,
|
||||
ixFn: InstructionFn
|
||||
): TransactionFn {
|
||||
const txFn = (...args: any[]): Transaction => {
|
||||
const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
|
||||
const tx = new Transaction();
|
||||
|
@ -31,3 +21,41 @@ export default class TransactionFactory {
|
|||
return txFn;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The namespace provides functions to build [[Transaction]] objects for each
|
||||
* method of a program.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```javascript
|
||||
* program.transaction.<method>(...args, ctx);
|
||||
* ```
|
||||
*
|
||||
* ## Parameters
|
||||
*
|
||||
* 1. `args` - The positional arguments for the program. The type and number
|
||||
* of these arguments depend on the program being used.
|
||||
* 2. `ctx` - [[Context]] non-argument parameters to pass to the method.
|
||||
* Always the last parameter in the method call.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* To create an instruction for the `increment` method above,
|
||||
*
|
||||
* ```javascript
|
||||
* const tx = await program.transaction.increment({
|
||||
* accounts: {
|
||||
* counter,
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export interface TransactionNamespace {
|
||||
[key: string]: TransactionFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tx is a function to create a `Transaction` for a given program instruction.
|
||||
*/
|
||||
export type TransactionFn = (...args: any[]) => Transaction;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { sha256 } from "crypto-hash";
|
||||
import * as bs58 from "bs58";
|
||||
import * as rpc from "./rpc";
|
||||
import * as publicKey from "./pubkey";
|
||||
|
||||
export function decodeUtf8(array: Uint8Array): string {
|
||||
const decoder =
|
||||
typeof TextDecoder === "undefined"
|
||||
? new (require("util").TextDecoder)("utf-8") // Node.
|
||||
: new TextDecoder("utf-8"); // Browser.
|
||||
return decoder.decode(array);
|
||||
}
|
||||
|
||||
export default { sha256, bs58, rpc, publicKey };
|
|
@ -0,0 +1,78 @@
|
|||
import BN from "bn.js";
|
||||
import { sha256 as sha256Sync } from "js-sha256";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
// Sync version of web3.PublicKey.createWithSeed.
|
||||
export function createWithSeedSync(
|
||||
fromPublicKey: PublicKey,
|
||||
seed: string,
|
||||
programId: PublicKey
|
||||
): PublicKey {
|
||||
const buffer = Buffer.concat([
|
||||
fromPublicKey.toBuffer(),
|
||||
Buffer.from(seed),
|
||||
programId.toBuffer(),
|
||||
]);
|
||||
const hash = sha256Sync.digest(buffer);
|
||||
return new PublicKey(Buffer.from(hash));
|
||||
}
|
||||
|
||||
// Sync version of web3.PublicKey.createProgramAddress.
|
||||
export function createProgramAddressSync(
|
||||
seeds: Array<Buffer | Uint8Array>,
|
||||
programId: PublicKey
|
||||
): PublicKey {
|
||||
const MAX_SEED_LENGTH = 32;
|
||||
|
||||
let buffer = Buffer.alloc(0);
|
||||
seeds.forEach(function (seed) {
|
||||
if (seed.length > MAX_SEED_LENGTH) {
|
||||
throw new TypeError(`Max seed length exceeded`);
|
||||
}
|
||||
buffer = Buffer.concat([buffer, toBuffer(seed)]);
|
||||
});
|
||||
buffer = Buffer.concat([
|
||||
buffer,
|
||||
programId.toBuffer(),
|
||||
Buffer.from("ProgramDerivedAddress"),
|
||||
]);
|
||||
let hash = sha256Sync(new Uint8Array(buffer));
|
||||
let publicKeyBytes = new BN(hash, 16).toArray(undefined, 32);
|
||||
if (PublicKey.isOnCurve(new Uint8Array(publicKeyBytes))) {
|
||||
throw new Error(`Invalid seeds, address must fall off the curve`);
|
||||
}
|
||||
return new PublicKey(publicKeyBytes);
|
||||
}
|
||||
|
||||
// Sync version of web3.PublicKey.findProgramAddress.
|
||||
export function findProgramAddressSync(
|
||||
seeds: Array<Buffer | Uint8Array>,
|
||||
programId: PublicKey
|
||||
): [PublicKey, number] {
|
||||
let nonce = 255;
|
||||
let address: PublicKey | undefined;
|
||||
while (nonce != 0) {
|
||||
try {
|
||||
const seedsWithNonce = seeds.concat(Buffer.from([nonce]));
|
||||
address = createProgramAddressSync(seedsWithNonce, programId);
|
||||
} catch (err) {
|
||||
if (err instanceof TypeError) {
|
||||
throw err;
|
||||
}
|
||||
nonce--;
|
||||
continue;
|
||||
}
|
||||
return [address, nonce];
|
||||
}
|
||||
throw new Error(`Unable to find a viable program address nonce`);
|
||||
}
|
||||
|
||||
const toBuffer = (arr: Buffer | Uint8Array | Array<number>): Buffer => {
|
||||
if (arr instanceof Buffer) {
|
||||
return arr;
|
||||
} else if (arr instanceof Uint8Array) {
|
||||
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
|
||||
} else {
|
||||
return Buffer.from(arr);
|
||||
}
|
||||
};
|
|
@ -1,14 +1,7 @@
|
|||
import * as bs58 from "bs58";
|
||||
import { sha256 } from "crypto-hash";
|
||||
import assert from "assert";
|
||||
import { PublicKey, AccountInfo, Connection } from "@solana/web3.js";
|
||||
import { idlAddress } from "./idl";
|
||||
|
||||
export const TOKEN_PROGRAM_ID = new PublicKey(
|
||||
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
||||
);
|
||||
|
||||
async function getMultipleAccounts(
|
||||
export async function getMultipleAccounts(
|
||||
connection: Connection,
|
||||
publicKeys: PublicKey[]
|
||||
): Promise<
|
||||
|
@ -68,20 +61,3 @@ async function getMultipleAccounts(
|
|||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function decodeUtf8(array: Uint8Array): string {
|
||||
const decoder =
|
||||
typeof TextDecoder === "undefined"
|
||||
? new (require("util").TextDecoder)("utf-8") // Node.
|
||||
: new TextDecoder("utf-8"); // Browser.
|
||||
return decoder.decode(array);
|
||||
}
|
||||
|
||||
const utils = {
|
||||
bs58,
|
||||
sha256,
|
||||
getMultipleAccounts,
|
||||
idlAddress,
|
||||
};
|
||||
|
||||
export default utils;
|
Loading…
Reference in New Issue